Skip to content

Commit b3a2444

Browse files
authored
Merge pull request #243 from Walther/push-ttktswlqptkq
improvement: use exr crate directly
2 parents 857fa38 + f21befe commit b3a2444

File tree

3 files changed

+49
-36
lines changed

3 files changed

+49
-36
lines changed

clovers-cli/Cargo.toml

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,10 @@ clovers = { path = "../clovers", features = [
2525
# External
2626
blue-noise-sampler = "0.1.0"
2727
clap = { version = "4.5.51", features = ["std", "derive"] }
28+
exr = "1.74.0"
2829
human_format = "1.1.0"
2930
humantime = "2.3.0"
30-
image = { version = "0.25.9", features = [
31-
"png",
32-
"exr",
33-
], default-features = false }
31+
image = { version = "0.25.9", features = ["png"], default-features = false }
3432
img-parts = "0.4.0"
3533
indicatif = { version = "0.18.3", features = [
3634
"rayon",

clovers-cli/src/render.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ pub(crate) fn render(
172172

173173
match format {
174174
Format::Png => write::png(&pixelbuffer, &target, &duration, &render_options),
175-
Format::Exr => write::exr(&pixelbuffer, &target, &duration, &render_options),
175+
Format::Exr => write::exr(&pixelbuffer, width, height, &target),
176176
}?;
177177

178178
info!("Image saved to {}", target);

clovers-cli/src/write.rs

Lines changed: 46 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,13 @@ use std::fs::File;
22
use std::io::Cursor;
33

44
use clovers::Float;
5+
use exr::{
6+
image::{Encoding, Layer, PixelImage},
7+
meta::attribute::Chromaticities,
8+
prelude::{LayerAttributes, WritableImage},
9+
};
510
use humantime::FormattedDuration;
6-
use image::{ImageBuffer, ImageFormat, Rgb32FImage, RgbImage};
11+
use image::{ImageBuffer, ImageFormat, RgbImage};
712
use img_parts::png::{Png, PngChunk};
813
use palette::{chromatic_adaptation::AdaptInto, white_point::E, Xyz};
914
use tracing::info;
@@ -60,7 +65,7 @@ pub fn png(
6065
let stats = format!("Rendering finished in {duration}, using {threads} threads.");
6166
let comment = format!("{common}{details} {stats}");
6267

63-
let software = "Software\0https://github.com/walther/clovers".to_string();
68+
let software = "Software\0clovers".to_string();
6469

6570
for metadata in [comment, software] {
6671
let bytes = metadata.as_bytes().to_owned();
@@ -76,35 +81,45 @@ pub fn png(
7681
Ok(())
7782
}
7883

79-
pub fn exr(
80-
pixelbuffer: &[Xyz<E>],
81-
target: &String,
82-
_duration: &FormattedDuration,
83-
render_options: &RenderOptions,
84-
) -> Result<(), String> {
85-
let RenderOptions {
86-
input: _,
87-
output: _,
88-
width,
89-
height,
90-
samples: _,
91-
max_depth: _,
92-
mode: _,
93-
sampler: _,
94-
bvh: _,
95-
formats: _,
96-
} = render_options;
97-
// TODO: metadata?
98-
84+
/// Save the pixelbuffer as an OpenEXR file.
85+
///
86+
/// From the specification:
87+
/// > In an OpenEXR file whose pixels represent CIE XYZ tristimulus values,
88+
/// > the pixels’ X, Y and Z components should be stored in the file’s R, G and B channels.
89+
/// > The file header should contain a chromaticities attribute with the following values:
90+
/// > | | CIE x,y |
91+
/// > |-------|----------|
92+
/// > | red | 1, 0 |
93+
/// > | green | 0, 1 |
94+
/// > | blue | 0, 0 |
95+
/// > | white | 1/3, 1/3 |
96+
/// <cite><https://openexr.com/en/latest/TechnicalIntroduction.html#cie-xyz-color></cite>
97+
pub fn exr(pixelbuffer: &[Xyz<E>], width: u32, height: u32, target: &String) -> Result<(), String> {
9998
info!("Converting pixelbuffer to an image");
100-
let mut img: Rgb32FImage = ImageBuffer::new(*width, *height);
101-
img.enumerate_pixels_mut().for_each(|(x, y, pixel)| {
102-
let index = y * width + x;
103-
// NOTE: EXR format expects linear rgb
104-
let color: palette::LinSrgb<Float> = pixelbuffer[index as usize].adapt_into();
105-
*pixel = image::Rgb([color.red, color.green, color.blue]);
99+
let dimensions = (width as usize, height as usize);
100+
let layer_attributes = LayerAttributes {
101+
// capture_date: todo!(),
102+
software_name: Some("clovers".into()),
103+
..Default::default()
104+
};
105+
let encoding = Encoding::SMALL_FAST_LOSSLESS;
106+
let channels = exr::prelude::SpecificChannels::build()
107+
.with_channel::<f32>("R")
108+
.with_channel::<f32>("G")
109+
.with_channel::<f32>("B")
110+
.with_pixel_fn(|coord| {
111+
let index = coord.y() * width as usize + coord.x();
112+
let pixel: Xyz<E> = pixelbuffer[index];
113+
pixel.into_components()
114+
});
115+
let mut image =
116+
PixelImage::from_layer(Layer::new(dimensions, layer_attributes, encoding, channels));
117+
image.attributes.chromaticities = Some(Chromaticities {
118+
red: (1.0, 0.0).into(),
119+
green: (0.0, 1.0).into(),
120+
blue: (0.0, 0.0).into(),
121+
white: (1.0 / 3.0, 1.0 / 3.0).into(),
106122
});
107-
108-
img.save_with_format(target, ImageFormat::OpenExr)
109-
.or(Err("Unable to write to file".to_owned()))
123+
image.write().to_file(target).unwrap();
124+
Ok(())
110125
}

0 commit comments

Comments
 (0)