1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588
|
# Guide
This document talks about the capabilities of OpenEXR and outlines the design of this library.
In addition to reading this guide, you should also have a look at the examples.
Contents:
- Wording
- Why this is complicated
- One-liners for reading and writing simple images
- Reading a complex image
- The Image data structure
- Writing a complex image
## Wording
Some names in this library differ from the classic OpenEXR conventions.
For example, an OpenEXR "multipart" is called a file with multiple "layers" in this library.
The old OpenEXR "layers" are called "grouped channels" instead.
- `Image` Contains everything that an `.exr` file can contain. Includes metadata and multiple layers.
- `Layer` A grid of pixels that can be placed anywhere on the two-dimensional canvas
- `Channel` All samples of a single color component, such as red or blue. Also contains metadata.
- `Pixel` The color at an exact location in the image. Contains one sample for each channel.
- `Sample` The value (either f16, f32 or u32) of one channel at an exact location in the image.
Usually a simple number, such as the red value of the bottom left pixel.
- `Grouped Channels` Multiple channels may be grouped my prepending the same prefix to the name.
This behaviour is opt-in; it has to be enabled explicitly:
By default, channels are stored in a plain list, and channel names are unmodified.
- `pedantic: bool` When reading, pedantic being false will generally ignore
invalid information instead of aborting the reading process where possible.
When writing, pedantic being false will generally skip some expensive image validation checks.
## OpenEXR | Complexity
This image format supports some features that you won't find in other image formats.
As a consequence, an exr file cannot necessarily be converted to other formats,
even when the loss of precision is acceptable. Furthermore,
an arbitrary exr image may include possibly unwanted data.
Supporting deep data, for example, might be unnecessary for some applications.
To read an image, `exrs` must know which parts of an image you want to end up with,
and which parts of the file should be skipped. That's why you need
a little more code to read an exr file, compared to simpler file formats.
### Possibly Undesired Features
- Arbitrary Channels:
`CMYK`, `YCbCr`, `LAB`, `XYZ` channels might not be interesting for you,
maybe you only want to accept `RGBA` images
- Deep Data: Multiple colors per pixel might not be interesting for you
- Resolution Levels: Mip Maps or Rip Maps might be unnecessary and can be skipped,
loading only the full resolution image instead
<!-- - TODO: Meta Data: Skip reading meta data -->
# Simple Reading and Writing
There are a few very simple functions for the most common use cases.
For decoding an image file, use one of these functions
from the `exr::image::read` module (data structure complexity increasing):
1. `read_first_rgba_layer_from_file(path, your_constructor, your_pixel_setter)`
1. `read_all_rgba_layers_from_file(path, your_constructor, your_pixel_setter)`
1. `read_first_flat_layer_from_file(path)`
1. `read_all_flat_layers_from_file(path)`
1. `read_all_data_from_file(path)`
If you don't have a file path, or want to load any other channels than `rgba`,
then these simple functions will not suffice. The more complex approaches are
described later in this document.
For encoding an image file, use one of these functions in the `exr::image::write` module:
1. `write_rgba_file(path, width, height, |x,y| my_image.get_rgb_at(x,y))`
1. `write_rgb_file(path, width, height, |x,y| my_image.get_rgba_at(x,y))`
These functions are only syntactic sugar. If you want to customize the data type,
the compression method, or write multiple layers, these simple functions will not suffice.
Again, the more complex approaches are described in the following paragraph.
# Reading an Image
Reading an image involves three steps:
1. Specify how to load an image by constructing an image reader.
1. Start with `read()`
1. Chain method calls to customize the reader
1. Call `from_file(path)`, `from_buffered(bytes)`, or `from_unbuffered(bytes)`
on the reader to actually load an image
1. Process the resulting image data structure or the error in your application
The type of the resulting image depends on the reader you constructed. For example,
if you configure the reader to load mip map levels, the resulting image type
will contain an additional vector with the mip map levels.
### Deep Data
The first choice to be made is whether you want to load deep data or not.
Deep data is where multiple colors are stored in one pixel at the same location.
Currently, deep data is not supported yet, so we always call `no_deep_data()`.
```rust
fn main(){
use exr::prelude::*;
let reader = read().no_deep_data();
}
```
### Resolution Levels
Decide whether you want to load the largest resolution level, or all Mip Maps from the file.
Loading only the largest level actually skips portions of the image, which should be faster.
Calling `largest_resolution_level()` will result in a single image (`FlatSamples`),
whereas calling `all_resolution_levels()` will result in multiple levels `Levels<FlatSamples>`.
```rust
fn main(){
use exr::prelude::*;
let reader = read().no_deep_data().largest_resolution_level();
let reader = read().no_deep_data().all_resolution_levels();
}
```
### Channels
Decide whether you want to load all channels in a dynamic list, or only load a fixed set of channels.
Calling `all_channels()` will result in a `Vec<Channel<_>>`.
```rust
fn main(){
use exr::prelude::*;
let reader = read().no_deep_data().largest_resolution_level().all_channels();
}
```
The alternative, `specific_channels()` allows you to exactly specify which channels should be loaded.
The usage follows the same builder pattern as the rest of the library.
First, call `specific_channels()`. Then, for each channel you desire,
call either `required(channel_name)` or `optional(channel_name, default_value)`.
At last, call `collect_pixels()` to define how the pixels should be stored in an image.
This additional mechanism will not simply store the pixels in a `Vec<Pixel>`, but instead
works with a closure. This allows you to instantiate your own existing image type with
the pixel data from the file.
```rust
fn main(){
use exr::prelude::*;
let reader = read()
.no_deep_data().largest_resolution_level()
// load LAB channels, with chroma being optional
.specific_channels().required("L").optional("A", 0.0).optional("B", 0.0).collect_pixels(
// create our image based on the resolution of the file
|resolution: Vec2<usize>, (l,a,b): &(ChannelDescription, Option<ChannelDescription>, Option<ChannelDescription>)|{
if a.is_some() && b.is_some() { MyImage::new_lab(resolution) }
else { MyImage::new_luma(resolution) }
},
// insert a single pixel into out image
|my_image: &mut MyImage, position: Vec<usize>, (l,a,b): (f32, f16, f16)|{
my_image.set_pixel_at(position.x(), position.y(), (l, a, b));
}
);
}
```
The first closure is the constructor of your image, and the second closure is the setter for a single pixel in your image.
The tuple containing the channel descriptions and the pixel tuple depend on the channels that you defined earlier.
In this example, as we defined to load L,A and B, each pixel has three values. The arguments of the closure
can usually be inferred, so you don't need to declare the type of your image and the `Vec2<usize>`.
However, the type of the pixel needs to be defined. In this example, we define the pixel type to be `(f32, f16, f16)`.
All luma values will be converted to `f32` and all chroma values will be converted to `f16`.
The pixel type can be any combination of `f16`, `f32`, `u32` or `Sample` values, in a tuple with as many entries as there are channels.
The `Sample` type is a dynamic enum over the other types, which allows you to keep the original sample type of each image.
_Note: Currently, up to 32 channels are supported, which is an implementation problem.
Open an issue if this is not enough for your use case. Alternatively,
you can always use `all_channels()`, which has no limitations._
####RGBA Channels
For rgba images, there is a predefined simpler alternative to `specific_channels` called `rgb_channels` and `rgba_channels`.
It works just the same as `specific_channels` and , but you don't need to specify the names of the channels explicitly.
```rust
fn main(){
use exr::prelude::*;
let reader = read()
.no_deep_data().largest_resolution_level()
// load rgba channels
// with alpha being optional, defaulting to 1.0
.rgba_channels(
// create our image based on the resolution of the file
|resolution, &(r,g,b,a)|{
if a.is_some() { MyImage::new_with_alpha(resolution.x(), resolution.y()) }
else { MyImage::new_without_alpha(resolution.x(), resolution.y()) }
},
// insert a single pixel into out image
|my_image, position, (r,g,b,a): (f32, f32, f32, f16)|{
my_image.set_pixel_at(position.x(), position.y(), (r,g,b,a));
}
);
}
```
### Layers
Use `all_layers()` to load a `Vec<Layer<_>>` or use `first_valid_layer()` to only load
the first `Layer<_>` that matches the previously defined requirements
(for example, the first layer without deep data and cmyk channels).
```rust
fn main() {
use exr::prelude::*;
let image = read()
.no_deep_data().largest_resolution_level()
.all_channels().all_layers();
let image = read()
.no_deep_data().largest_resolution_level()
.all_channels().first_valid_layer();
}
```
### Attributes
Currently, the only option is to load all attributes by calling `all_attributes()`.
### Progress Notification
This library allows you to listen for the file reading progress by calling `on_progress(callback)`.
If you don't need this, you can just omit this call.
```rust
fn main() {
use exr::prelude::*;
let image = read().no_deep_data().largest_resolution_level()
.all_channels().first_valid_layer().all_attributes()
.on_progress(|progress: f64| println!("progress: {:.3}", progress));
}
```
### Parallel Decompression
By default, this library uses all the available CPU cores if the pixels are compressed.
You can disable this behaviour by additionally calling `non_parallel()`.
```rust
fn main() {
use exr::prelude::*;
let image = read().no_deep_data().largest_resolution_level()
.all_channels().first_valid_layer().all_attributes()
.non_parallel();
}
```
### Byte Sources
Any `std::io::Read` byte source can be used as input. However, this library also offers a simplification for files.
Call `from_file(path)` to load an image from a file. Internally, this wraps the file in a buffered reader.
Alternatively, you can call `from_buffered` or `from_unbuffered` (which wraps your reader in a buffered reader) to read an image.
```rust
fn main() {
use exr::prelude::*;
let read = read().no_deep_data().largest_resolution_level()
.all_channels().first_valid_layer().all_attributes();
let image = read.clone().from_file("D:/images/file.exr"); // also accepts `Path` and `PathBuf` and `String`
let image = read.clone().from_unbuffered(web_socket);
let image = read.clone().from_buffered(Cursor::new(byte_vec));
}
```
### Results and Errors
The type of image returned depends on the options you picked.
The image is wrapped in a `Result<..., exr::error::Error>`.
This error type allows you to differentiate between three types of errors:
- `Error::Io(std::io::Error)` for file system errors (for example, "file does not exist" or "missing access rights")
- `Error::NotSupported(str)` for files that may be valid but contain features that are not supported yet
- `Error::Invalid(str)` for files that do not contain a valid exr image (files that are not exr or damaged exr)
## Full Example
Loading all channels from the file:
```rust
fn main() {
use exr::prelude::*;
// the type of the this image depends on the chosen options
let image = read()
.no_deep_data() // (currently required)
.largest_resolution_level() // or `all_resolution_levels()`
.all_channels() // or `rgba_channels` or `specific_channels() ...`
.all_layers() // or `first_valid_layer()`
.all_attributes() // (currently required)
.on_progress(|progress| println!("progress: {:.1}", progress * 100.0)) // optional
//.non_parallel() // optional. discouraged. just leave this line out
.from_file("image.exr").unwrap(); // or `from_buffered(my_byte_slice)`
}
```
# The `Image` Data Structure
For great flexibility, this crate does not offer a plain data structure to represent an exr image.
Instead, the `Image` data type has a generic parameter, allowing for different image contents.
```rust
fn main(){
// this image contains only a single layer
let single_layer_image: Image<Layer<_>> = Image::from_layer(my_layer);
// this image contains an arbitrary number of layers (notice the S for plural on `Layers`)
let multi_layer_image: Image<Layers<_>> = Image::new(attributes, smallvec![ layer1, layer2 ]);
// this image can contain the compile-time specified channels
let single_layer_rgb_image : Image<Layer<SpecificChannels<_, _>>> = Image::from_layer(Layer::new(
dimensions, attributes, encoding,
RgbaChannels::new(sample_types, rgba_pixels)
));
// this image can contain all channels from a file, even unexpected ones
let single_layer_image : Image<Layer<AnyChannels<_>>> = Image::from_layer(Layer::new(
dimensions, attributes, encoding,
AnyChannels::sort(smallvec![ channel_x, channel_y, channel_z ])
));
}
```
The following pseudo code illustrates the image data structure.
The image should always be constructed using the constructor functions such as `Image::new(...)`,
because these functions watch out for invalid image contents.
```
Image {
attributes: ImageAttributes,
// the layer data can be either a single layer a list of layers
layer_data: Layer | SmallVec<Layer> | Vec<Layer> | &[Layer] (writing only),
}
Layer {
// the channel data can either be a fixed set of known channels, or a dynamic list of arbitrary channels
channel_data: SpecificChannels | AnyChannels,
attributes: LayerAttributes,
size: Vec2<usize>,
encoding: Encoding,
}
SpecificChannels {
channels: [any tuple containing `ChannelDescription` or `Option<ChannelDescription>`],
// the storage is usually a closure or a custom type which implements the `GetPixel` trait
storage: impl GetPixel | impl Fn(Vec2<usize>) -> Pixel,
where Pixel = any tuple containing f16 or f32 or u32 values
}
AnyChannels {
list: SmallVec<AnyChannel>
}
AnyChannel {
name: Text,
sample_data: FlatSamples | Levels,
quantize_linearly: bool,
sampling: Vec2<usize>,
}
Levels = Singular(FlatSamples) | Mip(FlatSamples) | Rip(FlatSamples)
FlatSamples = F16(Vec<f16>) | F32(Vec<f32>) | U32(Vec<u32>)
```
As a consequence, one of the simpler image types is `Image<Layer<AnyChannels<FlatSamples>>>`. If you
enable loading multiple resolution levels, you will instead get the type `Image<Layer<AnyChannels<Levels<FlatSamples>>>>`.
While you can put anything inside an image,
it can only be written if the content of the image implements certain traits.
This allows you to potentially write your own channel storage system.
# Writing an Image
Writing an image involves three steps:
1. Construct the image data structure, starting with an `exrs::image::Image`
1. Call `image_data.write()` to obtain an image writer
1. Customize the writer, for example in order to listen for the progress
1. Write the image by calling `to_file(path)`, `to_buffered(bytes)`, or `to_unbuffered(bytes)` on the reader
### Image
You will currently need an `Image<_>` at the top level. The type parameter is the type of layer.
The following variants are recommended:
- `Image::from_channels(resolution, channels)` where the pixel data must be `SpecificChannels` or `AnyChannels`.
- `Image::from_layer(layer)` where the layer data must be one `Layer`.
- `Image::empty(attributes).with_layer(layer1).with_layer(layer2)...` where the two layers can have different types
- `Image::new(image_attributes, layer_data)` where the layer data can be `Layers` or `Layer`.
- `Image::from_layers(image_attributes, layer_vec)` where the layer data can be `Layers`.
```rust
fn main() {
use exr::prelude::*;
// single layer constructors
let image = Image::from_layer(layer);
let image = Image::from_channels(resolution, channels);
// use this if the layers have different types
let image = Image::empty(attributes).with_layer(layer1).with_layer(layer2);
// use this if the layers have the same type and the above method does not work for you
let image = Image::from_layers(attributes, smallvec![ layer1, layer2 ]);
// this constructor accepts any layers object if it implements a certain trait, use this for custom layers
let image = Image::new(attributes, layers);
// create an image writer
image.write()
// print progress (optional, you can remove this line)
.on_progress(|progress:f64| println!("progress: {:.3}", progress))
// use only a single cpu (optional, you should remove this line)
// .non_parallel()
// alternatively call to_buffered() or to_unbuffered()
// the file path can be str, String, Path, PathBuf
.to_file(path);
}
```
### Layers
The simple way to create layers is to use `Layers<_>` or `Layer<_>`.
The type parameter is the type of channels.
Use `Layer::new(resolution, attributes, encoding, channels)` to create a layer.
Alternatively, use `smallvec![ layer1, layer2 ]` to create `Layers<_>`, which is a type alias for a list of layers.
```rust
fn main() {
use exr::prelude::*;
let layer = Layer::new(
(1024, 800),
LayerAttributes::named("first layer"), // name required, other attributes optional
Encoding::FAST_LOSSLESS, // or Encoding { .. } or Encoding::default()
channels
);
let image = Image::from_layer(layer);
}
```
### Channels
You can create either `SpecificChannels` to write a fixed set of channels, or `AnyChannels` for a dynamic list of channels.
```rust
fn main() {
use exr::prelude::*;
let channels = AnyChannels::sort(smallvec![ channel1, channel2, channel3 ]);
let image = Image::from_channels((1024, 800), channels);
}
```
Alternatively, write specific channels. Start with `SpecificChannels::build()`,
then call `with_channel(name)` as many times as desired, then call `collect_pixels(..)` to define the colors.
You need to provide a closure that defines the content of the channels: Given the pixel location,
return a tuple with one element per channel. The tuple can contain `f16`, `f32` or `u32` values,
which then will be written to the file, without converting any value to a different type.
```rust
fn main() {
use exr::prelude::*;
let channels = SpecificChannels::build()
.with_channel("L").with_channel("B")
.with_pixel_fn(|position: Vec2<usize>| {
let (l, b) = my_image.lookup_color_at(position.x(), position.y());
(l as f32, f16::from_f32(b))
});
let image = Image::from_channels((1024, 800), channels);
}
```
#### RGB, RGBA
There is an even simpler alternative for rgba images, namely `SpecificChannels::rgb` and `SpecificChannels::rgba`:
This is mostly the same as the `SpecificChannels::build` option.
The rgb method works with three channels per pixel,
whereas the rgba method works with four channels per pixel. The default alpha value of `1.0` will be used
if the image does not contain alpha.
```rust
fn main() {
use exr::prelude::*;
let channels = SpecificChannels::rgba(|_position|
(0.4_f32, 0.2_f32, 0.1_f32, f16::ONE)
);
let channels = SpecificChannels::rgb(|_position|
(0.4_f32, 0.2_f32, 0.1_f32)
);
let image = Image::from_channels((1024, 800), channels);
}
```
### Channel
The type `AnyChannel` can describe every possible channel and contains all its samples for this layer.
Use `AnyChannel::new(channel_name, sample_data)` or `AnyChannel { .. }`.
The samples can currently only be `FlatSamples` or `Levels<FlatSamples>`, and in the future might be `DeepSamples`.
### Samples
Currently, only flat samples are supported. These do not contain deep data.
Construct flat samples directly using `FlatSamples::F16(samples_vec)`, `FlatSamples::F32(samples_vec)`, or `FlatSamples::U32(samples_vec)`.
The vector contains all samples of the layer, row by row (from top to bottom), from left to right.
### Levels
Optionally include Mip Maps or Rip Maps.
Construct directly using `Levels::Singular(flat_samples)` or `Levels::Mip { .. }` or `Levels::Rip { .. }`.
Put this into the channel, for example`AnyChannel::new("R", Levels::Singular(FlatSamples::F32(vec)))`.
## Full example
Writing a flexible list of channels:
```rust
fn main(){
// construct an image to write
let image = Image::from_layer(
Layer::new( // the only layer in this image
(1920, 1080), // resolution
LayerAttributes::named("main-rgb-layer"), // the layer has a name and other properties
Encoding::FAST_LOSSLESS, // compress slightly
AnyChannels::sort(smallvec![ // the channels contain the actual pixel data
AnyChannel::new("R", FlatSamples::F32(vec![0.6; 1920*1080 ])), // this channel contains all red values
AnyChannel::new("G", FlatSamples::F32(vec![0.7; 1920*1080 ])), // this channel contains all green values
AnyChannel::new("B", FlatSamples::F32(vec![0.9; 1920*1080 ])), // this channel contains all blue values
]),
)
);
image.write()
.on_progress(|progress| println!("progress: {:.1}", progress*100.0)) // optional
.to_file("image.exr").unwrap();
}
```
### Pixel Closures
When working with specific channels, the data is not stored directly.
Instead, you provide a closure that stores or loads pixels in your existing image data structure.
If you really do not want to provide your own storage, you can use the predefined structures from
`exr::image::pixel_vec`, such as `PixelVec<(f32,f32,f16)>` or `create_pixel_vec`.
Use this only if you don't already have a pixel storage.
```rust
fn main(){
let read = read()
.no_deep_data().largest_resolution_level()
.rgba_channels(
PixelVec::<(f32,f32,f32,f16)>::constructor, // how to create an image
PixelVec::set_pixel, // how to update a single pixel in the image
)/* ... */;
}
```
## Low Level Operations
The image abstraction builds up on some low level code.
You can use this low level directly,
as shown in the examples `custom_write.rs` and `custom_read.rs`.
This allows you to work with
raw OpenEXR pixel blocks and chunks directly,
or use custom parallelization mechanisms.
You can find these low level operations in the `exr::block` module.
Start with the `block::read(...)`
and `block::write(...)` functions.
|