Resize and crop images before displaying in UITableViewCells
Question
i have an UITableView in my app and i have to load some images that have a fixed width but different heights. I download the images async using an NSOperationQueue and for resizing and cropping i tried to use the solution provided by Jane Sales in this post link text.
i made a custom UITableViewCell class and in it i have a method that is called when the queued operation finishes to download the image. The method is called properly and the images are displayed. When i try to resize the images using the method proposed by Jane the problems appear. When it reaches [sourceImage drawInRect:thumbnailRect]; i receive an exec bad access error and i can't figure out why. I call the method like this:
- (void) setupImage:(UIImage *) anImage{
UIImage *resized = [anImage imageByScalingAndCroppingForSize:CGSizeMake(64, 59)];
if(resized == nil)
resized = [UIImage newImageFromResource:@"thumb2.png"];
[thumbnailView setImage:resized];
}
setupImage is the function called when the NSOperationQueue finishes the download action of anImage.
Could someone give me a clue why i receive the exec bad access error when i try to resize and crop the images? I tried using the same function outside the table view. 80% of the cases it works but there are cases when i receive the same exec bad access error.
Thank you in advance, Sorin
Solution
this is what i'm using for resizing and croping the UIImages (the code is from Jane Sales solution)
@implementation UIImage (Extras)
#pragma mark -
#pragma mark Scale and crop image
- (UIImage*)imageByScalingAndCroppingForSize:(CGSize)targetSize
{
UIImage *sourceImage = self;
UIImage *newImage = nil;
CGSize imageSize = sourceImage.size;
CGFloat width = imageSize.width;
CGFloat height = imageSize.height;
CGFloat targetWidth = targetSize.width;
CGFloat targetHeight = targetSize.height;
CGFloat scaleFactor = 0.0;
CGFloat scaledWidth = targetWidth;
CGFloat scaledHeight = targetHeight;
CGPoint thumbnailPoint = CGPointMake(0.0,0.0);
if (CGSizeEqualToSize(imageSize, targetSize) == NO)
{
CGFloat widthFactor = targetWidth / width;
CGFloat heightFactor = targetHeight / height;
if (widthFactor > heightFactor)
scaleFactor = widthFactor; // scale to fit height
else
scaleFactor = heightFactor; // scale to fit width
scaledWidth = width * scaleFactor;
scaledHeight = height * scaleFactor;
// center the image
if (widthFactor > heightFactor)
{
thumbnailPoint.y = (targetHeight - scaledHeight) * 0.5;
}
else
if (widthFactor < heightFactor)
{
thumbnailPoint.x = (targetWidth - scaledWidth) * 0.5;
}
}
UIGraphicsBeginImageContext(targetSize); // this will crop
CGRect thumbnailRect = CGRectZero;
thumbnailRect.origin = thumbnailPoint;
thumbnailRect.size.width = scaledWidth;
thumbnailRect.size.height = scaledHeight;
[sourceImage drawInRect:thumbnailRect];
newImage = UIGraphicsGetImageFromCurrentImageContext();
if(newImage == nil)
NSLog(@"could not scale image");
//pop the context to get back to the default
UIGraphicsEndImageContext();
return newImage;
}
i call the above function like i said before:
- (void) setupImage:(UIImage *) anImage
{
UIImage *resized = [anImage imageByScalingAndCroppingForSize:CGSizeMake(64, 59)];
[thumbnailView setImage:resized];
}
the images are downloaded async when a cell is displayed in the table view. the setupImage function receives the image from an NSOperation that downloads it async. the problem is, like i said above, when it reaches [sourceImage drawInRect:thumbnailRect];
thumbnailView is an UIImageView that is a subview of my custom TableViewCell.
hope this clears things a bit about what i'm using in my code. Thank you for the help. Sorin
OTHER TIPS
Try the following code. The createImage:image:width:height method requires a source CGImageRef and the new width and height of the final UIImage that will be returned. When using as source an UIImage, you can obtain the corresponding CGImageRef as follows:
UIImage *sourceImage;
CGImageRef *sourceRef = [sourceImage CGImage];
// Draw the image into a pixelsWide x pixelsHigh bitmap and use that bitmap to
// create a new UIImage
- (UIImage *) createImage: (CGImageRef) image width: (int) pixelWidth height: (int) pixelHeight
{
// Set the size of the output image
CGRect aRect = CGRectMake(0.0f, 0.0f, pixelWidth, pixelHeight);
// Create a bitmap context to store the new thumbnail
CGContextRef context = MyCreateBitmapContext(pixelWidth, pixelHeight);
// Clear the context and draw the image into the rectangle
CGContextClearRect(context, aRect);
CGContextDrawImage(context, aRect, image);
// Return a UIImage populated with the new resized image
CGImageRef myRef = CGBitmapContextCreateImage (context);
UIImage *img = [UIImage imageWithCGImage:myRef];
free(CGBitmapContextGetData(context));
CGContextRelease(context);
CGImageRelease(myRef);
return img;
}
// MyCreateBitmapContext: Source based on Apple Sample Code
CGContextRef MyCreateBitmapContext (int pixelsWide,
int pixelsHigh)
{
CGContextRef context = NULL;
CGColorSpaceRef colorSpace;
void * bitmapData;
int bitmapByteCount;
int bitmapBytesPerRow;
bitmapBytesPerRow = (pixelsWide * 4);
bitmapByteCount = (bitmapBytesPerRow * pixelsHigh);
colorSpace = CGColorSpaceCreateDeviceRGB();
bitmapData = malloc( bitmapByteCount );
if (bitmapData == NULL)
{
fprintf (stderr, "Memory not allocated!");
CGColorSpaceRelease( colorSpace );
return NULL;
}
context = CGBitmapContextCreate (bitmapData,
pixelsWide,
pixelsHigh,
8,
bitmapBytesPerRow,
colorSpace,
kCGImageAlphaPremultipliedLast);
if (context== NULL)
{
free (bitmapData);
CGColorSpaceRelease( colorSpace );
fprintf (stderr, "Context not created!");
return NULL;
}
CGColorSpaceRelease( colorSpace );
return context;
}