views:

1182

answers:

4

I have this code to mask an image. Basically, I only work with PNG images. So I have a 300x400 PNG image with 24bits of color (PNG-24). I am not sure if it also has an alpha channel. But there's no transparency in it.

Then, there is the image mask which is PNG-8bit without alpha channel. It is just black, grayscale and white.

I create both images as UIImage. Both display correctly when putting them into an UIImageView.

Then I create an UIImage out of them which contains the results of the masking operation, with this code:

+ (UIImage*)maskImage:(UIImage*)image withMask:(UIImage*)maskImage {
 CGImageRef maskRef = maskImage.CGImage;
 CGImageRef mask = CGImageMaskCreate(CGImageGetWidth(maskRef),
          CGImageGetHeight(maskRef),
          CGImageGetBitsPerComponent(maskRef),
          CGImageGetBitsPerPixel(maskRef),
          CGImageGetBytesPerRow(maskRef),
          CGImageGetDataProvider(maskRef), NULL, false);
 CGImageRef masked = CGImageCreateWithMask([image CGImage], mask);
 return [UIImage imageWithCGImage:masked];
}

here's what I do with that:

 UIImage *image = [UIImage imageNamed:@"coloredImagePNG24.png"];
 UIImage *maskImage = [UIImage imageNamed:@"theMaskPNG8_Grayscale_NoAlpha.png"];
 UIImage *maskedImage = [MyGraphicUtils maskImage:image withMask:maskImage];
 UIImageView *testImageView = [[UIImageView alloc] initWithImage:maskedImage];
 testImageView.backgroundColor = [UIColor clearColor];
 testImageView.opaque = NO;

After all that, the coloredImagePNG24.png stays totally intact as it is. No masking is happening. But now the weird thing is: If I turn that around, i.e. use this image as the mask, and the mask as the color-image-to-mask, then I get something very ugly in grayscale (but masked ;) ).

Any idea what's wrong with my code?

UPDATE: I just googled for an different b/w png to use it as a mask. And then this one worked! But the one I made by myself does not work. So I assume that the code has big image decoding problems. I would have to "normalize" the images to a specific format, so that it works.

A: 

See if it works any better this way:

+ (UIImage*)maskImage:(UIImage*)image withMask:(UIImage*)maskImage {
   UIGraphicsBeginImageContext(image.size);
   CGImageRef maskRef = maskImage.CGImage;
   CGImageRef mask = CGImageMaskCreate(CGImageGetWidth(maskRef), CGImageGetHeight(maskRef), CGImageGetBitsPerComponent(maskRef), CGImageGetBitsPerPixel(maskRef), CGImageGetBytesPerRow(maskRef), CGImageGetDataProvider(maskRef), NULL, false);
   CGImageRef masked = CGImageCreateWithMask(image.CGImage, mask);
   UIImage* result = [UIImage imageWithData:UIImagePNGRepresentation(UIGraphicsGetImageFromCurrentImageContext())];
   UIGraphicsEndImageContext();
   CGImageRelease(mask);
   CGImageRelease(masked);
   return result;
}
Ramin
Although that looks pretty, in my case it just makes the image completely transparent. See my update above. I figured out something strange.
Thanks
I've used a variation on the above code with masks created in Photoshop converted to "Grayscale" and exported as 24-bit PNG.
Ramin
A: 

Dave, I've heard of PNG images having an Alpha Channel that can affect such code.

I just googled PNG Alpha Channel and came up with this link: link text

So, make sure the PNG you're using has a properly set Alpha Channel

Brian Ball
+3  A: 

The CGImageMaskCreate does not do what you expect. In particular:

For image masks that are 2, 4, or 8 bits per component, each component is mapped to a range of 0 to 1 by scaling using this formula:

1/(2^bits per component – 1)

For example, a 4-bit mask has values that range from 0 to 15. These values are scaled by 1/15 so that each component ranges from 0 to 1. Component values that rescale to 0 or 1 behave the same way as they behave for 1-bit image masks. Values that scale to between 0 and 1 act as an inverse alpha. That is, the fill color is painted as if it has an alpha value of (1 – MaskSampleValue). For example, if the sample value of an 8-bit mask scales to 0.8, the current fill color is painted as if it has an alpha value of 0.2, that is (1–0.8).

My guess is that the function is reinterpreting your RGB data in this different format, which distorts the color values. Have you tried using the mask CGImage directly?

Quick edit: what I mean by "directly" is just to write

+ (UIImage*)maskImage:(UIImage*)image withMask:(UIImage*)maskImage
{
    CGImageRef masked = CGImageCreateWithMask([image CGImage], [maskImage CGImage]);
    return [UIImage imageWithCGImage:masked];
}
ianh
+2  A: 

As you discovered, how you save the file can make a difference, because Core Graphics is very particular about the format of the bits it uses for masking.

I’ve found that the best and most reliable way to generate an image mask from an arbitrary image is to do this:

  1. Create a bitmap graphics context that is in an acceptable format for image masks (you can't use UIGraphicsBeginImageContext() for this)
  2. Draw your image into this bitmap graphics context
  3. Create the image mask from the bits of the bitmap graphics context.

Try this function:

CGImageRef createMaskWithImage(CGImageRef image)
{
    int maskWidth               = CGImageGetWidth(image);
    int maskHeight              = CGImageGetHeight(image);
    //  round bytesPerRow to the nearest 16 bytes, for performance's sake
    int bytesPerRow             = (maskWidth + 15) & 0xfffffff0;
    int bufferSize              = bytesPerRow * maskHeight;

    //  allocate memory for the bits 
    CFMutableDataRef dataBuffer = CFDataCreateMutable(kCFAllocatorDefault, 0);
    CFDataSetLength(dataBuffer, bufferSize);

    //  the data will be 8 bits per pixel, no alpha
    CGColorSpaceRef colourSpace = CGColorSpaceCreateDeviceGray();
    CGContextRef ctx            = CGBitmapContextCreate(CFDataGetMutableBytePtr(dataBuffer),
                                                        maskWidth, maskHeight,
                                                        8, bytesPerRow, colourSpace, kCGImageAlphaNone);
    //  drawing into this context will draw into the dataBuffer.
    CGContextDrawImage(ctx, CGRectMake(0, 0, maskWidth, maskHeight), image);
    CGContextRelease(ctx);

    //  now make a mask from the data.
    CGDataProviderRef dataProvider  = CGDataProviderCreateWithCFData(dataBuffer);
    CGImageRef mask                 = CGImageMaskCreate(maskWidth, maskHeight, 8, 8, bytesPerRow,
                                                        dataProvider, NULL, FALSE);

    CGDataProviderRelease(dataProvider);
    CGColorSpaceRelease(colourSpace);
    CFRelease(dataBuffer);

    return mask;
}
bunnyhero