Masking changes colors of UIImage- iOS - ios

Here is what I am doing to mask a UIImage dynamically. It is working but for some reason the colors of output image is not the same as original one. What would be causing this? Thanks..
- (void) setClippingPath:(UIBezierPath *)clippingPath : (UIImageView *)imgView {
CAShapeLayer *maskLayer = [CAShapeLayer layer];
maskLayer.frame = self.imgView.frame;
maskLayer.path = [clippingPath CGPath];
maskLayer.fillColor = [[UIColor whiteColor] CGColor];
maskLayer.backgroundColor = [[UIColor clearColor] CGColor];
self.imgView.image = [self maskImage:self.imgView.image withClippingMask:[self imageFromLayer:maskLayer]];
}
- (UIImage *)imageFromLayer:(CALayer *)layer
{
UIGraphicsBeginImageContext([layer frame].size);
[layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *outputImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return outputImage;
}
-(UIImage*)maskImage:(UIImage *)image withClippingMask:(UIImage *)maskImage
{
CGImageRef maskRef = image.CGImage;
CGImageRef mask = CGImageMaskCreate(CGImageGetWidth(maskRef),
CGImageGetHeight(maskRef),
CGImageGetBitsPerComponent(maskRef),
CGImageGetBitsPerPixel(maskRef),
CGImageGetBytesPerRow(maskRef),
CGImageGetDataProvider(maskRef), NULL, false);
CGImageRef maskedImageRef = CGImageCreateWithMask([maskImage CGImage], mask);
UIImage *maskedImage = [UIImage imageWithCGImage:maskedImageRef];
CGImageRelease(mask);
CGImageRelease(maskedImageRef);
// returns new image with mask applied
return maskedImage;
}
Original Image
Mask
Output Image

The documentation for CGImageMaskCreate mentions:
When you draw into a context with a bitmap image mask, Quartz uses the mask to determine where and how the current fill color is applied to the image rectangle.
So if you want to just replace the black with white then you should be able to set the context color before creating the mask:
-(UIImage*)maskImage:(UIImage *)image withClippingMask:(UIImage *)maskImage
{
CGImageRef maskRef = image.CGImage;
CGContextSetFillColorWithColor( UIGraphicsGetCurrentContext( ), [ UIColor whiteColor ] );
CGImageRef mask = CGImageMaskCreate(CGImageGetWidth(maskRef),
CGImageGetHeight(maskRef),
CGImageGetBitsPerComponent(maskRef),
CGImageGetBitsPerPixel(maskRef),
CGImageGetBytesPerRow(maskRef),
CGImageGetDataProvider(maskRef), NULL, false);
CGImageRef maskedImageRef = CGImageCreateWithMask([maskImage CGImage], mask);
UIImage *maskedImage = [UIImage imageWithCGImage:maskedImageRef];
CGImageRelease(mask);
CGImageRelease(maskedImageRef);
// returns new image with mask applied
return maskedImage;
}
You might also want to update your mask to a more basic, greyscale JPG, something like this:

Related

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;
}
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];

Keeping right tints after image masking

I need to fill programmatically image with different colors with mask I got from our designer in photoshop file:
http://image.openlan.ru/images/83339350420766181806.png
I almost finish this except of details I missed:
UIImage *mask = [UIImage imageNamed:#"mask.png"];
UIImage *image = [UIImage imageNamed:#"vehicle.png"];
mask = [image maskImage:image withMask:mask];
image = [image coloredImageWithColor:[UIColor blueColor]];
image = [image drawImage:mask inImage:image];
Where methods implementations:
- (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);
UIImage *im = [UIImage imageWithCGImage:masked];
CGImageRelease(masked);
CGImageRelease(maskRef);
CGImageRelease(mask);
return im;
}
- (UIImage *) coloredImageWithColor:(UIColor *)color
{
UIGraphicsBeginImageContext(self.size);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextTranslateCTM(context, 0, self.size.height);
CGContextScaleCTM(context, 1.0, -1.0);
CGContextSetBlendMode(context, kCGBlendModeDarken);
CGRect rect = (CGRect){ CGPointZero, self.size };
CGContextDrawImage(context, rect, self.CGImage);
CGContextClipToMask(context, rect, self.CGImage);
[color set];
CGContextFillRect(context, rect);
UIImage *coloredImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return coloredImage;
}
- (UIImage *) drawImage:(UIImage *)fgImage inImage:(UIImage *)bgImage
{
UIGraphicsBeginImageContextWithOptions(bgImage.size, FALSE, 0.0);
[bgImage drawInRect:(CGRect){ CGPointZero, bgImage.size }];
[fgImage drawInRect:(CGRect){ CGPointZero, fgImage.size }];
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return newImage;
}
My current result is:
http://image.openlan.ru/images/34429534616655410787.png
I don't think if I use gradient, I'll see right result. What I don't like now is lack of tints in some image areas. What am I doing wrong here?
Inspired by this post I implemented my task eventually.
It's an origin image:
Then I drew a mask for this image do not fill special areas:
Below main method drawing this:
- (void) vehicleConfigure
{
NSString *vehicleName = #"sports.png";
UIImage *vehicleImage = [UIImage imageNamed:vehicleName];
vehicleName = [vehicleName stringByReplacingOccurrencesOfString:#".png" withString:#"-mask.png"];
UIImage *mask = [UIImage imageNamed:vehicleName];
mask = [vehicleImage applyingMask:mask];
vehicleImage = [vehicleImage coloredImageWithColor:RGB(30, 128, 255)];
vehicleImage = [vehicleImage drawMask:mask];
m_vehicleImageView.contentMode = UIViewContentModeScaleAspectFit;
m_vehicleImageView.image = vehicleImage;
}
Where I use category of UIImage with methods:
- (UIImage *) drawMask:(UIImage *)mask
{
UIGraphicsBeginImageContextWithOptions(self.size, FALSE, 0.0);
[self drawInRect:(CGRect){ CGPointZero, self.size }];
[mask drawInRect:(CGRect){ CGPointZero, mask.size }];
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return newImage;
}
- (UIImage *) applyingMask:(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([self CGImage], mask);
UIImage *im = [UIImage imageWithCGImage:masked
scale:[[UIScreen mainScreen] scale]
orientation:UIImageOrientationUp];
// CGImageRelease(masked);
// CGImageRelease(maskRef);
// CGImageRelease(mask);
return im;
}
- (UIImage *) coloredImageWithColor:(UIColor *)color
{
UIGraphicsBeginImageContext(self.size);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextTranslateCTM(context, 0, self.size.height);
CGContextScaleCTM(context, 1.0, -1.0);
CGContextSetBlendMode(context, kCGBlendModeColorBurn);
CGRect rect = (CGRect){ CGPointZero, self.size };
CGContextDrawImage(context, rect, self.CGImage);
CGContextClipToMask(context, rect, self.CGImage);
[color set];
CGContextFillRect(context, rect);
UIImage *coloredImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return coloredImage;
}
And result looks:

How to get UIBezierPath of shape in UIImage or crop UIImage in a certain shape

I am new in iOS, I want to know if I can get the UIBezierPath of a UIImage. I have a UIImage of face layout and want to get the UIBezierPath, which helps me in cropping the UIImage.
Or, can any one tell me about other ways of cropping UIImages?, but make sure cropping is in a custom shape (like: face, heart etc), not in a rectangle.
Here is the code to mask image with image:
- (UIImage *)cerateImageFromImage:(UIImage *)image
withMaskImage:(UIImage *)mask {
CGImageRef imageRef = image.CGImage;
CGImageRef maskRef = mask.CGImage;
CGImageRef imageMask = CGImageMaskCreate(CGImageGetWidth(maskRef),
CGImageGetHeight(maskRef),
CGImageGetBitsPerComponent(maskRef),
CGImageGetBitsPerPixel(maskRef),
CGImageGetBytesPerRow(maskRef),
CGImageGetDataProvider(maskRef),
NULL,
YES);
CGImageRef maskedReference = CGImageCreateWithMask(imageRef, imageMask);
CGImageRelease(imageMask);
UIImage *maskedImage = [UIImage imageWithCGImage:maskedReference];
CGImageRelease(maskedReference);
return maskedImage;
}
Usage:
UIImage *image = [UIImage imageNamed:#"Photo.png"];
UIImage *mask = [UIImage imageNamed:#"Mask.png"];
self.imageView.image = [self cerateImageFromImage:image
withMaskImage:mask];

Use CGContextClip to clip a circle with image

Have a imageViewA (frame{0,0,320,200}) in my app, and I want to clip a circle with the imageViewA's center, radius=50, and draw into another imageViewB (frame{0,0,100,100});
the original image effect like this :
and use below code to clip:
UIGraphicsBeginImageContext(self.frame.size);
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGFloat height = self.bounds.size.height;
CGContextTranslateCTM(ctx, 0.0, height);
CGContextScaleCTM(ctx, 1.0, -1.0);
CGContextAddArc(ctx, self.frame.size.width/2, self.frame.size.height/2, 50, 0, 2*M_PI, 0);
CGContextClosePath(ctx);
CGContextSaveGState(ctx);
CGContextClip(ctx);
CGContextDrawImage(ctx, CGRectMake(0,0,self.frame.size.width, self.frame.size.height), image.CGImage);
CGContextRestoreGState(ctx);
CGImageRef imageRef = CGBitmapContextCreateImage (ctx);
UIImage *newImage = [UIImage imageWithCGImage:imageRef];
NSString *headerPath = [NSString stringWithFormat:#"%#/header.png",HomeDirectory];
NSData *imageData = UIImageJPEGRepresentation(newImage, 1.0);
if([imageData writeToFile:headerPath atomically:YES]){
imageViewB.image = [UIImage imageWithContentsOfFile:headerPath];
}
clip image effect like this:
I only need the circle view, but the clip effect display that imageViewB has white space.
How to clip this image correctly?
Thanks!
imageViewB.layer.cornerRadius = 50.0;
imageViewB.layer.masksToBounds = YES;
JPEG doesn't support transparency. Use UIImagePNGRepresentation instead of UIImageJPEGRepresentation, and you will have the result you want.
NSData *imageData = UIImagePNGRepresentation(newImage);
if([imageData writeToFile:headerPath atomically:YES]){
imageViewB.image = [UIImage imageWithContentsOfFile:headerPath];
}
Just use masking for this purposes
This code will help you to mask images
+ (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);
UIImage *uiMasked = [UIImage imageWithCGImage:masked];
CFRelease(mask);
CFRelease(masked);
return uiMasked;
}
This article can help you
http://www.abdus.me/ios-programming-tips/how-to-mask-image-in-ios-an-image-masking-technique/
After masking you need to create an imageView with that masked image
and then subview it into another imageView.
Before you draw the image:
CGContextSetBlendMode(ctx, kCGBlendModeClear);
CGContextFillRect(ctx, self.bounds);
CGContextSetBlendMode(ctx, kCGBlendModeNormal);

Image Blur/Quality Loss when Masking in iOS

I am masking an image (left) with this function
- (UIImage*)maskWithMask:(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([self CGImage], mask);
UIImage *maskedImage = [UIImage imageWithCGImage:masked];
CGImageRelease(masked);
UIGraphicsBeginImageContextWithOptions(maskedImage.size,NO,0.0);
[maskedImage drawAtPoint:CGPointZero];
UIImage *newImg = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return newImg;
}
after the mask the resulting image appears to have lost quality / blurred slightly as shown on the right of the photo. I cannot seam to figure out why. I know it isn't a major loss, but its enough to notice on a retina display, which is what I am developing on. Any thoughts?

Resources