2

I want to mask an image by another image using CGLayerRef in off-screen rendering since it's suitable for high-quality rendering as it's mentioned in documentation

Core Graphics Layer Drawing : CGLayer objects allow your application to use layers for drawing. Layers are suited for the following: High-quality offscreen rendering of drawing that you plan to reuse.

But I couldn't find any example in objective-c that I could understand how it works. On my context, I want to mask an image with a shape image (solid color shape) I used to do it this way

//The context I use to mask my image with the mask image.
CGColorSpaceRef colorSpace= CGColorSpaceCreateDeviceRGB();
    CGContextRef mainViewContentContext = CGBitmapContextCreate (NULL, _bigImageRect.size.width, _bigImageRect.size.height, 8, 0, colorSpace, kCGImageAlphaPremultipliedLast);
CGColorSpaceRelease(colorSpace);

//Mask image
CGContextClipToMask(mainViewContentContext,
                       CGRectMake(0,
                                   -_bigImageRect.origin.y,
                                   _bigImageRect.size.width,
                                   _bigImageRect.size.height),
                                   _maskImage.CGImage);

//Drawing the image on the mask image.
CGContextDrawImage(mainViewContentContext,
                       CGRectMake(0,
                                  0,
                                  _bigImageRect.size.width,
                                  _bigImageRect.size.height),
                                  _ImageToBeMasked.CGImage);

CGImageRef mainViewContentBitmapContext = CGBitmapContextCreateImage(mainViewContentContext);
CGContextRelease(mainViewContentContext);
UIImage*maskedImage = [UIImage imageWithCGImage:mainViewContentBitmapContext];
CGImageRelease(mainViewContentBitmapContext)
return maskedImage;

But how can I mask using CGLayers as below? I appreciate any help.

CGLayerRef layer = CGLayerCreateWithContext(mainViewContentContext, _bigImageRect.size, NULL);
CGContextDrawLayerInRect(mainViewContentContext, _bigImageRect, layer);
CGLayerRelease(layer);
//And then.. how can I do the masking using this CGLayerRef???
Reza.Ab
  • 1,195
  • 1
  • 11
  • 21
  • "I want to mask an image by another image using CGLayerRef" But what if CGLayerRef is not a way to do masking? – matt Feb 11 '18 at 17:50
  • The reason I have to use this way is that I have tried literally every single possible way out there to do "High quality" masking in off-screen, Yes I used to do imageView.layer.mask = maskImageView.layer but that's low-quality on-screen masking. My current code above works but the problem I had was the strange positioning that CGContextDrawImage and CGContextClipToMask show. Even though I understand Core Graphics uses LLO, Still the position of the mask doesn't make sense. So I decided to use CGLayer since documentation says it's the way for high quality off-screen rendering. @matt – Reza.Ab Feb 11 '18 at 18:08
  • "imageView.layer.mask = maskImageView.layer but that's low-quality on-screen masking" That's true, but only because you are applying it to an image view's layer. You can apply a `mask` to _any_ layer, including one that is offscreen. And as for "low quality", everything depends upon the layer's `contentsScale`, doesn't it? If you create a CALayer and it is not part of the interface, and you forget to set its `contentsScale`, then you have only yourself to blame. – matt Feb 11 '18 at 18:11
  • The point is, I find the terms of your question misleading. It seems to me to be an x-y question. Instead of insisting that we answer in terms of CGLayer, shouldn't you be more open-ended about it? If the question is how to mask, shouldn't you just ask about that, instead of saying "Don't answer except in the following terms", especially if those terms are not appropriate? – matt Feb 11 '18 at 18:13
  • The reason I asked "how to do it with CGLayerRef" is that I have tried all possible ways. If there's any way you can come up with, then even better and in regards to " If you create a CALayer and it is not part of the interface, and you forget to set its contentsScale" YES I have tried that before and it doesn't give me the high-quality masked image. Maybe your way of masking by CALayer would work in off-screen but I'm not sure how you use it. @matt – Reza.Ab Feb 11 '18 at 18:17
  • Well, maybe if you gave an actual example (here is my image, here is my mask, here is the desired result) I could help. Or maybe not, of course. But all this abstract talk going down a road that I'm not convinced is relevant doesn't get us anywhere. I'm not going to try to use CGLayer because I don't see how it is relevant or needed. In fact as far as I know I've never found a use for CGLayer in practice. – matt Feb 11 '18 at 18:23
  • Okay, I will show the code in which I'm using CALayer with content scale for masking high-quality render in a bit. – Reza.Ab Feb 11 '18 at 18:25

2 Answers2

3

I normaly write Swift code, but once I also had to deal with masking and CGLayer.

I solved the problem with the following steps:

  1. Draw your mask
  2. Change blend mode to CGBlendMode.sourceIn
  3. Draw your "color" image.

This works because CGBlendMode.sourceIn tells the context to multiply the alpha value of the source image with the color of the existing pixel. Therefore invisible parts of the mask (alpha = 0.0) will stay invisible.

Apple docs for CGBlendMode: https://developer.apple.com/documentation/coregraphics/cgblendmode

anfark
  • 116
  • 4
1

It doesn't really answer the question but I finally ended up using the conventional methods such as CGContextClipToMask and CGContextDrawImage but I managed to gain my desired high-quality before masking. Again, I need to mention that this works just in my case (which was masking an image with a rendered bezier path on it but at image's full resolution) If you have the same problem for more information you can check my other related question here

Masking an image using bezierpath with image's full resolution

and my github working project as well.

https://github.com/Reza-Abdolahi/HighResMasking

Here's the code that worked for me:

CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    CGImageRef maskImageRef = [_maskImage CGImage];
    CGContextRef myContext = CGBitmapContextCreate (NULL, _highResolutionImage.size.width, _highResolutionImage.size.height, 8, 0, colorSpace, kCGImageAlphaPremultipliedLast);
    CGColorSpaceRelease(colorSpace);
    if (myContext==NULL)
        return NULL;

    CGFloat ratio = 0;
    ratio = _maskImage.size.width/ _highResolutionImage.size.width;
    if(ratio * _highResolutionImage.size.height < _maskImage.size.height) {
        ratio = _maskImage.size.height/ _highResolutionImage.size.height;
    }

    CGRect rectForMask  = {{0, 0}, {_maskImage.size.width, _maskImage.size.height}};
    CGRect rectForImageDrawing  = {{-((_highResolutionImage.size.width*ratio)-_maskImage.size.width)/2 , -((_highResolutionImage.size.height*ratio)-_maskImage.size.height)/2},
        {_highResolutionImage.size.width*ratio, _highResolutionImage.size.height*ratio}};

    CGContextClipToMask(myContext, rectForMask, maskImageRef);
    CGContextDrawImage(myContext, rectForImageDrawing, _highResolutionImage.CGImage);
    CGImageRef newImage = CGBitmapContextCreateImage(myContext);
    CGContextRelease(myContext);
    UIImage *theImage = [UIImage imageWithCGImage:newImage];
    CGImageRelease(newImage);
    return theImage;
Reza.Ab
  • 1,195
  • 1
  • 11
  • 21