人生得意须尽欢,莫使金樽空对月。
天生我材必有用,千金散尽还复来。
前记
说到UIImage
大家都不会感到陌生,最近在研究UIImage
,就顺便把之前工作中用到的一些category
总结了一下,记录记录。
在这之前先看一下一些简答的效果图
1、简单处理
12.png2、GIF图片加载
12.gif3、图片添加文字、图片及图片截屏、擦除
添加文字.png 添加图片.png 截图.png wipe.gif代码分析
1、图片圆角处理
关于图片的圆角处理,这个可能用的比较多点,当然你也可以用给UIIImageView
设置圆角来达到目的,但是在性能上特别是用在tableview
上的时候,就没那么好了。
在这之前,我们先来看看CGContextRef
,这个怎么解释呢?我的理解就是画布
,就相当于我们在画画的时候的画板,我们需要画图片、文字、线条都需要在这上面进行。
在了解这个之后,我们就开始看代码
带圆圈的圆图
+ (UIImage*)gl_circleImage:(UIImage*)image withBorder:(CGFloat)border color:(UIColor *)color
{
//通过自己创建一个context来绘制,通常用于对图片的处理
//在retian屏幕上要使用这个函数,才能保证不失真
//该函数会自动创建一个context,并把它push到上下文栈顶,坐标系也经处理和UIKit的坐标系相同
UIGraphicsBeginImageContextWithOptions(CGSizeMake(image.size.width, image.size.height), NO, [UIScreen mainScreen].scale);
//获取上下文
CGContextRef context = UIGraphicsGetCurrentContext();
CGRect rect = CGRectMake(0, 0, image.size.width, image.size.height);
//设置宽度
CGContextSetLineWidth(context, 4*border);
//设置边框颜色
CGContextSetStrokeColorWithColor(context, color.CGColor);
//画椭圆 当宽和高一样的时候 为圆 此处设置可视范围
CGContextAddEllipseInRect(context, rect);
//剪切可视范围
CGContextClip(context);
//绘制图片
[image drawInRect:rect];
CGContextAddEllipseInRect(context, rect);
// 绘制当前的路径 只描绘边框
CGContextStrokePath(context);
UIImage *newimg = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return newimg;
}
本想讲点什么,但是代码中已经有注释,就不说什么了,这里需要注意的是UIGraphicsBeginImageContextWithOptions
这个函数,最后一个参数--缩放因子,和我们平时用的倍图相关,比如@x
,@2x
,@3x
,在4s
上为1
,后面的机型中,plus
为3
,其余为2
,在这里可以设置为0
,这样会自动匹配。
不带圆圈的圆图
/**
创建圆形图片
@param image 原始图片
@return 返回
*/
+ (UIImage *)gl_circleImage:(UIImage *)image;
由于和带圆圈的原图代码几乎一致,只是少了边框这部分代码,这里就不在贴出来了,后面会给出demo
2、根据颜色创建图片
这个比较简单,最主要的是靠下面两个函数来进行实现
//设置填充颜色
CGContextSetFillColorWithColor(context, color.CGColor);
//直接按rect的范围覆盖
CGContextFillRect(context, CGRectMake(0, 0, size.width, size.height));
在这里有两个函数,CGContextSetFillColorWithColor
和CGContextSetStrokeColorWithColor
,是相对的两个函数,一个是设置覆盖区域的颜色,一个是设置边框的颜色,在使用的时候应该和其他函数对应一起使用。
下面还是列出代码
+ (UIImage *)gl_imageWithColor:(UIColor *)color size:(CGSize)size{
CGSize imageSize = size;
//通过自己创建一个context来绘制,通常用于对图片的处理
UIGraphicsBeginImageContextWithOptions(imageSize, NO, [UIScreen mainScreen].scale);
//获取上下文
CGContextRef context = UIGraphicsGetCurrentContext();
//设置填充颜色
CGContextSetFillColorWithColor(context, color.CGColor);
//直接按rect的范围覆盖
CGContextFillRect(context, CGRectMake(0, 0, size.width, size.height));
UIImage *newimg = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return newimg;
}
由于根据颜色来绘制圆形图片代码和上面几乎一致,只是将其中的
CGContextFillRect
换成了CGContextAddEllipseInRect
和CGContextFillPath
,这里就不在详细讲解
3、给图片设置圆角
这个可能用的比较多点,比如当美工不给我切图的时候,我们又需要使图片其中的几个角有圆角,那么这时候这个方法就派上用场了。
在这里,主要是通过方法CGContextAddPath
和UIBezierPath
一起来实现,详细看到这里,大家都知道怎么用的了,下面我们就来看源码
+ (UIImage*)gl_cornerImage:(UIImage*)image corner:(CGFloat)corner rectCorner:(UIRectCorner)rectCorner
{
CGSize imageSize = image.size;
UIGraphicsBeginImageContextWithOptions(imageSize, NO, [UIScreen mainScreen].scale);
CGContextRef context = UIGraphicsGetCurrentContext();
CGRect rect = CGRectMake(0,
0,
imageSize.width,
imageSize.height);
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:rect
byRoundingCorners:rectCorner
cornerRadii:CGSizeMake(corner,
corner)];
//添加路径
CGContextAddPath(context, [path CGPath]);
//剪切可视范围
CGContextClip(context);
[image drawInRect:rect];
UIImage *newimg = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return newimg;
}
4、压缩图片
说到压缩图片,这个应该是用的最多的了,比如有一天突然项目经理给你说,我们上传的这个图片啊,太大了,不仅浪费流量还特别慢,而且服务器压力还不小....你们客户端处理下把,最好在250kb
左右,但是又不能裁剪图片,这个时候,这个方法就能大展身手了。
其主要思路是通过一个while
循环,不停的去缩小我们所要的图片,当然计算图片的大小,我们用的是UIImageJPEGRepresentation
,虽然这个和真实图片的大小还是有那么点误差,但是一般情况下还是没什么问题的,下面我们来看源码
+ (UIImage*)gl_compressImage:(UIImage *)image maxSize:(CGFloat)maxSize maxSizeWithKB:(CGFloat)maxSizeKB
{
if (maxSize <= 0) {
return nil;
}
if (maxSizeKB <= 0) {
return nil;
}
CGSize compressSize = image.size;
//获取缩放比 进行比较
CGFloat widthScale = compressSize.width*1.0 / maxSize;
CGFloat heightScale = compressSize.height*1.0 / maxSize;
if (widthScale > 1 && widthScale > heightScale) {
compressSize = CGSizeMake(image.size.width/widthScale, image.size.height/widthScale);
}
else if (heightScale > 1 && heightScale > widthScale){
compressSize = CGSizeMake(image.size.width/heightScale, image.size.height/heightScale);
}
//创建图片上下文 并获取剪切尺寸后的图片
UIGraphicsBeginImageContextWithOptions(compressSize, NO, 1);
CGRect rect = {CGPointZero,compressSize};
[image drawInRect:rect];
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
//循环缩小图片大小
NSData *imageData = UIImageJPEGRepresentation(newImage, 1.0);
//获取当前图片的大小
CGFloat currentImageSizeOfKB = imageData.length/1024.0;
//压缩比例
CGFloat compress = 0.9;
while (currentImageSizeOfKB > maxSizeKB && compress > 0.1) {
imageData = UIImageJPEGRepresentation(newImage, compress);
currentImageSizeOfKB = imageData.length/1024.0;
compress -= 0.1;
}
return [UIImage imageWithData:imageData];
}
5、加载GIF图片
说到加载动态图片,需求量虽然不大,但还是偶尔会用到,这里我们有一个很关键的API
---+ (nullable UIImage *)animatedImageWithImages:(NSArray<UIImage *> *)images duration:(NSTimeInterval)duration
,通过这个函数,我们可以简单的来播放动态图片。下面我们要做的就是怎么得到GIF
图片的图片资源和其播放时间duration
。
关于图片源,在ImageIo
中有这么一个类CGImageSource
,其中的CGImageSourceRef
也就是我们所说的图片源,查看API,我们可以得到三种方式:
1、CGImageSourceCreateWithURL
基于一个URL链接来读取图片信息。这个方法也是苹果推荐的方法,因为有些时候我们想获取照片的信息,但不需要将照片加载到内存中(因为这是没必要的),所以只需要给出照片的URL地址。
2、CGImageSourceCreateWithData
这个方法是基于一个NSData对象来获取照片信息。所以如果想使用此方法,必需将一个UIImage对象转换成NSData对象,例如:
NSString *filePath = [[NSBundle mainBundle]pathForResource:@"24" ofType:@"jpg"];
NSData *data = [NSData dataWithContentsOfFile:filePath];
CGImageSourceRef imageSource = CGImageSourceCreateWithData((__bridge CFDataRef)data, nil);
3、CGImageSourceCreateWithDataProvider
这种方法主要是需要将一个CGImageRef
生成一个CGDataProvider
。而参数"options"
选项是一个字典,用于创建图片源时提供的附加属性,例如是否对图片进行缓存等。
在有了得到图片源的方法后,我们就可以通过图片源来得到其中的相关信息,最主要的是动画时间,具体代码如下
static CGFloat frameDuration(NSInteger index,CGImageSourceRef source)
{
//获取每一帧的信息
CFDictionaryRef frameProperties = CGImageSourceCopyPropertiesAtIndex(source,index, nil);
//转换为dic
NSDictionary *framePropertiesDic = (__bridge NSDictionary *)frameProperties;
//获取每帧中关于GIF的信息
NSDictionary *gifProperties = framePropertiesDic[(__bridge NSString *)kCGImagePropertyGIFDictionary];
/*
苹果官方文档中的说明
kCGImagePropertyGIFDelayTime
The amount of time, in seconds, to wait before displaying the next image in an animated sequence
kCGImagePropertyGIFUnclampedDelayTime
The amount of time, in seconds, to wait before displaying the next image in an animated sequence. This value may be 0 milliseconds or higher. Unlike the kCGImagePropertyGIFDelayTime property, this value is not clamped at the low end of the range.
看了翻译瞬间蒙了 感觉一样 但是kCGImagePropertyGIFDelayTime 可能为0 所以我觉得可以先判断kCGImagePropertyGIFDelayTime
*/
CGFloat duration = 0.1;
NSNumber *unclampedPropdelayTime = gifProperties[(__bridge NSString *)kCGImagePropertyGIFUnclampedDelayTime];
NSNumber *delayTime = gifProperties[(__bridge NSString *)kCGImagePropertyGIFDelayTime];
if (unclampedPropdelayTime) {
duration = unclampedPropdelayTime.floatValue;
}else{
if (delayTime) {
duration = delayTime.floatValue;
}
}
CFRelease(frameProperties);
return duration;
}
在有了动态图片时间后,我们来看看后续动态图片的实现
//动态图片处理
static UIImage *animatedImageWithAnimateImageSource(CGImageSourceRef imageSource)
{
if (imageSource) {
//得到图片资源的数量
size_t imageCount = CGImageSourceGetCount(imageSource);
//最终图片资源
UIImage *resultImage = nil;
//动态图片时间
NSTimeInterval duration = 0.0;
//取图片资源
NSMutableArray *images = [NSMutableArray arrayWithCapacity:imageCount];
for (size_t i = 0; i < imageCount; i ++) {
//此处用到了create 后面记得释放
CGImageRef cgImage = CGImageSourceCreateImageAtIndex(imageSource, i, NULL);
if (cgImage) {
//将图片加入到数组中
[images addObject:[UIImage imageWithCGImage:cgImage scale:[UIScreen mainScreen].scale orientation:UIImageOrientationUp]];
}
duration += frameDuration(i, imageSource);
//释放掉 不然会内存泄漏
CGImageRelease(cgImage);
}
if (duration == 0.0) {
duration = 0.1 * imageCount;
}
resultImage = [UIImage animatedImageWithImages:images duration:duration];
CFRelease(imageSource);
return resultImage;
}
return nil;
}
这样的话,我们就可以加载我们所需要的动态图片了。在上述代码中,需要注意的几个地方
1、使用了create和copy
这样的函数,我们需要手动对其内存进行管理
2、在封装方法的时候,我只封装了用图片url
和图片二进制文件以及图片资源地址的方法,需要注意的是,在使用图片二进制文件的时候,需要特别注意二进制文件的转化,不能使用UIImageJPEGRepresentation
,亲测不对,不知道gif
用此方法为什么不行,建议通过路径的方式来获取data
,如NSData *data = [NSData dataWithContentsOfFile:imagePath]
,上面是核心代码,其他部分,可以参考后面的demo
6、图片添加文字或者图片
这个比较简单,最主要的方法就是通过drawInRect
来将文字或者图片画到当前的画布上,这里就只列一个添加文字的代码出来,添加图片的后面可以参考demo
+ (UIImage *)gl_addTitleAboveImage:(UIImage *)image addTitleText:(NSString *)text
attributeDic:(NSDictionary *)attributeDic point:(CGPoint)point
{
UIGraphicsBeginImageContextWithOptions(image.size, NO, [UIScreen mainScreen].scale);
CGRect imageRect = CGRectMake(0, 0, image.size.width, image.size.height);
[image drawInRect:imageRect];
[text drawAtPoint:point withAttributes:attributeDic];
//获取上下文中的新图片
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return newImage;
}
需要注意的是函数UIGraphicsBeginImageContextWithOptions
中的最后一个参数,需要根据当前屏幕来,否则添加的文字和图片可能会出现模糊,如在5S
上面,将其设为1
。
7、图片截屏或者擦除
在这两个功能中,最主要用到的函数是renderInContext
和CGContextClearRect
,第一个主要是用来截屏用的,渲染当前图片,与之接近的还有一个函数drawInContext
,当然,我们不能用这个函数,因为这个函数不能渲染图片,只针对layer。而后面的一个函数主要是用来清除该区域的。通过上面两个函数,我们就能够轻松的实现擦除功能,就如橡皮擦功能。
代码如下
+ (UIImage *)gl_wipeImageWithView:(UIView *)view movePoint:(CGPoint)point brushSize:(CGSize)size
{
//开启上下文
UIGraphicsBeginImageContext(view.bounds.size);
CGContextRef context = UIGraphicsGetCurrentContext();
//此方法不能渲染图片 只针对layer
//[view.layer drawInContext:context];
//以point为中心,然后size的一半向两边延伸 坐画笔 橡皮擦
CGRect clearRect = CGRectMake(point.x - size.width/2.0, point.y - size.width/2.0, size.width, size.height);
//渲染图片
[view.layer renderInContext:context];
//清除该区域
CGContextClearRect(context, clearRect);
//得到新图片
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
//避免内存泄漏
view.layer.contents = nil;
return newImage;
}
移动路径的代码就不在这里贴了,demo
中有的,截屏的核心代码也就不贴了,很简单的,可以查看demo
。
写在最后
其实上面的这些都是比较简单的,主要是通过CGContextRef
及其中的一些方法进行实习的,真正让人东西的,还是图形的处理,如添加马赛克、图片滤镜等,今天面试就问道了滤镜,当场就蒙了,后面我会努力研究下,希望到时候也能有好的东西分享给大家。
下面附上demo地址,如果觉得还可以,可以给个star
哦,不甚感激~
网友评论