【问题标题】:Finding image type from NSData or UIImage从 NSData 或 UIImage 中查找图像类型
【发布时间】:2010-11-10 17:39:32
【问题描述】:

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

我想将此数据保存到文件中。但是,我不知道数据是什么格式(PNG、JPG、BMP)?我认为它是 JPG(因为它是来自网络的图像),但是是否有一种程序化的方式可以确定?我环顾了 StackOverflow 和文档,但一无所获。

TIA。


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

【问题讨论】:

  • 为什么需要知道?如果 UIImage 显示它很好,我看不出没有扩展名你不能坚持它的原因。
  • 该图像将来也将显示在网站上。我现在确实看到浏览器可以正确显示原始图像(没有文件扩展名)。

标签: iphone ios uiimage nsdata persist


【解决方案1】:

如果你有图片文件的 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;
}

【讨论】:

  • 是否有任何等效的解决方案来识别其他类型的文件类型......例如 rtf、mov、mp3?
  • 这对我识别 UIImage 对象没有帮助。要从 UIImage 中提取 NSData,我需要使用 UIImagePNGRepresentation() 或 UIImageJPEGRepresentation() 来自行转换 imageData。
  • 不幸的是,UIImage 不提供对图像上下文的任何访问——如果不参考原始字节流,您将无法确定图像类型。
  • 另一种方法:CGImageSource(ImageIO 框架的一部分)可以为图像数据提供适当的统一类型标识符。
  • 有人知道 pdf、word(doc, docx), excel(xls, xlsx), powerpoint(ppt, pptx), text, rtf 的字节数..
【解决方案2】:

wl.'s answer 的基础上进行了改进,这是一种基于签名预测图像 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 实例中获取此类信息,因为无法访问其封装的位图数据。

【讨论】:

  • 很棒的清单,谢谢。我在使用 iOS11 中使用的新 .heic 图像类型时遇到问题。您能否更新您的答案以包含这种新类型?
【解决方案3】:

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

【讨论】:

    【解决方案4】:

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

    【讨论】:

    • 哦,是的。我没有想到这个!好主意。
    【解决方案5】:

    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 数据,您的数据属性将始终返回 PNG。顺便说一句,在 Swift 3 或更高版本中 Data 它已经是字节的集合,为什么不简单地 first
    【解决方案6】:

    接受答案的替代方法是使用 image I/O frameWork 检查图像的 UTI。您可以实现图像类型表单 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。

    【讨论】:

      【解决方案7】:

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

      【讨论】:

        【解决方案8】:

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

        【讨论】:

          【解决方案9】:

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

          但这对你来说真的很重要吗?你不能把它当作一个未知的图像,让 Cocoa Touch 来做吗?

          【讨论】:

          • 嗯,我将它存储在第三方 (Amazon S3) 上,并最终打算在网站上使用此图像。虽然,现在我让它工作了,我看到浏览器知道如何渲染图像,而不管文件扩展名如何。我们知道 iOS 的 UIImage 不在乎。所以也许没关系?
          • 我的猜测是没关系,但如果你关心 IE 对没有扩展名的文件做正确的事情,你可能也应该测试它。
          【解决方案10】:

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

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

          【讨论】:

            【解决方案11】:

            我做了一个库来检查 NSData 的图像类型:

            https://github.com/sweetmandm/ImageFormatInspector

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2017-03-30
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多