Reputation: 2618
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.
Upvotes: 78
Views: 51288
Reputation: 236508
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)
}
Upvotes: 4
Reputation: 8676
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
Upvotes: 3
Reputation: 689
@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
}
}
Upvotes: 16
Reputation: 2127
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.
Upvotes: 6
Reputation: 9296
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
}
}
Upvotes: 7
Reputation: 690
I made a library to check the image type of NSData:
https://github.com/sweetmandm/ImageFormatInspector
Upvotes: 1
Reputation: 45210
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.
Upvotes: 30
Reputation: 4425
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;
}
Upvotes: 1
Reputation: 2270
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;
}
Upvotes: 147
Reputation: 44886
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?
Upvotes: 1
Reputation: 243156
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)
Upvotes: 9