Resize and Save NSImage?
Asked Answered
R

7

5

I have an NSImageView which I get an image for from an NSOpenPanel. That works great.

Now, how can I take that NSImage, half its size and save it as the same format in the same directory as the original as well?

If you can help at all with anything I'd appreciate it, thanks.

Roller answered 10/3, 2011 at 19:43 Comment(0)
L
8

Check the ImageCrop sample project from Matt Gemmell:
http://mattgemmell.com/source/

Nice example how to resize / crop images.
Finally you can use something like this to save the result (dirty sample):

// Write to TIF
[[resultImg TIFFRepresentation] writeToFile:@"/Users/Anne/Desktop/Result.tif" atomically:YES];

// Write to JPG
NSData *imageData = [resultImg  TIFFRepresentation];
NSBitmapImageRep *imageRep = [NSBitmapImageRep imageRepWithData:imageData];
NSDictionary *imageProps = [NSDictionary dictionaryWithObject:[NSNumber numberWithFloat:0.9] forKey:NSImageCompressionFactor];
imageData = [imageRep representationUsingType:NSJPEGFileType properties:imageProps];
[imageData writeToFile:@"/Users/Anne/Desktop/Result.jpg" atomically:NO];
Lobel answered 10/3, 2011 at 20:45 Comment(2)
There is a bug in Matt Gemmell's code in the way the cropping is done: if you have say a 640x480 image and tell its implementation to crop it to 240x240, it arrives at the 240x240 target image by cropping the source image down to 480x480 and then scaling that down to 240x240, rather than truly cropping a 240x240 chunk out of the source image. If anyone needs the fix for that, message me. I sent him the fix by email but haven't gotten a response.Courtland
@AndreasZollmann I wouldn't consider that a bug. If you need to resize to a different aspect ratio, you need to make a choice. I would argue that in most cases, you would want to retain aspect ratio.Parallelogram
K
4

Since NSImage objects are immutable you will have to:

  1. Create a Core Graphics context the size of the new image.
  2. Draw the NSImage into the CGContext. It should automatically scale it for you.
  3. Create an NSImage from that context
  4. Write out the new NSImage
  5. Don't forget to release any temporary objects you allocated.

There are definitely other options, but this is the first one that came to mind.

Kamseen answered 10/3, 2011 at 20:25 Comment(2)
Could you expand on the CGContext please? I might have it but could be wrong. Put the image from the NSImageView into NSData which becomes NSBitmapImageRep which I can then put into a NSGraphicsContext? At which point do I change its size? This right or wrong?Roller
Core Graphics (Quartz) is a large topic. I did not know about the technique in @Anne's answer. That is much less code. I would try that first. Otherwise there is Apple's Quartz documentation.Kamseen
U
3
+(NSImage*) resize:(NSImage*)aImage scale:(CGFloat)aScale
{
    NSImageView* kView = [[NSImageView alloc] initWithFrame:NSMakeRect(0, 0, aImage.size.width * aScale, aImage.size.height* aScale)];
    [kView setImageScaling:NSImageScaleProportionallyUpOrDown];
    [kView setImage:aImage];

    NSRect kRect = kView.frame;
    NSBitmapImageRep* kRep = [kView bitmapImageRepForCachingDisplayInRect:kRect];
    [kView cacheDisplayInRect:kRect toBitmapImageRep:kRep];

    NSData* kData = [kRep representationUsingType:NSJPEGFileType properties:nil];
    return [[NSImage alloc] initWithData:kData];
}
Utilize answered 19/7, 2012 at 1:10 Comment(1)
That might cause you trouble when called on a background thread, and it's horribly slow: you create an image view, that in turn is rendering into a new image (this is where the scaling happens). This is already bad, but then you serialize that image to JPEG (losing detail) and then read it again. That part is totally unnecessary and just wastes resources.Pandich
P
2

Here is a specific implementation

-(NSImage*)resizeImage:(NSImage*)input by:(CGFloat)factor
{    
    NSSize size = NSZeroSize;      
    size.width = input.size.width*factor;
    size.height = input.size.height*factor; 

    NSImage *ret = [[NSImage alloc] initWithSize:size];
    [ret lockFocus];
    NSAffineTransform *transform = [NSAffineTransform transform];
    [transform scaleBy:factor];  
    [transform concat]; 
    [input drawAtPoint:NSZeroPoint fromRect:NSZeroRect operation:NSCompositeCopy fraction:1.0];    
    [ret unlockFocus];        

    return [ret autorelease];
}

Keep in mind that this is pixel based, with HiDPI the scaling must be taken into account, it is simple to obtain :

-(CGFloat)pixelScaling
{
    NSRect pixelBounds = [self convertRectToBacking:self.bounds];
    return pixelBounds.size.width/self.bounds.size.width;
}
Pylon answered 23/5, 2012 at 22:0 Comment(0)
H
1

Apple has source code for downscaling and saving images found here http://developer.apple.com/library/mac/#samplecode/Reducer/Introduction/Intro.html

Heterogony answered 21/9, 2011 at 7:34 Comment(1)
This link now says "Important: This sample code may not represent best practices for current development" ;-)Topaz
A
0

Here is some code that makes a more extensive use of Core Graphics than other answers. It's made according to hints in Mark Thalman's answer to this question.

This code downscales an NSImage based on a target image width. It's somewhat nasty, but still useful as an extra sample for documenting how to draw an NSImage in a CGContext, and how to write contents of CGBitmapContext and CGImage into a file.

You may want to add extra error checking. I didn't need it for my use case.

- (void)generateThumbnailForImage:(NSImage*)image atPath:(NSString*)newFilePath forWidth:(int)width
{
    CGSize size = CGSizeMake(width, image.size.height * (float)width / (float)image.size.width);
    CGColorSpaceRef rgbColorspace = CGColorSpaceCreateDeviceRGB();

    CGBitmapInfo bitmapInfo = kCGImageAlphaPremultipliedLast;
    CGContextRef context = CGBitmapContextCreate(NULL, size.width, size.height, 8, size.width * 4, rgbColorspace, bitmapInfo);
    NSGraphicsContext * graphicsContext = [NSGraphicsContext graphicsContextWithGraphicsPort:context flipped:NO];
    [NSGraphicsContext setCurrentContext:graphicsContext];

    [image drawInRect:NSMakeRect(0, 0, size.width, size.height) fromRect:NSMakeRect(0, 0, image.size.width, image.size.height) operation:NSCompositeCopy fraction:1.0];

    CGImageRef outImage = CGBitmapContextCreateImage(context);
    CFURLRef outURL = (CFURLRef)[NSURL fileURLWithPath:newFilePath];
    CGImageDestinationRef outDestination = CGImageDestinationCreateWithURL(outURL, kUTTypeJPEG, 1, NULL);
    CGImageDestinationAddImage(outDestination, outImage, NULL);
    if(!CGImageDestinationFinalize(outDestination))
    {
        NSLog(@"Failed to write image to %@", newFilePath);
    }
    CFRelease(outDestination);
    CGImageRelease(outImage);
    CGContextRelease(context);
    CGColorSpaceRelease(rgbColorspace);
}
Ariel answered 19/10, 2012 at 13:18 Comment(3)
graphicsContextWithGraphicsPort:flipped: is deprecated since (OS X v10.10)Period
That is greatly disappointing. Luckily, this question has better answers when dealing with NSImage (as OP asked) and I don't feel like concocting a different approach when using Core Graphics for this.Nich
Just use graphicsContextWithCGContext instead of graphicsContextWithGraphicsPort.Hartal
J
0

To resize image

- (NSImage *)scaleImage:(NSImage *)anImage newSize:(NSSize)newSize
{
    NSImage *sourceImage = anImage;
    if ([sourceImage isValid])
    {
        if (anImage.size.width == newSize.width && anImage.size.height == newSize.height && newSize.width <= 0 && newSize.height <= 0) {
            return anImage;
        }

        NSRect oldRect = NSMakeRect(0.0, 0.0, anImage.size.width, anImage.size.height);
        NSRect newRect = NSMakeRect(0,0,newSize.width,newSize.height);
        NSImage *newImage = [[NSImage alloc] initWithSize:newSize];

        [newImage lockFocus];
        [sourceImage drawInRect:newRect fromRect:oldRect operation:NSCompositeCopy fraction:1.0];
        [newImage unlockFocus];

        return newImage;
    }
}
Jess answered 6/4, 2016 at 12:33 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.