大话SDWebImage(四)-- 图片数据处理层

早过忘川 提交于 2019-12-16 18:29:32

【推荐】2019 Java 开发者跳槽指南.pdf(吐血整理) >>>

五、图片数据处理层

5.1 图片类型分析

图片类型分析是通过判断图片数据的第一个字节,有点类似MachO文件的MagicNumber。

使用十六进制打开图片可以验证

  1. jpg图像的十六进制,第一个字节为0xFF

  1. png图像的十六进制,第一个字节为0x89,可以参考 RFC 文档中12.11. PNG file signature这一章节对PNG格式的介绍

对应的代码如下:

+ (SDImageFormat)sd_imageFormatForImageData:(nullable NSData *)data {
    if (!data) {
        return SDImageFormatUndefined;
    }
    
    uint8_t c;
    [data getBytes:&c length:1];
    switch (c) {
        case 0xFF:
            return SDImageFormatJPEG;
        case 0x89:
            return SDImageFormatPNG;
        case 0x47:
            return SDImageFormatGIF;
        case 0x49:
        case 0x4D:
            return SDImageFormatTIFF;
        case 0x52:
            // R as RIFF for WEBP
            if (data.length < 12) {
                return SDImageFormatUndefined;
            }
            
            NSString *testString = [[NSString alloc] initWithData:[data subdataWithRange:NSMakeRange(0, 12)] encoding:NSASCIIStringEncoding];
            if ([testString hasPrefix:@"RIFF"] && [testString hasSuffix:@"WEBP"]) {
                return SDImageFormatWebP;
            }
    }
    return SDImageFormatUndefined;
}

5.2 GIF支持

这个版本的GIF支持是通过第三发的组件实现FLAnimatedImage,主要代码是在FLAnimatedImageView+WebCache分类的sd_setImageWithURL方法中,主要代码如下,使用imageData创建FLAnimatedImage对象,设置到``FLAnimatedImageViewimage`属性中即可

- (void)sd_setImageWithURL:(nullable NSURL *)url
          placeholderImage:(nullable UIImage *)placeholder
                   options:(SDWebImageOptions)options
                  progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                 completed:(nullable SDExternalCompletionBlock)completedBlock {
    __weak typeof(self)weakSelf = self;
    [self sd_internalSetImageWithURL:url
                    placeholderImage:placeholder
                             options:options
                        operationKey:nil
                       setImageBlock:^(UIImage *image, NSData *imageData) {
                           SDImageFormat imageFormat = [NSData sd_imageFormatForImageData:imageData];
                           if (imageFormat == SDImageFormatGIF) {
                               weakSelf.animatedImage = [FLAnimatedImage animatedImageWithGIFData:imageData];
                               weakSelf.image = nil;
                           } else {
                               weakSelf.image = image;
                               weakSelf.animatedImage = nil;
                           }
                       }
                            progress:progressBlock
                           completed:completedBlock];
}

5.3 Webp支持

Web解码可以查看这篇文章的介绍 Webp Decode ,其中最重要是解码器WebPDemuxer

  1. 创建解码器,其中dataNSData *类型
WebPData webpData;
WebPDataInit(&webpData);
webpData.bytes = data.bytes;
webpData.size = data.length;
WebPDemuxer *demuxer = WebPDemux(&webpData);
  1. 获取图片信息,使用WebPDemuxGetI方法,可以获取图片宽高等信息,下面使用WEBP_FF_FORMAT_FLAGS获取图片格式信息,判断是否是动态的webP,如果不是解码出单张图
uint32_t flags = WebPDemuxGetI(demuxer, WEBP_FF_FORMAT_FLAGS);
if (!(flags & ANIMATION_FLAG)) {
    // for static single webp image
    UIImage *staticImage = [self sd_rawWepImageWithData:webpData];
    WebPDemuxDelete(demuxer);
    return staticImage;
}

单张图的步骤如下 :

  • 解码图像,图像的信息和解码的数据会保存在WebPDecoderConfig结构体中
	WebPDecoderConfig config;
	if (!WebPInitDecoderConfig(&config)) {
	    return nil;
	}
	if (WebPGetFeatures(webpData.bytes, webpData.size, &config.input) != VP8_STATUS_OK) {
	    return nil;
	}
	config.output.colorspace = config.input.has_alpha ? MODE_rgbA : MODE_RGB;
	config.options.use_threads = 1;
	
	// Decode the WebP image data into a RGBA value array.
	if (WebPDecode(webpData.bytes, webpData.size, &config) != VP8_STATUS_OK) {
	    return nil;
	}
	
	int width = config.input.width;
	int height = config.input.height;
	if (config.options.use_scaling) {
	    width = config.options.scaled_width;
	    height = config.options.scaled_height;
	}
  • 创建图像,解码数据会保存在config.output.u.RGBA中,先创建CGDataProviderRef对象provider,然后再使用provider创建CGImageRef对象,重要的是CGImageCreate这个方法,对应的参数做了注释提供参考
	// Construct a UIImage from the decoded RGBA value array.
  // 解码数据会保存在`config.output.u.RGBA`中,先创建`CGDataProviderRef`对象
	CGDataProviderRef provider =
	CGDataProviderCreateWithData(NULL, config.output.u.RGBA.rgba, config.output.u.RGBA.size, FreeImageData);
	CGColorSpaceRef colorSpaceRef = CGColorSpaceCreateDeviceRGB();
	CGBitmapInfo bitmapInfo = config.input.has_alpha ? kCGBitmapByteOrder32Big | kCGImageAlphaPremultipliedLast : 0;
	size_t components = config.input.has_alpha ? 4 : 3;
	CGColorRenderingIntent renderingIntent = kCGRenderingIntentDefault;
  // `provider`创建`CGImageRef`对象
//    CGImageRef CGImageCreate(
//    size_t width,// 图片宽度
//    size_t height,// 图片高度
//    size_t bitsPerComponent,// 图片颜色选项,文档中说明了32位真彩色(RGBA)的图像,该值需要传8
//    size_t bitsPerPixel,// 图片单个像素点的位数,32位真彩色,带有透明通道(RGBA)的图像一个像素点为4字节32位,24位没有透明通道(RGB)的图像一个像素点为3字节24位
//    size_t bytesPerRow,// 每一行的字节数,宽度*(4或者3)
//    CGColorSpaceRef space,// RGB颜色空间,CGColorSpaceCreateDeviceRGB
//    CGBitmapInfo bitmapInfo,// 位图的布局信息,带有alpha的使用kCGBitmapByteOrder32Big;没有alpha信息的使用kCGImageAlphaPremultipliedLast,可以参考 https://blog.csdn.net/jeffasd/article/details/78142067  https://blog.csdn.net/q375537943/article/details/59106787
//    CGDataProviderRef provider, // 数据提供者
//    const CGFloat *decode,
//    bool shouldInterpolate, // 是否使用差值,如果没有插值,在分辨率高于图像数据的输出设备上绘制时,图像可能会出现锯齿或像素化。因为创建的图像没有放大,所以不用
//    CGColorRenderingIntent intent // 这个值使用kCGRenderingIntentDefault即可,具体的详细资料没有找到
//    );
	CGImageRef imageRef = CGImageCreate(width, height, 8, components * 8, components * width, colorSpaceRef, bitmapInfo, provider, NULL, NO, renderingIntent);
	
	CGColorSpaceRelease(colorSpaceRef);
	CGDataProviderRelease(provider);
  
	UIImage *image = [[UIImage alloc] initWithCGImage:imageRef];

5.4 图片解码

解码的方法,主要的处理就是调用CGContextDrawImage把图片进行绘制,然后使用CGBitmapContextCreateImage获取绘制之后的图片,这个步骤是在异步队列中的,不会阻塞主线程。注意到这里使用了@autoreleasepool是为了中间的临时图片数据能够被及时释放,这样能够有效的防止临时对象不能被及时释放导致高的内存峰值。

+ (nullable UIImage *)decodedImageWithImage:(nullable UIImage *)image {
    if (![UIImage shouldDecodeImage:image]) {
        return image;
    }
    
    // autorelease the bitmap context and all vars to help system to free memory when there are memory warning.
    // on iOS7, do not forget to call [[SDImageCache sharedImageCache] clearMemory];
    @autoreleasepool{
        
        CGImageRef imageRef = image.CGImage;
        CGColorSpaceRef colorspaceRef = [UIImage colorSpaceForImageRef:imageRef];
        
        size_t width = CGImageGetWidth(imageRef);
        size_t height = CGImageGetHeight(imageRef);
        size_t bytesPerRow = kBytesPerPixel * width;

        // kCGImageAlphaNone is not supported in CGBitmapContextCreate.
        // Since the original image here has no alpha info, use kCGImageAlphaNoneSkipLast
        // to create bitmap graphics contexts without alpha info.
        CGContextRef context = CGBitmapContextCreate(NULL,
                                                     width,
                                                     height,
                                                     kBitsPerComponent,
                                                     bytesPerRow,
                                                     colorspaceRef,
                                                     kCGBitmapByteOrderDefault|kCGImageAlphaNoneSkipLast);
        if (context == NULL) {
            return image;
        }
        
        // Draw the image into the context and retrieve the new bitmap image without alpha
        CGContextDrawImage(context, CGRectMake(0, 0, width, height), imageRef);
        CGImageRef imageRefWithoutAlpha = CGBitmapContextCreateImage(context);
        UIImage *imageWithoutAlpha = [UIImage imageWithCGImage:imageRefWithoutAlpha
                                                         scale:image.scale
                                                   orientation:image.imageOrientation];
        
        CGContextRelease(context);
        CGImageRelease(imageRefWithoutAlpha);
        
        return imageWithoutAlpha;
    }
}
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!