Square thumbnail from UIImagePickerViewController image
Asked Answered
C

4

15

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?

Constanceconstancia answered 26/7, 2013 at 14:50 Comment(3)
Note - a Meta user with no knowledge of iOS edited away some super-handy code from Clever's answer below. if you're actually programming and actual want a useful answer and actually want to use Clever's (spectacular) ideas, just click the "edited" button below, you will see the history and can cut, paste, and send to the app store :)Listen
@JoeBlow I don't see any removed code in the edit history of Clever Error's answer.Berton
stackoverflow.com/posts/17884863/revisions rev "5"Listen
D
54

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;
}
Drivel answered 26/7, 2013 at 15:4 Comment(5)
This approach distort the image, i already tried something like this.Constanceconstancia
I added a version that should not distort the image.Drivel
Now it does not distort, but the thumbnail image is not squared, i mean, it is 90x90 but the image was not scaled to fill the square.Constanceconstancia
Oops... The MIN should have been a MAX.Drivel
@JoeBlow and Clever Error this answer is being discussed in meta meta.https://mcmap.net/q/245179/-why-has-my-app_data-aspnetdb-mdf-file-grown-to-10mb/792066Peripheral
A
6

Here is a link you can refer to:

1) UIImage+Resize

2) UIImage+ProportionalFill

Here you can resize the view and also add proportional fill so that proportionately the aspect ratio and other functions like crop, scale, etc.

You can use -(UIImage*)resizedImageToSize:(CGSize*)size method under reference link UIImage+Resize above just to reize the image.

Hope this helps you.

Let me know if you want more help.

Albertinaalbertine answered 26/7, 2013 at 15:1 Comment(2)
The second link was really helpful! I still don't know what the problem was, but now is working, and it will save me a lot of time. Thanks!Constanceconstancia
@douglasd3: Second link is really helpful for lot of features like cropping and Scaling.Albertinaalbertine
E
6

Swift version of Clever Error's answer above: Updated to swift 4.1

func resizeImageToCenter(image: UIImage) -> UIImage {
    let size = CGSize(width: 100, height: 100)

    // Define rect for thumbnail
    let scale = max(size.width/image.size.width, size.height/image.size.height)
    let width = image.size.width * scale
    let height = image.size.height * scale
    let x = (size.width - width) / CGFloat(2)
    let y = (size.height - height) / CGFloat(2)
    let thumbnailRect = CGRect.init(x: x, y: y, width: width, height: height)

    // Generate thumbnail from image
    UIGraphicsBeginImageContextWithOptions(size, false, 0)
    image.draw(in: thumbnailRect)
    let thumbnail = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()
    return thumbnail!
}
Emblazonment answered 16/4, 2016 at 0:53 Comment(1)
For Swift 3, change the let thumbnailRect assignment to CGRect(x: x, y: y, width: width, height: height)Ascetic
S
0

Another way to do this - ran into an issue the other day and other people might have the same issue when using the apple sample code which is best for large images.

As mentioned in the accepted answer, this has the problem of loading the entire image into memory and iOS can kill your app if it's too large. CGImageSourceCreateThumbnailAtIndex in Apple's example code is more efficient, however there is a bug in the example. The 3rd parameter in the CFDictionaryCreate is the "numValues" to copy. Needs to be 3 not 2.

myOptions = CFDictionaryCreate(NULL, (const void **) myKeys,  
                               (const void **) myValues, 
                               3, //Changed to 3  
                               &kCFTypeDictionaryKeyCallBacks,  
                               & kCFTypeDictionaryValueCallBacks);  
Sheerness answered 10/9, 2015 at 16:33 Comment(1)
ok, but I tested this method against the accept answer using Quartz and the accepted answer is 15 times faster, specially if you already have the UIImage, because you have to convert it to CFData so you can use CGImageSourceCreateThumbnailAtIndex. This whole process is 15 times slower.Token

© 2022 - 2024 — McMap. All rights reserved.