I am trying to create a NSImage
or NSImageCell
with rounded corners inside a NSTableView
. I can't get anything to work. Here is the best I have so far inside my custom NSCell
:
- (void)drawInteriorWithFrame:(NSRect)frame inView:(NSView *)controlView {
if (thumbnailLink) {
NSURL *url = [NSURL URLWithString:thumbnailLink];
if (url) {
NSRect imageFrame = [self _imageFrameForInteriorFrame:frame];
NSImage *image = [[NSImage alloc] initWithContentsOfURL:url];
[image setScalesWhenResized:YES];
[image setSize:NSMakeSize(IMAGE_HEIGHT, IMAGE_WIDTH)];
[NSGraphicsContext saveGraphicsState];
imageFrame = NSInsetRect(imageFrame, 1, 1);
NSBezierPath *clipPath = [NSBezierPath bezierPathWithRoundedRect:imageFrame cornerRadius:5.0];
[clipPath setWindingRule:NSEvenOddWindingRule];
[clipPath addClip];
[NSGraphicsContext restoreGraphicsState];
[image drawInRect:imageFrame fromRect:NSMakeRect(0, 0, 0, 0) operation:NSCompositeSourceIn fraction:1.0];
[image release];
}
}
...
Any ideas on how to this?
You need to keep the clip set when drawing your image, and restore the context after instead. Also I don't think you want to be using "in" compositing but rather "over" if you just want to draw your image normally without taking the destination opacity in consideration. Try something like:
[NSGraphicsContext saveGraphicsState];
NSBezierPath *path = [NSBezierPath bezierPathWithRoundedRect:imageFrame
xRadius:5
yRadius:5];
[path addClip];
[image drawInRect:imageFrame
fromRect:NSZeroRect
operation:NSCompositeSourceOver
fraction:1.0];
[NSGraphicsContext restoreGraphicsState];
+ (NSImage*)roundCorners:(NSImage *)image
{
NSImage *existingImage = image;
NSSize existingSize = [existingImage size];
NSSize newSize = NSMakeSize(existingSize.width, existingSize.height);
NSImage *composedImage = [[[NSImage alloc] initWithSize:newSize] autorelease];
[composedImage lockFocus];
[[NSGraphicsContext currentContext] setImageInterpolation:NSImageInterpolationHigh];
NSRect imageFrame = NSRectFromCGRect(CGRectMake(0, 0, 1024, 1024));
NSBezierPath *clipPath = [NSBezierPath bezierPathWithRoundedRect:imageFrame xRadius:200 yRadius:200];
[clipPath setWindingRule:NSEvenOddWindingRule];
[clipPath addClip];
[image drawAtPoint:NSZeroPoint fromRect:NSMakeRect(0, 0, newSize.width, newSize.height) operation:NSCompositeSourceOver fraction:1];
[composedImage unlockFocus];
return composedImage;
}
Swift version:
func roundCorners(image: NSImage, width: CGFloat = 192, height: CGFloat = 192) -> NSImage {
let xRad = width / 2
let yRad = height / 2
let existing = image
let esize = existing.size
let newSize = NSMakeSize(esize.width, esize.height)
let composedImage = NSImage(size: newSize)
composedImage.lockFocus()
let ctx = NSGraphicsContext.currentContext()
ctx?.imageInterpolation = NSImageInterpolation.High
let imageFrame = NSRect(x: 0, y: 0, width: width, height: height)
let clipPath = NSBezierPath(roundedRect: imageFrame, xRadius: xRad, yRadius: yRad)
clipPath.windingRule = NSWindingRule.EvenOddWindingRule
clipPath.addClip()
let rect = NSRect(x: 0, y: 0, width: newSize.width, height: newSize.height)
image.drawAtPoint(NSZeroPoint, fromRect: rect, operation: NSCompositingOperation.CompositeSourceOver, fraction: 1)
composedImage.unlockFocus()
return composedImage
}
// then
roundCorners(image)
roundCorners(image, width: 512, height: 512)
Just to provide a bit more info on what you did wrong:
The purpose of saving and restoring the gstate is to be able to undo your changes to the gstate. In your case, restoring the gsave after clipping undid the clip. That's why the solution (as explained by Rhult) is to draw what you want clipped before you restore the gstate.