0

On a macOS (OSX) desktop app, I use NSBezierPath to draw a random closed shape which looks like this.

enter image description here

As you can see the dashed line shows the closed path which was drawn.

Now am trying to extract the masked image as per this path. But i get a small portion of the image. The masked image seems to correctly get the outline from the BezierPath. But size is an issue.

enter image description here

This is the method which returns the masked/clipped image. The sourceImage.size for this drawing - 1021 * 1031

  - (NSImage *)imageByApplyingClippingBezierPath:(NSImage *)sourceImage
                                bezierPath:(NSBezierPath *)bezierPath
                                  newFrame:(NSRect)newFrame
  {
    NSImage* newImage = [[NSImage alloc] initWithSize:newFrame.size];
    NSBitmapImageRep* rep = [[NSBitmapImageRep alloc]
                             initWithBitmapDataPlanes:NULL
                             pixelsWide:newFrame.size.width
                             pixelsHigh:newFrame.size.height
                             bitsPerSample:8
                             samplesPerPixel:4
                             hasAlpha:YES
                             isPlanar:NO
                             colorSpaceName:NSCalibratedRGBColorSpace
                             bytesPerRow:0
                             bitsPerPixel:0];

    [newImage addRepresentation:rep];

    [newImage lockFocus];

    CGContextRef context = [[NSGraphicsContext currentContext] graphicsPort];
    CGContextSaveGState(context);

    [bezierPath addClip];

    NSRect targetFrame = NSMakeRect(0, 0, newFrame.size.width, newFrame.size.height);
    [sourceImage drawInRect:targetFrame];

    [newImage unlockFocus];

    CGContextRestoreGState(context);

    return newImage;
}

How can i get perfectly sized image outlined by the BezierPath? Any tips would be appreciated!

UPDATE

Just clarifying, how i draw the image. I get rectangle bounds of the bezier path and rectangular image cropped like this.

CGRect bezierBounds = CGPathGetPathBoundingBox([self.smartLassoWavyBezierPath quartzPath]);
 NSRect targetFrame = NSMakeRect(0, 0, bezierBounds.size.width, bezierBounds.size.height);
 NSImage *targetImage = [[NSImage alloc initWithSize:targetFrame.size];
[targetImage lockFocus];
[self.view.originalLoadImageOnCanvas 
drawInRect:targetFrame fromRect:bezierBounds operation:NSCompositeCopy 
 fraction:1.0f];
[targetImage unlockFocus];
KamyFC
  • 858
  • 9
  • 17
  • How do you draw the image? – Willeke Jul 24 '18 at 10:56
  • @Willeke I updated the answer - to show you code. I get the rectangular image cropped from the BezierPath's bounds. Then i send this rectangular image to the 'imageByApplyingClippingBezierPath' function. – KamyFC Jul 24 '18 at 12:23
  • How do you draw the image to draw on? Is it scaled? – Willeke Jul 24 '18 at 15:25
  • @Willeke - am not sure what you mean? Can u be specific? – KamyFC Jul 24 '18 at 15:57
  • When you draw the path on an image, how do you display this image? I mean the first image in the question with the buildings. It looks like the image on screen is scaled at 40% and the mask at 100%. – Willeke Jul 24 '18 at 21:58
  • @Willeke Thanks for your patience. I draw a path on the image using Stroke method of NSBezierPath .... Then i get the rectangular bounds of the path using - CGPathGetPathBoundingBox ..... Then i crop/extract the rectangular image based on the bounds from the original image ...... (see code snippet on UPDATE)...... If i show this cropped image is shown on NSImageView, i get the correct rectangular crop of the image as per the Bezier Path..... But i dont need rectangular crop, i need masked image outlined to the BezierPath. Hope that clarifies things. – KamyFC Jul 25 '18 at 05:55
  • If you draw the lasso near the edges of the image, are `bezierBounds.size` and the original image size almost the same? – Willeke Jul 25 '18 at 10:38
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/176713/discussion-between-kamyfc-and-willeke). – KamyFC Jul 25 '18 at 11:24

1 Answers1

2

For Swift 5;

func imageByApplyingClippingBezierPath(source: NSImage, path: NSBezierPath, frame: NSRect) -> NSImage? {
    let new = NSImage(size: frame.size)
    guard let rep = NSBitmapImageRep(bitmapDataPlanes: nil, pixelsWide: Int(frame.size.width), pixelsHigh: Int(frame.size.height), bitsPerSample: 8, samplesPerPixel: 4, hasAlpha: true, isPlanar: false, colorSpaceName: NSColorSpaceName.calibratedRGB, bitmapFormat: .alphaFirst, bytesPerRow: 0, bitsPerPixel: 0) else { return nil}

    new.addRepresentation(rep)
    new.lockFocus()

    let context = NSGraphicsContext.current?.cgContext
    context?.saveGState()
    path.addClip()

    let targetFrame = NSRect(x: 0, y: 0, width: Int(frame.size.width), height: Int(frame.size.height))

    source.draw(in: targetFrame)
    new.unlockFocus()
    context?.restoreGState()

    return new
}
Misha Stone
  • 631
  • 7
  • 22