Advertise here




Advertise here

Howdy, Stranger!

It looks like you're new here. If you want to get involved, click one of these buttons!

Resize Image High Quality

shawnshawn Posts: 31Registered Users
edited September 2011 in iOS SDK Development
I have done lots of searching for a way to resize images via the iPhone SDK and I have come across a few methods which "work" but the resulting image does not look nearly as good as if you took the full resolution image and told it to draw inside a rectangle; which obviously if you could use the same interpolation routines as that drawing call does you should be able to get the same result. So what I am looking for is a way to resize an image with minimal visual anomalies. I have found that if I use a re-sized image when drawing it is much quicker than using the full resolution image (obviously).

Please make sure any links provided are current; I have been given countless links to methods which are void given the current version of the SDK as well as some links that provide "questionable" means to accomplish this task using functions which are not fully supported by the SDK and could easily change in the future.
Post edited by shawn on
«1

Replies

  • shawnshawn Posts: 31Registered Users
    edited October 2008
    The best method I have found that even comes close to doing what I want is the following line of code:
    UIImage *img = [[UIImage imageNamed:@image.png] _imageScaledToSize:CGSizeMake(32.0f, 32.0f) interpolationQuality:1];
    
    The problem with this code is that it is un-documented which means it could go by the wayside and the interpolation is poor.
  • PhoneyDeveloperPhoneyDeveloper Posts: 1,431Registered Users
    edited October 2008
    Almost certainly the way that simply drawing an image onscreen works is by CGContextDrawImage(). To scale an image create a bitmap context whose axial ratio is the same as the original image's. CGContextDrawImage(), CGBitmapContextCreateImage(), UIImage imageWithCGImage.

    Have you tried doing it that way?
  • shawnshawn Posts: 31Registered Users
    edited October 2008
    Thanks for the reply Phoney!

    I have not tried it using the CG library; I am not terribly familiar with how to create a bitmap and draw to it. Do you have any resources showing a similar action or anything that may help me down the right path?
  • PhoneyDeveloperPhoneyDeveloper Posts: 1,431Registered Users
    edited October 2008
    //	==============================================================
    //	resizedImage
    //	==============================================================
    // Return a scaled down copy of the image.  
    
    UIImage* resizedImage(UIImage *inImage, CGRect thumbRect)
    {
    	CGImageRef			imageRef = [inImage CGImage];
    	CGImageAlphaInfo	alphaInfo = CGImageGetAlphaInfo(imageRef);
    	
    	// There's a wierdness with kCGImageAlphaNone and CGBitmapContextCreate
    	// see Supported Pixel Formats in the Quartz 2D Programming Guide
    	// Creating a Bitmap Graphics Context section
    	// only RGB 8 bit images with alpha of kCGImageAlphaNoneSkipFirst, kCGImageAlphaNoneSkipLast, kCGImageAlphaPremultipliedFirst,
    	// and kCGImageAlphaPremultipliedLast, with a few other oddball image kinds are supported
    	// The images on input here are likely to be png or jpeg files
    	if (alphaInfo == kCGImageAlphaNone)
    		alphaInfo = kCGImageAlphaNoneSkipLast;
    
    	// Build a bitmap context that's the size of the thumbRect
    	CGContextRef bitmap = CGBitmapContextCreate(
    				NULL,
    				thumbRect.size.width,		// width
    				thumbRect.size.height,		// height
    				CGImageGetBitsPerComponent(imageRef),	// really needs to always be 8
    				4 * thumbRect.size.width,	// rowbytes
    				CGImageGetColorSpace(imageRef),
    				alphaInfo
    		);
    
    	// Draw into the context, this scales the image
    	CGContextDrawImage(bitmap, thumbRect, imageRef);
    
    	// Get an image from the context and a UIImage
    	CGImageRef	ref = CGBitmapContextCreateImage(bitmap);
    	UIImage*	result = [UIImage imageWithCGImage:ref];
    
    	CGContextRelease(bitmap);	// ok if NULL
    	CGImageRelease(ref);
    
    	return result;
    }
    
  • shawnshawn Posts: 31Registered Users
    edited October 2008
    Thanks Phoney! With a few tweaks that method worked great with no warnings!
  • tgersictgersic Posts: 7New Users
    edited October 2008
    Hi All,

    I've tried using the function provided by Phoney, and I've found that the memory associated with "ref" never gets released. If I call this function enough times, I'll get a memory warning from the system, and then a crash. It's strange because ref is pretty clearly being released properly with CGImageRelease(ref), but when I watch the object allocations with Leaks, I can see that the memory associated with it is never actually freed. Even more bizarre is that it doesn't get detected as a leak by Leaks, it just never gets freed. I have the exact same problem with _imageScaleToSize. Every time I call it, my memory footprint balloons up by 7MB or so, never to go back down.

    Has anybody experienced this? Any possible solutions?

    Thanks,
    Tom
  • PhoneyDeveloperPhoneyDeveloper Posts: 1,431Registered Users
    edited October 2008
    What are you doing with the UIImage that's returned by this method?
  • tgersictgersic Posts: 7New Users
    edited October 2008
    What are you doing with the UIImage that's returned by this method?

    Hi Phoney, thanks for writing. Ultimately, I'm saving it to an sqlite database. The image is taken from the camera, reduced in size, and then saved to sqlite. However, if I change resizedImage to something like this, I don't have the memory problem:

    UIImage* resizedImage(UIImage *inImage, CGRect thumbRect)
    {
    return inImage;
    }

    Could it be something to do with the database?

    Thanks,
    Tom
  • PhoneyDeveloperPhoneyDeveloper Posts: 1,431Registered Users
    edited October 2008
    Does Leaks report this as a leak or just as memory that's in use? There's no memory leak, per se, in this code. It's what happens to the UIImage object after it's returned that determines whether a leak happens or not. Do you add the UIImage to an array or a dictionary or a UIImageView or anything like that? The db doesn't have anything directly to do with this. Can you tell if it's the CGImageRef itself or the buffer that it creates that's living on?

    There's a possibility that the images are being cached by the framework intentionally.

    You could try changing that line to
    UIImage*	result = [[[UIImage alloc] initWithCGImage:ref] autorelease];
    

    and see if that makes any difference.
  • tgersictgersic Posts: 7New Users
    edited October 2008
    You're correct. Leaks does not report it as a leak. It's just memory that is never freed. What I've found is this:

    After this line...
    CGImageRef	ref = CGBitmapContextCreateImage(bitmap);
    

    ...ref has a retain count of 1. But after the next line...
    UIImage*	result = [UIImage imageWithCGImage:ref];
    

    ...ref has a retain count of 2. I'm not really sure why. The obvious answer of calling CGImageRelease twice to bring the retain count down to 0 causes an EXC_BAD_ACCESS later when I try to access the returned image. I think that may be what you were trying to handle with the autorelease above, which I just tried, but I don't think CGImageRef is autoreleaseable, so no dice...

    Thanks,
    Tom
  • PhoneyDeveloperPhoneyDeveloper Posts: 1,431Registered Users
    edited October 2008
    The CGImageRef is retained by the UIImage. Most likely all UIImages are just a wrapper for CGImageRefs. I would expect that the CGImageRef would be released when the UIImage is released. That's why I asked what you were doing with it when it was returned from the function. The lifespan of the image ref and the UIImage should be the same.

    If you don't do anything at all with the UIImage, just let its lifespan expire when the autorelease pool is drained, then what happens? If the memory goes away then it's a bug in your code. If the memory still remains then it's probably a system issue.
  • ecumeecume Posts: 80Registered Users
    edited October 2008
    managing memory with UIImages is very tricky because the iPhone does so much to try to optimize the use of memory, including caching things behind your back. The more you can stick within the CG domain, the more you will have predictable control of your memory.
  • chuckbrandtchuckbrandt Posts: 2New Users
    edited November 2008
    //	
    
    UIImage* resizedImage(UIImage *inImage, CGRect thumbRect)
    {
    ...
    }
    

    I get the following uncaught exception when I call this code:
    2008-11-02 06:35:15.265 GiftFinder[3208:20b] *** -[NSCFData CGImage]: unrecognized selector sent to instance 0x45d4000
    2008-11-02 06:35:15.267 GiftFinder[3208:20b] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[NSCFData CGImage]: unrecognized selector sent to instance 0x45d4000'

    Is there a framework I need to add or something?

    I am very new to iPhone development and Objective-C so it seems more likely I've done something wrong. Please advise

    in .h file:

    UIImage* resizedImage(UIImage *inImage, CGRect thumbRect);

    in .m file:

    UIImage* resizedImage(UIImage *inImage, CGRect thumbRect)
    {
    ... Exactly as above
    }

    UIImage *image = (UIImage *)ABPersonCopyImageData(person);
    CGRect sz = CGRectMake(0.0f, 0.0f, 69.0f, 69.0f);
    UIImage *smallImage = resizedImage(image, sz);


    Thanks, Chuck Brandt
  • PhoneyDeveloperPhoneyDeveloper Posts: 1,431Registered Users
    edited November 2008
    On this line:
    UIImage *image = (UIImage *)ABPersonCopyImageData(person); 
    
    You're typecasting an NSData* to a UIImage* and they're not the same.

    Try this:
    NSData* imageData = (NSData*)ABPersonCopyImageData(person);
    UIImage* image = [UIImage imageWithData: imageData];
    [imageData release];
    CGRect sz = CGRectMake(0.0f, 0.0f, 69.0f, 69.0f); 
    UIImage *smallImage = resizedImage(image, sz); 
    
  • chuckbrandtchuckbrandt Posts: 2New Users
    edited November 2008
    That worked, thank you very much.

    Chuck
  • ecumeecume Posts: 80Registered Users
    edited November 2008
    where does CGImageRef imageRef get released? shouldn't that be released along with 'ref'?
  • PhoneyDeveloperPhoneyDeveloper Posts: 1,431Registered Users
    edited November 2008
    That doesn't get released. CGImage is an accessor. It doesn't have alloc or copy in its name.
  • caldwell_jasoncaldwell_jason Posts: 1New Users
    edited November 2008
    The code works great for me...almost. I am doing something wrong because my image is rotating counterclock-wise.

    I am grabbing an image from the camera, running it through this code to resample it to screen resolution, then displaying it. At that point, it is rotated. That was not happening before I started resampling it.

    I think it must have something to do with the origin of the CGContext being in the botton left corner. But I've tried several variations on the rect I pass in, all to no avail.

    Any thoughts on getting my images back to right-side-up?

    Thanks.
  • hkkhkk Posts: 11Registered Users
    edited November 2008
    To get the highest possible interpolation quality you might want to add the following line to PhoneyDeveloper's code, right after the creation of the CGContext:
    CGContextSetInterpolationQuality(bitmap, kCGInterpolationHigh)
    
    I have not done any real tests how much this matters (if at all). But I think it can't hurt (unless performance is a big issue).
  • StitchStitch Posts: 401Registered Users @ @
    edited November 2008
    I'm trying to resize UIImages before saving them as images to the documents folder.

    I've implemented the code below from PhoneyDeveloper which is working well for resizing my images.

    The trouble is the images are rotated 90 degrees anti-clockwise. Is there an easy way to fix this? Or another solution to resize UIImages? It must be documented though.

    Thanks
    //	==============================================================
    //	resizedImage
    //	==============================================================
    // Return a scaled down copy of the image.  
    
    UIImage* resizedImage(UIImage *inImage, CGRect thumbRect)
    {
    	CGImageRef			imageRef = [inImage CGImage];
    	CGImageAlphaInfo	alphaInfo = CGImageGetAlphaInfo(imageRef);
    	
    	// There's a wierdness with kCGImageAlphaNone and CGBitmapContextCreate
    	// see Supported Pixel Formats in the Quartz 2D Programming Guide
    	// Creating a Bitmap Graphics Context section
    	// only RGB 8 bit images with alpha of kCGImageAlphaNoneSkipFirst, kCGImageAlphaNoneSkipLast, kCGImageAlphaPremultipliedFirst,
    	// and kCGImageAlphaPremultipliedLast, with a few other oddball image kinds are supported
    	// The images on input here are likely to be png or jpeg files
    	if (alphaInfo == kCGImageAlphaNone)
    		alphaInfo = kCGImageAlphaNoneSkipLast;
    
    	// Build a bitmap context that's the size of the thumbRect
    	CGContextRef bitmap = CGBitmapContextCreate(
    				NULL,
    				thumbRect.size.width,		// width
    				thumbRect.size.height,		// height
    				CGImageGetBitsPerComponent(imageRef),	// really needs to always be 8
    				4 * thumbRect.size.width,	// rowbytes
    				CGImageGetColorSpace(imageRef),
    				alphaInfo
    		);
    
    	// Draw into the context, this scales the image
    	CGContextDrawImage(bitmap, thumbRect, imageRef);
    
    	// Get an image from the context and a UIImage
    	CGImageRef	ref = CGBitmapContextCreateImage(bitmap);
    	UIImage*	result = [UIImage imageWithCGImage:ref];
    
    	CGContextRelease(bitmap);	// ok if NULL
    	CGImageRelease(ref);
    
    	return result;
    }
    
    <a href="http://itunes.apple.com/WebObjects/MZStore.woa/wa/viewSoftware?id=311941449&mt=8"; target="_blank">BUZZER!</a> : <font color="Sienna">iTunes Library Music Quiz (1 or 2 Player)</font>
  • PhoneyDeveloperPhoneyDeveloper Posts: 1,431Registered Users
    edited November 2008
    Howdy,

    I think the 90 degrees rotated issue is the way things work. I don't have an iPhone so I haven't run into it.

    I think that the solution is to apply a transform to the context to rotate it 90 degrees before the CGContextDrawImage call.
  • alonesalones Posts: 4New Users
    edited August 2009
    One is using Phoney's way (in fact, I've fixed Phoney's function considering width and height).

    And the othere is using UIGraphicsBeginImageContext() and UIGraphicsGetImageFromCurrentImageContext().

    F.Y.I, I've tested them.

    Anyway resizing functions are as below,

    #1 - using UIGraphicsBeginImageContext() and UIGraphicsGetImageFromCurrentImageContext()
    -(UIImage*)resizedImage1:(UIImage*)inImage  inRect:(CGRect)thumbRect {
    	// Creates a bitmap-based graphics context and makes it the current context.
    	UIGraphicsBeginImageContext(thumbRect.size);
    	[inImage drawInRect:thumbRect];
    	
    	return UIGraphicsGetImageFromCurrentImageContext();
    }
    

    #2 - updating Phoney's way
    -(UIImage*)resizedImage2:(UIImage*)inImage  inRect:(CGRect)thumbRect { 
    	
    	CGImageRef			imageRef = [inImage CGImage];
    	CGImageAlphaInfo	alphaInfo = CGImageGetAlphaInfo(imageRef);
    	
    	// There's a wierdness with kCGImageAlphaNone and CGBitmapContextCreate
    	// see Supported Pixel Formats in the Quartz 2D Programming Guide
    	// Creating a Bitmap Graphics Context section
    	// only RGB 8 bit images with alpha of kCGImageAlphaNoneSkipFirst, kCGImageAlphaNoneSkipLast, kCGImageAlphaPremultipliedFirst,
    	// and kCGImageAlphaPremultipliedLast, with a few other oddball image kinds are supported
    	// The images on input here are likely to be png or jpeg files
    	if (alphaInfo == kCGImageAlphaNone)
    		alphaInfo = kCGImageAlphaNoneSkipLast;
    	
    	// Build a bitmap context that's the size of the thumbRect
    	CGFloat bytesPerRow;
    	
    	if( thumbRect.size.width > thumbRect.size.height ) {
    		bytesPerRow = 4 * thumbRect.size.width;
    	} else {
    		bytesPerRow = 4 * thumbRect.size.height;
    	}
    	
    	CGContextRef bitmap = CGBitmapContextCreate(	
                    NULL,
                    thumbRect.size.width,		// width
                    thumbRect.size.height,		// height
                    8, //CGImageGetBitsPerComponent(imageRef),	// really needs to always be 8
                    bytesPerRow, //4 * thumbRect.size.width,	// rowbytes
                    CGImageGetColorSpace(imageRef),
                    alphaInfo
            );
    	
    	// Draw into the context, this scales the image
    	CGContextDrawImage(bitmap, thumbRect, imageRef);
    	
    	// Get an image from the context and a UIImage
    	CGImageRef	ref = CGBitmapContextCreateImage(bitmap);
    	UIImage*	result = [UIImage imageWithCGImage:ref];
    	
    	CGContextRelease(bitmap);	// ok if NULL
    	CGImageRelease(ref);
    	
    	return result;
    }
    
  • vocarovocaro Posts: 15Registered Users
    edited August 2009
    alones wrote: »
    (in fact, I've fixed Phoney's function considering width and height).
    ...
    	CGFloat bytesPerRow;
    	
    	if( thumbRect.size.width > thumbRect.size.height ) {
    		bytesPerRow = 4 * thumbRect.size.width;
    	} else {
    		bytesPerRow = 4 * thumbRect.size.height;
    	}
    

    Alones, your fix is wrong. The bytesPerRow parameter is independent of aspect ratio. Even if the image is wider than it is tall, that doesn't affect the bytes per row. PhoneyDeveloper's original code is correct.
  • vocarovocaro Posts: 15Registered Users
    edited August 2009
    Stitch wrote: »
    The trouble is the images are rotated 90 degrees anti-clockwise. Is there an easy way to fix this?

    Yes, if you take a picture with the iPhone's camera, the UIImage.imageOrientation property might be set to UIImageOrientationDown, UIImageOrientationRight, or whatever, and the resize code posted here doesn't take that into account. For a fix, see this thread.
  • vocarovocaro Posts: 15Registered Users
    edited August 2009
    //	resizedImage
    ...
    

    Phoney, there are some problems with your code. In particular, the last parameter takes a CGBitmapInfo type, but you're passing a CGImageAlphaInfo type. This can chop off the byte ordering information stored in CGBitmapInfo, resulting in odd problems such as pink-tinted images. Also, you're hard coding the bytesPerRow value when in fact it should be based on the original image. The following is a revised version of your code:
    // Returns a rescaled copy of the image; its imageOrientation will be UIImageOrientationUp
    // If the new size is not integral, it will be rounded up
    - (UIImage *)makeResizedImage:(CGSize)newSize quality:(CGInterpolationQuality)interpolationQuality {
        CGRect newRect = CGRectIntegral(CGRectMake(0, 0, newSize.width, newSize.height));
        CGImageRef imageRef = self.CGImage;
    
        // Compute the bytes per row of the new image
        size_t bytesPerRow = CGImageGetBitsPerPixel(imageRef) / CGImageGetBitsPerComponent(imageRef) * newRect.size.width;
        bytesPerRow = (bytesPerRow + 15) & ~15;  // Make it 16-byte aligned
        
        // Build a bitmap context that's the same dimensions as the new size
        CGContextRef bitmap = CGBitmapContextCreate(NULL,
                                                    newRect.size.width,
                                                    newRect.size.height,
                                                    CGImageGetBitsPerComponent(imageRef),
                                                    bytesPerRow,
                                                    CGImageGetColorSpace(imageRef),
                                                    CGImageGetBitmapInfo(imageRef));
        
        CGContextSetInterpolationQuality(bitmap, interpolationQuality);
    
        // Draw into the context; this scales the image
        CGContextDrawImage(bitmap, newRect, imageRef);
        
        // Get the resized image from the context and a UIImage
        CGImageRef resizedImageRef = CGBitmapContextCreateImage(bitmap);
        UIImage *resizedImage = [UIImage imageWithCGImage:resizedImageRef];
        
        // Clean up
        CGContextRelease(bitmap);
        CGImageRelease(resizedImageRef);
        
        return resizedImage;
    }
    

    As with your original function, this code works as expected only when UIImage.imageOrientation == UIImageOrientationUp.
«1
Sign In or Register to comment.