Is OpenEXR supported

I am trying to find out if reading OpenEXR files are supported or not on iOS? Here is what I found out:


1. The docs barely mention OpenEXR, and where they do they say that it's only supported on recent macOS.

2. Yet, the official WWDC 2017 samples for ARKit simply load .exr images with UIImage(named: "image.exr")

This is found in the following samples:
- Handling 3D Interaction and UI Controls in Augmented Reality

- Interactive Content with ARKit

- Audio in ARKit
- Placing Objects [old, I think it's removed now]


Can you point me to anything about how is OpenEXR support on iOS and on macOS?

Replies

ImageIO.framework has supported OpenEXR for many major releases now. If you are just trying to get RGBA content up on the screen or loaded into one of these other frameworks, the CGImageSourceRef should be your first stop. It works just like for JPEGs, TIFFs, and such. For example, in this case, we draw the image into a CG context:

Code Block    
#include <ImageIO/ImageIO.h>
#include <CoreGraphics/CoreGraphics.h>
const char * path_to_file = ...;
CFStringRef s = CFStringCreateWithCString(NULL, path_to_file, kCFStringEncodingUTF8 );
    CFURLRef url = CFURLCreateWithFileSystemPath(NULL, s, kCFURLPOSIXPathStyle, false);
    CFRelease(s);
    CGImageSourceRef source = CGImageSourceCreateWithURL( url, NULL);
    CFRelease(url);
    CGImageRef image = CGImageSourceCreateImageAtIndex(source, 0, NULL);
    CFRelease(source);
    CGContextRef context = CGBitmapContextCreate( NULL,
                                                   CGImageGetWidth(image),
                                                   CGImageGetHeight(image),
                                                   16,
                                                   CGImageGetWidth(image) * 8, // 8 = 4 channels * 16bit/chan RGBA
                                                   CGColorSpaceCreateDeviceRGB(),
                                                   kCGBitmapByteOrder16Host | kCGImageAlphaPremultipliedLast | kCGBitmapFloatComponents );
    CGRect where = CGRectMake(0, 0, CGImageGetWidth(image), CGImageGetHeight(image));
    CGContextClearRect( context, where);
    CGContextDrawImage( context, where, image);
    CGContextFlush(context);
    CGImageRelease(image);
    CGContextRelease(context);

__________________

New in Big Sur, we've replaced ImageIO's underlying OpenEXR implementation with an Apple developed library, AppleEXR. It is accelerated for Neon (Apple Silicon) and AVX2 (Intel) and uses GCD. It is also available on iOS, iPadOS, watchOS and tvOS. You may find it is quite a bit faster than the ImageIO EXR plugin that was there before. It has a C interface so that it is callable from C/C++/ObjC/Swift as API and supports ARC:

#include <AppleEXR.h> <== /usr/include/AppleEXR.h
link: -lAppleEXR <== /usr/bin/libAppleEXR.dylib

It is exposed to provide more direct access to lower level features in the OpenEXR file format important to some apps. The file format is extremely flexible and not all of its feature set fits entirely under the CoreGraphics / ImageIO feature space.

It is not source level compatible with the Academy Software Foundation's OpenEXR implementation, just file format compatible, so some refactoring of your preexisting OpenEXR code would be needed to use it if you had any. (OpenEXR has a C++ interface which can be challenging to export stably over the long term as a dynamic library.) Relevant documentation is in the C version of the header. AppleEXR.h is annotated in Doxygen style, so you might be able to get something nice with the Doxygen tool, though I will confess that as of the WWDC release, I have not yet made it a priority to make the Doxygen output looking pretty. AppleEXR presumes some basic knowledge of the OpenEXR file format or experience with OpenEXR, so for most developers, I would start with CGImageSourceRef first and then drill down if you can't get what you need out of it.

For more details on OpenEXR itself and the file format features, you may visit the Academy Software foundation website at OpenEXR dot com / documentation.html

@IanOllmann There seems to be a bug in the AppleEXR encoder, causing a BAD_ACCESS when encoding images with height < 16.
Could you please have a look? (FB9080694)

Thanks!
  • We took a look! (some months ago?) The fix is in and is slowly ambling its way toward you in some release when all the gears have fully and safely come to a halt. If you are still having problems, please attach a small sample test case illustrating the problem to a Feedback and send it in.

Add a Comment

Hi, can someone give me a starting example of how to write an openexr file into disk? I tried the following toy example to write a 2-by-2 checker-box:

import ModelIO

var floatData = UnsafeMutableBufferPointer<Float32>.allocate(capacity: 4)

floatData[0] = Float32(0.99)
floatData[1] = Float32(0.0)
floatData[2] = Float32(0.0)
floatData[3] = Float32(0.99)

var floatBuffer = Data(buffer: floatData)

let exrImg = MDLTexture(data: floatBuffer,
                                 topLeftOrigin: true,
                                 name: "output.exr",
                                 dimensions: SIMD2<Int32>(2,2),
                                 rowStride: 2 * MemoryLayout<Float32>.size,
                                 channelCount: 1,
                                 channelEncoding: MDLTextureChannelEncoding.float32,
                                 isCube: false)
 
let resourcesURL = URL(fileURLWithPath: "/Users/myDirectory", isDirectory: true)
let fileName = resourcesURL.appendingPathComponent(exrImgTest.name)

if(exrImg.write(to: fileName)){
        print("Success! Image written in disk")
 } else {
        print("could not write image")
    }

When I run this I get thread 1: EXC_BAD_ACCESS (code=1, address=0xf0) pointing to the write method of the MDLTexture. If I change the name to output.png the image is written without a problem.

I guess I am missing something basic here. Moreover, how could I write a multiple channel openEXR image to disk?

  • Can you please try creating a larger image (16x16, for instance) and try again? I was also observing strange behavior (see above) for small EXR images. It would be interesting if these issues are related.

  • see below ;)

Add a Comment

I tried it with the following code with a larger image:

import ModelIO

if #available(macOS 11.0, *) {

var floatData = UnsafeMutableBufferPointer<Float32>.allocate(capacity: 16*16)

for i in 0..<256{
    if (i%2 == 0){
        floatData[i] = Float32(0.99)
    } else {
        floatData[i] = Float32(0)
    }
}

    let floatBuffer = Data(buffer: floatData)

    let exrImg = MDLTexture(data: floatBuffer,
                            topLeftOrigin: true,
                            name: "output.exr",
                            dimensions: SIMD2<Int32>(16,16),
                            rowStride: 16 * MemoryLayout<Float32>.size,
                            channelCount: 1,
                            channelEncoding: MDLTextureChannelEncoding.float32,
                            isCube: false)
    
    let resourcesURL = URL(fileURLWithPath: "/Users/myDirectory", isDirectory: true)
    let fileName = resourcesURL.appendingPathComponent(exrImg.name)

    if(exrImg.write(to: fileName)){
            print("Success! Image written in disk")
     } else {
            print("could not write image")
        }
} else {
    // Fallback on earlier versions
}

I get the following runtime error:

Error>: kvImagePrintDiagnosticsToConsole: vImageConverter: In this version of the converter, most pixel formats must be packed arrays of samples with no empty space, except as appropriate for kCGImageAlphaNoneSkipFirst/Last. Here, the colorspace has 1 channels, with 1 more for alpha, which should mean (1+1 channels/pixel)*16 bits per channel = 32 bits per pixel.  You passed 16 in format->bitsPerPixel.

doubling the size of the buffer and changing the Float32 to Float16 did not work either. The write method returns false.

I'm able to run the 16 by 16 swift code (MacOS 12.5.1) and it writes out an exr file that I can load into preview and see the pattern of vertical bars. The only problem is that preview says the depth is 16, not 32. Does anyone know why the depth isn't the full 32 bits?