Writing a masked image to disk as a PNG file
Asked Answered
R

3

12

Basically I'm downloading images off of a webserver and then caching them to the disk, but before I do so I want to mask them. I'm using the masking code everyone seems to point at which can be found here: http://iosdevelopertips.com/cocoa/how-to-mask-an-image.html

What happens though, is that the image displays fine, but the version that gets written to the disk with

UIImage *img = [self maskImage:[UIImage imageWithData:data] withMask:self.imageMask];
[UIImagePNGRepresentation(img) writeToFile:cachePath atomically:NO];

has it's alpha channel inverted when compared to the one displayed later on (using the same UIImage instance here).

Any ideas? I do need the cached version to be masked, otherwise displaying the images in a table view get's awfully slow if I have to mask them every time.

Edit: So yeah, UIImagePNGRepresentation(img) seems to invert the alpha channel, doesn't have anything to do with the code that writes to disk, which is rather obvious but I checked anyway.

Rhizomorphous answered 10/11, 2009 at 14:8 Comment(5)
Do you get the same result if you use UIImageJPEGRepresentation? I’m not suggesting you switch to JPEG. Just wondering if it has the same effect.Diamond
I'll try, although JPEG does defeat the purpose of having an alpha mask.Rhizomorphous
Oddly enough the JPEG looks fine, the mask has been applied properly.Rhizomorphous
I am struggling with this issue also. In my case a PNG is required as the masked image is itself semi-transparent. The mask created in code appears fine (it is a vignette effect to be placed over a view), however after saving the UIImagePNGRepresentation() to disk and using that to create the UIImage, the vignette is back-to-front. An answer to address this issue would be greatly appreciated.Gilmore
I encounter the same issue and when I use UIImageJPEGRepresentation to create a image on disk, it has also an inverted mask applied to it.Kevon
C
5

See the description in CGImageCreateWithMask in CGImage Reference:

The resulting image depends on whether the mask parameter is an image mask or an image. If the mask parameter is an image mask, then the source samples of the image mask act as an inverse alpha value. That is, if the value of a source sample in the image mask is S, then the corresponding region in image is blended with the destination using an alpha value of (1-S). For example, if S is 1, then the region is not painted, while if S is 0, the region is fully painted.

If the mask parameter is an image, then it serves as an alpha mask for blending the image onto the destination. The source samples of mask' act as an alpha value. If the value of the source sample in mask is S, then the corresponding region in image is blended with the destination with an alpha of S. For example, if S is 0, then the region is not painted, while if S is 1, the region is fully painted.

It seems for some reason the image mask is treated as a mask image to mask with while saving. According to:

to correctly save with UIImagePNGRepresentation, there are several choices:

  1. Use inverse version of the image mask.
  2. Use "mask image" instead of "image mask".
  3. Render to a bitmap context and then save it, like epatel mentioned.
Croatian answered 23/4, 2012 at 3:6 Comment(0)
G
14

How about drawing into a new image, and then save that?

UIGraphicsBeginImageContext(img.size);
[img drawAtPoint:CGPointZero];
UIImage *newImg = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
[UIImagePNGRepresentation(newImg) writeToFile:cachePath atomically:NO];

(untested)

Group answered 17/4, 2012 at 20:55 Comment(3)
Yep, that works. It's not ideal though, since according to my (very crude) measurements it takes longer to redraw the image as in the code above (minus the -writeToFile:atomically) than it does to draw the image from scratch using Core Graphics commands. Do you have any further insights on why your suggestion works, and whether there is a more efficient way of doing the same thing?Gilmore
My guess would be that the mask is a "thing" separate from the image. Something UIKit/Quartz can handle and draw, but when you want to save it as an image with mask as a alpha channel the image needs to be composited with the mask to get the its final "bitmap" before saving.Group
@Group I love you, man. Been puling my hair out. I am trying to mask and save to file (cache) before any of my normal code reads from the file (that way I mask once, not every time the view is displayed), and your code was necessary after my "UIImage *maskedIcon = [UIImage imageWithCGImage:maskedImageRef];" to make it work. Thank you!Stannite
C
5

See the description in CGImageCreateWithMask in CGImage Reference:

The resulting image depends on whether the mask parameter is an image mask or an image. If the mask parameter is an image mask, then the source samples of the image mask act as an inverse alpha value. That is, if the value of a source sample in the image mask is S, then the corresponding region in image is blended with the destination using an alpha value of (1-S). For example, if S is 1, then the region is not painted, while if S is 0, the region is fully painted.

If the mask parameter is an image, then it serves as an alpha mask for blending the image onto the destination. The source samples of mask' act as an alpha value. If the value of the source sample in mask is S, then the corresponding region in image is blended with the destination with an alpha of S. For example, if S is 0, then the region is not painted, while if S is 1, the region is fully painted.

It seems for some reason the image mask is treated as a mask image to mask with while saving. According to:

to correctly save with UIImagePNGRepresentation, there are several choices:

  1. Use inverse version of the image mask.
  2. Use "mask image" instead of "image mask".
  3. Render to a bitmap context and then save it, like epatel mentioned.
Croatian answered 23/4, 2012 at 3:6 Comment(0)
R
0
Swift and UIGraphicsImageRenderer
let renderer = UIGraphicsImageRenderer(size: maskedImage.size)
let newImage = renderer.image { _ in maskedImage.draw(at: .zero) }
newImage.pngData()?.write(to: ...)
Remanent answered 4/7, 2023 at 8:48 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.