swiftiphoneuikittransparencysave-image

Incorrect saving of transparent UIImage to Photo Library as png with UIImageWriteToSavedPhotosAlbum


I have a function cropAlpha() that trims the extra space defined by the transparency.

func cropAlpha() -> UIImage {
    let cgImage = self.cgImage!
    
    let width = cgImage.width
    let height = cgImage.height
    
    let colorSpace = CGColorSpaceCreateDeviceRGB()
    let bytesPerPixel:Int = 4
    let bytesPerRow = bytesPerPixel * width
    let bitsPerComponent = 8
    let bitmapInfo: UInt32 = CGImageAlphaInfo.premultipliedLast.rawValue | CGBitmapInfo.byteOrder32Big.rawValue

    guard let context = CGContext(data: nil, width: width, height: height, bitsPerComponent: bitsPerComponent, bytesPerRow: bytesPerRow, space: colorSpace, bitmapInfo: bitmapInfo),
          let ptr = context.data?.assumingMemoryBound(to: UInt8.self)
    else { return self }

    context.draw(self.cgImage!, in: CGRect(x: 0, y: 0, width: width, height: height))
    
    var minX = width
    var minY = height
    var maxX: Int = 0
    var maxY: Int = 0
    
    for x in 1 ..< width {
        for y in 1 ..< height {
            let i = bytesPerRow * Int(y) + bytesPerPixel * Int(x)
            let a = CGFloat(ptr[i + 3]) / 255.0
            
            if a == 1 {
                if (x < minX) { minX = x }
                if (x > maxX) { maxX = x }
                if (y < minY) { minY = y }
                if (y > maxY) { maxY = y }
            }
        }
    }
    
    let rect = CGRect(x: CGFloat(minX),y: CGFloat(minY), width: CGFloat(maxX - minX), height: CGFloat(maxY-minY))
    
    let croppedImage = self.cgImage!.cropping(to: rect)!
    let ret = UIImage(cgImage: croppedImage)

    return ret
}

The image returned by this function has transparent elements and I put it in the ImageView: presenterImageView.image = imagePNG. It works as it should. But when I try to save UIImage to Photo Gallery, transparent background turns white.

let image = maskedImage?.cropAlpha()
    
let imagePNGData = image!.pngData()
let imagePNG = UIImage(data: imagePNGData!)

UIImageWriteToSavedPhotosAlbum(imagePNG!, nil, nil, nil)

If I don't use that function, I get the result I want, but the image has too much wasted space. I don't understand what could be the reason. Any ideas?


Solution

  • The problem is that UIImageWriteToSavedPhotosAlbum does not handle properly saving a UIImage with premultiplied alpha (or at least the result of saving such image is not what you expect) and your cropping method uses premultipliedLast format. You also can't just simply change CGImageAlphaInfo to a non-premultiplied format because it is not supported there (you will see an error CGBitmapContextCreate: unsupported parameter combination if you try that). But what you can do is convert the cropped image to CIImage, unpremultiply alpha and convert back to UIImage. To do that your saving code could look like below (however I recommend removing force unwrapping from this code if you plan to use it in final app):

    let image = maskedImage?.cropAlpha()
    
    let ciImage = CIImage(image: image!)!.unpremultiplyingAlpha()
    let uiImage = UIImage(ciImage: ciImage)
        
    let imagePNGData = uiImage.pngData()
    let imagePNG = UIImage(data: imagePNGData!)
    
    UIImageWriteToSavedPhotosAlbum(imagePNG!, nil, nil, nil)