从 NSData 或 UIImage 查找图像类型

我从第三方提供的 URL 加载图像。URL 上没有文件扩展名(或文件名)(因为它是一个模糊的 URL)。我可以从中获取数据(以 NSData 的形式) ,并将其加载到 UIImage 中,并进行良好的显示。

我希望将此数据保存到一个文件中。但是,我不知道数据的格式是什么(PNG,JPG,BMP) ?我假设它是 JPG (因为它是来自网络的图像) ,但是有没有一种程序化的方法来确定呢?我查看了 StackOverflow 和文档,一无所获。

TIA.


编辑: 我真的需要文件扩展名吗?我把它保存在外部存储器(Amazon S3)中,但考虑到它总是在 iOS 或浏览器上使用(这两者似乎都可以在没有扩展的情况下解释数据) ,也许这不是问题。

47940 次浏览

If you're retrieving the image from a URL, then presumably you can inspect the HTTP response headers. Does the Content-Type header contain anything useful? (I'd imagine it would since a browser would probably be able to display the image correctly, and it could only do that if the content type were appropriately set)

If it really matters to you, I believe you'll have to examine the bytestream. A JPEG will start with the bytes FF D8. A PNG will start with 89 50 4E 47 0D 0A 1A 0A. I don't know if BMP has a similar header, but I don't think you're too likely to run into those on the web in 2010.

But does it really matter to you? Can't you just treat it as an unknown image and let Cocoa Touch do the work?

If you have NSData for the image file, then you can guess at the content type by looking at the first byte:

+ (NSString *)contentTypeForImageData:(NSData *)data {
uint8_t c;
[data getBytes:&c length:1];


switch (c) {
case 0xFF:
return @"image/jpeg";
case 0x89:
return @"image/png";
case 0x47:
return @"image/gif";
case 0x49:
case 0x4D:
return @"image/tiff";
}
return nil;
}

Implement a signature check for each known image format. Here is a quick Objective-C function that does that for PNG data:

// Verify that NSData contains PNG data by checking the signature


- (BOOL) isPNGData:(NSData*)data
{
// Verify that the PNG file signature matches


static const
unsigned char   png_sign[8] = {137, 80, 78, 71, 13, 10, 26, 10};


unsigned char   sig[8] = {0, 0, 0, 0, 0, 0, 0, 0};


if ([data length] <= 8) {
return FALSE;
}


[data getBytes:&sig length:8];


BOOL same = (memcmp(sig, png_sign, 8) == 0);


return same;
}

Improving upon wl.'s answer, here's a much more extended and precise way to predict the image's MIME type based on the signature. The code was largely inspired by php's ext/standard/image.c.

- (NSString *)mimeTypeByGuessingFromData:(NSData *)data {


char bytes[12] = {0};
[data getBytes:&bytes length:12];


const char bmp[2] = {'B', 'M'};
const char gif[3] = {'G', 'I', 'F'};
const char swf[3] = {'F', 'W', 'S'};
const char swc[3] = {'C', 'W', 'S'};
const char jpg[3] = {0xff, 0xd8, 0xff};
const char psd[4] = {'8', 'B', 'P', 'S'};
const char iff[4] = {'F', 'O', 'R', 'M'};
const char webp[4] = {'R', 'I', 'F', 'F'};
const char ico[4] = {0x00, 0x00, 0x01, 0x00};
const char tif_ii[4] = {'I','I', 0x2A, 0x00};
const char tif_mm[4] = {'M','M', 0x00, 0x2A};
const char png[8] = {0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a};
const char jp2[12] = {0x00, 0x00, 0x00, 0x0c, 0x6a, 0x50, 0x20, 0x20, 0x0d, 0x0a, 0x87, 0x0a};




if (!memcmp(bytes, bmp, 2)) {
return @"image/x-ms-bmp";
} else if (!memcmp(bytes, gif, 3)) {
return @"image/gif";
} else if (!memcmp(bytes, jpg, 3)) {
return @"image/jpeg";
} else if (!memcmp(bytes, psd, 4)) {
return @"image/psd";
} else if (!memcmp(bytes, iff, 4)) {
return @"image/iff";
} else if (!memcmp(bytes, webp, 4)) {
return @"image/webp";
} else if (!memcmp(bytes, ico, 4)) {
return @"image/vnd.microsoft.icon";
} else if (!memcmp(bytes, tif_ii, 4) || !memcmp(bytes, tif_mm, 4)) {
return @"image/tiff";
} else if (!memcmp(bytes, png, 8)) {
return @"image/png";
} else if (!memcmp(bytes, jp2, 12)) {
return @"image/jp2";
}


return @"application/octet-stream"; // default type


}

The above method recognizes the following image types:

  • image/x-ms-bmp (bmp)
  • image/gif (gif)
  • image/jpeg (jpg, jpeg)
  • image/psd (psd)
  • image/iff (iff)
  • image/webp (webp)
  • image/vnd.microsoft.icon (ico)
  • image/tiff (tif, tiff)
  • image/png (png)
  • image/jp2 (jp2)

Unfortunately, there is no simple way to get this kind of information from a UIImage instance, because its encapsulated bitmap data cannot be accessed.

I made a library to check the image type of NSData:

https://github.com/sweetmandm/ImageFormatInspector

Swift3 version:

let data: Data = UIImagePNGRepresentation(yourImage)!


extension Data {
var format: String {
let array = [UInt8](self)
let ext: String
switch (array[0]) {
case 0xFF:
ext = "jpg"
case 0x89:
ext = "png"
case 0x47:
ext = "gif"
case 0x49, 0x4D :
ext = "tiff"
default:
ext = "unknown"
}
return ext
}
}

An alternative of accepted answer is checking image's UTI with image I/O frameWork. You can achieve image type form UTI. try this:

CGImageSourceRef imgSrc = CGImageSourceCreateWithData((CFDataRef)data, NULL);
NSString *uti = (NSString*)CGImageSourceGetType(imgSrc);
NSLog(@"%@",uti);

For example, a GIF image's UTI is "com.compuserve.gif" and PNG image's UTI is "public.png".BUT you can't achieve UTI from image which image I/O frameWork doesn't recognized.

@Tai Le's solution for Swift 3 is assigning whole data into byte array. If an image large, it can cause crash. This solution just assigns single byte:

import Foundation


public extension Data {
var fileExtension: String {
var values = [UInt8](repeating:0, count:1)
self.copyBytes(to: &values, count: 1)


let ext: String
switch (values[0]) {
case 0xFF:
ext = ".jpg"
case 0x89:
ext = ".png"
case 0x47:
ext = ".gif"
case 0x49, 0x4D :
ext = ".tiff"
default:
ext = ".png"
}
return ext
}
}

My improved solution based on @ccoroom

//  Data+ImageContentType.swift


import Foundation


extension Data {
enum ImageContentType: String {
case jpg, png, gif, tiff, unknown


var fileExtension: String {
return self.rawValue
}
}


var imageContentType: ImageContentType {


var values = [UInt8](repeating: 0, count: 1)


self.copyBytes(to: &values, count: 1)


switch (values[0]) {
case 0xFF:
return .jpg
case 0x89:
return .png
case 0x47:
return .gif
case 0x49, 0x4D :
return .tiff
default:
return .unknown
}
}
}

Some usage examples:

//load some image
do {
let imageData = try Data(contentsOf: URL(string: "https://myServer/images/test.jpg")!)
} catch {
print("Unable to load image: \(error)")
}


//content type check
guard [Data.ImageContentType.jpg,
Data.ImageContentType.png].contains(imageData.imageContentType) else {
print("unsupported image type")
return
}


//set file extension
let image = "myImage.\(imageData.imageContentType.fileExtension)" //myImage.jpg

To get the image type from an UIImage you can get the type identifier (UTI) from the underlying Quartz image data:

extension UIImage {
var typeIdentifier: String? {
cgImage?.utType as String?
}
}

To get the image type identifier from a URL it will depend on whether the URL points to a local resource or not:

extension URL {
// for local resources (fileURLs)
var typeIdentifier: String? { (try? resourceValues(forKeys: [.typeIdentifierKey]))?.typeIdentifier }
// for non local resources (web) you can get it asyncronously
func asyncTypeIdentifier(completion: @escaping ((String?, Error?) -> Void)) {
var request = URLRequest(url: self)
request.httpMethod = "HEAD"
URLSession.shared.dataTask(with: request) { _ , response , error in
completion((response as? HTTPURLResponse)?.mimeType, error)
}.resume()
}
}

let imageURL = URL(string: "https://i.stack.imgur.com/varL9.jpg")!
imageURL.asyncTypeIdentifier { typeIdentifier, error in
guard let typeIdentifier = typeIdentifier, error == nil else { return }
print("typeIdentifier:", typeIdentifier)
}