Finding image type from NSData or UIImage
Asked Answered
B

11

77

I am loading an image from a URL provided by a third-party. There is no file extension (or filename for that matter) on the URL (as it is an obscured URL). I can take the data from this (in the form of NSData) and load it into a UIImage and display it fine.

I want to persist this data to a file. However, I don't know what format the data is in (PNG, JPG, BMP)? I assume it is JPG (since it's an image from the web) but is there a programmatic way of finding out for sure? I've looked around StackOverflow and at the documentation and haven't been able to find anything.

TIA.


Edit: Do I really need the file extension? I'm persisting it to an external storage (Amazon S3) but considering that it will always be used in the context of iOS or a browser (both of whom seem fine in interpreting the data without an extension) perhaps this is a non-issue.

Blackbeard answered 10/11, 2010 at 17:39 Comment(2)
Why do you need to know? If the UIImage display it fine I don't see a reason why can't you persist it without the extension.Expostulation
The image will also be displayed on a website in the future. I do see, now, that browsers can display the raw image (without a file extension) correctly.Blackbeard
H
147

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;
}
Hypozeugma answered 18/2, 2011 at 14:20 Comment(7)
is there any equivalent solution to identify type of files of other kinds ... such as rtf, mov, mp3?Tears
This doesn't help me in case of identifying an UIImage object. To extract NSData from an UIImage, I need to use either UIImagePNGRepresentation() or UIImageJPEGRepresentation() which converts the imageData by itself.Guyton
Unfortunately, UIImage doesn't provide any access to image context – you won't be able to determine the image type without reference to the original byte stream.Strahan
An alternative approach: CGImageSource (part of the ImageIO framework) can provide you with the appropriate uniform type identifier for image data.Strahan
I had a jpg with a first byte of 0xDB?Ison
Did anyone know bytes for pdf, word(doc, docx), excel(xls, xlsx), powerpoint(ppt, pptx), text, rtf..Eruption
@ArunGupta a text can start with any byte. Btw a text that starts with the letter "G" could be interpreted as a gif image using this method. try String(data: Data([0x47]), encoding: .utf8)!. // GLiken
S
30

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.

Salop answered 28/12, 2014 at 18:59 Comment(1)
Great list, thanks. I'm having trouble with the new .heic image type used in iOS11. Could you update your answer to include this new type?Precipitancy
R
16

@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
    }
}
Ragout answered 6/4, 2017 at 9:18 Comment(0)
B
9

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)

Brittnybritton answered 10/11, 2010 at 17:44 Comment(1)
Oh yes. I hadn't thought of this! Good idea.Blackbeard
H
7

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
    }
}
Hasin answered 29/10, 2016 at 8:1 Comment(1)
if you use UIImagePNGRepresentation to get the UIImage data your data property will always return PNG. Btw in Swift 3 or later Data it is already a collection of bytes why not simply first?Liken
H
6

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.

Householder answered 29/3, 2017 at 7:32 Comment(0)
L
4

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.sstatic.net/varL9.jpg")!
imageURL.asyncTypeIdentifier { typeIdentifier, error in
    guard let typeIdentifier = typeIdentifier, error == nil else { return }
    print("typeIdentifier:", typeIdentifier)
}
Liken answered 16/5, 2020 at 5:11 Comment(0)
F
3

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
Flee answered 5/8, 2019 at 15:38 Comment(0)
O
1

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?

Outside answered 10/11, 2010 at 17:45 Comment(2)
Well I'm storing it on a third-party (Amazon S3) and eventually intend on using this image on a web site as well. Although, now that I have it working, I see that the browser knows how to render the image regardless of a file extension. And we know iOS's UIImage doesn't care. So maybe it doesn't matter?Blackbeard
My guess is that it doesn't matter, but if you care about IE doing the right thing to the files without extensions you should probably test it as well.Outside
D
1

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;
}
Deutschland answered 9/6, 2011 at 20:46 Comment(0)
B
1

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

https://github.com/sweetmandm/ImageFormatInspector

Bacchius answered 26/3, 2015 at 15:15 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.