I'd like to take some time to play with API ideas, to see if we can come up with something better than we currently have, and also account for supporting the various features of OpenEXR.
I don't expect to figure out a final 1.0 API or anything, but rather a first draft that we can implement and use for a while to get more experience for the next iteration.
Some notes of my own below.
Using the builder pattern, pros and cons
For OutputFile
I'm currently using a builder pattern, so creating a file and writing to it looks something like this:
let mut fb = {
// Create the frame buffer ...
};
OutputFile::new()
.resolution(256, 256)
.channel("R", PixelType::FLOAT)
.channel("G", PixelType::FLOAT)
.channel("B", PixelType::FLOAT)
.open(Path::new("/path/to/file.exr"))
.unwrap();
.write_pixels(&mut fb).unwrap();
This seems to make sense, because there are a lot of things that can potentially be specified about the file (but which mostly have obvious defaults), and due to OpenEXR's C++ API they all need to be specified before actually opening the file for writing. It's also convenient for creating an OutputFile
inline somewhere.
However, a weakness of this is, for example, that if you have an array of channels already, the builder pattern actually makes it a bit more awkward. Instead of something like this:
let mut out = OutputFile::new().resolution(256, 256);
for (name, pixel_type) in channel_vec.iter() {
out.add_channel(name, pixel_type);
}
// ...
You have to do this:
let mut out = OutputFile::new().resolution(256, 256);
for (name, pixel_type) in channel_vec.iter() {
out = out.channel(name, pixel_type);
}
// ...
OutputFile::new()
.resolution(256, 256)
That isn't awful, but it does IMO read a bit strange with the out = out.whatever()
in the loop.
Supporting different InputFile and OutputFile types
OpenEXR supports scanline files, tiled files, multi-part files, and deep files and all the permutations thereof. For simple input/output this isn't too much of a concern, and I would like to make sure there are simple API's for cases where people just don't care. But first I want to make sure we wrap all of the lower-level functionality well, and then we can build on top of that later for simpler use-cases.
For output, my current idea is to have multiple different *Writer
types that you can get from *_open()
calls. So, for example, if you want a create a tiled file you would do:
let mut out: TiledWriter = OutputFile::new()
.resolution(256, 256)
// ...
.open_tiled(Path::new("/path/to/file.exr"));
And for a tiled and multi-part file:
let mut out: TiledMultiPartWriter = OutputFile::new()
.resolution(256, 256)
// ...
.open_tiled_multipart(Path::new("/path/to/file.exr"));
Etc.
This would correspond closely to the various *OutputFile
types in the C++ API, and would allow us to present a tailer-made API for each output type (e.g. writing tiles to a tiled file).
For InputFile I'm thinking we could do something similar by returning an enum
of possible *Reader
types.
Accessing InputFile's channels
To properly read an input file, it should either be verified that the expected channels exist in it and are of the right type, or the channels that do exist should be used to build the frame buffer. This requires accessing the channel descriptors.
So far all I've implemented for that is an iterator API:
let input = // Get input file
for (name, channel) in input.channels() {
println!("{} {:#?}", name, channel);
}
But that is unlikely to cut it on its own. Some kind of direct access would probably be good:
let channel_descriptor = input.get_channel("R").unwrap();
Custom Attributes
OpenEXR files support custom attributes to be written to and read from exr files. I haven't investigated the API closely enough to know exactly how they work (e.g. what types are supported, if it's extensible, etc.). But at first blush, I think using an API similar to what we have for channels would be good:
OutputFile::new()
.resolution(256, 256)
.attribute("attribute_name_1", /* some way of specifying an attribute value */)
.attribute("attribute_name_2", /* some way of specifying an attribute value */)
// ...
let input = // Get input file
for (name, attribute) in input.attributes() {
println!("{} {:#?}", name, attribute);
}
let attribute_value = input.get_attribute("attribute_name_1").unwrap();