SaldaVonSchwartz
SaldaVonSchwartz

Reputation: 3809

how can I save an NSImageView layer to a PNG on disk?

I'm trying to do the following batch processing:

  1. load an image
  2. scale it
  3. add a rounded border to it
  4. save the new image as PNG

So far, I came up with this:

CGPoint center = CGPointMake(self.window.frame.size.width / 2., self.window.frame.size.height / 2.);
      [[NSGraphicsContext currentContext] setImageInterpolation:NSImageInterpolationHigh];
      NSImage * newThumbnail = [[NSImage alloc] initWithData:[NSData dataWithContentsOfURL:[files objectAtIndex:0]]];
      NSImageView *imageView = [[NSImageView alloc] initWithFrame:CGRectMake(center.x - (57/2.), center.y - (80/2.), 57, 80)];
      [imageView setWantsLayer:YES];

      imageView.layer.borderColor = [[NSColor blackColor] CGColor];
      imageView.layer.cornerRadius = 4;
      imageView.layer.masksToBounds = YES;
      imageView.layer.borderWidth = 1.0;
      imageView.image = newThumbnail;

And now I think I'm missing telling the layer to redender to some context? maybe something like [imageView.layer drawInContext: some context (NS o CG?)]; and saving that context to disk. But I'm confused as to:

what context,

whether to go from context to NSData to disk,

or if I need an intermediate image somewhere before saving.

Basically, between CALayers and CGLayers and NS objects and UI objects (I know I'm in OS X Cocoa here and it's no UIKit and I'm also not using CG stuff so far) I have no idea how to turn the above into a png.

Upvotes: 1

Views: 1168

Answers (1)

Rob Keniger
Rob Keniger

Reputation: 46020

You shouldn't be using a view at all, since you're not needing to display to screen. You should be using an offscreen bitmap (an NSBitmapImageRep) and using drawing commands to draw the image.

You have two choices with drawing in Cocoa. The easiest way is to use the various Cocoa drawing classes such as NSBezierPath and friends. The other option is the more powerful but also more low-level and complex Quartz APIs, which are not object-oriented but use a C function syntax.

Here is an example of how to do what you want using Cocoa drawing:

NSImage* anImage = [NSImage imageNamed:@"Lenna.tiff"]; //or some other source

//create a bitmap at a specific size
NSRect offscreenRect = NSMakeRect(0.0, 0.0, 250.0, 250.0);
NSBitmapImageRep* bitmap = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:nil
                                                       pixelsWide:offscreenRect.size.width
                                                       pixelsHigh:offscreenRect.size.height
                                                    bitsPerSample:8
                                                  samplesPerPixel:4
                                                         hasAlpha:YES
                                                         isPlanar:NO
                                                   colorSpaceName:NSCalibratedRGBColorSpace
                                                     bitmapFormat:0
                                                      bytesPerRow:(4 * offscreenRect.size.width)
                                                     bitsPerPixel:32];

//save the current graphics context and lock focus on the bitmap
NSGraphicsContext* originalContext = [NSGraphicsContext currentContext];
[NSGraphicsContext setCurrentContext:[NSGraphicsContext
                                      graphicsContextWithBitmapImageRep:bitmap]];
[NSGraphicsContext saveGraphicsState];

//clear the image rep. This is faster than filling with [NSColor clearColor].
unsigned char *bitmapData = [bitmap bitmapData];
if (bitmapData)
    bzero(bitmapData, [bitmap bytesPerRow] * [bitmap pixelsHigh]);

//create the border path
CGFloat borderWidth = 2.0;
CGFloat cornerRadius = 14.0;
NSRect borderRect = NSInsetRect(offscreenRect, borderWidth/2.0, borderWidth/2.0);
NSBezierPath* border = [NSBezierPath bezierPathWithRoundedRect:borderRect xRadius:cornerRadius yRadius:cornerRadius];
[border setLineWidth:borderWidth];

//set the border as a clipping path
[NSGraphicsContext saveGraphicsState];
[border addClip];

//scale and draw the image
[anImage setSize:offscreenRect.size];
[anImage drawAtPoint:NSZeroPoint fromRect:NSZeroRect operation:NSCompositeSourceOver fraction:1.0];
[NSGraphicsContext restoreGraphicsState];

//set the border color
[[NSColor blackColor] set];

//draw the border
[border stroke];

//restore the original graphics context
[NSGraphicsContext restoreGraphicsState];
[NSGraphicsContext setCurrentContext:originalContext];

//get PNG data from the image rep
NSData* pngData = [bitmap representationUsingType:NSPNGFileType properties:nil];
NSError* error;
if(![pngData writeToURL:[NSURL fileURLWithPath:[NSHomeDirectory() stringByAppendingPathComponent:@"test.png"]] options:NSDataWritingAtomic error:&error])
{
    NSLog(@"%@",error);
}

Upvotes: 2

Related Questions