Skip to content


Scaling images and creating thumbnails from UIViews

In this blog post I would like to show how you can do some common image-related tasks, like scaling an UIImage object or creating a thumbnail image from a UIView. I’m implementing these features as category for the UIImage class, so these can be used with all UIImage objects without the need to subclass the UIImage class.

Scaling UIImages

Scaling images is important for example when you want to create thumbnail images which are used as an overview for real documents or larger pictures. The standard UIImage class doesn’t provide a method to scale images, but scaling an image isn’t very complicated. You can do it with just 4 lines of code…

UIImage *originalImage = ...;
CGSize destinationSize = ...;

UIGraphicsBeginImageContext(destinationSize);
[originalImage drawInRect:CGRectMake(0,0,destinationSize.width,destinationSize.height)];
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();

At first we have to create and start an image context with the size of the resulting image. All drawing operations which take place after the UIGraphicsBeginImageContext() call will be done in the newly created image context. This means, we are no longer drawing on the screen. All we need to do now is to draw the original image in the size of the final image. The drawing takes place in the newly created image context. Afterwards we can use UIGraphicsGetImageFromCurrentImageContext() to get an image from the image context. Finally we need to call UIGraphicsEndImageContext() to make sure that the system will release the image context again, because we don’t need it anymore. And of course the drawing will take place on the screen again after calling this function.

Creating an UIImage from a UIView

If you need an image from the content of a UIView object (like a whole web page that is displayed in a UIWebView object) or even a complex hierarchy of UIView objects so you can save it as PNG or JPEG image or use it as thumbnail image, you can do this also with just a few lines of code…

UIView *view = ...;

CGSize size = [view bounds].size;
UIGraphicsBeginImageContext(size);
[[view layer] renderInContext:UIGraphicsGetCurrentContext()];
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();

The code is very similar to the code from above to scale the image. But instead of drawing an image into the new image context, we use the “renderInContext:” method to draw the content of the view (which includes its subviews) into the image context. The resulting image will have the same size as the original UIView.

Retina Display

With the new iPhone 4 and the 4th generation iPod Touch, the iOS does also support high resolution screens. But the larger screen resolution is not used to display more content, instead it is used to display a sharper image with more details. Basically the iOS uses the same coordinate system on all screens with a size of 480×320 points. The only difference of the retinal displays is that there’re additional pixels between these points. Most drawing functions of the iOS automatically use these additional pixels to draw sharper and more detailed content. But there are exceptions when the iOS can not create more details itself. For example for bitmap images (UIImage objects), there’s no way to automatically generate a more detailed image for the retina displays. So iOS 4.0 introduces a new “scale” property for images. This property can be 1.0 (the default value) which is used for standard resolution and 2.0 for the more detailed retina resolution with twice as much details. If the “scale” property has a value of 1.0, one pixel of the image will be drawn on 2*2 (= 4 pixels) on the retina display, if the “scale” property has a value of 2, one image pixel is drawn on one screen pixel.

The above code will work with all iOS releases (including iOS 3.x which is the latest version that can be used on the first generation iPhone and iPod Touch devices), but it will always use the default “scale” property of 1.0 for images. Which means the code can not improve the image quality on the new retina displays. In order to get more detailed images on retina displays, we need to use “UIGraphicsBeginImageContextWithOptions()” instead of “UIGraphicsBeginImageContext()”, so we can define the “scale” property for the new image context as well. But “UIGraphicsBeginImageContextWithOptions()” is only available since iOS 4, so we have to check if we can use this function. If it is not available or if the device does not have a retina display, we still need to use the default scaling of 1.0 …

if ([[UIScreen mainScreen] respondsToSelector:@selector(scale)]) {
     if ([[UIScreen mainScreen] scale] == 2.0) {
        UIGraphicsBeginImageContextWithOptions(size, YES, 2.0);
     } else {
        UIGraphicsBeginImageContext(size);
    }
} else {
    UIGraphicsBeginImageContext(size);
}

Combining everything into a Objective-C Category

Now it’s time to combine all of the above and create a category for the UIImage class. I’m implementing three simple methods to create a new UIImage from a UIView or UIImage object.

  • imageWithImage:scaledToSize:” creates an image from a UIImage and scales it to a given size
  • imageFromView:” creates an image from a UIView
  • imageFromView:” creates an image from a UIView and scales it to a given size.

MyImage.h:

@interface UIImage (MyImage)
+ (UIImage*)imageFromView:(UIView*)view;
+ (UIImage*)imageFromView:(UIView*)view scaledToSize:(CGSize)newSize;
+ (UIImage*)imageWithImage:(UIImage*)image scaledToSize:(CGSize)newSize;
@end

MyImage.m:

#import "MyImage.h"

@implementation UIImage (MyImage)

+ (void)beginImageContextWithSize:(CGSize)size
{
    if ([[UIScreen mainScreen] respondsToSelector:@selector(scale)]) {
        if ([[UIScreen mainScreen] scale] == 2.0) {
            UIGraphicsBeginImageContextWithOptions(size, YES, 2.0);
        } else {
            UIGraphicsBeginImageContext(size);
        }
    } else {
        UIGraphicsBeginImageContext(size);
    }
}

+ (void)endImageContext
{
    UIGraphicsEndImageContext();
}

+ (UIImage*)imageFromView:(UIView*)view
{
    [self beginImageContextWithSize:[view bounds].size];
    BOOL hidden = [view isHidden];
    [view setHidden:NO];
    [[view layer] renderInContext:UIGraphicsGetCurrentContext()];
    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
    [self endImageContext];
    [view setHidden:hidden];
    return image;
}

+ (UIImage*)imageFromView:(UIView*)view scaledToSize:(CGSize)newSize
{
    UIImage *image = [self imageFromView:view];
    if ([view bounds].size.width != newSize.width ||
            [view bounds].size.height != newSize.height) {
        image = [self imageWithImage:image scaledToSize:newSize];
    }
    return image;
}

+ (UIImage*)imageWithImage:(UIImage*)image scaledToSize:(CGSize)newSize
{
    [self beginImageContextWithSize:newSize];
    [image drawInRect:CGRectMake(0,0,newSize.width,newSize.height)];
    UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
    [self endImageContext];
    return newImage;
}
@end

Optimizations

Scaling images can temporarily require lots of additional memory, depending of the size of the images. The original image is first drawn in an image context. This image context must provide a bitmap buffer internally in which the drawing takes place. This buffer needs some memory. Also when the final image is created from this image context, some more memory is required. Only afterwards it is possible to free up the memory again by releasing the image context and maybe the original image.

For retina displays, the bitmap buffers for images where the “scale” property has a value of 2.0 are four times larger as for non-retina displays (scale = 1.0). So a single full screen image requires approximately 2.5 MB of memory. And because multiple bitmap buffers are required while scaling, the memory requirements can be much higher than without retina display.

This is not a big deal for the iPhone 4 which has much more main memory (512 MB) than all other devices. But for the 4th generation iPod Touch, the increased memory usage for the image buffers for the retina display is a problem. The 4th generation iPod Touch only has 256 MB of main memory, so it’s almost always running on its memory limits.

So to reduce the memory usage when creating a small thumbnail image from a UIView (which means you’re only interested in a scaled-down(!) image from a UIView), you should first create an image from the UIView with a “scale” property of 1.0. This will reduce the memory usage for the newly created image from the view. And because you’re about to scale down this image, it doesn’t matter that it doesn’t have all the details it would have with scale=2.0. When you then scale down this image using a “scale” property of 2.0, the final (smaller) image can still can get all the details it needs from the image you’ve created before.

If you have to create many images form large UIViews, you may consider to try to reduce the memory requirements this way (especially for iPod Touch with its limited amount of RAM). If you only need to deal with a few smaller views, you probably don’t need to care about this.

Posted in iPhone & iPod Touch, Programming, Tips & tricks.

Tagged with , , , .


13 Responses

Stay in touch with the conversation, subscribe to the RSS feed for comments on this post.

  1. Brian says

    Great post, thanks. Why is setHidden:NO required? I was hoping to avoid having a view be visible. My goal is to leverage UIViews, render to a UIImage, and ultimately to an OpenGL ES texture object which I will then use in a 3D scene. I don’t want the view itself to ever be visible on screen.

  2. Alexander Alexander says

    @Brian
    Hidden views won’t draw, so I make them visible before drawing them. But because a new image context is created before doing this, all drawing takes place in this image context. So nothing is visible on screen, don’t worry.

  3. Brian says

    Ah, makes sense. Thanks.

  4. Carlos says

    I’m trying to load a https webpage using a self-signed certificate inside an UIWebView, but haven’t been able to find how, I always get error -1201 (untrusted server certificate). After searching and trying different solutions for hours I got it working on the simulator (using allowsAnyHTTPSCertificateForHost), but doesn’t work on hardware, and I have read this method is not approved by Apple. Could you give me any advice on this?

    I know this is not very related to the current post, but it’s driving me crazy and I’ve seen you have it implemented on iCab (great app by the way).

    Thanks.

  5. Alexander Alexander says

    @Carlos
    You should check your code again. This should work on the hardware as well just like it does work in the simulator.

  6. nemo says

    “This is not a big deal for the iPhone 4 which has much more main memory (512 MB) than all other devices.”

    Are you sure? I have just got bit by a 22MB limit for running app within iOS.
    When this limit is reached, iOS just kills the app if it won’t release mem.
    And I got e.g. iPad2 with 256MB with nothing else running on it so there’s lot’s of free mem…
    Of course it is not officially documented, just try it :)
    Another ‘feature’ from Apple…

  7. Alexander Alexander says

    @nemo
    There’s definitely no 22MB limit for iOS Apps. iOS Apps can get much more memory, if available.

    But please note that the main memory of an iOS device will be shared by all running Apps, background tasks and the iOS itself. And there’s no such thing as a swap file, like on normal computers, where the disk space can be used to get more memory.

    And there’s never something like “nothing is running”. The iOS is always running, so the system is always using memory for screen buffers, background tasks like the audio player, network stuff, many system tasks etc.

    On devices with only 256 MB or even only 128 MB (the very first iPhone and iPod Touch models), your App might only have 22 MB memory left for your Apps data, all the rest can be used by the iOS and the application code of the App itself.

    But because of the serious memory limits, you have to be very careful with your own memory management. Make sure that you release the memory you don’t need anymore as early as possible, especially if you might need a lot of temporary memory while processing data in a loop.

  8. Abhijit Sathe says

    Hi, This is a very good post.
    But my problem is something with offscreen content. I have a UIScrollview It has a large uiImage view on it . So, at start the image view is displaying 200,200 size so the part of large image i.e 200,200 is rendered :( and i want the whole image to be rendered so can you please help me on this i have searched but not getting help on rendering content i.e not viewable or displayed on screen
    Thanks Please Reply…

  9. Alexander Alexander says

    @Abhijit Sathe
    I think, if your problem is that only the visible part of the image is copied and you can’t make the source big enough so that it can be copied in one step, then you need to copy the whole image step by step. You create the image context of the required size and then scroll source view to the first “tile”, then draw it to the correct location within the destination image context and then scroll to the next tile and draw it as well, etc.

    But be careful with the memory requirement of really large views. Images can consume much memory (usually 4 Bytes per Pixel: RGB and Alpha). So creating images of views that are much larger than the screen can be a problem on some devices.

  10. Dave says

    Note: If you use 0 for the scale in UIGraphicsBeginImageContextWithOptions(), the scale factor is set to the scale factor of the device‚Äôs main screen. You don’t have to do the check yourself.

  11. Alexander Alexander says

    @Dave
    Yes, you’re right. Thanks for pointing this out.

    In my own Apps I also have to use a scale of 1 on devices with Retina display sometimes, so I did use the scaling of 1 or 2 explicitly. And I’ve totally forgotten that there’s also the value 0… ;-)

  12. Kris says

    Any tip on how to just copy a part of the UIView? say from CGRectMake(18, 199, 180, 160)

    So basically from the original UIView, I want to move 18 pixels to the right, and 199 pixels down and copy from there.

    I need it, so I can save to photo album.

    I’ve been searching and no answer to this anywhere online.

    Thanks in advance.

  13. Kris says

    I figured it out. I just had to [viewImage drawInRect:CGRectMake(-18,-199,851,363)];

    I set them to negative values, and it worked fine, and set the size to be the exact size of the view I’m copying.



Some HTML is OK

or, reply to this post via trackback.