I have created below masked image and I have masked image like this
Here is my code
//
// Collage_Layout_1.swift
// Snapcial
//
// Created by Jecky Modi on 15/02/25.
//
class Collage_Layout_1: CollageLayoutBaseView {
var maskedImage: String = ""
var originalImage = UIImage()
private let maskImageView = UIImageView()
var viewFrame : CGRect = .zero
// private var originalImageCenter: CGPoint?
init(frame: CGRect, maskedImage: String, originalImage: UIImage) {
super.init(frame: frame)
self.maskedImage = maskedImage
self.originalImage = originalImage
self.viewFrame = frame
}
override func setUpCollage() {
let maskedContainer = self.arrViews[0].containerView
maskedContainer.tag = 100
let maskImage = UIImage(named: maskedImage)?.withRenderingMode(.alwaysTemplate)
maskImageView.translatesAutoresizingMaskIntoConstraints = false
maskImageView.image = maskImage
maskImageView.contentMode = .scaleAspectFit
maskImageView.tintColor = .clear
addSubview(maskImageView) // Keep the mask image visible
// Create a mask layer from the black shape
let maskLayer = CALayer()
maskLayer.contents = maskImage?.cgImage
maskLayer.frame = self.viewFrame
maskLayer.contentsGravity = .resizeAspect
maskedContainer.layer.mask = maskLayer
maskedContainer.clipsToBounds = true
maskedContainer.backgroundColor = .clear
self.backgroundColor = .clear
addSubview(maskedContainer) // Add container with mask
NSLayoutConstraint.activate([
maskImageView.leadingAnchor.constraint(equalTo: self.leadingAnchor),
maskImageView.topAnchor.constraint(equalTo: self.topAnchor),
maskImageView.trailingAnchor.constraint(equalTo: self.trailingAnchor),
maskImageView.bottomAnchor.constraint(equalTo: self.bottomAnchor),
maskedContainer.leadingAnchor.constraint(equalTo: self.leadingAnchor),
maskedContainer.topAnchor.constraint(equalTo: self.topAnchor),
maskedContainer.trailingAnchor.constraint(equalTo: self.trailingAnchor),
maskedContainer.bottomAnchor.constraint(equalTo: self.bottomAnchor),
])
self.layoutIfNeeded()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
I want to add borders to my masked image that border should take shape of mask.
Any help would be appreciated.
Based on this answer: How do I get a Path from an Image( )?
Code stripped-down a bit so we can see how we're using a UIImage
(the black leaf with transparent background), instead of a generating a SF Symbol image.
enum ContourError: Error {
case cgImageFailed
case noContour
}
extension CGPath {
static func imgContourPath(image: UIImage) throws -> CGPath {
guard let cgImage = image.cgImage else { throw ContourError.cgImageFailed }
return try cgImage.contourPath(size: image.size)
}
}
extension CGImage {
func contourPath(size: CGSize? = nil, contrastAdjustment: Float = 2, maximumImageDimension: Int? = nil) throws -> CGPath {
let size = size ?? CGSize(width: width, height: height)
let contourRequest = VNDetectContoursRequest()
contourRequest.maximumImageDimension = maximumImageDimension ?? Int(max(size.width, size.height))
contourRequest.contrastAdjustment = contrastAdjustment
let requestHandler = VNImageRequestHandler(cgImage: self, options: [:])
try requestHandler.perform([contourRequest])
var transform = CGAffineTransform(translationX: 0, y: CGFloat(size.height))
.scaledBy(x: CGFloat(size.width), y: -CGFloat(size.height))
guard let path = contourRequest.results?.first?.normalizedPath.mutableCopy(using: &transform) else {
throw ContourError.noContour
}
return path
}
}
extension UIImage {
func withBackground(_ background: UIColor) -> UIImage? {
let format = UIGraphicsImageRendererFormat()
format.scale = scale
let rect = CGRect(origin: .zero, size: size)
return UIGraphicsImageRenderer(bounds: rect, format: format).image { _ in
background.setFill()
UIBezierPath(rect: rect).fill()
draw(in: rect)
}
}
}
Using this 1200x1200 "image to mask":
and this 1200x1200 "mask image" (with transparent background):
We can generate a Path to use as both the Mask and the Border / Outline:
class ExampleVC: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor(white: 0.95, alpha: 1.0)
guard let img = UIImage(named: "maskImage"),
let origImage = UIImage(named: "imageToMask")
else { fatalError("Could not load images") }
// let's use a 300x300 image view
let r: CGRect = .init(origin: .zero, size: .init(width: 300, height: 300)).offsetBy(dx: 20, dy: 50)
let imgView = UIImageView(image: origImage)
imgView.frame = r
view.addSubview(imgView)
let szOrig = img.size
// for Contour detection, we want a black shape on a white background
// NOT transparent background
guard let wImage = img.withBackground(.white) else { fatalError("Failed to create white background image") }
if let cp = try? CGPath.imgContourPath(image: wImage) {
// the Contour Path matches the image size...
// we want to scale the path to fit the view size
let bz = UIBezierPath(cgPath: cp)
let scaleX = r.width / szOrig.width
let scaleY = r.height / szOrig.height
bz.apply(CGAffineTransform(scaleX: scaleX, y: scaleY))
// pathed shape layer to mask the image view
let mskLayer = CAShapeLayer()
mskLayer.path = bz.cgPath
// pathed shape layer for the border / outline
let sl = CAShapeLayer()
sl.strokeColor = UIColor.red.cgColor
sl.fillColor = nil
sl.lineWidth = 3
sl.path = bz.cgPath
// mask the image view
imgView.layer.mask = mskLayer
// add the border / outline layer
imgView.layer.addSublayer(sl)
}
}
}
Giving us this output: