How to draw a rounded NSImage

2020-02-23 04:29发布

问题:

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?

回答1:

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


回答2:

+ (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;
}


回答3:

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)


回答4:

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.