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.

pschang
  • 2,568
  • 3
  • 28
  • 23
  • 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. – kennytm Nov 10 '10 at 17:44
  • 1
    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. – pschang Nov 10 '10 at 18:29

11 Answers11

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;
}
wl.
  • 2,270
  • 3
  • 18
  • 13
  • 4
    is there any equivalent solution to identify type of files of other kinds ... such as rtf, mov, mp3? – Devarshi Sep 28 '13 at 20:44
  • 5
    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. – Shyam Bhat Feb 21 '14 at 17:04
  • 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. – nzeltzer Aug 26 '14 at 18:19
  • 1
    An alternative approach: CGImageSource (part of the ImageIO framework) can provide you with the appropriate uniform type identifier for image data. – nzeltzer Aug 26 '14 at 18:22
  • I had a jpg with a first byte of 0xDB? – rob5408 Oct 07 '14 at 00:39
  • 2
    Did anyone know bytes for pdf, word(doc, docx), excel(xls, xlsx), powerpoint(ppt, pptx), text, rtf.. – Arun Gupta Feb 10 '15 at 08:48
  • @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)!`. // G – Leo Dabus Apr 16 '22 at 21:44
29

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.

Community
  • 1
  • 1
akashivskyy
  • 44,342
  • 16
  • 106
  • 116
  • 3
    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? – alfonso Feb 06 '18 at 19:56
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
    }
}
ccoroom
  • 699
  • 9
  • 23
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)

Dave DeLong
  • 242,470
  • 58
  • 448
  • 498
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
    }
}
Tai Le
  • 8,530
  • 5
  • 41
  • 34
  • 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`? – Leo Dabus Oct 13 '20 at 15:04
5

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.

ooOlly
  • 1,997
  • 21
  • 31
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.stack.imgur.com/varL9.jpg")!
imageURL.asyncTypeIdentifier { typeIdentifier, error in
    guard let typeIdentifier = typeIdentifier, error == nil else { return }
    print("typeIdentifier:", typeIdentifier)
}
Leo Dabus
  • 229,809
  • 59
  • 489
  • 571
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
Peter Kreinz
  • 7,979
  • 1
  • 64
  • 49
1

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

https://github.com/sweetmandm/ImageFormatInspector

jankins
  • 670
  • 5
  • 16
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?

Steven Fisher
  • 44,462
  • 20
  • 138
  • 192
  • 1
    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? – pschang Nov 10 '10 at 18:15
  • 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. – Steven Fisher Nov 10 '10 at 20:30
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;
}
MoDJ
  • 4,309
  • 2
  • 30
  • 65