iOS Quart2D绘图之UIImage简单使用

人生得意须尽欢,莫使金樽空对月。
   天生我材必有用,千金散尽还复来。

前记

说到UIImage大家都不会感到陌生,最近在研究UIImage,就顺便把之前工作中用到的一些category总结了一下,记录记录。

在这之前先看一下一些简答的效果图

1、简单处理
12.png
2、GIF图片加载
12.gif
3、图片添加文字、图片及图片截屏、擦除
添加文字.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,后面的机型中,plus3,其余为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));

在这里有两个函数,CGContextSetFillColorWithColorCGContextSetStrokeColorWithColor,是相对的两个函数,一个是设置覆盖区域的颜色,一个是设置边框的颜色,在使用的时候应该和其他函数对应一起使用。
下面还是列出代码

+ (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换成了CGContextAddEllipseInRectCGContextFillPath,这里就不在详细讲解

3、给图片设置圆角

这个可能用的比较多点,比如当美工不给我切图的时候,我们又需要使图片其中的几个角有圆角,那么这时候这个方法就派上用场了。

在这里,主要是通过方法CGContextAddPathUIBezierPath一起来实现,详细看到这里,大家都知道怎么用的了,下面我们就来看源码

+ (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、图片截屏或者擦除

在这两个功能中,最主要用到的函数是renderInContextCGContextClearRect,第一个主要是用来截屏用的,渲染当前图片,与之接近的还有一个函数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哦,不甚感激~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容