No Extensions Necessary: Determining Image Type from Data in Cocoa

By on 2014/08/29 in Development

Cocoa relies – almost exclusively – on file extensions to determine file types. Applications are expected to declare which file types they support, and both iOS and OS-X will use those declarations in deciding which applications can open what files. Cocoa will neither read or write file type metadata to the actual file itself, nor will it analyze the file’s raw byte’s to determine file type from the data’s header. Instead, file types are canonized into “uniform type identifiers”, or UTIs. These reverse domain format strings are used by the system to express what “type” a given file is, or may be. When you remove the path extension from a file name, you destroy this relationship, and therefore the system’s ability to distinguish a Microsoft Word document from a JPEG. Or do you? It turns out that there is an exception to the general rule: images. If you remove the file extension from a Windows Bitmap file, for example, a Mac will still recognize the image for what it is: a BMP. Obviously, when it comes to images, Cocoa is perfectly able (and willing) to determine file type by reference to the byte stream itself. So how do we take advantage of this? The ImageIO framework will allow us to inspect image data and make a decision about what “type” it actually is. This can be extremely useful when you’re working with data that lost their context (i.e., filename) when they were dumped from a database, were returned from an inane web service, or were transferred from an extension-hating Windows’ user’s machine. The CoreServices framework, or MobileCoreServices if you’re writing for iOS, is the repository of the system’s declared file type constants. You will need to import this framework to make intelligent use of the uniform type identifiers that ImageIO returns from data inspection. Here’s just enough sample code to get you started: @import ImageIO #if TARGET_OS_IPHONE @import MobileCoreServices; #else @import CoreServices; #endif We’ll need two methods: one to determine the image type from data that’s already been loaded into memory, and another for examining data on disk. Let’s keep things clean by keeping the common code in a share function: NSString * typeOfImage(CGDataProviderRef imgDataProvider, NSString *typeHint) { const void * keys[] = { kCGImageSourceShouldCache, kCGImageSourceTypeIdentifierHint }; const void * values[] = { kCFBooleanFalse, (__bridge CFStringRef)typeHint }; CFDictionaryRef options = CFDictionaryCreate(kCFAllocatorDefault, keys, values, 2, NULL, NULL); CGImageSourceRef imageSource = CGImageSourceCreateWithDataProvider(imgDataProvider, options); NSString *imageType = (__bridge NSString *)CGImageSourceGetType(imageSource); CFRelease(options); CFRelease(imageSource); return imageType; } Next, our convenience function for handling image data: NSString * typeOfImageData(NSData *imageData, NSString *typeHint) { NSString *imageType = nil; CGDataProviderRef provider = NULL; if ((provider = CGDataProviderCreateWithCFData((__bridge CFDataRef)imageData))) { imageType = typeOfImage(provider, typeHint); CGDataProviderRelease(provider); } return imageType; } And, finally, our convenience function for handling images on disk: NSString * typeOfImageAtURL(NSURL *imageFileURL, NSString *typeHint) { if (![imageFileURL isFileURL]) { // Limit to local files. return nil; } NSString *imageType = nil; CGDataProviderRef provider = NULL; if ((provider = CGDataProviderCreateWithURL((__bridge CFURLRef)imageFileURL))) { imageType = typeOfImage(provider, typeHint); CGDataProviderRelease(provider); } return imageType; } Here’s a sample method to get you up and running. - (void)test_typeOfImage; { // Load our intentionally mislabeled JPEG image: "aMislabeledJPEGImage.tiff" NSURL *imageURL = [[NSBundle mainBundle] URLForResource:@"aMislabeledJPEGImage" withExtension:@"tiff"]; NSString *imageTypeIdentifier = typeOfImageAtURL(imageURL, nil); if ([imageTypeIdentifier isEqualToString:(__bridge NSString *)kUTTypeTIFF]) { NSLog(@"Image is of TIFF type: %@", imageTypeIdentifier); } if ([imageTypeIdentifier isEqualToString:(__bridge NSString *)kUTTypeJPEG]) { NSLog(@"Image is of JPEG type: %@", imageTypeIdentifier); } } That’s all there is to it,...