`
bengan
  • 浏览: 201723 次
  • 性别: Icon_minigender_1
  • 来自: 广州
社区版块
存档分类
最新评论

cocoa 图片操作若干

阅读更多
转载自 http://stackoverflow.com/questions/1282830/uiimagepickercontroller-uiimage-memory-and-more



I've noticed that there are many questions about how to handle UIImage objects, especially in conjunction with UIImagePickerController and then displaying it in a view (usually a UIImageView). Here is a collection of common questions and their answers. Feel free to edit and add your own.

I obviously learnt all this information from somewhere too. Various forum posts, StackOverflow answers and my own experimenting brought me to all these solutions. Credit goes to those who posted some sample code that I've since used and modified. I don't remember who you all are - but hats off to you!

How Do I Select An Image From the User's Images or From the Camera?

You use UIImagePickerController. The documentation for the class gives a decent overview of how one would use it, and can be found here.

Basically, you create an instance of the class, which is a modal view controller, display it, and set yourself (or some class) to be the delegate. Then you'll get notified when a user selects some form of media (movie or image in 3.0 on the 3GS), and you can do whatever you want.

My Delegate Was Called - How Do I Get The Media?

The delegate method signature is the following:

-(void)imagePickerController:(UIImagePickerController*)picker
didFinishPickingMediaWithInfo:(NSDictionary*)info;
You should put a breakpoint in the debugger to see what's in the dictionary, but you use that to extract the media. For example:

UIImage* image =[info objectForKey:UIImagePickerControllerOriginalImage];
There are other keys that work as well, all in the documentation.

OK, I Got The Image, But It Doesn't Have Any Geolocation Data. What gives?

Unfortunately, Apple decided that we're not worthy of this information. When they load the data into the UIImage, they strip it of all the EXIF/Geolocation data. But, see the answer to the next question for a way to get at the original image data (on iOS 4+)

Can I Get To The Original File Representing This Image on the Disk?

As of iOS 4, you can, but it's very annoying. Use the following code to get an AssetsLibrary URL for the image, and then pass the URL to assetForURL:resultBlock:failureBlock:

NSURL *referenceURL =[info objectForKey:UIImagePickerControllerReferenceURL];
ALAssetsLibrary*library =[[ALAssetsLibrary alloc] init];
[library assetForURL:referenceURL resultBlock:^(ALAsset*asset){
    // code to handle the asset here
} failureBlock:^(NSError*error){
    // error handling
}];
[library release];
It's annoying because the user is asked if your application can access your current location, which is rather confusing since you are actually trying to access the user's photo library. Unless you're actually trying to get at the EXIF location data, the user is going to be a bit confused.

Make sure to include the AssetsLibrary framework to make this work.

How Can I Look At The Underlying Pixels of the UIImage?

Since the UIImage is immutable, you can't look at the direct pixels. However, you can make a copy. The code to this looks something like this:

UIImage* image =...;// An image
NSData* pixelData =(NSData*)CGDataProviderCopyData(CGImageGetDataProvider(image.CGImage));
unsignedchar* pixelBytes =(unsignedchar*)[pixelData bytes];

// Take away the red pixel, assuming 32-bit RGBA
for(int i =0; i <[pixelData length]; i +=4){
        pixelBytes[i]=0;// red
        pixelBytes[i+1]= pixelBytes[i+1];// green
        pixelBytes[i+2]= pixelBytes[i+2];// blue
        pixelBytes[i+3]= pixelBytes[i+3];// alpha
}
However, note that CGDataProviderCopyData provides you with an "immutable" reference to the data - meaning you can't change it (and you may get a BAD_ACCESS error if you do). Look at the next question if you want to see how you can modify the pixels.

How Do I Modify The Pixels of the UIImage?

The UIImage is immutable, meaning you can't change it. Apple posted a great article on how to get a copy of the pixels and modify them, and rather than copy and paste it here, you should just go read the article.

Once you have the bitmap context as they mention in the article, you can do something similar to this to get a new UIImage with the modified pixels:

CGImageRefref=CGBitmapContextCreateImage(bitmap);
UIImage* newImage =[UIImage imageWithCGImage:ref];
Do remember to release your references though, otherwise you're going to be leaking quite a bit of memory.

After I Select 3 Images From The Camera, I Run Out Of Memory. Help!

You have to remember that even though on disk these images take up only a few hundred kilobytes at most, that's because they're compressed as a PNG or JPG. When they are loaded into the UIImage, they become uncompressed. A quick over-the-envelope calculation would be:

width x height x 4= bytes in memory
That's assuming 32-bit pixels. If you have 16-bit pixels (some JPGs are stored as RGBA-5551), then you'd replace the 4 with a 2.

Now, images taken with the camera are 1600 x 1200 pixels, so let's do the math:

1600 x 1200 x 4=7,680,000 bytes =~8 MB
8 MB is a lot, especially when you have a limit of around 24 MB for your application. That's why you run out of memory.

OK, I Understand Why I Have No Memory. What Do I Do?

There is never any reason to display images at their full resolution. The iPhone has a screen of 480 x 320 pixels, so you're just wasting space. If you find yourself in this situation, ask yourself the following question: Do I need the full resolution image?

If the answer is yes, then you should save it to disk for later use.

If the answer is no, then read the next part.

Once you've decided what to do with the full-resolution image, then you need to create a smaller image to use for displaying. Many times you might even want several sizes for your image: a thumbnail, a full-size one for displaying, and the original full-resolution image.

OK, I'm Hooked. How Do I Resize the Image?

Unfortunately, there is no defined way how to resize an image. Also, it's important to note that when you resize it, you'll get a new image - you're not modifying the old one.

There are a couple of methods to do the resizing. I'll present them both here, and explain the pros and cons of each.

Method 1: Using UIKit

+(UIImage*)imageWithImage:(UIImage*)image scaledToSize:(CGSize)newSize;
{
    // Create a graphics image context
    UIGraphicsBeginImageContext(newSize);

    // Tell the old image to draw in this new context, with the desired
    // new size
    [image drawInRect:CGRectMake(0,0,newSize.width,newSize.height)];

    // Get the new image from the context
    UIImage* newImage =UIGraphicsGetImageFromCurrentImageContext();

    // End the context
    UIGraphicsEndImageContext();

    // Return the new image.
    return newImage;
}
This method is very simple, and works great. It will also deal with the UIImageOrientation for you, meaning that you don't have to care whether the camera was sideways when the picture was taken. However, this method is not thread safe, and since thumbnailing is a relatively expensive operation (approximately ~2.5s on a 3G for a 1600 x 1200 pixel image), this is very much an operation you may want to do in the background, on a separate thread.

Method 2: Using CoreGraphics

+(UIImage*)imageWithImage:(UIImage*)sourceImage scaledToSize:(CGSize)newSize;
{
    CGFloat targetWidth = targetSize.width;
    CGFloat targetHeight = targetSize.height;

    CGImageRef imageRef =[sourceImage CGImage];
    CGBitmapInfo bitmapInfo =CGImageGetBitmapInfo(imageRef);
    CGColorSpaceRef colorSpaceInfo =CGImageGetColorSpace(imageRef);

    if(bitmapInfo == kCGImageAlphaNone){
        bitmapInfo = kCGImageAlphaNoneSkipLast;
    }

    CGContextRef bitmap;

    if(sourceImage.imageOrientation ==UIImageOrientationUp|| sourceImage.imageOrientation ==UIImageOrientationDown){
        bitmap =CGBitmapContextCreate(NULL, targetWidth, targetHeight,CGImageGetBitsPerComponent(imageRef),CGImageGetBytesPerRow(imageRef), colorSpaceInfo, bitmapInfo);

    }else{
        bitmap =CGBitmapContextCreate(NULL, targetHeight, targetWidth,CGImageGetBitsPerComponent(imageRef),CGImageGetBytesPerRow(imageRef), colorSpaceInfo, bitmapInfo);

    }  

    if(sourceImage.imageOrientation ==UIImageOrientationLeft){
        CGContextRotateCTM(bitmap, radians(90));
        CGContextTranslateCTM(bitmap,0,-targetHeight);

    }elseif(sourceImage.imageOrientation ==UIImageOrientationRight){
        CGContextRotateCTM(bitmap, radians(-90));
        CGContextTranslateCTM(bitmap,-targetWidth,0);

    }elseif(sourceImage.imageOrientation ==UIImageOrientationUp){
        // NOTHING
    }elseif(sourceImage.imageOrientation ==UIImageOrientationDown){
        CGContextTranslateCTM(bitmap, targetWidth, targetHeight);
        CGContextRotateCTM(bitmap, radians(-180.));
    }

    CGContextDrawImage(bitmap,CGRectMake(0,0, targetWidth, targetHeight), imageRef);
    CGImageRefref=CGBitmapContextCreateImage(bitmap);
    UIImage* newImage =[UIImage imageWithCGImage:ref];

    CGContextRelease(bitmap);
    CGImageRelease(ref);

    return newImage;
}
The benefit of this method is that it is thread-safe, plus it takes care of all the small things (using correct color space and bitmap info, dealing with image orientation) that the UIKit version does.

How Do I Resize and Maintain Aspect Ratio (like the AspectFill option)?

It is very similar to the method above, and it looks like this:

+(UIImage*)imageWithImage:(UIImage*)sourceImage scaledToSizeWithSameAspectRatio:(CGSize)targetSize;

    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;
        }
        elseif(widthFactor < heightFactor){
            thumbnailPoint.x =(targetWidth - scaledWidth)*0.5;
        }
    }    

    CGImageRef imageRef =[sourceImage CGImage];
    CGBitmapInfo bitmapInfo =CGImageGetBitmapInfo(imageRef);
    CGColorSpaceRef colorSpaceInfo =CGImageGetColorSpace(imageRef);

    if(bitmapInfo == kCGImageAlphaNone){
        bitmapInfo = kCGImageAlphaNoneSkipLast;
    }

    CGContextRef bitmap;

    if(sourceImage.imageOrientation ==UIImageOrientationUp|| sourceImage.imageOrientation ==UIImageOrientationDown){
        bitmap =CGBitmapContextCreate(NULL, targetWidth, targetHeight,CGImageGetBitsPerComponent(imageRef),CGImageGetBytesPerRow(imageRef), colorSpaceInfo, bitmapInfo);

    }else{
        bitmap =CGBitmapContextCreate(NULL, targetHeight, targetWidth,CGImageGetBitsPerComponent(imageRef),CGImageGetBytesPerRow(imageRef), colorSpaceInfo, bitmapInfo);

    }  

    // In the right or left cases, we need to switch scaledWidth and scaledHeight,
    // and also the thumbnail point
    if(sourceImage.imageOrientation ==UIImageOrientationLeft){
        thumbnailPoint =CGPointMake(thumbnailPoint.y, thumbnailPoint.x);
        CGFloat oldScaledWidth = scaledWidth;
        scaledWidth = scaledHeight;
        scaledHeight = oldScaledWidth;

        CGContextRotateCTM(bitmap, radians(90));
        CGContextTranslateCTM(bitmap,0,-targetHeight);

    }elseif(sourceImage.imageOrientation ==UIImageOrientationRight){
        thumbnailPoint =CGPointMake(thumbnailPoint.y, thumbnailPoint.x);
        CGFloat oldScaledWidth = scaledWidth;
        scaledWidth = scaledHeight;
        scaledHeight = oldScaledWidth;

        CGContextRotateCTM(bitmap, radians(-90));
        CGContextTranslateCTM(bitmap,-targetWidth,0);

    }elseif(sourceImage.imageOrientation ==UIImageOrientationUp){
        // NOTHING
    }elseif(sourceImage.imageOrientation ==UIImageOrientationDown){
        CGContextTranslateCTM(bitmap, targetWidth, targetHeight);
        CGContextRotateCTM(bitmap, radians(-180.));
    }

    CGContextDrawImage(bitmap,CGRectMake(thumbnailPoint.x, thumbnailPoint.y, scaledWidth, scaledHeight), imageRef);
    CGImageRefref=CGBitmapContextCreateImage(bitmap);
    UIImage* newImage =[UIImage imageWithCGImage:ref];

    CGContextRelease(bitmap);
    CGImageRelease(ref);

    return newImage;
}
The method we employ here is to create a bitmap with the desired size, but draw an image that is actually larger, thus maintaining the aspect ratio.

So We've Got Our Scaled Images - How Do I Save Them To Disk?

This is pretty simple. Remember that we want to save a compressed version to disk, and not the uncompressed pixels. Apple provides two functions that help us with this (documentation is here):

NSData*UIImagePNGRepresentation(UIImage*image);
NSData*UIImageJPEGRepresentation(UIImage*image,CGFloat compressionQuality);
And if you want to use them, you'd do something like:

UIImage* myThumbnail =...;// Get some image
NSData* imageData =UIImagePNGRepresentation(myThumbnail);
Now we're ready to save it to disk, which is the final step (say into the documents directory):

// Give a name to the file
NSString* imageName =@"MyImage.png";

// Now, we have to find the documents directory so we can save it
// Note that you might want to save it elsewhere, like the cache directory,
// or something similar.
NSArray* paths =NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask, YES);
NSString* documentsDirectory =[paths objectAtIndex:0];

// Now we get the full path to the file
NSString* fullPathToFile =[documentsDirectory stringByAppendingPathComponent:imageName];

// and then we write it out
[imageData writeToFile:fullPathToFile atomically:NO];
You would repeat this for every version of the image you have.

How Do I Load These Images Back Into Memory?

Just look at the various UIImage initialization methods, such as +imageWithContentsOfFile: in the Apple documentation.
分享到:
评论

相关推荐

    Cocoa基本原理指南

    Cocoa是苹果公司为MacOSX操作系统开发的应用程序框架,其提供了丰富的组件和工具,旨在帮助开发者快速创建出功能丰富、外观独特的应用程序。Cocoa框架基于Objective-C编程语言,这种语言是C语言的超集,并加入了面向...

    cocoa编程之菜鸟入门

    - **数据绑定技术**:数据绑定是Cocoa中的一个重要特性,它可以自动同步模型层数据与视图层显示,简化了开发过程中对用户界面的更新操作。 #### 四、Objective-C编程基础 - **语言基础**:Objective-C是Cocoa开发...

    Cocoa Programming for OS X_ The Big Nerd Ranch Guide, 5第五版 高清 带图片

    Cocoa是苹果公司为Mac OS X操作系统开发的一套面向对象的应用程序框架,主要用于开发Mac OS X上的原生应用程序。Cocoa框架提供了丰富的一组编程接口,让开发者能够创建具有图形用户界面的应用程序。此外,它还支持...

    Cocoa基本原理指南(Cocoa Fundamentals Guide)

    《Cocoa基本原理指南》是苹果公司为开发者提供的关于Mac OS X和iOS平台应用程序开发的重要文档,它深入讲解了Cocoa框架的基础知识和核心概念。Cocoa是Apple的面向对象的应用程序开发框架,用于构建高性能、高质量的...

    Learn Cocoa on the Mac, 2nd Edition

    本章介绍了如何使用Cocoa框架来进行文件操作,包括读取、写入和管理文件。 - **文件系统访问**:解释了如何访问文件系统以及如何读取和写入文件。 - **文件管理**:教授如何创建、移动和删除文件。 - **文件加密**...

    cocoa框架深入了解

    Cocoa框架是苹果公司为Mac OS X和iOS操作系统开发应用程序的核心框架,它基于Objective-C语言构建,提供了丰富的组件和工具,使得开发者能够创建功能强大的图形用户界面和复杂的系统交互。对于初学者而言,掌握Cocoa...

    苹果开发之Cocoa编程原书第4版

    Foundation是Cocoa的基础,提供了一组用于数据管理、文件操作、网络通信等核心功能的类。AppKit(在Mac上)或UIKit(在iOS上)则专注于用户界面的设计和交互。 1. **Objective-C与Swift选择**:Cocoa最初是基于...

    Learning Cocoa With Objective-C

    Core Data不仅处理数据库操作,还提供了模型层的抽象,简化了数据管理。 网络编程也是Cocoa的一部分,本书会介绍URL Loading System和Bonjour服务,前者用于HTTP请求和响应,后者则支持设备间的发现和连接。 最后...

    iOS and macOS Performance Tuning Cocoa, Cocoa Touch, Objective-C, and Swift

    《iOS和macOS性能优化:Cocoa,Cocoa Touch,Objective-C和Swift》是一本深入探讨移动设备和桌面操作系统性能提升的专业书籍。本书主要针对苹果的两大操作系统平台,即iOS和macOS,提供了丰富的实践经验和专业指导,...

    Cocoa 基本原理指南.pdf (中文)

    - **定义与特点**:Cocoa是一个面向对象的应用程序开发环境,主要应用于Mac OS X操作系统之上。它不仅是一组面向对象的软件库集合,同时也是一个运行时环境,为开发者提供了构建高质量应用程序所需的工具与框架。 - ...

    swift Cocoa编程教程

    Cocoa是Apple的开发框架,它为这些操作系统提供了丰富的API和工具,使得开发者能够创建功能丰富的原生应用。这篇“Swift Cocoa编程教程”显然是针对想要学习或提升Swift与Cocoa结合使用的开发者。 在Swift中,Cocoa...

    Cocoa Programming for Mac OS X

    - **发展历程**:Cocoa 的历史可以追溯到 NextStep 操作系统,随着苹果收购 NeXT,Cocoa 成为了 macOS 应用开发的核心技术之一,并逐渐扩展到了 iOS 平台。 #### 二、Cocoa 的关键技术点 - **Objective-C 语言**:...

    cocoa design patterns.pdf

    Cocoa是苹果操作系统MacOS上的一个应用程序开发框架,基于Objective-C语言开发。本书以MVC(模型-视图-控制器)设计模式为核心,介绍了Cocoa框架中广泛使用的设计模式,并指导读者如何在实际的项目开发中应用这些...

    Cocoa Design Patterns 2018

    《Cocoa设计模式》是一本专注于苹果公司iOS和Mac OS X平台上Cocoa框架设计模式的书籍。设计模式是软件开发中用于解决常见问题的模板,它们能够帮助开发人员以更高效和更优雅的方式编写代码。本书致力于展示如何在...

Global site tag (gtag.js) - Google Analytics