离屏渲染及图片压缩、滤镜等原理解析

一张图像是像素点的集合,每一个像素都是一个独立,明了的颜色RGBA。
当成百上千万的像素集合到一起后,就构成了图像。

就像下图这样:


image.png

图片压缩

  • 采用系统的方式进行压缩
    //PNG格式 文件属性格式并不会被压缩,压缩的是图片内容(像素)
    //压缩的时候,最好不要采取这种方式,而是去用上下文重新生成一个图片,这样的图片才是最小的
    //png这类文件本身已经是图像压缩格式了,再用压缩格式去压缩,很可能会出现压缩不了的情况,再加上压缩文件本身是有文件结构信息的(也就是压缩文件头之类的),所以体积自然会增大。所以对于png、mp3等等这类格式,压缩它们往往只是出于打包到一起的目的,并不是为了减少体积。

    NSData *pngData = UIImagePNGRepresentation(_albumImage);  // 这种压缩耗时长,体积大,但是质量高
    NSData *jpgData = UIImageJPEGRepresentation(_albumImage, 0.1);  // 耗时短,体积小,质量低
    
    _pngImageView.image = [UIImage imageWithData:pngData];
    _jpgImageView.image = [UIImage imageWithData:jpgData];

最好不要采取这种方式压缩,除了有可能压缩下来,体积反而会增大。而且当压缩的时候,只能压缩图片的内容,会装换成bitmap格式,这样所占用的内存会非常大。比如下图:


3.jpg

大小虽然只有836KB,但是当转成bitmap格式进行压缩的时候,所占用的内存至少达:
4288 * 2848 * 4 / 1024 / 1024 = 46.6MB
4288 和 2848 是 图片宽高,4 是 一个像素的字节数。

  • 采用上下文,生成一个新的图片
// 因为生成一个bitmap,所以压缩的很小
- (UIImage *)scaleImage:(UIImage *)image size:(CGSize)imageSize {
    
    UIGraphicsBeginImageContext(imageSize);
    [image drawInRect:CGRectMake(0, 0, imageSize.width, imageSize.height)];
    UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    
    return newImage;
}

推荐使用这种方式,因为不会占内存,而且也压缩的很小。

图片过滤

我们先要明白一个东西,就是如果要对data类型的数据进行修改,我们需要将它转换成字符数组类型,然后取出数组里面的每个值,修改过后再放回原来的位置。就好像下面这样:

- (void)demoDataC {
    
    //NSString *testStr = ;//@"RGBARGBA" ;//@"255255255255"

    NSData *data = [@"0123456789" dataUsingEncoding:NSUTF8StringEncoding];
    NSInteger testDataLength = data.length;
    Byte *testC = (Byte *)[data bytes];  // 字符数组 testC = "0123456789"
    
    for (NSInteger i = 0; i < testDataLength; i++) {
        Byte testChar = (Byte)testC[i];  // 获取每一个元素,进行修改  //  当i=0 testChar=0
        printf("%c", testChar);
        testChar = testChar + 1;
        testC[i] = testChar;             // 修改完成后,再赋值回去
    }
    printf("\n");

    
    for (NSInteger i = 0; i < testDataLength; i++) {
        Byte testChar = (Byte)testC[i];  // 修改完成后  当i=0 testChar=1
        printf("%c", testChar);
    }
    
}

所以我们要过滤图片,即是要修改图片的data。我们也要先将它转换成字符数组,修改完成过后,再按照原来的样子通过上下文,生成一个bitmap,一一还原。原来的样子,就包括它的宽,高,每一行的字节数和位数,颜色空间和AlphaInfo等。

/*
 从图片文件把 图片数据 的像素拿出来(RGBA), 对像素进行操作, 进行一个转换(Bitmap (GPU))
 修改完之后,还原(图片的属性 RGBA,RGBA (宽度,高度,色值空间,拿到宽度和高度,每一个画多少个像素,画多少行))
 //256(11111111)
 */

- (void)filterImage {
    
    CGImageRef imageRef = self.image.CGImage;
    
    // 一个字节 = 8bit(位) 每行有 17152 个字节,即每行有 17152 * 8 个位   一个像素有 4 个字节
    size_t width  = CGImageGetWidth(imageRef);
    size_t height = CGImageGetHeight(imageRef);
    size_t bits   = CGImageGetBitsPerComponent(imageRef);  // 8
    size_t bitsPerrow = CGImageGetBytesPerRow(imageRef);   // width * 4  (每一行的字节 = 宽度 * 4(即RGBA各对应一个字节))
    
    
    // 颜色空间
    CGColorSpaceRef colorSpace = CGImageGetColorSpace(imageRef);
    // AlphaInfo : RGBA  AGBR  RGB
    CGImageAlphaInfo alphaInfo = CGImageGetAlphaInfo(imageRef);
    
    // 把图片转化为 bitmap 的数据
    CGDataProviderRef providerRef = CGImageGetDataProvider(imageRef);
    CFDataRef bitmapData = CGDataProviderCopyData(providerRef);
    
    NSInteger pixLength = CFDataGetLength(bitmapData);
    // 像素byte数组   要将data类型的装换成为C字符的数组类型才能进行操作
    Byte *pixbuff = CFDataGetMutableBytePtr((CFMutableDataRef)bitmapData);
    
    // 处理像素 RGBA为一个单元
    for (int i = 0; i < pixLength; i += 4) {
        
        // 在这里可以计算出每个像素的具体位置。比如:int pixX = i / width(类似这样,但是肯定这不是正确的表达式);

        [self eocImageFiletPixBuf:pixbuff offset:i];
    }
    
    // 处理好数据过后,就要准备用处理完的数组进行绘制图片了
    // bitmap 生成一个上下文,然后用上下文生成一个过滤后的图片 (用上前面保存的图片数据和属性)
    CGContextRef contextRef = CGBitmapContextCreate(pixbuff, width, height, bits, bitsPerrow, colorSpace, alphaInfo);
    
    CGImageRef filterImageRef = CGBitmapContextCreateImage(contextRef); // 用上下文生成图片
    
    UIImage *filterImage = [UIImage imageWithCGImage:filterImageRef];
    
    _filterImageV.image = filterImage;
    
    
}
// RGBA 为一个单元  彩色照变黑白照  pixBuf是一个数组 offset是每个RGBA的起点
- (void)eocImageFiletPixBuf:(Byte*)pixBuf offset:(int)offset{
    
    int offsetR = offset;
    int offsetG = offset + 1;
    int offsetB = offset + 2;
    int offsetA = offset + 3;
    
    int red = pixBuf[offsetR];
    int gre = pixBuf[offsetG];
    int blu = pixBuf[offsetB];
   // int alp = pixBuf[offsetA];
    
    int gray = (red + gre + blu)/3;
    
    pixBuf[offsetR] = gray;
    pixBuf[offsetG] = gray;
    pixBuf[offsetB] = gray;
    
}

这里取红绿蓝的平均值,并且将新的红绿蓝都赋值成这一个平均值,就能达到灰白的效果。

截图

  • 规则截图
    开启一个图像上下文,并且获取这个上下文,在将规则的path添加到上下文中,再截出上下文,最后将图片画到上下文中,最后再从上下文中获取图片,再最后结束上下文。
// 规则 截图

- (void)shotScreen {
    
    // 开启上下文
    UIGraphicsBeginImageContext(CGSizeMake(200, 200));
    
    // 获取上下文
    CGContextRef context = UIGraphicsGetCurrentContext();
    
    // 先 clip context ,才能限制后面上下文操作的范围,使图片画在一个圆内
    CGRect rect = CGRectMake(0, 0, 200, 200);
    
    CGContextAddEllipseInRect(context, rect); // path 加一个圆到上下文中
    CGContextClip(context); // 截出上下文,对于后面进行的上下文操作起限制作用
    
    UIImage *image = [UIImage imageNamed:@"3.jpg"];
    // 将图片滑到上下文中
    [image drawInRect:rect];
    
    // 从上下文中获取图片,这里的上下文还是 (0, 0, 200, 200) 大小的上下文
    UIImage *clipImage = UIGraphicsGetImageFromCurrentImageContext();
    
    // 结束上下文
    UIGraphicsEndImageContext();
    
    _eocImageV.image = clipImage;

}
  • 非规则截图
    道理同上,只是path是一个点的数组,这里点构成的图形可以是不规则的。
// 非规则的截图 (画点,画线)
- (void)noRectClip{
    
    // 开启上下文
    UIGraphicsBeginImageContext(CGSizeMake(200, 200));
    
    // 获取上下文
    CGContextRef context = UIGraphicsGetCurrentContext();
    
    // 非规则的path
    CGMutablePathRef pathRef = CGPathCreateMutable();
    
    // 必须是一个闭环,如果不是闭环,会自己回到最初的点
    CGPoint lines[] = {
        CGPointMake(0, 0),
        CGPointMake(150, 70),
        CGPointMake(200, 200),
        CGPointMake(50, 120),
        CGPointMake(30, 30)
    };
    
    CGPathAddLines(pathRef, NULL, lines, 5);
    CGContextAddPath(context, pathRef);
    CGContextClip(context);
    
    UIImage *imageTwo = [UIImage imageNamed:@"3.jpg"];
    [imageTwo drawInRect:CGRectMake(0, 0, 200, 200)];
    
    UIImage *clipImage = UIGraphicsGetImageFromCurrentImageContext();
    
    UIGraphicsEndImageContext();
    
    _eocImageV.image = clipImage;

    
}
  • 红色渲染
// 红色渲染
- (void)blend {
    
    UIImage *imageTwo = [UIImage imageNamed:@"3.jpg"];
    UIGraphicsBeginImageContext(CGSizeMake(200, 200));
    CGContextRef context = UIGraphicsGetCurrentContext();
    [imageTwo drawInRect:CGRectMake(0, 0, 200, 200)];
    
    // 这里要将不透明度设置为0.5,才是覆盖在上面的效果,如果为1,就直接只能看到红色,不能看到红色下面的图片
    UIColor *redColor = [UIColor colorWithRed:1 green:0 blue:0 alpha:0.5];
    
    // 给上下文设置颜色
    CGContextSetFillColorWithColor(context, redColor.CGColor);
    
    // 设置渲染模式
    CGContextSetBlendMode(context, kCGBlendModeNormal);
    
    // 填充上下文
    CGContextFillRect(context, CGRectMake(0, 0, 200, 200));
    
    CGImageRef imageRef = CGBitmapContextCreateImage(context);
    
    _eocImageV.image = [UIImage imageWithCGImage:imageRef];
    
    UIGraphicsEndImageContext();
    
}
  • 截屏
// 截屏
- (void)imageFromFullView{
    // 这里要是屏幕的大小
    UIGraphicsBeginImageContext(self.view.frame.size);
    
    CGContextRef context = UIGraphicsGetCurrentContext();
    
    [self.view.layer renderInContext:context];
    
    CGImageRef imageRef =  CGBitmapContextCreateImage(context);
    
    _eocImageV.image = [UIImage imageWithCGImage:imageRef];
    
    UIGraphicsEndImageContext();
    
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,402评论 6 499
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,377评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,483评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,165评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,176评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,146评论 1 297
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,032评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,896评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,311评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,536评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,696评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,413评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,008评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,659评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,815评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,698评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,592评论 2 353

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 172,072评论 25 707
  • 用两张图告诉你,为什么你的 App 会卡顿? - Android - 掘金 Cover 有什么料? 从这篇文章中你...
    hw1212阅读 12,712评论 2 59
  • 摘要:对android 上图片压缩,其实总结起来基本可以分为两类压缩:尺寸压缩和质量压缩, 尺寸压缩其实也可以理解...
    男爵是只猫丶阅读 8,783评论 2 14
  • 2021期待与你一起共事,点击查看岗位[https://www.jianshu.com/p/6f4d67fa406...
    闲庭阅读 16,631评论 0 75
  • 文/兰舟酱 盲女看不见的那年是十七岁的秋天,盲女觉得那个秋天的一切都在疯狂地从自己的视网膜上剥落,一个彩色的世界像...
    兰舟酱阅读 732评论 0 12