Quartz2D 图像处理

  首先感谢一片枫叶总结出这么好的文章,文章出处: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 一片-枫叶

@星之愿

时间: 2024-10-15 14:35:08

Quartz2D 图像处理的相关文章

图像处理之三种常见双立方插值算法

http://blog.csdn.net/jia20003/article/details/40020775 图像处理之三种常见双立方插值算法 双立方插值计算涉及到16个像素点,其中(i', j')表示待计算像素点在源图像中的包含 小数部分的像素坐标,dx表示X方向的小数坐标,dy表示Y方向的小数坐标.具体 可以看下图: 根据上述图示与双立方插值的数学表达式可以看出,双立方插值本质上图像16个像素点 权重卷积之和作为新的像素值. 其中R(x)表示插值表达式,可以根据需要选择的表达式不同.常见有基

【20160924】GOCVHelper 图像处理部分(1)

增强后的图像需要通过图像处理获得定量的值.在实际程序设计过程中,轮廓很多时候都是重要的分析变量.参考Halcon的相关函数,我增强了Opencv在这块的相关功能.      //寻找最大的轮廓 VP FindBigestContour(Mat src){ int imax = 0; //代表最大轮廓的序号 int imaxcontour = -1; //代表最大轮廓的大小 std::vector<std::vector<cv::Point>>contours; findContou

详解OS X和iOS图像处理框架Core Image

转自:http://www.csdn.net/article/2015-02-13/2823961-core-image 摘要:本 文结合实例详解了OS X和iOS图像处理框架Core Image的使用,如何通过Core Image来创建和使用iOS的内置滤镜,非常适合初学者学习.虽然示例代码是用Swift写的iOS程序,不过实现概念很容易转换到 Objective-C和OS X. 这篇文章会为初学者介绍一下Core Image,一个OS X和iOS的图像处理框架. 如果你想跟着本文中的代码学习

图像处理算法交流群群规和近期福利活动

作为一名资深的图像处理算法业余爱好者,为了方便同道中人交流学习,我特别建立了一个QQ群(群号:529549320),出于新人培训之目的,也为了免去我每次都重复群规之繁琐,更为了方便群活动之开展特借CSDN宝地发此贴,以做一劳永逸之举. 关于入群资质: 1)无论你是大神还是小白,我们都欢迎: 2)无论你是业界专家还是在校学子,我们都欢迎: 3)无论你是专业精英还是业余爱好者,我们都欢迎. 总之,"是故无贵无贱,无长无少,道之所存,师之所存也". 但是,为了防止你因为不慎而加错群,我们特别

UIKit框架(17)Quartz2D

Quartz2D介绍 什么是Quartz2D ? 是一个二维绘图引擎,同时支持iOS和Mac系统. Quartz2D的价值? 但是有些UI界面极其复杂,而且比较个性化,用普通的UI控件无法实现,这时可以利用Quartz2D技术将控件内部的结构画出来,自定义控件的样子. 其实,iOS中大部分控件的内容都是由Quartz2D画出来的 (有一些是通过WebKit渲染的) 开发方式? 使用框架CoreGraphics Quartz2D是一套C语言API,但使用了面向对象的开发方式 图像上下文对象: 是最

图像处理中的数学原理详解17——卷积定理及其证明

欢迎关注我的博客专栏"图像处理中的数学原理详解" 全文目录请见 图像处理中的数学原理详解(总纲) http://blog.csdn.net/baimafujinji/article/details/48467225 图像处理中的数学原理详解(已发布的部分链接整理) http://blog.csdn.net/baimafujinji/article/details/48751037 1.4.5   卷积定理及其证明 卷积定理是傅立叶变换满足的一个重要性质.卷积定理指出,函数卷积的傅立叶变

STM32F103+ov7670的简单图像处理

突然意识到自己这块东西没有发过,所以补上一发,之前搜过淘宝,这次搜了CSDN发现写着这个的还真不多,用过淘宝骑飞和战舰家的配套例程,自己学习的时候看的是红白皮的< STM32F 32位ARM微控制器应用设计与实践>,里面专门一章帮着入门,稍稍懂点的都能看得懂. 然后我自己用骑飞的板子做过简单的灰度化.二值化.拉普拉斯得边界,简单的寻找已知目标,meanshift等等,后来还做了下怀旧色.复古色的滤镜切换.最终考虑到f103的资源.运算速度跟不上图像处理,果断点到为止了. 再之后有帮人弄了下战舰

DICOM医学图像处理:DCMTK在VS2012中的配置

背景: 最近由于项目需要,将原本的开发IDE环境由VS2008升级到了VS2012.本以为编译完成后的DCMTK开源库可以直接从VS2008移植到VS2012.但是通过项目属性添加完包含目录和依赖库后,编译会出现大量的链接错误(大多是跟dcmdata.lib.oflog.lib有关). 解决方法: 重新按照原本的博客前辈柳北风儿(大神目前已经博客转移到网易:http://blog.163.com/[email protected]/),利用CMake工具,选择VS2012本地编译器对DCMTK3

我学习图像处理的小结

前一段时间,我一直在制作OpenCV基础知识的课件(<学习OpenCV3.0初级实战视频课程> http://edu.51cto.com/course/10381.html,<学习OpenCV3.0中级实战视频课程>http://edu.51cto.com/course/10712.html).因为一方面我感觉如果仅仅是有比如"答题卡识别"http://edu.51cto.com/course/8637.html,"图像拼接"http://e