天天看点

简单分析YYWebImage 是如何判断图片格式的原理 (附上SDWebImage)

导语

目前关于网络下载图片的框架,除了十分热门的SDWebImage之外还有很多,比如PINRemoteImage、FlyImage等许许多多优秀的框架。这篇文章主要讲的YYWebImage当然也是其中的一员。写这篇文章的主要目的是希望能和大家一起探讨一下网络下载图片是如何来判定格式的原理

前言

YYWebImage的作者是ibireme,Git网址:https://github.com/ibireme/YYWebImage

此网站上十分详细的写了如何安装以及使用YYWebImage

SDWebImage Git网址:https://github.com/rs/SDWebImage

在闲暇之余,研究一下大神写的代码,分析一下他们是如何实现判断图片格式的代码。

图片的格式

首先,为大家普及一下图片的格式(PS:不知道的可以看看,知道的就跳过哈)

图片常见的格式有:GIF 、PNG、JPEG、BMP、TIFF 等。

其实每一个图片格式都有对应的十六进制数据(PS:当然十六进制也是从二进制转换过来的),也可以说就是这些十六进制数据组成了一张图片,然后再通过计算机内部的渲染等一系列算法从而显示了一张图片,而往往前面的4~8个字节往往都代表了这张图片的格式

如:

89 50 4E 47 0D 0A 1A 0A 00 00 …………

前面的4个字节(89 50 4E 47)的ASCII 对应的就是 ‘.’ ‘P’ ‘N’ ‘G’;

再如:

47 49 46 38 39 61 64 00 …….

前面的4个字节的ASCII 对应的就是’G’ ‘I’ ‘F’ ‘8’

(PS:为啥上传不了图片,要不然截个图超省事…….)

正文

接下来咱们就一起看看YYWebImage

在YYWebImage的 YYWebImageOperation.m 中找到了这么一段代码

YYImageType imageType = YYImageDetectType((__bridge CFDataRef)self.data);
                switch (imageType) {
                    case YYImageTypeJPEG:
                    case YYImageTypeGIF:
                    case YYImageTypePNG:
                    case YYImageTypeWebP: { // save to disk cache
                        if (!hasAnimation) {
                            if (imageType == YYImageTypeGIF ||
                                imageType == YYImageTypeWebP) {
                                self.data = nil; // clear the data, re-encode for disk cache
                            }
                        }
                    } break;
                    default: {
                        self.data = nil; // clear the data, re-encode for disk cache
                    } break;
                }
           

这是一段已经要返回出图片格式了,咱们点进YYImageDetectType这个方法看看,里面是如何实现的

YYImageType YYImageDetectType(CFDataRef data) {
    if (!data) return YYImageTypeUnknown;
    uint64_t length = CFDataGetLength(data);
    if (length < ) return YYImageTypeUnknown;

    const char *bytes = (char *)CFDataGetBytePtr(data);

    uint32_t magic4 = *((uint32_t *)bytes);
    switch (magic4) {
        case YY_FOUR_CC(, , , ): { // big endian TIFF
            return YYImageTypeTIFF;
        } break;
        case YY_FOUR_CC(, , , ): { // little endian TIFF
            return YYImageTypeTIFF;
        } break;

        case YY_FOUR_CC(, , , ): { // ICO
            return YYImageTypeICO;
        } break;
 ......
           

正如我们预料的那样,YYWebImage同样也是用每个图片的开头前4个或前8个字节的数据来判断的

接下来进一步分析一下:

这个方法很长我就截取了一部分,我们先来看看上半部分

uint64_t length = CFDataGetLength(data); 
           

上面这句话就是可以拿到data数据中前8个字节长度的数据(PS:uint64_t 相当于8个字节)

const char *bytes = (char*)CFDataGetBytePtr(data); //转成char类型的数据
uint32_t magic4 = *((uint32_t *)bytes);  //这句话再转成位也就是的个字节的数据
           

接下来就开始进行判断了

咱们来就以PNG的判断方法来分析

case YY_FOUR_CC(, 'P', 'N', 'G'): {  // PNG
            uint32_t tmp = *((uint32_t *)(bytes + ));
            if (tmp == YY_FOUR_CC('\r', '\n', , '\n')) {
                return YYImageTypePNG;
            }
        } break;
           

嗯……YY_FOUR_CC 是啥,咱们再点进去看看。

看了之后我们发现是一个宏定义,就是拼接这几个十六进制位置的。

咱们接下来看,就来看

case YY_FOUR_CC(0x89, 'P', 'N', 'G')

前面我们也已经提到了,一般PNG格式的前4个字节都是(89 50 4E 47)正好对应的就是 ‘.’ ‘P’ ‘N’ ‘G’;

其实我个人觉得前4个字节其实已经可以完成判断,但是YYWebImage的作者一定是个追求细节的人,他再判断了4个字节来完整的判断出这张是一张PNG格式的图片

if (tmp == YY_FOUR_CC('\r', '\n', 0x1A, '\n'))

(PS:完整的判断一张PNG格式的图片确实需要8个字节)

判断完成后就把图片的格式返回出去,这就是YYWebImage判断图片格式的基本原理啦。

SDWebImage判断图片格式的方法

接下来咱们粗略的参考一下SDWebImage的图片格式,基本上应该差不多

咱们来看一下在NSData+ImageContentType.h 的头文件夹下有这么一个方法

+ (SDImageFormat)sd_imageFormatForImageData:(nullable NSData *)data;

再来看看实现原理

+ (SDImageFormat)sd_imageFormatForImageData:(nullable NSData *)data {
    if (!data) {
        return SDImageFormatUndefined;
    }

    uint8_t c;
    [data getBytes:&c length:];
    switch (c) {
        case :
            return SDImageFormatJPEG;
        case :
            return SDImageFormatPNG;
        case :
            return SDImageFormatGIF;
        case :
        case :
            return SDImageFormatTIFF;
        case :
            // R as RIFF for WEBP
            if (data.length < ) {
                return SDImageFormatUndefined;
            }

            NSString *testString = [[NSString alloc] initWithData:[data subdataWithRange:NSMakeRange(, )] encoding:NSASCIIStringEncoding];
            if ([testString hasPrefix:@"RIFF"] && [testString hasSuffix:@"WEBP"]) {
                return SDImageFormatWebP;
            }
    }
    return SDImageFormatUndefined;
}
           

就是那么的简单粗暴。

uint8_t c; [data getBytes:&c length:1];

这直接就截取了前1个字节长度的数据

再判断每个字节中的十六进制数所对应的格式

case 0x89: return SDImageFormatPNG;

结束语

条条大路通罗马,能实现功能的就是好方法,区别在于快慢。

希望大家阅读了本人的文章可以有所帮助

这是本人的初个博客,希望大家不喜勿喷,如有错误或者想法可留言,我会吸取各个方面的建议,多多完善,还希望大家多多关照,互帮互助。谢谢大家抽出宝贵的时间来阅读本人的文章。