UIKit Images

UIKit提供了许多函数可以让我们操作Image,甚至我们可以仅通过代码的方式,获取一个UIImage。

UIImage *SwatchWithColor(UIColor *color, CGFloat side) {UIGraphicsBeginImageContextWithOptions(CGSizeMake(side, side), YES, 0.0);[color setFill];UIImage *image = UIGraphicsGetImageFromCurrentImageContext();UIGraphicsEndImageContext();return image;
}

NOTO:

UIImage suports TIFF, JPEG, GIF, PNG, DIB(that is, BMP), ICO, CUR and XBM formats. You can load additional formats (like RAW) by using the ImageIO framework)

Image 的缩略图

制作Image的缩略图的代码很简单,核心代码是UIImage的方法drawRect:

UIImage *image = [UIImage imageNamed:@"myImage"];
[image drawInRect:destinationRect];
UIImage *thumbnail = UIGraphicsGetImageFromCurrentImageContext();

而这里的难点在于,如何确定destinationRect。如果我们不做任何调整,直接使用目标rect的话,图像的比例就会失真。如果下图所示,图片的尺寸为高宽 : 2833 像素 1933像素, 当我们将其draw到一个矩形rect后,其高度就会压缩来适应:

乍看之下,似乎并不影响什么。但如果我们是在现实人脸等有长宽特征的图片是,就会感觉很奇怪。

要解决这种问题,可以使用我们在上一篇中提到的Fitting和Filling模式。下面是具体的代码:

UIImage *BuildThumbnail(UIImage *sourceImage, CGSize targetSize, BOOL useFitting){UIGraphicsBeginImageContextWithOptions(targetSize, NO, 0.0);CGRect targetRect = SizeMakeRect(targetSize);CGRect naturalRect = CGSizeMake(.size = sourceImage.size);// RectByFittingRect 和 RectByFillingRect的定义见上一篇博客CGRect destinationRect = useFitting?RectByFittingRect(nartualRect, targetRect):RectByFillingRect(naturalRect, targetRect);[sourceImage drawInRect:destinationRect];UIImage *thumbinal = UIGraphicsGetImageFromCurrentImageContext();UIGraphicsEndImageContext();return thumbinal;
}CGRect SizeMakeRect(CGSize size)
{return (CGRect){.size = size};
}

得出的结果如下所示:

图片裁剪

不同于缩略图,缩略图会将原始图的data压缩,而裁剪图片则会使用原始的分辨率,但仅是截取原始图片的一部分。
如下图所示,当我们截取雪貂的头部放大时,图片会变得模糊,因为其分辨率是一定的。

我们需要使用Quartz提供的

CGImageCreateWithImageInRect()

方法来裁剪图片。

注意,当我使用Core Graphics方法来裁剪时,可以利用方法CGRectIntegral()来调整裁剪图片的范围(以像素为单位),使得裁剪范围落在原始图片范围内。

//CGRectIntegral 用法/*将origin值向下调整到最近整数,size向上调整到最近整数,使生成的CGRect可以完全包含原来的CGRect.*/CGRect integralRect = CGRectIntegral(originalRect);NSLog(@"integralRect = %@",NSStringFromCGRect(integralRect));

下面分别给出Quartz和UIKit两个版本的裁剪图片方法,注意,Quartz是以像素为单位的,而UIKit则是以逻辑单位点为单位。
Quartz :

UIImage *ExtractRectFromImage(UIImage *sourceImage, CGRect subRect) {CGImageRef imageRef = CGImageCreateWithImageInRect(sourceImage.CGImage, subRect);if (imageRef != NULL) {UIImage *output = [UIImage imageWithCGImage:imageRef];CGImageRelease(imageRef);return output;}return nil;
}

UIKit:

UIImage *ExtractSubimageFromRect(UIImage *sourceImage, CGRect rect) {UIGraphicsBeginImageContextWithOptions(rect.size, NO, 1);CGRect destRect = CGRectMake(-rect.origin.x, -rect.origin.y, sourceImage.size.width, sourceImage.size.height);[sourceImage drawInRect:destRect];UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();UIGraphicsEndImageContext();return newImage;
}

灰度图

我们可以将图片的颜色信息完全抹去,仅留下灰度信息。称之为灰度图。

灰度图每一个像素占用一个字节(8 bits),没有透明度信息。

当我们需要创建一个灰度图时,需要先创建一个grayscale的color space。在这个color space中,你所添加的任何颜色,都会别Quartz自动转换为灰度强弱信息,而不会显示原始的颜色。创建灰度图的代码如下:

UIImage *GrayscaleVersionOfImage(UIImage *sourceImage) {// 创建灰度颜色空间CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceGray();if (colorSpace == NULL) {return nil;}int width = sourceImage.size.width;int height = sourceImage.size.height;// 创建 context:每一个像素 8 bits,没有透明度CGContextRef context = CGBitmapContextCreate(NULL,width,height,8,width, colorSpace,(CGBitmapInfo)kCGImageAlphaNone);if (context == NULL) {return nil;}// 在灰度空间中,绘制图片CGRect rect = SizeMakeRect(sourceImage.size);CGContextDrawImage(context, rect, sourceImage.CGImage);CGImageRef imageRef = CGBitmapContextCreateImage(context);// 等同于UICraphicsGetImageFromCurrentImageContextCGContextRelease(context);// 返回灰度图片UIImage *output = [UIImage imageWithCGImage:imageRef];CFRelease(imageRef);return output;
}

图像水印

水印会损坏原始的图像信息,因此,在加了水印的图片中去除水印,是十分困难的。

下面是一个水印的例子:

这只是一个简单的水印效果,主要是blend了string和图片。

CGSize targetSize = CGSizeMake(1,2);UIGraphicsBeginImageContextWithOptions(targetSize, NO, 0.0);CGContextRef context = UIGraphicsGetCurrentContext();// 将原始图片draw到当前的context中CGRect targetRect = SizeMakeRect(targetSize);UIImage *sourceImage = [UIImage imageNamed:@"pronghorn.jpg"];CGRect imgRect = RectByFillingRect(SizeMakeRect(sourceImage.size), targetRect);[sourceImage drawInRect:imgRect];// Rotate context,使得水印文字倾斜45°CGPoint center = RectGetCenter(targetRect);CGContextTranslateCTM(context, center.x, center.y);CGContextRotateCTM(context, M_PI_4);CGContextTranslateCTM(context, -center.x, -center.y);// 创建水印内容NSString *watermark = @"watermark";UIFont *font = [UIFont fontWithName:@"HelveticaNeue" size:48];CGSize size = [watermark sizeWithAttributes:@{NSFontAttributeName:font}];CGRect stringRect = RectCenteredInRect(SizeMakeRect(size), targetRect);// Draw 水印, 同时使用bleng将水印突出显示CGContextSetBlendMode(context, kCGBlendModeDifference);[watermark drawInRect:stringRect withAttributes:@{NSFontAttributeName:font, NSForegroundColorAttributeName:[UIColor whiteColor]}];// 生成图片UIImage *image = UIGraphicsGetImageFromCurrentImageContext();UIGraphicsEndImageContext();return image;

Retrieving Image Data

尽管我们可以通过函数

UIImagePNGRepresentation()
UIImageJPEGRepresentation()

来获取NSData类型的信息,但是这些信息中包含了文件头,压缩信息等。
而当我们需要对一张图片进行处理时,我们需要单纯的byte-by-byte的图片信息。这里,我们展示了如何获取图像的字节信息并存储为NSData类型的。

转换的主要思路为:
1. 将图片draw到context中
2. 调用CGBitmapContextGetData()获取图像的bytes信息

NOTE: 我们对于CGBitmapContextCreate函数已经不再陌生,其函数原型为:
CGBitmapContextCreate(data, width, height, bitsPerComponent, bytesPerRow, space, bitmapInfo)
其中第一个参数data,表示我们为Context所分配的内存。我们有两种选择,当传入NULL的时候,Quartz会自动管理Context内存,而不需要手动dealloc。如果我们传入了data值,则需要我们手动删除内存。
其实,当我们调用CGBitmapContextGetData()的时候,就是获取创建Context时所设定的data。

NSData *BytesFromRGBImage(UIImage *sourceImage) {if (!sourceImage) {return nil;}CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();if (colorSpace == NULL) {return nil;}// 创建Contextint width = sourceImage.size.width;int height = sourceImage.size.height;CGContextRef context = CGBitmapContextCreate(NULL, width, height, 8, width * 4, colorSpace, (CGBitmapInfo)kCGImageAlphaPremultipliedFirst);CGColorSpaceRelease(colorSpace);if (context == NULL) {return nil;}// 将图片draw到当前context中CGRect rect = (CGRect){.size = sourceImage.size};CGContextDrawImage(const, rect, sourceImage.CGImage);// 由bytes获取NSDataNSData *data = [NSData dataWithBytes:CGBitmapContextGetData(const) length:(width * height * 4)];CGContextRelease(context);return data;
}

由Bytes获取Images

由Bytes转换为UIImage通用是借助于

CGBitmapContextCreate

这里我们将第一个参数传入,即图片的data。这就告诉Quartz,不要在自动为我们分配内存,而是使用我们指定的内存。

现在,我们可以在bytes和image直接相互转换了,也就可以使得image可以修改了。

UIImage *ImageFromBytes(NSData *data, CGSize targetSize) {int width = targetSize.width;int height = targetSize.height;if (data.length < (width * height * 4)) {NSLog(@"Error: Got %d bytes. Expected %d bytes",data.length, width * height * 4);return nil;}CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();if (colorSpace == NULL) {return nil;}// 为bitmap创建contextByte *bytes = (Byte *)data.bytes;CGContextRef context = CGBitmapContextCreate(bytes,width, height,8,width * 4,colorSpace,(CGBitmapInfo)kCGImageAlphaPremultipliedFirst);CGColorSpaceRelease(colorSpace);if (context == NULL) {return nil;}// 将data转换为imageCGImageRef imageRef = CGBitmapContextCreateImage(context);UIImage *image = [UIImage imageWithCGImage:imageRef];// clean upCGContextRelease(context);CFRelease(imageRef);return image;}

Image的autolayout

我们在iOS中,通过Auto Layout来对齐各个View。

但有一个我们大家经常忽略的事实:Auto Layout并不是通过View的Frame来对齐的,而是通过Alignment rectangle。

通常,我们不必在意这个细节,因为大多数情况下,Frame和Alignment rectangle是相等的。

但是当我们的Image中包含阴影,高光等装饰性元素时,当我们想要将Image加载到ImageView中并利用Auto Layout对齐时,情况往往变得不尽如人意。(注意这里说的阴影的效果不是指我们通过layer代码添加的元素,而是指图片自带的内容)

例如,我们想居中对齐一个绿色矩形,带有黑色阴影的图片, 默认的Auto Layout居中效果如下:

可以明显的看到,绿色矩形并没有居中。这是为什么呢?我们继续看。

Debugging Alignment Rectangles

由于Auto Layout是根据Alignment Rectangle排列View的,因此我们可以查看Alignment Rectangle 来debug约束。
具体做法为在XCode中,选择Edit scheme,并在launch argument中输入

-UIViewShowAlignmentRects YES

可以看到,Alignment rectangle被黄线框了出来。可以看到,Alignment rectangle是和imageView的frame相同的,又因为阴影和绿色矩形在同一张图片,因此,绿色矩形就没有显示到正中央啦。

为了修正这种错误,我们需要将Alignment rectangle仅围绕绿色矩形。我们可以利用UIImage的方法

- (UIImage *)imageWithAlignmentRectInsets:(UIEdgeInsets)alignmentInsets 

来约定UIImage的Alignment Rectangle。这里我首先看一下矩形和阴影的inset:

知道了这个间隙,我们就通过函数

- (UIImage *)imageWithAlignmentRectInsets:(UIEdgeInsets)alignmentInsets 

以原始图片为蓝本,创建一个修正了Alignment Rectangle的新image:

UIImage *newImage = [image imageWithAlignmentRectInsets:UIEdgeInsetsMake(0, 0, 30, 30)];

将newImage加载如UIImageView中,在debug,则看到黄色的框现在是紧紧包裹着绿色矩形啦,这样,绿色矩形得到了居中。

示例代码:

- (void)viewDidLoad {[super viewDidLoad];// 调整图片的Alignment RectangleUIImage *image = [self createImage];// 居中imageViewUIImageView *imageView = [[UIImageView alloc] initWithImage:image];imageView.contentMode = UIViewContentModeScaleAspectFit; imageView.translatesAutoresizingMaskIntoConstraints = NO;[self.view addSubview:imageView];[imageView.centerXAnchor constraintEqualToAnchor:self.view.centerXAnchor].active = YES;[imageView.centerYAnchor constraintEqualToAnchor:self.view.centerYAnchor].active = YES;
}- (UIImage *)createImage {UIGraphicsBeginImageContext(CGSizeMake(250, 70));UIBezierPath *path = [UIBezierPath bezierPathWithRect:CGRectMake(50, 20, 200, 50)];[[UIColor blackColor] setFill];[path fill];path = [UIBezierPath bezierPathWithRect:CGRectMake(0, 0, 200, 50)];[[UIColor greenColor] setFill];[path fill];UIImage *image = UIGraphicsGetImageFromCurrentImageContext();UIImage *newImage = [image imageWithAlignmentRectInsets:UIEdgeInsetsMake(0, 0, 20, 50)];UIGraphicsEndImageContext();return newImage;
}

可拉伸的图片

当我们使用图片时,如果要显示的区域和原始图片尺寸不符时,为了填充显示区域,我们需要拉伸图片。如下图所示

这时,我们可以通过UIImage的方法:

- (UIImage *)resizableImageWithCapInsets:(UIEdgeInsets)capInsets resizingMode:(UIImageResizingMode)resizingMode

来指定图片可以拉伸的部位。
具体可以参考资料:
resizableImageWithCapInsets:方法的探析

View的背景图片

一般的,我们可以设置UIView的background color,却不能够设置背景图片(UIImageView除外)。在这里,介绍两种方法来设置普通View的背景图片:

  • UIColor的 colorWithPatternImage方法
self.view.backgroundColor=[UIColor colorWithPatternImage:[UIImage imageNamed:@"a"]]; 
  • UILayer的contents属性
self.view.layer.contents = (id)image.CGImage;

这里推荐使用第二种方法,因为第一种方法会占用大量的内存。

PS:
imageNamed: 这个方法用一个指定的名字在系统缓存中查找并返回一个图片对象如果它存在的话。如果缓存中没有找到相应的图片,这个方法从指定的文档中加载然后缓存并返回这个对象。因此imageNamed的优点是当加载时会缓存图片。所以当图片会频繁的使用时,那么用imageNamed的方法会比较好。例如:你需要在 一个TableView里的TableViewCell里都加载同样一个图标,那么用imageNamed加载图像效率很高。系统会把那个图标Cache到内存,在TableViewCell里每次利用那个图 像的时候,只会把图片指针指向同一块内存。正是因此使用imageNamed会缓存图片,即将图片的数据放在内存中,iOS的内存非常珍贵并且在内存消耗过大时,会强制释放内存,即会遇到memory warnings。而在iOS系统里面释放图像的内存是一件比较麻烦的事情,有可能会造成内存泄漏。例如:当一 个UIView对象的animationImages是一个装有UIImage对象动态数组NSMutableArray,并进行逐帧动画。当使用imageNamed的方式加载图像到一个动态数组NSMutableArray,这将会很有可能造成内存泄露。原因很显然的。

imageWithContentsOfFile:仅加载图片,图像数据不会缓存。因此对于较大的图片以及使用情况较少时,那就可以用该方法,降低内存消耗。

《iOS Drawing Practical UIKit Solutions》读书笔记(三) —— Drawing Images相关推荐

  1. 《iOS Drawing Practical UIKit Solutions》读书笔记(四) —— 遮罩,模糊和动画

    遮罩,模糊和动画会为我们的APP增色不少,现在,就让我们了解一下吧. 用Blocks绘制Images 利用下面工具函数,可以简化创建image的过程. typedef void(^DrawingSta ...

  2. mysql数据库权威指南_MySQL_MySQL权威指南读书笔记(三),第二章:MYSQL数据库里面的数 - phpStudy...

    MySQL权威指南读书笔记(三) 第二章:MYSQL数据库里面的数据 用想用好MYSQL,就必须透彻理解MYSQL是如何看待和处理数据的.本章主要讨论了两个问题:一是SQL所能处理的数据值的类型:二是 ...

  3. 《编程之美》读书笔记(三):烙饼问题与搜索树

    <编程之美>读书笔记三:烙饼问题与搜索树 薛笛 EMail:jxuedi#gmail.com 前面已经写了一些关于烙饼问题的简单分析,但因为那天太累有些意犹未尽,今天再充实一些内容那这个问 ...

  4. 《How Tomcat Works》读书笔记(三)--Connector(连接器)

    <How Tomcat Works>读书笔记(三)--Connector(连接器) 这是<How Tomcat Works>第三四章的读书笔记.主要写了Tomcat4.0默认的 ...

  5. TCPIP详解Protocol 读书笔记(三) IP协议讲解

    TCP/IP详解:Protocol 读书笔记(三) Chapter3 IP:网际协议 文章目录 TCP/IP详解:Protocol 读书笔记(三) Chapter3 IP:网际协议 IP协议 IP数据 ...

  6. 《大型网站技术架构》读书笔记三:大型网站核心架构要素

    来源:http://www.cnblogs.com/edisonchou/p/3806348.html 此篇已收录至<大型网站技术架构>读书笔记系列目录贴,点击访问该目录可获取更多内容. ...

  7. 《淘宝技术这十年》读书笔记 (三). 创造技术TFS和Tair

    前面两篇文章介绍了淘宝的发展历程和Java时代的变迁:             <淘宝技术这十年>读书笔记 (一).淘宝网技术简介及来源             <淘宝技术这十年&g ...

  8. Spring揭秘 读书笔记 三 bean的scope与FactoryBean

    本书可作为王富强所著<<Spring揭秘>>一书的读书笔记  第四章 BeanFactory的xml之旅 bean的scope scope有时被翻译为"作用域&quo ...

  9. 《你的灯亮着吗》 读书笔记三

    紧接<你的灯亮着吗>读书笔记二 4.这是谁的问题? 当别人可以妥善解决自己的问题时,不要越俎代庖,如果这是他们的麻烦,就让它成为他们的麻烦,如果一个人处于解决问题的位置,却并不受问题困扰, ...

最新文章

  1. 精确到秒的JQuery日期控件,jquery日历插件,jquery日期插件
  2. 余敖的实验整理(还没完成)
  3. 常用的JVM调优参数总结汇总【随时查阅学习】
  4. 诗词歌赋,样样精通!诗词古语小程序带你领略魅力古风丨实战
  5. Mysql中用between...and...查询日期时注意事项
  6. Python编程的10个经典错误及解决办法
  7. Spring框架 IOC
  8. (转)如何使用CodeSmith批量生成代码
  9. robot framework 使用四:分层设计和截图以及注意事项
  10. 数据库 设计中的英文术语
  11. 深度学习:鞍点与海森矩阵的问题
  12. 联想笔记本摄像头故障处理方法
  13. 学数学,读原著,勤思考,效果好
  14. Mac book 合并分区,报错文件系统验证失败的解决办法
  15. java keystore php,KeyStoreSpi
  16. 魅族20值得入手吗 魅族20参数配置
  17. Codeforces 1144 D
  18. shell的一些基础
  19. SpringBoot 项目@Value 注解取不到值
  20. z-index使用以及失效的处理方法

热门文章

  1. 串行口数据缓冲寄存器 SBUF 之 初步了解
  2. 【问题记录】更换域名的DNS时,浏览器许久不生效。
  3. 猿创征文|点亮JAVA技术之灯(线程篇)
  4. 回忆老友蒋新松先生及庆贺《机器人产业发展规划》的发布
  5. 利用python绘制自定义棋盘格
  6. 【未】Dynamic incentive schemes for managing dockless bike-sharing systems
  7. 第一次将项目push到gitlab
  8. BI神器Power Query(14)-- PQ制作时间维度表(3)
  9. 工业相机之镜头基础知识
  10. 1348:【例4-9】城市公交网建设问题