首先感谢一片枫叶总结出这么好的文章,文章出处:http://www.cnblogs.com/smileEvday/archive/2013/05/25/IOSImageEdit.html

  本文将为大家介绍常见的IOS图像处理操作包括以下四部分:旋转,缩放,裁剪以及像素和UIImage之间的转化,主要使用的知识是quartz2D。Quartz2D是CoreGraphics框架中的一个重要组成部分,可以完成几乎所有的2D图像绘制,处理功能。跟window编程中GDI的功能一样,而且很多概念都差不多。

一、图像旋转

  图像旋转是图像处理过程中一中常见操作,按照旋转的角度不同,可以分为以下两种:

  1、特殊角度旋转

  特殊角度旋转是指对图像做90°,180°,270°等这一类旋转,这一类旋转操作通常是最频繁的,如看照片时偶尔会碰到一些方向有问题,我们只需要进行简单的左转90°,右转90°就可以装好。关于特殊角度旋转的处理我的上一篇博客《IOS:聊一聊UIImage几点知识》有介绍过创建图像时指定imageOrientation来完成,有兴趣可以去看看。这种方法由于没有牵扯到具体的绘制操作,因此速度很快,在IOS和Mac系统中都可以正确显示,但是如果将图片倒到windows系统中,方向可能依然是错的,具体原因上一篇文章也解释过了。

  2、任意角度旋转

  任意角度旋转顾名思义即对图像做任意角度的旋转,可能是30°也可能是35°等等。很显然这一种旋转是没法通过imageOrientaion来完成的,因此我们得想点儿别的办法。我们知道UIView有一个transform属性,通过设置transform可以实现偏移,缩放,旋转的效果。在quartz2D中我们也同样可以通过对context设置不同的transform来完成相应的功能,下面我们要介绍的任意角度旋转的方法就是基于对context的一系列操作来完成的。

  这块儿你可能有个疑问,问什么让UIView旋转只需要设置一个旋转的transform就可以了,而context则需要通过“一系列”的transform操作才能完成相应的功能?

  原因是UIView中我们通过transform进行的所有操作都是基于view的中心点的,而context中我们进行的操作是基于context的坐标原点。下面我们首先看一下UIView进行旋转时的图示:

  由于旋转时绕着中心点转动,所以我们只需要一步就可以从原位置(黑色表示)转到目标位置(蓝色表示),其中黑色虚线和蓝色虚线之间的夹角就是转过的角度。我们想一下如果转动时绕着左上角的原点转动,完成同样角度转动后会是怎么一种情况呢?请看下图

  

  如上图所示,由于旋转是绕着原点进行的,虽然我们转过了相同的角度,但是得到的结果却相差甚远。因此context中如果想把一幅图片旋转任意角度的话,至少得进行两步:旋转和平移。

  第一步旋转很好做,问题是第二部如何从旋转过后图片的中心移动到原图中心,这个计算还不是那么直观。于是我们想着去模拟UIView的旋转,我们分如下三步走:

  

  我们设图片的宽度为width,高度为height,旋转的三个步骤依次如上图所示:

  a、将context进行平移,将原点移动到原图的中心位置,x,y方向的平移距离分别为width / 2,height / 2。

  b、对context进行旋转操作。

  c、将旋转后的图像的中心点重新移回原图的中心点,即x,y方向的平移距离分别是-width / 2,-height / 2。

  进过这三步我们就可以很方便的实现图片的任意角度旋转了。你可能会发现步骤a中向下移动了半个图片宽高,步骤c中又向相反方向移动了半个图片宽高。这两个操作不会抵消吗?答案是NO,步骤a中我们的移动是基于原坐标系统进行移动的,到了步骤c时我们的移动是基于这个时候的坐标系移动的,两个坐标系是不一样的,所以才能通过一来一回完成对图片的旋转。

  图片旋转的代码如下:

//
// UIImage+Rotate_Flip.m
// SvImageEdit
//
// Created by maple on 5/14/13.
// Copyright (c) 2013 smileEvday. All rights reserved.
// #import "UIImage+Rotate_Flip.h" /*
* @brief rotate image with radian
*/
- (UIImage*)rotateImageWithRadian:(CGFloat)radian cropMode:(SvCropMode)cropMode
{
CGSize imgSize = CGSizeMake(self.size.width * self.scale, self.size.height * self.scale);
CGSize outputSize = imgSize;
if (cropMode == enSvCropExpand) {
CGRect rect = CGRectMake(0, 0, imgSize.width, imgSize.height);
rect = CGRectApplyAffineTransform(rect, CGAffineTransformMakeRotation(radian));
outputSize = CGSizeMake(CGRectGetWidth(rect), CGRectGetHeight(rect));
} UIGraphicsBeginImageContext(outputSize);
CGContextRef context = UIGraphicsGetCurrentContext(); CGContextTranslateCTM(context, outputSize.width / 2, outputSize.height / 2);
CGContextRotateCTM(context, radian);
CGContextTranslateCTM(context, -imgSize.width / 2, -imgSize.height / 2); [self drawInRect:CGRectMake(0, 0, imgSize.width, imgSize.height)]; UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext(); return image;
}

  其中的CropMode定义如下:

enum {
enSvCropClip, // the image size will be equal to orignal image, some part of image may be cliped
enSvCropExpand, // the image size will expand to contain the whole image, remain area will be transparent
};
typedef NSInteger SvCropMode;

  clip模式下,旋转后的图片和原图一样大,部分图片区域会被裁剪掉;expand模式下,旋转后的图片可能会比原图大,所有的图片信息都会保留,剩下的区域会是全透明的。

  

  小结:第一部分讲述了两种图片旋转的方法,第一种方法处理速度块,但是只能处理特殊角度旋转。第二种方法处理速度比第一种要慢,因为牵扯到了实际的绘制和重新采样生成图片的过程。在实际操作中如果第一种方法满足需求,应该尽量使用第一种方法完成图片旋转。

二、图像缩放

  图像缩放顾名思义即对图片的尺寸进行缩放,由于尺寸不同所以在生成新图的过程中像素不可能是一一对应,因此会有插值操作。所谓插值即根据原图和目标图大小比例,结合原图像素信息生成的新的像素的过程。常见的插值算法有线性插值,双线性插值,立方卷积插值等。网上有很多现成的算法,感兴趣的话可以去看看。

  下面我们看看图像缩放的原理图示:

  上图中,我们假设黑色代表原图尺寸,蓝色代表缩放后的尺寸。我们将图片放大两倍,那么原图中的每一个像素将会对应缩放后图片中的四个像素。如何从一个像素生成四个像素,这个就是插值算法要解决的问题。

  今天我们主要讨论IOS图像处理,使用quartz2D帮助我们完成图像缩放,只需要通过CGContextSetInterpolationQuality函数即可完成插值质量的设置。之于底层具体使用哪种插值算法,我们无从得知,也不需要去关心。使用quartz2D解决图像缩放的时候,所有我们需要做的事情只有生成一个目标大小的画布,然后设置插值质量,再使用UIImage的draw方法将图片绘制到画布中即可。

  下面看代码:

 UIImage+Zoom.h
 UIImage+Zoom.m

  这个工具类里面,实现了三种缩放模式(与缩放质量无关),分别是: enSvResizeScale,enSvResizeAspectFit,enSvResizeAspectFill。

a、拉伸填充。即不管目标尺寸中宽高的比例如何,我们都将对原图进行拉伸,使之充满整个目标图像。

b、保持比例显示。即缩放后尽量使原图最大,同事维持原图本身的比例,剩余区域将会做全透明的填充。这个类似于UIImageView中contentMode中的UIViewContentModeScaleAspectFit模式。

c、保持比例填充。即缩放后的图像依旧保持原图比例的基础上进行填充,部分图片可能会被裁剪。这个类似于UIImageView中contentMode中的UIViewContentModeScaleAspectFill模式。

  

  小结: 第二部分讲述使用quartz2D进行图像缩放的知识,我们可以看出quartz2D帮我们完成了图像缩放过程中插值的处理,十分方便。  

三、图像裁剪

  图像裁剪即去除不必要的图像区域,抠出我们希望保留的信息。按照裁剪形状可以分为以下两种:

  1、矩形裁剪

  矩形裁剪是最常见的裁剪操作,操作方法比较简单。下面我们看一下矩形裁剪示意图:

  上图中黑色的框代表原图大小,蓝色的虚线框代表要裁剪出来的大小。很显然裁剪出来的图片不会比原图更大,如果你裁剪出来的图片比原图更大的话通常情况下就错了,当然除非你刻意为之。我们设裁剪区域的左上角坐标为(x,y),裁剪的宽高分别为cropWidth,cropHeight,原图像宽高分别为width,height。要完成裁剪功能,我们只需要三步:

  a、创建目标大小(cropWidth,cropHeight)的画布。

  b、使用UIImage的drawInRect方法进行绘制的时候,指定rect为(-x,-y,width,height)。

  c、从画布中得到裁剪后的图像。

  关键是在第二步,指定原图像的绘制区域,因为我们需要得到从x,y位置开始的图像,所做一个简单的坐标转换,只需要从-x,-y位置开始绘制即可。

  下面是裁剪部分的源码:

//  UIImage+SvImageEdit.m
// SvImageEdit
//
// Created by maple on 5/8/13.
// Copyright (c) 2013 maple. All rights reserved.
// #import "UIImage+Crop.h" @implementation UIImage (SvImageEdit) /*
* @brief crop image
*/
- (UIImage*)cropImageWithRect:(CGRect)cropRect
{
CGRect drawRect = CGRectMake(-cropRect.origin.x , -cropRect.origin.y, self.size.width * self.scale, self.size.height * self.scale); UIGraphicsBeginImageContext(cropRect.size);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextClearRect(context, CGRectMake(0, 0, cropRect.size.width, cropRect.size.height)); [self drawInRect:drawRect]; UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext(); return image;
} @end

  

  2、任意形状裁剪

  任意形状裁剪一个比较典型的例子就是photo中通过磁性套索进行抠图,通过指定一系列的关键点来控制要扣出的图片区域。这种裁剪的实现比矩形裁剪要稍微复杂一点,主要用到quartz2D中的两个知识: Path,Clipping Area。任意形状裁剪的示意图如下:

  上图中黑色的框代表原图大小,虚线代表实际裁剪的形状,蓝色的框代表着时间裁剪路径的边框,完成任意形状抠图,通常需要以下六步:

  a、通过给定的点集确定出整个裁剪区域的尺寸和位置cropRect,即目标画布的大小和裁剪区域的左上角的位置。

    通常有两种方法可以完成这个需求: 第一种创建一个空的画布,然后开始一个Path,添加所有的点到path中,CGContextGetPathBoundingBox获取到裁剪区域的边框。或者直接创建一个mutablePath,然后添加所有点到该path中,通过通过CGPathGetBoundingBox获取裁剪区域的边框。当然也可以通过自己遍历点集重的每一个点,找到最小点的坐标和最大点的坐标计算出裁剪区域的边框。

b、创建目标大小的画布。

c、在目标画布中开启一个path,然后添加所有点到path中。

   这块需要对path进行一个移动操作,因为传入的点集是相对于原图的原点位置的,因此我们需要对该path做一个(-cropRect.origin.x,-cropRect.origin.y)的平移操作。

d、通过该path设置裁剪区域。

e、使用UIImage的drawInRect方法进行绘制的时候,指定rect为(-cropRect.origin.x,-cropRect.origin.x,cropRect.size.width,cropRect.size.height)。

   f、从画布中获取目标图像。

  下面是任意形状裁剪的源码:

 UIImage+SvImageEdit.m

  小结: 第三部分讲述了两种裁剪: 矩形裁剪,任意形状裁剪,主要用到的知识是quartz2D中的path和clipping area。

四、获取UIImage中图像的像素和使用像素创建UIImage

  UIImage是UIKit中一个存储和绘制图像的工具类,可以打开常见的jpg,png,tif等格式的图片。IOS中通常情况下使用该类就可以满足日常使用了,但有些时候我们也需要获取到图像的像素,进行更细粒度的编辑操作,例如灰度化,二值话等等。

  1、从UIImage获取像素

  要获取到UIImage所表示的图像的像素,我们需要借助quartz2D中的CGBitmapContext,前面我们创建BitmapContext的时候都是使用UIKit中的一个便利方法UIGraphicsBeginImageContext,这个方法的好处是方便易用,但易用的同时也就导致了很多细节我们不能控制。为了得到图片中的像素我们需要使用更低级别的CGBitmapContextCreate方法,该方法需要指定位深(RGB中每一位所占的字节),颜色空间(前面的博客中有提到)以及alpha信息等。

  完成获取像素需要以下四步:

  a、申请图像大小的内存。

  b、使用CGBitmapContextCreate方法创建画布。

  c、使用UIImage的draw方法绘制图像到画布中。

  d、使用CGBitmapContextGetData方法获取画布对应的像素数据。

  代码如下:

// return bmpData is rgba
- (BOOL)getImageData:(void**)data width:(NSInteger*)width height:(NSInteger*)height alphaInfo:(CGImageAlphaInfo*)alphaInfo
{
int imgWidth = self.size.width * self.scale;
int imgHegiht = self.size.height * self.scale; CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceRGB();
if (colorspace == NULL) {
NSLog(@"Create Colorspace Error!");
return NO;
} void *imgData = NULL;
imgData = malloc(imgWidth * imgHegiht * 4);
if (imgData == NULL) {
NSLog(@"Memory Error!");
return NO;
} CGContextRef bmpContext = CGBitmapContextCreate(imgData, imgWidth, imgHegiht, 8, imgWidth * 4, colorspace, kCGImageAlphaPremultipliedLast);
CGContextDrawImage(bmpContext, CGRectMake(0, 0, imgWidth, imgHegiht), self.CGImage); *data = CGBitmapContextGetData(bmpContext);
*width = imgWidth;
*height = imgHegiht;
*alphaInfo = kCGImageAlphaLast; CGColorSpaceRelease(colorspace);
CGContextRelease(bmpContext); return YES;
}

  

  2、从像素创建UIImage

  上面讲到了从UIImage获取像素,在我们编辑完像素以后,大部分情况会需要重新生成UIImage并显示出来。这一部分的逻辑跟上一部分差不多,通过传进来的像素创建画布,然后通过CGBitmapContextCreateImage方法从画布中获取到CGImage,最后再创建出UIImage。注意如果指定的alpha信息需要和实际的像素格式对应,否则会得到错误的效果。

  下面是从像素创建UIImage的源码:

// the data should be RGBA format
+ (UIImage*)createImageWithData:(Byte*)data width:(NSInteger)width height:(NSInteger)height alphaInfo:(CGImageAlphaInfo)alphaInfo
{
CGColorSpaceRef colorSpaceRef = CGColorSpaceCreateDeviceRGB();
if (!colorSpaceRef) {
NSLog(@"Create ColorSpace Error!");
}
CGContextRef bitmapContext = CGBitmapContextCreate(data, width, height, 8, width * 4, colorSpaceRef, kCGImageAlphaPremultipliedLast);
if (!bitmapContext) {
NSLog(@"Create Bitmap context Error!");
CGColorSpaceRelease(colorSpaceRef);
return nil;
} CGImageRef imageRef = CGBitmapContextCreateImage(bitmapContext);
UIImage *image = [[UIImage alloc] initWithCGImage:imageRef];
CGImageRelease(imageRef); CGColorSpaceRelease(colorSpaceRef);
CGContextRelease(bitmapContext); return image;
}

  

  小结: 第四部分主要讨论了一下UIImage和实际像素数据之间的相互转换,整个流程中最关键的函数就是CGBitmapContextCreateImage,如果传入参数错误,可能会得到错误的结果。

#2楼[楼主] 2013-05-26 01:27 一片-枫叶

@星之愿

Quartz2D 图像处理的更多相关文章

  1. Quartz2D总结

    天了噜,脑子完全懵了,最起码说出来个上下文啊,连这个都给忘了,特此总结一下,并以此缅怀这次面试 Quartz2D的API来自于Core Graphics(这就是为什么CGContextRef是以CG开 ...

  2. Atitit 图像处理和计算机视觉的分类 三部分 图像处理 图像分析 计算机视觉

    Atitit 图像处理和计算机视觉的分类 三部分 图像处理 图像分析 计算机视觉 1.1. 按照当前流行的分类方法,可以分为以下三部分:三部分 图像处理 图像分析 计算机视觉1 1.2. 图像处理需要 ...

  3. Atitit 图像处理的摩西五经attilax总结

    Atitit 图像处理的摩西五经attilax总结 1. 数字图像处理(第三版)1 2. 图像处理基础(第2版)(世界著名计算机教材精选)1 3. 计算机视觉特征提取与图像处理(第三版)2 4. Op ...

  4. Atitit 图像处理的心得与疑惑 attilax总结

    Atitit 图像处理的心得与疑惑 attilax总结 1.1. 使用类库好不好??还是自己实现算法1 1.2. 但是,如果遇到类库体积太大,后者没有合适的算法,那就只能自己开发算法了1 1.3. 如 ...

  5. Atitit 图像处理 调用opencv 通过java  api   attilax总结

    Atitit 图像处理 调用opencv 通过java  api   attilax总结 1.1. Opencv java api的支持 opencv2.4.2 就有了对java api的支持1 1. ...

  6. Atitit MATLAB 图像处理 经典书籍attilax总结

    Atitit MATLAB 图像处理 经典书籍attilax总结 1.1. MATLAB数字图像处理1 1.2. <MATLAB实用教程(第二版)>((美)穆尔 著)[简介_书评_在线阅读 ...

  7. Atitit 图像处理类库大总结attilax qc20

    Atitit 图像处理类库大总结attilax qc20 1.1. 选择与组合不同的图像处理类库1 1.2. Halcon 貌似商业工具,功能强大.1 1.3. Openvc  Openvc功能也是比 ...

  8. Atitit MATLAB 图像处理attilax总结

    Atitit MATLAB 图像处理attilax总结 1.1. 下载 Matlab7.0官方下载_Matlab2012 v7.0 官方简体中文版-办公软件-系统大全.html1 1.2. Matla ...

  9. 使用MATLAB对图像处理的几种方法(下)

     试验报告 一.试验原理: 图像点处理是图像处理系列的基础,主要用于让我们熟悉Matlab图像处理的编程环境.灰度线性变换和灰度拉伸是对像素灰度值的变换操作,直方图是对像素灰度值的统计,直方图均衡是对 ...

随机推荐

  1. CentOS 6系统下安装 JDK1.6

    CentOS 6系统下安装 JDK1.6 JDK(Java Development Kit)是Sun Microsystems针对Java开发员的产品.自从Java推出以来,JDK已经成为使用最广泛的 ...

  2. spring读取properties的方法

    首先在配置文件中配置PropertyPlaceholderConfigurer 单个配置文件: <bean id="propertyConfigurer" class=&qu ...

  3. 给出一个数组A,找出一对 (i, j)使得A[i] <= A[j] (i < j)并且j-i最大

    题目:给出一个数组A,找出一对 (i, j)使得A[i] <= A[j] (i <= j)并且j-i最大 ,若有多个这样的位置对,返回i最小的那一对. 最直接的想法就是对于每一个 i 从数 ...

  4. Python integer objects implementation

    http://www.laurentluce.com/posts/python-integer-objects-implementation/ Python integer objects imple ...

  5. 基础笔记(二)HTTP协议

    GET与POST的区别 1.GET提交的数据会放在URL之后,以?分割URL和传输数据,参数之间用&连接:POST是把提交的数据放在HTTP的body中. 2.GET提交的数据大小有限制(协议 ...

  6. maven学习(二)

    为了兼容之前基于ant构建的项目发布包结构,在基于maven做构建的时候,需要自定义打包方式. maven的maven-assembly-plugin插件支持任意格式的打包,比如:dir,zip等形式 ...

  7. Lua调用自定义C++类

    弄了一天终于会Lua调用自定义C++类.不容易啊. 我的电脑是64位的,装了64的Python不行,装了32位的就可以了,靠!下面是报错信息 python pyyaml Cheetah全都是装32位的 ...

  8. SCI&EI 英文PAPER投稿经验【转】

    英文投稿的一点经验[转载] From: http://chl033.woku.com/article/2893317.html 1. 首先一定要注意杂志的发表范围, 超出范围的千万别投,要不就是浪费时 ...

  9. 使用socket实现信用卡程序和迷你购物商城

    #-*- coding:utf-8 -*- from moudle import * import socketserver import json import os import time imp ...

  10. oracle数据库性能调优

    一:注意WHERE子句中的连接顺序: ORACLE采用自下而上的顺序解析WHERE子句,根据这个原理,表之间的连接必须写在其他WHERE条件之前, 那些可以过滤掉最大数量记录的条件必须写在WHERE子 ...