When deadlines loom, even skilled and experienced programmers can get a little sloppy. The pressure to ship may cause them to cut corners and look for a quick and easy solution, even if that solution is sure to cause trouble later on. Eventually, their coding style devolves into copy and paste programming, a lamentable tactic that involves cherry-picking snippets of code from a past project and putting them to use in the current one. Of course, the proper solution is to factor out the code into some kind of reusable library, but due to time constraints, it’s simply duplicated wherever it’s needed. Any bugs in the original code have now spread to a dozen different places in a dozen different projects. It’s an algorithm for chaos.

Yet in the world of iPhone applications, copy and paste programming seems to be disturbingly common. The fact that so many iPhone apps are short-term, one-off projects doesn’t help, but the situation has been aggravated even more by Apple’s security restrictions. In particular, dynamic linking to any shared library that doesn’t ship with the OS is strictly forbidden. One could argue that this rule is a necessary side-effect of the iPhone’s sandboxing security model, but even workarounds such as consolidating code into a static shared library are extraordinarily difficult. Another contributing factor is that the iPhone API is still relatively immature, and developers too often require custom code to fill in its gaps.

This situation has transformed more than a few iPhone programmers into copy and paste programmers. When they inevitably encounter some limitation with the iPhone API, the typical response is:

  1. Search online for a solution
  2. Find a snippet of code somewhere that gets the job done (usually at Stack Overflow or iPhone Dev SDK)
  3. Copy and paste the snippet into their project
  4. Move on to the next problem

Now imagine what happens when a thousand iPhone developers find the same snippet. Suddenly the problems of copy and paste programming have gone global. Offline, a bug in a single snippet of code may infect a dozen projects; online, it can spread to thousands.

As a reluctant copy and paste iPhone programmer myself, I’ve witnessed this scenario first-hand. I recently encountered a limitation with a certain iPhone class—UIImage—and I found in a discussion forum what seemed to be a popular, well-regarded solution. The code snippet was the first hit in a Google search, and many readers had replied with thanks to its author. However, a bit of testing showed that it worked for most images but completely failed for others. By the time I stumbled upon it, the buggy code had probably spread to countless programs already.

In the process of finding the bug and posting the fix, I ended up writing a substantial amount of additional code to address various other annoyances related to UIImage. The complete listing is available for download below. Though it won’t solve the copy and paste problem, it should be a welcome remedy for other iPhone developers who have run into similar obstacles.

Background

Programming for the iPhone, a highly graphical device, necessarily involves a substantial amount of image manipulation. Its SDK therefore provides an abstraction called UIImage that handles much of the effort in importing and drawing images. For example, imagine you want to load a JPEG file, scale it down to icon size, and give it rounded corners. These tasks may require tens or even hundreds of lines of code on other platforms, but on the iPhone, it’s only a matter of instantiating a UIImage, passing it to a UIImageView of the appropriate size, and setting the cornerRadius property.

Despite its ease of use, or perhaps because of it, UIImage suffers from some serious limitations. Key among these is its lack of support for resizing the image, a feature that is normally handled dynamically by its companion, the UIImageView component. However, should an iPhone application need to reduce the size of an image for storage or for exchange with an external entity (such as a web server), the UIImage class is insufficient.

Of course, UIImage is not the only means of image manipulation on the iPhone. It ships with a rather sophisticated graphics API, known as Quartz 2D, that offers low-level control of bitmap data. Clearly, the functionality for resizing an image exists, although taking advantage of it is not straightforward and requires the developer to write non-trivial code. How best to accomplish this task has been the source of much confusion and debate, particularly in forums such as iPhone Dev SDK:

  • Resizing a photo to a new UIImage

    This is crazy. I know there are threads that touch on
    this already, but none of them have led me to the answer. I can’t
    believe that it is really this difficult!

  • Resize Image High Quality

    I have done lots of searching for a way to resize images via the iPhone SDK and I have come across a few methods which work
    but the resulting image does not look nearly as good as if you took the
    full resolution image and told it to draw inside a rectangle.

These discussions have resulted in countless code snippets that claim to resize a UIImage, but many of them contain bugs, or they simply leave out functionality such as EXIF
orientation support, an absolute necessity when dealing with
photographs taken by the iPhone’s camera. For instance, a particularly
popular code snippet for UIImage resizing incorrectly processes alpha information, resulting in a pink tint for certain image files.


Image resized correctly


Image resized with buggy code

A Better Way to Resize Images

The following sections describe yet another collection of source code for resizing UIImage
objects. Functionally, it is similar to code samples that can be found
elsewhere on the Internet in discussion forums and blogs, but it
consolidates their features into a self-contained, reusable package and
offers several notable improvements:

  • Additional methods for cropping images, generating thumbnails, and more.
  • Implemented as Objective-C categories to facilitate reuse. With
    categories, you can simply plop the code into your project, import a
    header file, and all of your UIImage objects will automatically have access to the new methods.
  • Bugs that commonly plague other code of this type have been found
    and fixed. The categories have been vetted in a large, real-world iPhone
    app, and they contain no known bugs.
  • The code has been simplified as much as possible and is more thoroughly documented.

The source code to the categories can be downloaded from the links below or as a single archive.
If you are an experienced iPhone programmer, you can probably grab the
files and start using them right away. Continue reading for more detail
on how to apply them, as well as a run-down of the problems that
prompted their creation.

UIImage+Resize.h, UIImage+Resize.m
Extends the UIImage class to support resizing (optionally preserving
the original aspect ratio), cropping, and generating thumbnails.
UIImage+RoundedCorner.h, UIImage+RoundedCorner.m
Extends the UIImage class to support adding rounded corners to an image.
UIImage+Alpha.h, UIImage+Alpha.m
Extends the UIImage class with helper methods for working with alpha layers (transparencies).

UIImage+Alpha

The Alpha category is perhaps not as directly useful as
the others, though it provides some necessary functionality that they
build upon. Its methods include:

- (BOOL)hasAlpha;
Tells whether the image has an alpha layer.
- (UIImage *)imageWithAlpha;
Returns a copy of the image, adding an alpha channel if it doesn’t already have one. An alpha is required when adding transparent regions (e.g., rounded corners) to an image. It may also be necessary when loading certain kinds of image files that are not directly supported by Quartz 2D. For example, if you load a JPEG using imageNamed:, the resulting UIImage will have 32 bits per pixel with the first 8 bits unused (kCGImageAlphaNoneSkipFirst). But if you take the same image and save it in BMP format, and load it exactly the same way, the UIImage will have 24 bits per pixel (kCGImageAlphaNone), which is unsupported in Quartz 2D. Trying to render it to a graphics context will cause run-time errors. The obvious way around this problem is to make sure you only load image files that produce a Quartz-compatible pixel format. (A complete list is available in the Supported Pixel Formats section of the Quartz 2D Programming Guide.) If for some reason this is not possible, adding an alpha channel to the UIImage at runtime may also work.
- (UIImage *)transparentBorderImage:(NSUInteger)borderSize;
Returns a copy of the image with a transparent border of the given size added around its edges. This solves a special problem that occurs when rotating a UIImageView using Core Animation: Its borders look incredibly ugly. There’s simply no antialiasing around the view’s edges. Luckily, adding a one-pixel transparent border around the image fixes the problem. The extra border moves the visible edges of the image to the inside, and because Core Animation interpolates all inner pixels during rotation, the image’s borders will magically become antialiased. This trick also works for rotating a UIButton that has a custom image. The following before-and-after video shows the technique in action. (The top square is the original image; the bottom square has a one-pixel transparent border.)

UIImage+RoundedCorner

With the release of iPhone OS 3.0, a new Core Animation feature called cornerRadius became available. When applied to a layer, it makes the corners soft and round, just the thing for achieving a Web 2.0 or Mac OS X look-and-feel. For example, if you have a UIButton with a custom image like this:

And add a couple lines of code:

button.layer.cornerRadius = 30;
button.layer.masksToBounds = YES;

You get this:

The fun stops there. The cornerRadius setting only affects the run-time appearance of the layer. As soon as you save the image or send it over the network, the rounded corners go away. Also, if you animate the layer, perhaps by making it rotate, the cornerRadius property mysteriously reverts to zero, giving the image sharp corners again. This is a confirmed bug (#7235852) in iPhone OS 3.0 and 3.1.

To fix this problem, the RoundedCorner category can apply rounded corners to a UIImage permanently. It modifies the image data itself, adding an alpha layer if necessary. Not only does this work around the Core Animation bug, it also preserves the rounded corner effect when exporting the UIImage to a file or network stream, assuming that the output format supports transparency.

The category exposes a single method:

- (UIImage *)roundedCornerImage:(NSInteger)cornerSize
borderSize:(NSInteger)borderSize;
Creates a copy of the image, adding rounded corners of the specified radius. If borderSize is non-zero, a transparent border of the given size will also be added. (The primary purpose of this parameter is to work around the aforementioned aliasing problem that occurs when rotating an image view.) The implementation is based on code by Björn Sållarp.

UIImage+Resize

Resizing a UIImage is more complicated than it may seem. First, there’s simply the matter of learning Quartz 2D—a somewhat complex, low-level API. A mistake in a single parameter can suddenly affect thousands of pixels, yielding unexpected results like the pink tint problem shown previously.

Another issue to consider is the quality of the resulting image. By default, Quartz 2D applies a fast but not-so-high-quality interpolation algorithm when scaling images up or down. The effect is especially noticeable when reducing an image to a very small size, perhaps for an icon or thumbnail representation. The aliasing caused by the algorithm transforms smooth lines into jagged edges. Faces become a pixelated mess.

To illustrate, the following image is the result of squeezing a 1024×516-pixel JPEG (courtesy of PD Photo) into a 320×200-pixel UIImageView with automatic resizing enabled:

Note the serrated edges along the wings. To counteract the unsightliness, Quartz 2D can be configured for a different scaling algorithm by calling CGContextSetInterpolationQuality. Here is the same image, pre-scaled using the kCGInterpolationHigh option, and displayed in the same UIImageView:

The jaggies are now gone, replaced with smoother, cleaner lines.

Yet another obstacle, one of particular importance to iPhone developers, is image orientation. When a user takes a snapshot with the iPhone’s camera, the image is not upright but is in fact rotated 90 degrees counterclockwise. The reason is because the iPhone’s camera is positioned in a way that makes up (from the lens’s perspective) point to the left-hand side of the camera. The iPhone’s camera software knows this and therefore adds a special flag to the image data that indicates how the pixels should be rotated to produce the correct orientation. The software employs the same tactic when the user takes a picture in landscape mode (i.e., holding the phone sideways). It can rotate the image without having to apply a transformation across millions of pixels. Instead, it simply changes the orientation flag. Components such as UIImageView automatically read this flag—stored in the imageOrientation property of UIImage—and apply the proper rotation at run-time when displaying the image.

Unfortunately, as soon as you dip into the low-level Quartz 2D API, which has no knowledge of the high-level UIImage class, the orientation information is lost. An image resize algorithm written using this API will need to be provided with the orientation and perform the rotation explicitly.

The Resize category solves each of these problems while incorporating additional handy features. Its methods include:

- (UIImage *)croppedImage:(CGRect)bounds;
Returns a copy of the image that is cropped to the given bounds. The bounds will be adjusted using CGRectIntegral, meaning that any fractional values will be converted to integers.
- (UIImage *)thumbnailImage:(NSInteger)thumbnailSize
transparentBorder:(NSUInteger)borderSize
cornerRadius:(NSUInteger)cornerRadius
interpolationQuality:(CGInterpolationQuality)quality;
Returns a copy of the image reduced to the given thumbnail dimensions. If the image has a non-square aspect ratio, the longer portion will be cropped. If borderSize is non-zero, a transparent border of the given size will also be added. (The primary purpose of this parameter is to work around the aforementioned aliasing problem that occurs when rotating an image view.) Finally, the quality parameter determines the amount of antialiasing to perform when scaling the image.
- (UIImage *)resizedImage:(CGSize)newSize
interpolationQuality:(CGInterpolationQuality)quality;
Returns a resized copy of the image. The quality parameter determines the amount of antialiasing to perform when scaling the image. Note that the image will be scaled disproportionately if necessary to fit the specified bounds. In other words, the aspect ratio is not preserved.

This method, as well as all other methods described here that perform resizing, takes into account the orientation of the UIImage and transforms the pixels accordingly. The resulting image’s orientation will be up (UIImageOrientationUp), regardless of the current orientation value. The code to perform this transformation is based in part on the following sources:

- (UIImage *)
resizedImageWithContentMode:(UIViewContentMode)contentMode
bounds:(CGSize)bounds
interpolationQuality:(CGInterpolationQuality)quality;
UIImageView offers a remarkably helpful ability: It can resize displayed images while preserving their aspect ratio. The manner of preservation depends on a setting known as the content mode. For example, if a large JPEG (courtesy of PD Photo) is displayed in a small view with the content mode set to Center (UIViewContentModeCenter), only a portion of the image is visible:

To include the entire image, the view’s content can be scaled to fit within the bounds (UIViewContentModeScaleToFill). Note that Scale To Fill does not preserve the aspect ratio, resulting in a squashed image:

To scale the image without changing the aspect ratio, one option is to shrink the content until it fits entirely within the bounds (UIViewContentModeScaleAspectFit). Although this option shows the full image, it has the side-effect of not filling the entire view:

(Note that any area not covered by the image in Aspect Fill mode is actually transparent. It’s colored gray here to show the view boundary.)

Another aspect-preserving option is to shrink the content just enough to fit the smaller dimension within the view. The larger dimension (in this case, the length) will be cropped:

The correct choice of content mode depends, of course, on the desired appearance and the nature of the source image.

Because these modes are so useful, equivalent functionality has been rolled into the Resize category. Scale To Fill is the default behavior of resizedImage:interpolationQuality:, while resizedImageWithContentMode: supports both Aspect Fit and Aspect Fill. (Other content modes, such as Left and Bottom Right, were left unimplemented because they are rarely used.)

License

All code presented here is free for both personal and commercial use, with or without modification. No warranty is expressed or implied.

Resize a UIImage the right way的更多相关文章

  1. UIImage 相关操作

    修改UIImage大小 修改UISlider的最大值和最小值图片的时候,发现需要修改图片的大小,否则会导致UISlider变形.目前苹果还不支持直接修改UIImage类的大小,只能修改UIImageV ...

  2. UIImage 图片处理:截图,缩放,设定大小,存储

    图片的处理大概就分 截图(capture), 缩放(scale),设定大小(resize), 存储(save)这几样比较好处理, 另外还有滤镜,擦试等, 以后再说在这个Demo code裡, 我写了几 ...

  3. IOS - 常用宏定义和功能方法

    可能不定期添加新的东西 github地址:https://github.com/yuqingzhude/CommonUseDemo /************************Tools**** ...

  4. UIImageC处理

    UIImageC处理 1.等比缩放 - (UIImage *) scaleImage:(UIImage *)image toScale:(float)scaleSize { UIGraphicsBeg ...

  5. iOS,OC,图片相似度比较,图片指纹

    上周,正在忙,突然有个同学找我帮忙,说有个需求:图片相似度比较. 网上搜了一下,感觉不是很难,就写了下,这里分享给需要的小伙伴. 首先,本次采用的是OpenCV,图片哈希值: 先说一下基本思路: 1. ...

  6. IOS 多个UIImageView 加载高清大图时内存管理

    IOS 多个UIImageView 加载高清大图时内存管理 时间:2014-08-27 10:47  浏览:59人 当我们在某一个View多个UIImageView,且UIImageView都显示的是 ...

  7. IOS 多于UIImageView 当加载较大的高清闪存管理

    当我们是一家人View  多于UIImageView,和UIImageView表明一个更大的HD,可能存在的存储器的警告的问题.假设第一次走进这个view,无记忆出现预警.当重新进入view,在那曾经 ...

  8. iOS 7 二维码的生成

    //二维码生成 //UIImageView *theImageView = [[UIImageView alloc]init]; //[self.view addSubview:theImageVie ...

  9. iOS 图片比例缩放

    方法 //Resize image - (UIImage *)resizeImage:(UIImage *)image withQuality:(CGInterpolationQuality)qual ...

随机推荐

  1. 继承映射关系 joinedsubclass的查询

    会出现下面这样的错一般是配置文件中的mapping和映射文件中的package路径或者class中的name路径不一致 org.hibernate.MappingException: Unknown ...

  2. 算法学习--Day4

    今天写了两章题目,仍然是比较基础的内容.感觉时间好紧张,怕来不及,所以以后要加快速度了. 今天写的最多的是查找类题目,关键是二分查找的掌握. 题目描述 输入一个数n,然后输入n个数值各不相同,再输入一 ...

  3. Lightoj1083【单调栈】

    #include <cstdio> #include <stack> #include <iostream> #include <string.h> # ...

  4. 大数据技术之_27_电商平台数据分析项目_02_预备知识 + Scala + Spark Core + Spark SQL + Spark Streaming + Java 对象池

    第0章 预备知识0.1 Scala0.1.1 Scala 操作符0.1.2 拉链操作0.2 Spark Core0.2.1 Spark RDD 持久化0.2.2 Spark 共享变量0.3 Spark ...

  5. sql数据库查询结果字段包含换行符导致复制到Excel发生错位问题的解决

    问题描述:在工作过程中,有时会遇到这样的问题,写好sql查询语句在数据库中查询数据,看到行数(比如说是1000行),但是把查询结果复制到Excel里面,却发生了行列错位问题,而导致Excel里面的行数 ...

  6. CF1175E Minimal Segment Cover 题解

    题意:给出\(n\)个形如\([l,r]\)的线段.\(m\)次询问,每次询问区间\([x,y]\),问至少选出几条线段,使得区间\([x,y]\)的任何一个部位都被至少一条线段覆盖. 首先有一个显然 ...

  7. 【IDEA】关于 IDEA 中新建 web 项目的 webapp 文件夹没有小蓝点 ,启动服务,访问不到解决方案

    问题描述: 新建的 maven 的 Module 项目,webapp 文件夹也是在创建完项目后手动添加的,出现了 webapp 文件夹不能被识别的情况. 解决方案: 第一步:  选中项目按 F4 键, ...

  8. luoguP3808[模板]AC自动机(简单版)

    传送门 ac自动机模板题,裸的多串匹配 代码: #include<cstdio> #include<iostream> #include<algorithm> #i ...

  9. OSPF-1-OSPF的数据库交换(1)

    一.OSPF路由器ID(RID) 选举过程: 1.使用router-id id 命令中配置的路由器ID 2.up着的环回接口最大的ip 3.up着的非环回接口最大ip   如果路由器的RID发生了变化 ...

  10. Day1课后作业:用户登录简单版

    user = "gaojun"password ="123abc"for i in range(3): user = input('请输入用户名:') pass ...