图片加载流程探索

  • 位图:就是一个像素数组,数组中的每个像素就代表着图片中的⼀个点。我们在应⽤中经常用到的 JPEG 和 PNG 图片就是位图。(压缩过的图⽚格式)
  • 像素:字面意思上来说就是图像的基本元素。举个列子,将一张图片放到 PS 中尽可能放⼤, 那么我们可以看到⼀个个的小格式,其中每个⼩格子就是⼀个像素点,每个像素点有且仅有⼀个颜⾊。

1、从磁盘读入缓冲区
2、缓冲区拷贝到用户空间 ------- Data Buffer
3、解压缩 ------- Image Buffer
4、图片处理
5、渲染
Data Buffer ---Decode---> Image Buffer

图片显示
1、Load
2、Decode
3、Render

  • DataBuffer:原始数据(jpg/png)
  • 图像缓冲区(ImageBuffer):表示一种特定缓冲区,保存了图像在内存中的标识;缓冲区中元素描述了图像中每个像素的颜色和透明度(也就是以 RGBA 四个向量来标识)。因此图像缓冲区的⼤⼩和图像的⼤小成正比
  • 帧缓冲区(FrameBuffer):负责在你的APP中保存实际渲染后的输出,因此当你的App更新其视图层次结构的时候,UIKit将重新渲染APP的窗⼝及其子视图到帧缓冲区当中,该帧缓冲区提供每个像素的颜色信息,而我们的硬件将读取这些信息以便点亮显示器上对应的像素。

一、磁盘上的图片文件大小和 imageView 上的图片大小的关系

  • 1.1、和尺寸有关
  • 1.2、和渲染图片的Color Profile有关,通常是ARGB
  • 1.3 imageView上的图片大小 等于 1920*1080*4bytes
  • 1.4、和磁盘上的图片文件大小3.8MB无关
2、代码验证
    // 隐式解码
    UIImage *image = [UIImage imageNamed:@"1920-1080"];
    _imageView.image = image;
    
    CFDataRef rawData = CGDataProviderCopyData(CGImageGetDataProvider(_imageView.image.CGImage));
    NSLog(@"%ld",[(__bridge NSData *)rawData length]);//8294400
3、Allocations验证
  • Xcode -> Open Developer -> Instruments -> Allocations
  • 3.1、选择系统选择要运行App
  • 3.2、选择Generations,快照
  • 3.3、点击Mark Generation
  • 3.4、点击Generation B,解码操作
Snip20190910_7.png

二、背后发生了什么

  • Data Buffer:原始数据(jpg/png)
  • Image Buffer:图像像素信息,和图像的尺寸大小成正比,存放在RAM中
  • Frame Buffer(帧缓冲区):存放在video RAM中

三、优化图片

1、优化的两个点
  • 1.内存的占用
  • 1.CPU的使用
2、主动解码
  • 1.Core Graphics
    UIImage *image = [UIImage imageNamed:@"1920-1080"];
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        UIGraphicsBeginImageContextWithOptions(image.size, YES, [UIScreen mainScreen].scale);
        [image drawAtPoint:CGPointZero];
        //Image Buffer 已经填充了!!
        UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();
        dispatch_async(dispatch_get_main_queue(), ^{
            self->_imageView.image = newImage;
        });
    });
  • 2.ImageIO
    NSData *data = [NSData dataWithContentsOfFile: [[NSBundle mainBundle] pathForResource:@"logic" ofType:@"png"]];
    //输入源!!!
    CGImageSourceRef sourceRef = CGImageSourceCreateWithData((__bridge CFDataRef)data, NULL);
    //获取图片的类型
    NSString *typeStr = (__bridge NSString *)CGImageSourceGetType(sourceRef);
    //获取图像的数量
    NSUInteger count = CGImageSourceGetCount(sourceRef);
    
    NSDictionary *imageProperties = (__bridge NSDictionary *) CGImageSourceCopyPropertiesAtIndex(sourceRef, 0, NULL);
    NSUInteger width = [imageProperties[(__bridge NSString *)kCGImagePropertyPixelWidth] unsignedIntegerValue]; //宽度,像素值
    NSUInteger height = [imageProperties[(__bridge NSString *)kCGImagePropertyPixelHeight] unsignedIntegerValue]; //高度,像素值
    BOOL hasAlpha = [imageProperties[(__bridge NSString *)kCGImagePropertyHasAlpha] boolValue]; //是否含有Alpha通道
    CGImagePropertyOrientation exifOrientation = [imageProperties[(__bridge NSString *)kCGImagePropertyOrientation] integerValue]; // 这里也能直接拿到EXIF方向信息,和前面的一样。如果是iOS 7,就用NSInteger取吧 :)
    
    //解码的操作!!!
    CGImageRef imageRef = CGImageSourceCreateImageAtIndex(sourceRef, 0, NULL);
    
    // UIImageOrientation和CGImagePropertyOrientation枚举定义顺序不同,封装一个方法搞一个switch case就行
    UIImageOrientation imageOrientation = LG_YYUIImageOrientationFromEXIFValue(exifOrientation);
    UIImage *image = [UIImage imageWithCGImage:imageRef scale:[UIScreen mainScreen].scale orientation:imageOrientation];
    // 清理,都是C指针,避免内存泄漏
    CGImageRelease(imageRef);
    CFRelease(sourceRef);
    
    //解码过后的图片数据(imageBuffer)
    _imageView.image = image;
  • 3.动图
    NSData *data = [NSData dataWithContentsOfFile: [[NSBundle mainBundle] pathForResource:@"test@2x" ofType:@"gif"]];
    CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)data, NULL);
    
    NSUInteger frameCount = CGImageSourceGetCount(source); //帧数
    //解码过后的数据!!!
    NSMutableArray <UIImage *> *images = [NSMutableArray array];
    double totalDuration = 0;
    for (size_t i = 0; i < frameCount; i++) {
        NSDictionary *frameProperties = (__bridge NSDictionary *) CGImageSourceCopyPropertiesAtIndex(source, i, NULL);
        NSDictionary *gifProperties = frameProperties[(NSString *)kCGImagePropertyGIFDictionary]; // GIF属性字典
        double duration = [gifProperties[(NSString *)kCGImagePropertyGIFUnclampedDelayTime] doubleValue]; // GIF原始的帧持续时长,秒数
        CGImagePropertyOrientation exifOrientation = [frameProperties[(__bridge NSString *)kCGImagePropertyOrientation] integerValue]; // 方向
        CGImageRef imageRef = CGImageSourceCreateImageAtIndex(source, i, NULL); // CGImage
        UIImageOrientation imageOrientation = LG_YYUIImageOrientationFromEXIFValue(exifOrientation);
        UIImage *image = [UIImage imageWithCGImage:imageRef scale:[UIScreen mainScreen].scale orientation:imageOrientation];
        totalDuration += duration;
        [images addObject:image];
3、降低采样率
// 大图缩小为显示尺寸的图
- (UIImage *)downsampleImageAt:(NSURL *)imageURL to:(CGSize)pointSize scale:(CGFloat)scale {
    NSDictionary *imageSourceOptions =
    @{
      (__bridge NSString *)kCGImageSourceShouldCache: @NO //原始图像不解码
      };
    CGImageSourceRef imageSource =
    CGImageSourceCreateWithURL((__bridge CFURLRef)imageURL, (__bridge CFDictionaryRef)imageSourceOptions);
    
    CGFloat maxDimensionInPixels = MAX(pointSize.width, pointSize.height) * scale;
    NSDictionary *downsampleOptions =
    @{
      (__bridge NSString *)kCGImageSourceCreateThumbnailFromImageAlways: @YES,
      (__bridge NSString *)kCGImageSourceShouldCacheImmediately: @YES,  // 缩小图像的同时进行解码
      (__bridge NSString *)kCGImageSourceCreateThumbnailWithTransform: @YES,
      (__bridge NSString *)kCGImageSourceThumbnailMaxPixelSize: @(maxDimensionInPixels)
      };
    CGImageRef downsampledImage =
    CGImageSourceCreateThumbnailAtIndex(imageSource, 0, (__bridge CFDictionaryRef)downsampleOptions);
    UIImage *image = [[UIImage alloc] initWithCGImage:downsampledImage];
    CGImageRelease(downsampledImage);
    CFRelease(imageSource);
    
    return image;
}

四、CGBitmapContextCreate

//彩色空间变成灰色空间
//CPU -- GPU  -- 帧缓冲区(显示的数据信息) - Vsync - 屏幕上!
//操作像素点!- 先拿到像素点 - 修改像素点
+ (UIImage *)grayscaleImageForImage:(UIImage *)image{
    
    NSUInteger width  = CGImageGetWidth(image.CGImage);
    NSUInteger height  = CGImageGetHeight(image.CGImage);
    CGColorSpaceRef colorSpaceRef = CGColorSpaceCreateDeviceRGB();

    //开辟内存空间 - 8bits
    UInt32 *imagePiexl = (UInt32 *)calloc(width * height, sizeof(UInt32));
    //创建一个画板(大小,每一个像素的大小,ARGB/RGBA)
    CGContextRef contextRef  = CGBitmapContextCreate(imagePiexl,
                                                     width,
                                                     height,
                                                     8,
                                                     0,   //64 的整数倍(对齐!!!)
                                                     colorSpaceRef,
                                                     kCGImageAlphaNoneSkipLast);
    
    //第四步:根据图片绘制上下文(imageBuffer)
    CGContextDrawImage(contextRef, CGRectMake(0, 0, width, height), image.CGImage);
    
    //取出每一个像素点,修改每一个像素点的值~
    for (int y = 0; y < height; y++) {
        for (int x = 0; x < width; x++) {
            uint8_t *rgbPiexl = (uint8_t *)&imagePiexl[y * width + x];
            //像素操作!!!
            //cpu - gpu 的作用
        }
    }
    
    CGImageRef imageRef = CGBitmapContextCreateImage(contextRef);
    CGContextRelease(contextRef);
    CGColorSpaceRelease(colorSpaceRef);
    free(imagePiexl);
    
    UIImage *resultUIImage = [UIImage imageWithCGImage:imageRef scale:image.scale orientation:UIImageOrientationUp];
    CGImageRelease(imageRef);
    
    return nil;
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念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

推荐阅读更多精彩内容