1The OpenEXRUtil Library
2----------------------
3
4The OpenEXRUtil library implements an in-memory image data structure, as
5well as simple function calls for saving images in OpenEXR files, and for
6constructing images from the contents of existing OpenEXR files.
7
8The OpenEXR file format has a fairly large number of options for on-file
9image storage, including arbitrary sets of channels, per-channel pixel
10format selection, sub-sampled channels, multi-resolution images, deep
11images, or storing images as tiles or scan lines. While reading a simple
12RGBA image does not require a lot of code, reading the contents of an
13arbitrary OpenEXR file, and representing those contents in main memory
14is not trivial. The OpenEXRUtil library simplifies those tasks.
15
16Image, Image Level, Image Channel
17---------------------------------
18
19An image (class Image) is a container for a set of image levels (class
20ImageLevel), and an image level is a container for a set of image channels
21(class ImageChannel). An image channel contains an array of pixel values.
22
23For example:
24
25 image --+-- level 0,0 --+-- channel "R" --- pixel data
26 | |
27 | +-- channel "G" --- pixel data
28 | |
29 | +-- channel "B" --- pixel data
30 |
31 +-- level 1,1 --+-- channel "R" --- pixel data
32 | |
33 | +-- channel "G" --- pixel data
34 | |
35 | +-- channel "B" --- pixel data
36 |
37 +-- level 2,2 --+-- channel "R" --- pixel data
38 |
39 +-- channel "G" --- pixel data
40 |
41 +-- channel "B" --- pixel data
42
43An image has a level mode (enum LevelMode), which can be ONE_LEVEL,
44MIPMAP_LEVELS or RIPMAP_LEVELS. A ONE_LEVEL image contains only a single
45level, but a multi-resolution image, that is, one with level mode set to
46MIPMAP_LEVELS or RIPMAP_LEVELS, contains multiple levels. The levels are
47analogous to the levels in an OpenEXR file, as described in the "Technical
48Introduction to OpenEXR" document.
49
50Levels are indexed by a pairs of level numbers. Level (0,0) contains the
51highest-resolution version of the image; level (lx,ly) contains an image
52whose resolution is reduced in x and y by a factor of 2^lx and 2^ly
53respectively. The level has a data window that indicates the range of
54x and y for which pixel data are stored in the level.
55
56All levels in an image have the same set of image channels.
57
58An image channel has a name (e.g. "R", "Z", or "xVelocity"), a type (HALF,
59FLOAT or UINT) and x and y sampling rates. A channel stores samples for
60a pixel if the pixel is inside the data window of the level to which the
61channel belongs, and the x and y coordinates of the pixel are divisible by
62the x and y sampling rates of the channel.
63
64An image can be either flat or deep. In a flat image each channel in each
65level stores at most one value per pixel. In a deep image each channel in
66each level stores an arbitrary number of values per pixel. As an exception,
67each level of a deep image has a sample count channel with a single value
68per pixel; this value determines how many values each of the other channels
69in the same level has at the same pixel location.
70
71The Image, ImageLevel and ImageChannel classes are abstact base classes.
72Two sets of classes, one for flat images and one for deep images, are
73derived from the base classes. The FlatImageChannel and DeepImageChannel
74classes, derived from ImageChannel, are themselves base classes for the
75templates TypedFlatImageChannel<T> and TypedDeepImageChannel<T>:
76
77 Image -> FlatImage
78 -> DeepImage
79
80 ImageLevel -> FlatImageLevel
81 -> DeepImageLevel
82
83
84 ImageChannel -> FlatImageChannel -> TypedFlatImageChannel<T>
85 -> DeepImageChannel -> TypedDeepImageChannel<T>
86 -> SampleCountChannel
87
88Channel objects of type TypedFlatImageChannel<T> and TypedDeepImageChannel<T>
89contain pixel values of type T, where T is either half, float or unsigned int.
90For convenience, the following typedefs are provided:
91
92 typedef TypedFlatImageChannel<half> FlatHalfChannel;
93 typedef TypedFlatImageChannel<float> FlatFloatChannel;
94 typedef TypedFlatImageChannel<unsigned int> FlatUIntChannel;
95
96 typedef TypedDeepImageChannel<half> DeepHalfChannel;
97 typedef TypedDeepImageChannel<float> DeepFloatChannel;
98 typedef TypedDeepImageChannel<unsigned int> DeepUIntChannel;
99
100
101File I/O
102--------
103
104An Image object can be saved in an OpenEXR file with a single function call:
105
106 saveImage ("foo.exr", myImage);
107
108The saveImage() function automatically creates a flat or a deep image file,
109depending on the type of the image. All channels and all image levels will
110be saved in the file.
111
112Optionally an OpenEXR Header object can be passed to the saveImage() function;
113this allows application code save custom attributes in the file, and to control
114how the file will be compressed:
115
116 Header myHeader;
117 myHeader.compression() = PIZ_COMPRESSION;
118 myHeader.pixelAspectRatio() = 1.5;
119
120 saveImage ("foo.exr", myHeader, myImage);
121
122Loading an image from an OpenEXR file also requires only one function call,
123either
124
125 Image* myImage = loadImage ("foo.exr");
126
127or
128
129 Header myHeader;
130 Image* myImage = loadImage ("foo.exr", myHeader);
131
132The application owns the image that is returned by the loadImage() call.
133It is the application's responsibility to delete the Image object.
134
135The OpenEXRUtil library also provides versions of the saveImage() and
136loadImage() functions that work only on flat images or only on deep images:
137
138 saveFlatImage()
139 saveFlatScanLineImage()
140 saveFlatTiledImage()
141 saveDeepImage()
142 saveDeepScanLineImage()
143 saveDeepTiledImage()
144
145For details the the ImfFlatImageIO.h and ImfDeepImageIO.h header files.
146
147
148Manipulating Images in Memory
149-----------------------------
150
151Creating a mip-mapped flat image with two channels:
152
153 FlatImage fimg (Box2i (V2i (0, 0), V2i (255, 255)), // data window
154 MIPMAP_LEVELS); // level mode
155
156 fimg.insertChannel ("R", HALF);
157 fimg.insertChannel ("Z", FLOAT);
158
159Creating a single-level deep image:
160
161 DeepImage dimg (Box2i (V2i (0, 0), V2i (255, 255)), // data window
162 ONE_LEVEL); // level mode
163
164 dimg.insertChannel ("R", HALF);
165 dimg.insertChannel ("Z", FLOAT);
166
167Reading and writing pixels in level (2,2) of the mip-mapped flat image
168(note: a mip-mapped image contains only levels where the x and y level
169numbers are equal. For convenience, mip-map levels can be addressed
170using a single level number):
171
172 FlatImageLevel &level = fimg.level (2);
173 FlatHalfChannel &R = level.typedChannel<half> ("R);
174
175 half r1 = R.at (20, 15); // read pixel (20,15), with bounds checking
176 // (exception for access outside data window)
177
178 half r2 = R (17, 4); // read pixel (17,4) without bounds checking
179 // faster, but crashes for access outside
180 // data window
181
182 R.at (20, 15) = 2 * r1; // change pixel value, with and
183 R (17, 4) = 2 * r2; // without bounds checking
184
185Reading and writing pixels in the single-level deep image:
186
187 DeepImageLevel &level = dimg.level();
188 DeepHalfChannel &R = level.typedChannel<half> ("R);
189
190 // with bounds checking
191
192 unsigned int n1 = R.sampleCounts().at (20, 15);
193 half r1;
194
195 if (n1 > 0)
196 r1 = R.at(20, 15)[n1 - 1]; // read the last sample in pixel (20,15)
197
198 // without bounds checking
199
200 unsigned int n2 = R.sampleCounts()(20, 15);
201 half r2;
202
203 if (n > 0)
204 r2 = R(17, 4)[n2 - 1]; // read the last sample in pixel (17,4)
205
206 // change the value of an existing sample
207
208 if (n1 > 0)
209 R(20,15)[n1 - 1] = r1 * 2;
210
211 // append a new sample to a pixel and set the sample to 3.0
212
213 R.sampleCounts().set (20, 15, n1 + 1);
214 R.(20, 15)[n1] = 3.0;
215
216In addition to functions for reading and writing individual pixels, there
217are functions for accessing a whole row of pixels with a single function
218call. For details see the ImfFlatImageChannel.h, ImfDeepImageChannel.h
219and ImfSampleCountChannel.h header files in the OpenEXR library:
220
221 T* TypedFlatImageChannel<T>::row (int r);
222 const T* TypedFlatImageChannel<T>::row (int r) const;
223
224 T * const * TypedDeepImageChannel<T>::row (int r);
225 const T * const * TypedDeepImageChannel<T>::row (int r) const;
226 const unsigned int * SampleCountChannel::row (int r) const;
227
228To change the number of samples in all pixels in one row of a deep image
229level, use:
230
231 void SampleCountChannel::set (int r, unsigned int newNumSamples[]);
232
233Use an Edit object to temporarily make all sample counts in a deep image
234level editable:
235
236 class SampleCountChannel::Edit;
237
238Miscellaneous Functions:
239------------------------
240
241Change the data window and the level mode of an image (pixel data are not
242preserved across the call):
243
244 void Image::resize (const Box2i &dataWindow, LevelMode levelMode);
245
246Shift the data window in x and y; shift the pixels along with the data window:
247
248 void Image::shiftPixels (int dx, int dy);
249
250Erase a channel, rename a channel, rename multiple channels at the same time:
251
252 void Image::eraseChannel (const string &name);
253 void Image::renameChannel (const string &oldName, const string &newName);
254 void Image::renameChannels (const RenamingMap &oldToNewNames);
255
256Missing Functionality:
257----------------------
258
259At this point, the OpenEXRUtil library cannot read or write multi-part
260files. A future version of the library should probably define a new class
261MultiPartImage that contains a set of regular images. The library should
262also define corresponding loadMultiPartImage() and saveMultiPartImage()
263functions.
264
265Sample Code
266-----------
267
268See the exrsave, exrmakescanlines, exrclip utilities.
269
270
271