Search code examples
iosswiftswiftuicore-graphics

How to apply mask to an image with different grey scale values?


I'm trying to apply this mask to the original image

enter image description here

Notice there are some grey areas, I want to keep everything except PURE black.

I've found this code

 func maskImage(image: UIImage, mask: UIImage) -> UIImage {
    let imageReference = (image.cgImage)!
    let maskReference = (mask.cgImage)!

    let imageMask = CGImage(
        maskWidth: maskReference.width
        , height: maskReference.height
        , bitsPerComponent: maskReference.bitsPerComponent
        , bitsPerPixel: maskReference.bitsPerPixel
        , bytesPerRow: maskReference.bytesPerRow
        , provider: maskReference.dataProvider!
        , decode: nil
        , shouldInterpolate: true
    )

    return (UIImage(cgImage: (imageReference.masking(imageMask!))!))
}

But it does the opposite, it removes all white pixels instead.

Edit ---

For example if the above mask is implied to a pure color background

enter image description here

right now so far the solution submitted gives this image

enter image description here

Notice there are holes in the middle, grey areas. Everything should be solid blue. In order words, the mask should only take out PURE black areas.


Solution

  • Update the mask before applying it like:

    let maskReference = (mask.turnedNotPureBlackIntoBlack()!.cgImage)!
    

    Using this extension:

    extension UIImage {
        func turnedNotPureBlackIntoBlack() -> UIImage? {
            guard let cgImage else { return nil }
    
            let width = cgImage.width
            let height = cgImage.height
    
            let bytesPerPixel = 4
            let bitsPerComponent = 8
    
            let bytesPerRow = bytesPerPixel * width
            let pixels = UnsafeMutablePointer<UInt32>.allocate(capacity: height * width)
    
            guard let context = CGContext(
                data: pixels,
                width: width,
                height: height,
                bitsPerComponent: bitsPerComponent,
                bytesPerRow: bytesPerRow,
                space: CGColorSpaceCreateDeviceRGB(),
                bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue | CGBitmapInfo.byteOrder32Big.rawValue
            ) else {
                return nil
            }
    
            context.draw(cgImage, in: CGRect(x: 0, y: 0, width: width, height: height))
    
            for j in 0..<height {
                for i in 0..<width {
                    let currentPixel = pixels + j * width + i
                    let color = currentPixel.pointee
                    let r = UInt32((color >> 00) & 0xFF)
                    let g = UInt32((color >> 08) & 0xFF)
                    let b = UInt32((color >> 16) & 0xFF)
                    let a = UInt32((color >> 24) & 0xFF)
    
                    let pureBlack = 0
                    switch (r, g, b) {
                    case (0, 0, 0): currentPixel.pointee = 0xFFFFFFFF
                    default: currentPixel.pointee = UInt32(pureBlack)
                    }
                }
            }
    
            guard let newCGImage = context.makeImage() else { return nil }
    
            let processedImage = UIImage(cgImage: newCGImage)
            pixels.deallocate()
    
            return processedImage
        }
    }
    
    Demo:
    Original Mask Converted Mask
    Original Converted

    Note that some pixels in the original image are NOT pure black but its not easy to see them with plain eyes

    By the way, I suggest inverting the mask outside the code for better performance if possible