Square thumbnail from UIImagePickerViewController image

douglasd3 picture douglasd3 · Jul 26, 2013 · Viewed 13k times · Source

In my app the user selects an image or take a picture using UIImagePickerViewController. Once the image was selected I want to display its thumbnail on a square UIImageView (90x90).

I'm using Apple's code to create a thumbnail. The problem is the thumbnail is not squared, the function, after set kCGImageSourceThumbnailMaxPixelSize key to 90, seems only to resize the image's height, and as far as I know the kCGImageSourceThumbnailMaxPixelSize key should be responsible for setting the height and width of the thumbnail.

Here is a glimpse of my code:

- (void)imagePickerController:(UIImagePickerController *)picker
    didFinishPickingMediaWithInfo:(NSDictionary *)info {

    UIImage *image = [info objectForKey:UIImagePickerControllerOriginalImage];

    NSData *imageData = UIImageJPEGRepresentation (image, 0.5);

    // My image view is 90x90
    UIImage *thumbImage = MyCreateThumbnailImageFromData(imageData, 90);

    [myImageView setImage:thumbImage];

    if (picker.sourceType == UIImagePickerControllerSourceTypeCamera) {

        UIImageWriteToSavedPhotosAlbum(image, self, @selector(image:didFinishSavingWithError:contextInfo:), nil);
    }

    [picker dismissViewControllerAnimated:YES completion:nil];
}

UIImage* MyCreateThumbnailImageFromData (NSData * data, int imageSize) {

    CGImageRef        myThumbnailImage = NULL;
    CGImageSourceRef  myImageSource;
    CFDictionaryRef   myOptions = NULL;
    CFStringRef       myKeys[3];
    CFTypeRef         myValues[3];
    CFNumberRef       thumbnailSize;

    // Create an image source from NSData; no options.
    myImageSource = CGImageSourceCreateWithData((__bridge CFDataRef)data,
                                                NULL);

    // Make sure the image source exists before continuing.
    if (myImageSource == NULL){
        fprintf(stderr, "Image source is NULL.");
        return  NULL;
    }

    // Package the integer as a  CFNumber object. Using CFTypes allows you
    // to more easily create the options dictionary later.
    thumbnailSize = CFNumberCreate(NULL, kCFNumberIntType, &imageSize);

    // Set up the thumbnail options.
    myKeys[0] = kCGImageSourceCreateThumbnailWithTransform;
    myValues[0] = (CFTypeRef)kCFBooleanTrue;
    myKeys[1] = kCGImageSourceCreateThumbnailFromImageIfAbsent;
    myValues[1] = (CFTypeRef)kCFBooleanTrue;
    myKeys[2] = kCGImageSourceThumbnailMaxPixelSize;
    myValues[2] = thumbnailSize;

    myOptions = CFDictionaryCreate(NULL, (const void **) myKeys,
                                   (const void **) myValues, 2,
                                   &kCFTypeDictionaryKeyCallBacks,
                                   & kCFTypeDictionaryValueCallBacks);

    // Create the thumbnail image using the specified options.
    myThumbnailImage = CGImageSourceCreateThumbnailAtIndex(myImageSource,
                                                           0,
                                                           myOptions);

    UIImage* scaled = [UIImage imageWithCGImage:myThumbnailImage];

    // Release the options dictionary and the image source
    // when you no longer need them.

    CFRelease(thumbnailSize);
    CFRelease(myOptions);
    CFRelease(myImageSource);

    // Make sure the thumbnail image exists before continuing.
    if (myThumbnailImage == NULL) {
        fprintf(stderr, "Thumbnail image not created from image source.");
        return NULL;
    }
    return scaled;
}

And this how my image view is instantiated:

myImageView = [[UIImageView alloc] init];
imageView.contentMode = UIViewContentModeScaleAspectFit;

CGRect rect = imageView.frame;
rect.size.height = 90;
rect.size.width = 90;

imageView.frame = rect;
[imageView setUserInteractionEnabled:YES];

If I don't set imageView.contentMode = UIViewContentModeScaleAspectFit; the thumbnail will be distorted, since it is just a version of my original image with height of 90 pixels.

So, why is my thumbnail not squared?

Answer

Craig Siemens picture Craig Siemens · Jul 26, 2013

The easiest thing to do would be to set the contentMode on your imageView to be UIViewContentModeScaleAspectFill instead. However, this may not be ideal since it would keep the whole image in memory.

Here is some code that I use to resize images.

+ (UIImage *)imageWithImage:(UIImage *)image scaledToSize:(CGSize)size
{
    UIGraphicsBeginImageContextWithOptions(size, NO, 0);
    [image drawInRect:CGRectMake(0, 0, size.width, size.height)];
    UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();    
    UIGraphicsEndImageContext();
    return newImage;
}

This version will keep the image from being distorted if the size is not the same aspect ratio as the image.

+ (UIImage *)imageWithImage:(UIImage *)image scaledToFillSize:(CGSize)size
{
    CGFloat scale = MAX(size.width/image.size.width, size.height/image.size.height);
    CGFloat width = image.size.width * scale;
    CGFloat height = image.size.height * scale;
    CGRect imageRect = CGRectMake((size.width - width)/2.0f,
                                  (size.height - height)/2.0f,
                                  width,
                                  height);

    UIGraphicsBeginImageContextWithOptions(size, NO, 0);
    [image drawInRect:imageRect];
    UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();    
    UIGraphicsEndImageContext();
    return newImage;
}

Often you want just the top square of an image (rather than the center). And you want the final image to be of a certain size, say 128x128, like in the example below.

- (UIImage *)squareAndSmall // as a category (so, 'self' is the input image)
{
    // fromCleverError's original
    // http://stackoverflow.com/questions/17884555
    CGSize finalsize = CGSizeMake(128,128);

    CGFloat scale = MAX(
        finalsize.width/self.size.width,
        finalsize.height/self.size.height);
    CGFloat width = self.size.width * scale;
    CGFloat height = self.size.height * scale;

    CGRect rr = CGRectMake( 0, 0, width, height);

    UIGraphicsBeginImageContextWithOptions(finalsize, NO, 0);
    [self drawInRect:rr];
    UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();    
    UIGraphicsEndImageContext();
    return newImage;
}