Creating memory efficient thumbnails using an NSImageView (cocoa/OSX) - macos

I am creating small NSImageViews (32x32 pixels) from large images often 512x512 or even 4096 x 2048 for an extreme test case. My problem is that with my extreme test case, my applicaiton memory footprint seems to go up by over 15MB when I display my thumbnail, this makes me think the NSImage is being stored in memory as a 4096x2048 instead of 32x32 and I was wondering if there is a way to avoid this. Here is the process I go through to create the NsImageView:
• First I create an NSImage using initByReferencingFile: (pointing to the 4096x2048 .png file)
• Next I initialize the NSImageView with a call to initWithFrame:
• Then I call setImage: to assign my NSImage to the NSImageView
• Finally I set the NSImageView to NSScaleProportionally
I clearly do nothing to force the NSImage to size down to 32x32 but I have had trouble finding a good way to handle this.

You can simply create a new 32x32 NSImage from the original and then release the original image.
First, create the 32x32 image:
NSImage *smallImage = [[NSImage alloc]initWithSize:NSMakeSize(32, 32)];
Then, lock focus on the image and draw the original on to it:
NSSize originalSize = [originalImage size];
NSRect fromRect = NSMakeRect(0, 0, originalSize.width, originalSize.height);
[smallImage lockFocus];
[originalImage drawInRect:NSMakeRect(0, 0, 32, 32) fromRect:fromRect operation:NSCompositeCopy fraction:1.0f];
[smallImage unlockFocus];
Then you may do as you please with the smaller image:
[imageView setImage:smallImage];
Remember to release!
[originalImage release];
[smallImage release];

Related

How to get CGImageForProposedRect to provide 1:1 pixel data on Retina Mac

In our app, we're creating an PDF NSImage (therefore scalable) and then using CGImage routines to write that data to a TIFF file. This works fine on non-retina display Macintoshes, but on retina machines, the data that is returned is twice the resolution we expect (just like the screen).
The code we're using works takes a newly formed NSView subclass referencing the data to draw (not the original on-screen view) as printingMapView.
NSData *pdfData = [printingMapView dataWithPDFInsideRect: frame];
NSImage *image = [[NSImage alloc] initWithData: pdfData];
[image setSize: size];
NSRect pRect = NSMakeRect( 0, 0, [image size].width, [image size].height);
CGImageRef cgImage = [image CGImageForProposedRect: &pRect context: NULL hints:NULL];
I have looked around for any hints that could be handed to the CGImageForProposedRect:context:hints call, but there's nothing in the Apple documentation relating to content scale.
Is there any way to do this other than creating an NSBitmapImageRep of the full size and passing that in as the context parameter to CGImageForProposedRect:context:hints?
That seems like it's likely to use a lot of memory during the operation.
So CGImageForProposedRect does return 1:1 pixel data, if you are getting a CGImage out of the function that is doubled in size the NSImageRep of that NSImage must also be doubled in size. Check your code to see if you have any calls to NSImage drawInRect where you are writing to an retina context. That is what was happening to me.

NSImage and PDFImageRep caching still draws at only one resolution

I have an NSImage, initialized with PDF data, created like this:
NSData* data = [view dataWithPDFInsideRect:view.bounds];
slideImage = [[NSImage alloc] initWithData:data];
The slideImage is now the size of the view.
When I try to render the image in an NSImageView, it only draws sharp when the image view is exactly the original size of the image, even if you clear the cache or change the image size. I tried setting the cacheMode to NSImageCacheNever, which also didn't work. The only image rep in the image is the PDF one, and when I render it to a PDF file it shows that it's vector.
As a workaround, I create a NSBitmapImageRep with a different size, call drawInRect on the original image, and put the bitmap representation inside a new NSImage and render that, which works, but it feels like it's not optimal:
- (NSBitmapImageRep*)drawToBitmapOfWidth:(NSInteger)width
andHeight:(NSInteger)height
withScale:(CGFloat)scale
{
NSBitmapImageRep *bmpImageRep = [[NSBitmapImageRep alloc]
initWithBitmapDataPlanes:NULL
pixelsWide:width * scale
pixelsHigh:height * scale
bitsPerSample:8
samplesPerPixel:4
hasAlpha:YES
isPlanar:NO
colorSpaceName:NSCalibratedRGBColorSpace
bitmapFormat:NSAlphaFirstBitmapFormat
bytesPerRow:0
bitsPerPixel:0
];
bmpImageRep = [bmpImageRep bitmapImageRepByRetaggingWithColorSpace:
[NSColorSpace sRGBColorSpace]];
[bmpImageRep setSize:NSMakeSize(width, height)];
NSGraphicsContext *bitmapContext = [NSGraphicsContext graphicsContextWithBitmapImageRep:bmpImageRep];
[NSGraphicsContext saveGraphicsState];
[NSGraphicsContext setCurrentContext:bitmapContext];
[self drawInRect:NSMakeRect(0, 0, width, height) fromRect:NSZeroRect operation:NSCompositeCopy fraction:1];
[NSGraphicsContext restoreGraphicsState];
return bmpImageRep;
}
- (NSImage*)rasterizedImageForSize:(NSSize)size
{
NSImage* newImage = [[NSImage alloc] initWithSize:size];
NSBitmapImageRep* rep = [self drawToBitmapOfWidth:size.width andHeight:size.height withScale:1];
[newImage addRepresentation:rep];
return newImage;
}
How can I get the PDF to render nicely at any size without resorting to hacks like mine?
The point of NSImage is that you create it with the size (in points) that you want it to be. The backing representation can be vector based (e.g. PDF), and the NSImage is resolution independent (i.e. it supports different pixels per point), but the NSImage still has a fixed size (in points).
One one the points of an NSImage is that it will / can add a cache representation to speed up subsequent drawing.
If you need to draw a PDF to multiple sizes, and you want to use an NSImage, you're probably best of creating an NSImage for your given target size. If you want to, you can keep the NSPDFImageRef around -- I don't think it'll save you much.
We tried the following:
NSPDFImageRep* rep = self.representations.lastObject;
return [NSImage imageWithSize:size flipped:NO drawingHandler:^BOOL (NSRect dstRect)
{
[[NSGraphicsContext currentContext] setImageInterpolation:NSImageInterpolationHigh];
[rep drawInRect:dstRect fromRect:NSZeroRect operation:NSCompositeCopy fraction:1 respectFlipped:YES hints:#{
NSImageHintInterpolation: #(NSImageInterpolationHigh)
}];
return YES;
}];
And that does give you nice results when scaling up, but makes for blurry images
when scaling down.

How to DrawImage on OSX with CGContext

I have a CGContext and I tried to draw an image on it.
I have a NSImage * load that i used on other method .
NSImage *check = [[NSImage alloc] initWithContentsOfFile:#"check.icns"];
CGContextDrawImage(arg2, CGRectMake(0, 0, 145, 15), check);
But CGContext want a CGImage * how can I used my NSImage * ?
Thanks
You should consider just opening it as a CGImage in this case, if that is all you will need it for. If an NSImage is what you need, then see -[NSImage CGImageForProposedRect:context:hints:]. This method may not require a copy, and it can produce a CGImage representation ideal for drawing into the destination context.
If needed, you can create a NSGraphicsContext from a CGContext using +[NSGraphicsContext graphicsContextWithGraphicsPort:flipped:].

image scaling performance in Quartz/Cocoa vs Qt4

I wrote a test application in Qt4 which uses QImage.scaled() or QPixmap.scaled() methods that turned to be very slow. Even a perspective transform is faster, while a scaling transform is the same slow.
[I tried to scale directly a QPainter but I do not master paintEvent() so I always get "painter not active" or paintEvent() is not called at all. So I do not know the painter scaling performaces.]
I ask here if the same issue is known for Quartz/Cocoa or instead their performances for similar tasks are better. I am particularly interested in native Quartz pdf rendering capability and subsequent image scaling.
NIRTimer *timer = [NIRTimer timer];
[timer start];
NSImage *image = [[NSImage alloc]initWithContentsOfFile:#"filename"];
NSImage *scaledImage = [[NSImage alloc]initWithSize:NSMakeSize(720, 480)];
[scaledImage lockFocus];
[image drawInRect:NSMakeRect(0, 0, 720, 480) fromRect:NSZeroRect operation:NSCompositeSourceAtop fraction:1];
[scaledImage unlockFocus];
[image release];
[scaledImage release];
NSLog(#"time: %ld", [timer microseconds]);
This is how to scale an image in Cocoa, and it takes 80000 microseconds (0.08 seconds).

How come IKImageBrowserView can resize images so much faster than I can?

This is my image resize code:
CALayer *newCALayer = [[CALayer layer] retain];
NSImage* image = [[NSImage alloc] initWithData:[NSData dataWithContentsOfFile:path]];
CGImageRef newCGImageFullResolution = [image CGImageForProposedRect:nil context:nil hints:nil];
CGContextRef context = CGBitmapContextCreate(NULL, drawRect.size.width, drawRect.size.height,
CGImageGetBitsPerComponent(newCGImageFullResolution),
CGImageGetBytesPerRow(newCGImageFullResolution),
CGImageGetColorSpace(newCGImageFullResolution),
CGImageGetAlphaInfo(newCGImageFullResolution));
CGContextDrawImage(context, CGRectMake(0, 0, drawRect.size.width, drawRect.size.height), newCGImageFullResolution);
CGImageRef scaledImage = CGBitmapContextCreateImage(context);
newCALayer.contents = (id)scaledImage;
CGImageRelease(scaledImage);
newCALayer.contentsGravity = kCAGravityResizeAspect;
newCALayer.opacity = 0.0;
newCALayer.anchorPoint = CGPointMake(0.0f,0.0f);
newCALayer.frame = CGRectMake( 0.0,
0.0,
[Singleton sharedSingleton].fullscreenRect.size.width,
[Singleton sharedSingleton].fullscreenRect.size.height);
[newCALayer setAutoresizingMask:kCALayerWidthSizable | kCALayerHeightSizable];
//CGImageRelease(cgImageFullResolution); (bonus points if you can explain why I can't release this! I mean, I can release the scaled image ok??)
CGContextRelease(context);
[image release];
I am doing all of this from a background thread in order to preload pictures so my GUI feels snappy. It took some work getting synchronization and what not set up so the CALayers ends up in view.
But I believe the term for describing how fast this is would be "it's a dog".
Comparing to IKImageView - that thing flings up thumbnails of images faster than I can scroll.
Does anybody have some suggestions for how to handle this better than I am doing it now?
In other words, my problem is that I want to have a super-fast UX. I believe the way to accomplish this is by preloading things to CALayers (this may be wrong? I tried NSImageView and some IK-stuff, but at least CALayer is better than that).
ImageKit is probably using CGImageSourceCreateThumbnailAtIndex() to quickly get an image appropriate to the destination, rather than reading in the entire image file.
Here:
NSImage *image = [[[NSImage alloc] initWithContentsOfFile:path] autorelease];
[image setScalesWhenResized:YES]; // *
[image setDataRetained:YES]; // *
[image setSize:desiredNewSize];
Then use the image as it is.
As for why your app is slow, run it under Instruments. That will tell you specifically where you are spending the majority of the processor time you use—it may not be in your scaling code after all.
*Since 10.6, these messages do nothing useful and are deprecated, so you can omit them if you are requiring Snow Leopard or later.

Resources