6

I'm trying to render some UIImages into 1 single image that I can save in my photo album. But it seems to as if the layer.renderInContext doesn't take a layermask into account?

Current behavior: the photo saves, and I see mosaicLayer, without the masking effect of maskLayer.

Expected behavior: the photo saves and I see the image in my view, with on top of that a masked mosaicLayer.

I use the following code to mask the image

UIImage *maskImg = [UIImage imageWithContentsOfFile:[[NSBundle mainBundle]
                        pathForResource:@"mask" ofType:@"png"]];

    maskLayer = [[UIImageView alloc] initWithImage:maskImg];
    maskLayer.multipleTouchEnabled = YES;
    maskLayer.userInteractionEnabled = YES;
    UIImageView *mosaicLayer = [[UIImageView alloc] initWithImage:img];
    mosaicLayer.contentMode = UIViewContentModeScaleAspectFill;

    mosaicLayer.frame = [imageView bounds]; 
    mosaicLayer.layer.mask = maskLayer.layer;

    [imageView addSubview:mosaicLayer];

And then i use this code to save my composed image:

UIGraphicsBeginImageContext(imageView.bounds.size);
    [imageView.layer renderInContext:UIGraphicsGetCurrentContext()];
    UIImage *saver = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();

    UIImageWriteToSavedPhotosAlbum(saver, self, @selector(image:didFinishSavingWithError:contextInfo:), nil);

EDIT: This applies the mask correctly

-(IBAction) saveImage { 
UIImage * saver = nil;
CGImageRef image = imageView.image.CGImage;

size_t cWidth = CGImageGetWidth(image);
size_t cHeight = CGImageGetHeight(image);
size_t bitsPerComponent = 8; 
size_t bytesPerRow = 4 * cWidth;

//Now we build a Context with those dimensions.
CGContextRef context = CGBitmapContextCreate(nil, cWidth, cHeight, bitsPerComponent, bytesPerRow, CGColorSpaceCreateDeviceRGB(), CGImageGetBitmapInfo(image));

//The location where you draw your image on the context is not always the same location you have in your UIView, 
//this could change and you need to calculate that position according to the scale between you images real size, and the size of the UIImage being show on the UIView. Hence the mod floats...
CGContextDrawImage(context, CGRectMake(0, 0, cWidth,cHeight), image);

float mod = cWidth/(imageView.frame.size.width);
float modTwo = cHeight/(imageView.frame.size.height);

//Make the drawing right with coordinate switch
CGContextTranslateCTM(context, 0, cHeight);
CGContextScaleCTM(context, 1.0, -1.0);

CGContextClipToMask(context, CGRectMake(maskLayer.frame.origin.x * mod, maskLayer.frame.origin.y * modTwo, maskLayer.frame.size.width * mod,maskLayer.frame.size.height * modTwo), maskLayer.image.CGImage);

//Reverse the coordinate switch
CGAffineTransform ctm = CGContextGetCTM(context);
ctm = CGAffineTransformInvert(ctm);
CGContextConcatCTM(context, ctm);

CGContextDrawImage(context, CGRectMake(0, 0, cWidth,cHeight), mosaicLayer.image.CGImage);

CGImageRef mergeResult  = CGBitmapContextCreateImage(context);
saver = [[UIImage alloc] initWithCGImage:mergeResult];
CGContextRelease(context);
CGImageRelease(mergeResult);

UIImageWriteToSavedPhotosAlbum(saver, self, @selector(image:didFinishSavingWithError:contextInfo:), nil);}
ThomasM
  • 2,647
  • 3
  • 25
  • 30
  • ,, I also need to make similar code , Can you please provide me some useful code. Thanks in advance – h.kishan Jan 15 '14 at 09:52

1 Answers1

2

The documentation for renderInContext: that, among other properties, mask is not supported on Mac OS X 10.5. I suspect it is the same on iOS, even if the documentation doesn't say so.

To solve your problem, you'll probably have to draw the layers separately into a graphics context, setting the correct mask on the graphics context itself.

Ole Begemann
  • 135,006
  • 31
  • 278
  • 256
  • as is suggested here?: http://stackoverflow.com/questions/2795151/combining-multiple-uiimageviews-and-preserve-resolution – ThomasM Feb 04 '11 at 13:27
  • @Ole I've updated my question, i have no idea how to apply the mask correctly. – ThomasM Feb 14 '11 at 11:13
  • 3
    Before you draw the second image, you have to apply a mask to the graphics context with `CGContextClipToMask()`. If your mask image is not a grayscale image, you might need to convert it to grayscale before you can use it as a mask in `CGContextClipToMask()`. See the documentation for details. – Ole Begemann Feb 14 '11 at 11:38
  • Yes i just found that one out, even without looking at your answer! :) The only problem left is that the mask does not have the correct size and position. I'm guessing this has something to do with the fact that the mask's position in the view isn't the same as in the context? – ThomasM Feb 14 '11 at 13:16
  • @Ole, I seem to not be able to find the modification factor to draw. I thought it was simple: I should multiply the position and scale with the ratio of (image-size / imageview-size).. How is this not correct? – ThomasM Feb 15 '11 at 14:34
  • @Ole, it has something to do with a flipped coordinatesystem. At least that's my best guess, since the X coordinates are ok, the Y coordinates seem to be flipped. I saw some solutions with CGContextTranslateCTM(context, 0, height); but they don't seem to do the trick – ThomasM Feb 15 '11 at 16:31
  • I finally solved it, by doing some extra lines in which I transform the ctm and back again. This seems like a hard approach. I edited my question with a working answer. If anybody has a beter way of solving this, let me know. – ThomasM Feb 16 '11 at 10:20
  • @OleBegemann The greyscale suggestion was brilliant and worked for me! Thankyou so much! +1 – Albert Renshaw Jul 15 '13 at 19:32