前言
对于判断一个文件的格式,不可以简单通过文件的后缀名来确定文件类型,因为后缀名是可以自由修改的。在平时开发中,需要判断不同图片的文件格式,就不能简单的判断图片文件后缀名或者是图片链接的后缀。
更有的时候,我们只有二进制数据。对此,我们可以通过分析二进制来判断该二进制代表的文件格式。
二进制文件结构
常见的二进制都是由文件头和分区组成,文件头描述了文件的整体信息,常见的字段有魔数、版本号、检验码、文件大小等等。
文件头魔数,一般作为文件格式的标识,我们可以通过分析这个魔数,来确定该二进制的文件格式。对于常见的魔数可以参阅:FILE SIGNATURES TABLE
SDWebImage 框架判断图片格式
SDWebImage 框架为 NSData 实现了个分类来判断二进制文件图片格式,主要是通过取出 Data 中的第一个字节来进行魔数判断,具体代码如下:
+ (SDImageFormat)sd_imageFormatForImageData:(nullable NSData *)data {
if (!data) {
return SDImageFormatUndefined;
}
// File signatures table: http://www.garykessler.net/library/file_sigs.html
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: {
if (data.length >= 12) {
//RIFF....WEBP
NSString *testString = [[NSString alloc] initWithData:[data subdataWithRange:NSMakeRange(0, 12)] encoding:NSASCIIStringEncoding];
if ([testString hasPrefix:@"RIFF"] && [testString hasSuffix:@"WEBP"]) {
return SDImageFormatWebP;
}
}
break;
}
case 0x00: {
if (data.length >= 12) {
//....ftypheic ....ftypheix ....ftyphevc ....ftyphevx
NSString *testString = [[NSString alloc] initWithData:[data subdataWithRange:NSMakeRange(4, 8)] encoding:NSASCIIStringEncoding];
if ([testString isEqualToString:@"ftypheic"]
|| [testString isEqualToString:@"ftypheix"]
|| [testString isEqualToString:@"ftyphevc"]
|| [testString isEqualToString:@"ftyphevx"]) {
return SDImageFormatHEIC;
}
}
break;
}
}
return SDImageFormatUndefined;
}
YYImage 框架判断图片格式
YYImage 框架提供一个 YYImageDetectType
函数来判断一个二进制图片格式,同理,也是取出 Data 前几个字节来进行魔数判断,具体代码如下:
YYImageType YYImageDetectType(CFDataRef data) {
if (!data) return YYImageTypeUnknown;
uint64_t length = CFDataGetLength(data);
if (length < 16) return YYImageTypeUnknown;
const char *bytes = (char *)CFDataGetBytePtr(data);
uint32_t magic4 = *((uint32_t *)bytes);
switch (magic4) {
case YY_FOUR_CC(0x4D, 0x4D, 0x00, 0x2A): { // big endian TIFF
return YYImageTypeTIFF;
} break;
case YY_FOUR_CC(0x49, 0x49, 0x2A, 0x00): { // little endian TIFF
return YYImageTypeTIFF;
} break;
case YY_FOUR_CC(0x00, 0x00, 0x01, 0x00): { // ICO
return YYImageTypeICO;
} break;
case YY_FOUR_CC(0x00, 0x00, 0x02, 0x00): { // CUR
return YYImageTypeICO;
} break;
case YY_FOUR_CC('i', 'c', 'n', 's'): { // ICNS
return YYImageTypeICNS;
} break;
case YY_FOUR_CC('G', 'I', 'F', '8'): { // GIF
return YYImageTypeGIF;
} break;
case YY_FOUR_CC(0x89, 'P', 'N', 'G'): { // PNG
uint32_t tmp = *((uint32_t *)(bytes + 4));
if (tmp == YY_FOUR_CC('\r', '\n', 0x1A, '\n')) {
return YYImageTypePNG;
}
} break;
case YY_FOUR_CC('R', 'I', 'F', 'F'): { // WebP
uint32_t tmp = *((uint32_t *)(bytes + 8));
if (tmp == YY_FOUR_CC('W', 'E', 'B', 'P')) {
return YYImageTypeWebP;
}
} break;
/*
case YY_FOUR_CC('B', 'P', 'G', 0xFB): { // BPG
return YYImageTypeBPG;
} break;
*/
}
uint16_t magic2 = *((uint16_t *)bytes);
switch (magic2) {
case YY_TWO_CC('B', 'A'):
case YY_TWO_CC('B', 'M'):
case YY_TWO_CC('I', 'C'):
case YY_TWO_CC('P', 'I'):
case YY_TWO_CC('C', 'I'):
case YY_TWO_CC('C', 'P'): { // BMP
return YYImageTypeBMP;
}
case YY_TWO_CC(0xFF, 0x4F): { // JPEG2000
return YYImageTypeJPEG2000;
}
}
// memcmp()用来比较s1 和s2 所指的内存区间前n 个字符。
// JPG FF D8 FF
if (memcmp(bytes,"\377\330\377",3) == 0) return YYImageTypeJPEG;
// JP2
if (memcmp(bytes + 4, "\152\120\040\040\015", 5) == 0) return YYImageTypeJPEG2000;
return YYImageTypeUnknown;
}
总结
我们可以通过二进制文件头魔数来确定文件格式。对于判断图片文件格式,YYImage 框架和 SDWebImage 框架都是采用了这种方法,这两个框架实现区别在于 SDWebImage 框架利用 Foundation 方法来操作,YYImage 框架利用 Core-Foundation 函数来实现。经过测试 YYImage 性能稍优于 SDWebImage,但也只是在体量很大的时候有差别,在平时开发中,我们不需要考虑这里区别。
网友评论