圖片加載流程探索

  • 位圖:就是一個(gè)像素?cái)?shù)組,數(shù)組中的每個(gè)像素就代表著圖片中的?個(gè)點(diǎn)。我們?cè)趹?yīng)?中經(jīng)常用到的 JPEG 和 PNG 圖片就是位圖。(壓縮過的圖?格式)
  • 像素:字面意思上來說就是圖像的基本元素。舉個(gè)列子,將一張圖片放到 PS 中盡可能放?, 那么我們可以看到?個(gè)個(gè)的小格式,其中每個(gè)?格子就是?個(gè)像素點(diǎn),每個(gè)像素點(diǎn)有且僅有?個(gè)顏?。

1、從磁盤讀入緩沖區(qū)
2、緩沖區(qū)拷貝到用戶空間 ------- Data Buffer
3、解壓縮 ------- Image Buffer
4、圖片處理
5、渲染
Data Buffer ---Decode---> Image Buffer

圖片顯示
1、Load
2、Decode
3、Render

  • DataBuffer:原始數(shù)據(jù)(jpg/png)
  • 圖像緩沖區(qū)(ImageBuffer):表示一種特定緩沖區(qū),保存了圖像在內(nèi)存中的標(biāo)識(shí);緩沖區(qū)中元素描述了圖像中每個(gè)像素的顏色和透明度(也就是以 RGBA 四個(gè)向量來標(biāo)識(shí))。因此圖像緩沖區(qū)的??和圖像的?小成正比
  • 幀緩沖區(qū)(FrameBuffer):負(fù)責(zé)在你的APP中保存實(shí)際渲染后的輸出,因此當(dāng)你的App更新其視圖層次結(jié)構(gòu)的時(shí)候,UIKit將重新渲染APP的窗?及其子視圖到幀緩沖區(qū)當(dāng)中,該幀緩沖區(qū)提供每個(gè)像素的顏色信息,而我們的硬件將讀取這些信息以便點(diǎn)亮顯示?上對(duì)應(yīng)的像素。

一、磁盤上的圖片文件大小和 imageView 上的圖片大小的關(guān)系

  • 1.1、和尺寸有關(guān)
  • 1.2、和渲染圖片的Color Profile有關(guān),通常是ARGB
  • 1.3 imageView上的圖片大小 等于 1920*1080*4bytes
  • 1.4、和磁盤上的圖片文件大小3.8MB無關(guān)
2、代碼驗(yàn)證
    // 隱式解碼
    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驗(yàn)證
  • Xcode -> Open Developer -> Instruments -> Allocations
  • 3.1、選擇系統(tǒng)選擇要運(yùn)行App
  • 3.2、選擇Generations,快照
  • 3.3、點(diǎn)擊Mark Generation
  • 3.4、點(diǎn)擊Generation B,解碼操作
Snip20190910_7.png

二、背后發(fā)生了什么

  • Data Buffer:原始數(shù)據(jù)(jpg/png)
  • Image Buffer:圖像像素信息,和圖像的尺寸大小成正比,存放在RAM中
  • Frame Buffer(幀緩沖區(qū)):存放在video RAM中

三、優(yōu)化圖片

1、優(yōu)化的兩個(gè)點(diǎn)
  • 1.內(nèi)存的占用
  • 1.CPU的使用
2、主動(dòng)解碼
  • 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 已經(jīng)填充了??!
        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);
    //獲取圖像的數(shù)量
    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枚舉定義順序不同,封裝一個(gè)方法搞一個(gè)switch case就行
    UIImageOrientation imageOrientation = LG_YYUIImageOrientationFromEXIFValue(exifOrientation);
    UIImage *image = [UIImage imageWithCGImage:imageRef scale:[UIScreen mainScreen].scale orientation:imageOrientation];
    // 清理,都是C指針,避免內(nèi)存泄漏
    CGImageRelease(imageRef);
    CFRelease(sourceRef);
    
    //解碼過后的圖片數(shù)據(jù)(imageBuffer)
    _imageView.image = image;
  • 3.動(dòng)圖
    NSData *data = [NSData dataWithContentsOfFile: [[NSBundle mainBundle] pathForResource:@"test@2x" ofType:@"gif"]];
    CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)data, NULL);
    
    NSUInteger frameCount = CGImageSourceGetCount(source); //幀數(shù)
    //解碼過后的數(shù)據(jù)?。。?    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原始的幀持續(xù)時(shí)長,秒數(shù)
        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,  // 縮小圖像的同時(shí)進(jìn)行解碼
      (__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  -- 幀緩沖區(qū)(顯示的數(shù)據(jù)信息) - Vsync - 屏幕上!
//操作像素點(diǎn)!- 先拿到像素點(diǎn) - 修改像素點(diǎn)
+ (UIImage *)grayscaleImageForImage:(UIImage *)image{
    
    NSUInteger width  = CGImageGetWidth(image.CGImage);
    NSUInteger height  = CGImageGetHeight(image.CGImage);
    CGColorSpaceRef colorSpaceRef = CGColorSpaceCreateDeviceRGB();

    //開辟內(nèi)存空間 - 8bits
    UInt32 *imagePiexl = (UInt32 *)calloc(width * height, sizeof(UInt32));
    //創(chuàng)建一個(gè)畫板(大小,每一個(gè)像素的大小,ARGB/RGBA)
    CGContextRef contextRef  = CGBitmapContextCreate(imagePiexl,
                                                     width,
                                                     height,
                                                     8,
                                                     0,   //64 的整數(shù)倍(對(duì)齊?。。。?                                                     colorSpaceRef,
                                                     kCGImageAlphaNoneSkipLast);
    
    //第四步:根據(jù)圖片繪制上下文(imageBuffer)
    CGContextDrawImage(contextRef, CGRectMake(0, 0, width, height), image.CGImage);
    
    //取出每一個(gè)像素點(diǎn),修改每一個(gè)像素點(diǎn)的值~
    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;
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容