postmates/PMHTTP

Unknown Hint Identifier for Image MIME Types

mattt opened this issue · 4 comments

mattt commented

On iOS 10 (building with Xcode 10.2), calling the parseAsImage(scale:) method to process HTTP requests for remote images causes the following error message to be logged to the console:

extractOptions:147: *** unknown hint identifier 'kCGImageSourceTypeIdentifierHint:image/jpeg' -- ignoring...

I can confirm that a similar error message occurs when attempting to load PNG images as well.

Looking at the implementation of this method, I traced the issue to the following line in the private UIImage initializer called from that method:

options = [kCGImageSourceTypeIdentifierHint: mimeType as CFString]

According to Apple's documentation, CGImageSourceCreateWithData expects kCGImageSourceTypeIdentifierHint to be a UTI, rather than a MIME type. For example, according to Apple's Image I/O Programming Guide, the correct UTI for a JPEG image would be public.jpeg.

One potential fix would be to use UTTypeCreatePreferredIdentifierForTag to get the UTI corresponding to the MIME type like so:

import CoreServices

let mimeType = "image/jpeg"
let uti = UTTypeCreatePreferredIdentifierForTag(kUTTagClassMIMEType, mimeType as! CFString, kUTTypeImage)

uti?.takeRetainedValue() // public.jpeg

My one concern with this approach is that I'm unfamiliar with the performance characteristics of UTTypeCreatePreferredIdentifierForTag, and don't know if calling this method incurs a static initialization cost to build up the list of supported UTIs (and if so, how often). It'd be unfortunate if the computation to generate the type hint took longer than it would otherwise.

If you also share this concern, an alternative approach would be to hardcode the lookup for only the supported MIME types from a list derived from either this table in the docs or by calling CGImageSourceCopyTypeIdentifiers(_:). The downside to this approach is that it is liable to become stale over time, and at the time of writing, the list is actually quite large (primarily because of disparate RAW camera image types):

["public.jpeg", "public.png", "com.compuserve.gif", "com.canon.tif-raw-image", "com.adobe.raw-image", "com.dxo.raw-image", "com.canon.cr2-raw-image", "com.canon.cr3-raw-image", "com.leafamerica.raw-image", "com.hasselblad.fff-raw-image", "com.hasselblad.3fr-raw-image", "com.nikon.raw-image", "com.nikon.nrw-raw-image", "com.pentax.raw-image", "com.samsung.raw-image", "com.sony.raw-image", "com.sony.sr2-raw-image", "com.sony.arw-raw-image", "com.epson.raw-image", "com.kodak.raw-image", "public.tiff", "public.jpeg-2000", "com.apple.atx", "org.khronos.astc", "org.khronos.ktx", "public.avci", "public.heic", "public.heif", "com.canon.crw-raw-image", "com.fuji.raw-image", "com.panasonic.raw-image", "com.panasonic.rw2-raw-image", "com.leica.raw-image", "com.leica.rwl-raw-image", "com.konicaminolta.raw-image", "com.olympus.sr-raw-image", "com.olympus.or-raw-image", "com.olympus.raw-image", "com.phaseone.raw-image", "com.microsoft.ico", "com.microsoft.bmp", "com.apple.icns", "com.adobe.photoshop-image", "com.microsoft.cur", "com.truevision.tga-image", "com.ilm.openexr-image", "com.sgi.sgi-image", "public.radiance", "public.pbm", "public.mpo-image", "public.pvr", "com.microsoft.dds"]

Finally, the null solution would be to simply remove the type hint altogether.

Thanks for the detailed issue! I'm surprised I didn't notice before that this is supposed to be a UTI.

I know that the initial set of UTIs are loaded from a bundle on disk somewhere, though LaunchServices also maintains a dynamic registry of UTIs from apps on the system. I'm not sure if creating a UTI from a MIME type does any XPC past the initial load to access that dynamic registry, though if it does, hopefully it would cache the results for a given MIME type. I'll do a bit of performance testing right now and see what it actually costs to look up UTIs.

In quick testing on macOS, first time calling UTTypeCreatePreferredIdentifierForTag costs about 2.5ms. Calling it repeatedly with the same input drops down to about 20µs instead. Switching to a different MIME type has a 60–90µs cost before dropping back down to 20µs.

If anything, iOS can be presumed to be faster, due to the lack of a central UTI registry beyond the built-in types.

This seems fast enough that it's worth keeping. I'll push out a fix for this today. Thanks again!

This is now published as v4.4.0.

mattt commented

In quick testing on macOS, first time calling UTTypeCreatePreferredIdentifierForTag costs about 2.5ms. Calling it repeatedly with the same input drops down to about 20µs instead. Switching to a different MIME type has a 60–90µs cost before dropping back down to 20µs.

That's exactly the kind of behavior I was hoping for.

Thanks so much for taking a look and for the quick turnaround on the fix!