Search code examples
iosuiimagecore-graphicsmask

CGImageCreateWithMask() not returning a masked image


I am trying to mask a UIImage with another UIImage. The image to be masked is generated from a gradient of colours, and the mask is a [UIImage systemImageNamed:]. The problem is that the resulting UIImage after masking looks like the gradient without the mask applied. I have provided the methods for creating the gradient and for masking it with the other image. Can anybody identify what is going wrong here? Thank you in advance!

// Creating the image

UIImage *mask = [UIImage systemImageNamed:@"person.crop.circle.fill.badge.plus"];
UIImage *gradient = [UIImage imageWithGradientColours:@[[UIColor redColor], [UIColor greenColor]] size:mask.size];
UIImage *masked = [gradient imageByApplyingMaskImage:mask];
UIImageView *imageView = [[UIImageView alloc] initWithImage:masked];
[self.view addSubview:imageView];

// UIImage+Gradient
// Generates a UIImage of the specified size with a gradient in the specified colours

+ (UIImage*)imageWithGradientColours:(NSArray*)colours size:(CGSize)size {
    CAGradientLayer *gradientLayer = [CAGradientLayer layer];
    gradientLayer.frame = CGRectMake(0, 0, size.width, size.height);
    NSMutableArray *cgColours = [NSMutableArray array];
    for (UIColor *colour in colours) {
        [cgColours addObject:(id)colour.CGColor];
    }
    gradientLayer.colors = cgColours;

    UIGraphicsBeginImageContext(size);
    [gradientLayer renderInContext:UIGraphicsGetCurrentContext()];
    UIImage *gradientImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();

    return [gradientImage imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
}

// UIImage+MaskWithImage
// Returns a UIImage with a mask applied using the provided mask image
- (UIImage *)imageByApplyingMaskImage:(UIImage *)maskImage {
    CGImageRef maskRef = maskImage.CGImage;

    CGImageRef mask = CGImageMaskCreate(CGImageGetWidth(maskRef),
                                        CGImageGetHeight(maskRef),
                                        CGImageGetBitsPerComponent(maskRef),
                                        CGImageGetBitsPerPixel(maskRef),
                                        CGImageGetBytesPerRow(maskRef),
                                        CGImageGetDataProvider(maskRef), NULL, false);

    CGImageRef maskedImageRef = CGImageCreateWithMask(self.CGImage, mask);
    UIImage *maskedImage = [UIImage imageWithCGImage:maskedImageRef scale:[[UIScreen mainScreen] scale] orientation:UIImageOrientationUp];

    CGImageRelease(mask);
    CGImageRelease(maskedImageRef);

    // returns new image with mask applied
    return maskedImage;
}

Solution

  • Ah, I found the answer. I had to do some processing on the mask image before it would work properly as a mask:

    - (UIImage *)imageAsMask {
        //Ensure the image isn't rendered as template
        //Tint the image with [UIColor blackColor]
        UIImage *mask = [[self imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal] imageWithTintColor:[UIColor blackColor]];
    
        //Context for redrawing the mask
        UIGraphicsBeginImageContextWithOptions(mask.size, YES, 1.0);
        //Fill the mask with white background
        [[UIColor whiteColor] setFill];
        CGContextFillRect(UIGraphicsGetCurrentContext(), CGRectMake(0, 0, mask.size.width, mask.size.height));
    
        //Draw the black mask on top of the white background
        [mask drawAtPoint:CGPointZero];
    
        //Get the image from the context and return
        mask = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();
        return mask;
    }
    

    And then to generate a masked image I just do:

    UIImage *mask = [[UIImage systemImageNamed:@"person.crop.circle.fill.badge.plus"] imageAsMask];
    UIImage *gradient = [UIImage imageWithGradientColours:@[[UIColor redColor], [UIColor greenColor]] size:mask.size];
    UIImage *masked = [gradient imageByApplyingMaskImage:mask];