从NSData或UIImage查找图像类型


70

我正在从第三方提供的URL加载图像。URL(因为它是模糊的URL)上没有文件扩展名(或与此相关的文件名)。我可以从中获取数据(以NSData的形式),并将其加载到UIImage中,然后很好地显示。

我想将此数据保存到文件中。但是,我不知道数据的格式(PNG,JPG,BMP)?我假设它是JPG(因为它是来自网络的图像),但是有确定的编程方式可以确定吗?我到处查看StackOverflow和文档,却找不到任何东西。

TIA。


编辑:我真的需要文件扩展名吗?我将其持久保存到外部存储(Amazon S3)中,但考虑到它将始终用于iOS或浏览器的上下文中(两者都可以很好地解释数据而无需扩展名),也许这不是问题。


你为什么要知道?如果UIImage可以正常显示,我看不到为什么没有扩展就不能持久保存它的原因。
kennytm 2010年

1
该图像将来还会显示在网站上。我确实看到,现在,浏览器可以正确显示原始图像(没有文件扩展名)。
pschang 2010年

Answers:


144

如果图像文件具有NSData,则可以通过查看第一个字节来猜测内容类型:

+ (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;
}

4
是否有等效的解决方案来识别其他类型的文件类型,例如rtf,mov,mp3?
Devarshi

5
这在识别UIImage对象的情况下对我没有帮助。要从UIImage中提取NSData,我需要使用UIImagePNGRepresentation()或UIImageJPEGRepresentation()来自动转换imageData。
Shyam Bhat 2014年

不幸的是,UIImage不提供对图像上下文的任何访问权限-您将无法在不参考原始字节流的情况下确定图像类型。
nzeltzer 2014年

一种替代方法:CGImageSource(ImageIO框架的一部分)可以为您提供适当的图像数据统一类型标识符。
nzeltzer 2014年

2
有没有人知道个字节的PDF,字(DOC,DOCX),EXCEL(XLS,XLSX)和PowerPoint(PPT,PPTX),文本,RTF ..
阿伦古普塔

29

改进wl。的答案,这是一种基于签名来预测图像的MIME类型的更扩展,更精确的方法。该代码主要受php的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

}

上面的方法可以识别以下图像类型:

  • 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)

不幸的是,没有简单的方法可以从UIImage实例中获取此类信息,因为无法访问其封装的位图数据。


3
很棒的清单,谢谢。我在iOS11中使用的新.heic图像类型遇到麻烦。您能否更新您的答案以包括此新类型?
alfonso

15

@Tai Le的Swift 3解决方案是将整个数据分配到字节数组中。如果图像较大,则可能导致崩溃。该解决方案仅分配单个字节:

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
    }
}

9

如果要从URL检索图像,则大概可以检查HTTP响应标头。是否Content-Type头包含任何有用吗?(我想是这样,因为浏览器可能能够正确显示图像,并且只有在适当设置内容类型的情况下,它才能这样做)


哦,是的。我没想到这个!好主意。
pschang 2010年

6

Swift3版本:

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
    }
}

如果使用UIImagePNGRepresentation来获取UIImage数据,则data属性将始终返回PNG。在Swift 3或更高版本中,Data它已经是字节的集合了,为什么不简单first呢?
Leo Dabus

3

可接受答案的替代方法是使用检查图像的UTI image I/O frameWork。您可以通过UTI实现图像类型。尝试这个:

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

例如,一个GIF图像的UTI是“ com.compuserve.gif”,而PNG图像的UTI是“ public.png”。但是您无法从image I/O frameWork无法识别的图像中获得UTI 。


2

要从中获取图像类型,UIImage您可以从基础Quartz图像数据中获取类型标识符(UTI):

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

要从URL获取图像类型标识符,将取决于URL是否指向本地资源:

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)
}

1

如果对您确实很重要,我相信您将必须检查字节流。JPEG将以字节FF D8开头。PNG将以89 50 4E 47 0D 0A 1A 0A开始。我不知道BMP是否具有类似的标头,但我认为您不太可能在2010年碰到网络上的标头。

但这对您真的重要吗?您不能只是将其视为未知图像,然后让Cocoa Touch进行处理吗?


1
好吧,我将其存储在第三方(Amazon S3)上,并最终打算在网站上也使用此图像。尽管现在可以正常工作了,但是我看到浏览器知道如何渲染图像,而不管文件扩展名如何。而且我们知道iOS的UIImage不在乎。所以也许没关系?
pschang 2010年

我的猜测是,这无关紧要,但是,如果您关心IE对没有扩展名的文件执行正确的操作,则也应该对其进行测试。
史蒂芬·费舍尔

1

对每种已知的图像格式实施签名检查。这是一个快速的Objective-C函数,可用于PNG数据:

// 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;
}

1

我基于@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
        }
    }
}

一些用法示例:

//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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.