Thomas
Thomas

Reputation: 2367

CGContextClipToMask does not clip

This code results in the image below. As far as I understand CGContextClipToMask, the red rectangle should not be visible, since it is outside of the clipped area. What am I missing here? Thanks for any help!

CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(context, [UIColor blackColor].CGColor);
CGContextFillRect(context, rect);
CGContextSetLineWidth(context, 20);
CGContextSetStrokeColorWithColor(context, [UIColor whiteColor].CGColor);

// draw partial circle
UIBezierPath *arc   = [UIBezierPath bezierPathWithArcCenter:center radius:radius startAngle:startAngle endAngle:endAngle clockwise:NO];
CGContextAddPath(context, [arc CGPath]);
CGContextStrokePath(context);

// create mask
CGImageRef mask = CGBitmapContextCreateImage(context);
self.maskCreated(mask);

// save state
CGContextSaveGState(context);

// clip with mask
CGContextClipToMask(context, rect, mask);

// draw test rect
CGContextSetFillColorWithColor(context, [UIColor redColor].CGColor);
CGContextFillRect(context, CGRectMake(0, 0, 100, 100));

// restore state
CGContextRestoreGState(context);

Code Result

Upvotes: 2

Views: 1132

Answers (2)

Kurt Revis
Kurt Revis

Reputation: 27984

The documentation for CGContextClipToMask says:

If mask is an image, then it must be in the DeviceGray color space, may not have an alpha component, and may not be masked by an image mask or masking color.

I'm assuming your code is in a the -drawRect: method of a subclass of UIView, so you are using the CGContext which was provided to you, which is in an RGB color space and probably has an alpha component. Your mask image is created from that context, so it gets the same attributes.

To fix this, use a separate bitmap context to generate the mask, using a gray colorspace with no alpha. Here's a self-contained example that does something similar to your code.

- (void)drawRect:(CGRect)rect
{
    // Create a context for the mask
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceGray();
    CGContextRef maskContext = CGBitmapContextCreate(NULL, rect.size.width, rect.size.height, 8, 0, colorSpace, kCGImageAlphaNone | kCGBitmapByteOrderDefault);
    CGColorSpaceRelease(colorSpace);

    // Fill with black
    CGContextSetFillColorWithColor(maskContext, [UIColor blackColor].CGColor);
    CGContextFillRect(maskContext, rect);

    // Draw an arc in white
    CGContextSetLineWidth(maskContext, 20);
    CGContextSetStrokeColorWithColor(maskContext, [UIColor whiteColor].CGColor);
    CGContextAddArc(maskContext, CGRectGetMidX(rect), CGRectGetMidY(rect), 50, M_PI, 0, false);
    CGContextStrokePath(maskContext);

    // Create the mask image from the context, and discard the context
    CGImageRef mask = CGBitmapContextCreateImage(maskContext);
    CGContextRelease(maskContext);

    // Now draw into the view itself
    CGContextRef context = UIGraphicsGetCurrentContext();

    // Apply the mask
    CGContextClipToMask(context, rect, mask);

    // Then draw something that overlaps the mask
    CGContextSetFillColorWithColor(context, [UIColor redColor].CGColor);
    CGContextFillRect(context, rect);

    // Make sure to clean up when we're done
    CGImageRelease(mask);
}

Upvotes: 2

Dipak Narigara
Dipak Narigara

Reputation: 1806

Actually don't understand your concern, but you can hide rectangle in your method like this:

// draw a test rect             
CGContextSetFillColorWithColor(context, [UIColor redColor].CGColor);

CGRect rect = CGRectZero;
CGContextFillRect(context, rect);

Upvotes: 0

Related Questions