1
2//! Write an exr image to a file.
3//!
4//! First, call `my_image.write()`. The resulting value can be customized, like this:
5//! ```no_run
6//! use exr::prelude::*;
7//! # let my_image: FlatImage = unimplemented!();
8//!
9//! my_image.write()
10//! .on_progress(|progress| println!("progress: {:.1}", progress*100.0))
11//! .to_file("image.exr").unwrap();
12//! ```
13//!
14
15pub mod layers;
16pub mod samples;
17pub mod channels;
18
19
20
21use crate::meta::Headers;
22use crate::error::UnitResult;
23use std::io::{Seek, BufWriter};
24use crate::io::Write;
25use crate::image::{Image, ignore_progress, SpecificChannels, IntoSample};
26use crate::image::write::layers::{WritableLayers, LayersWriter};
27use crate::math::Vec2;
28use crate::block::writer::ChunksWriter;
29
30/// An oversimplified function for "just write the damn file already" use cases.
31/// Have a look at the examples to see how you can write an image with more flexibility (it's not that hard).
32/// Use `write_rgb_file` if you do not need an alpha channel.
33///
34/// Each of `R`, `G`, `B` and `A` can be either `f16`, `f32`, `u32`, or `Sample`.
35// TODO explain pixel tuple f32,f16,u32
36pub fn write_rgba_file<R,G,B,A>(
37 path: impl AsRef<std::path::Path>, width: usize, height: usize,
38 colors: impl Sync + Fn(usize, usize) -> (R, G, B, A)
39) -> UnitResult
40 where R: IntoSample, G: IntoSample, B: IntoSample, A: IntoSample,
41{
42 let channels: SpecificChannels …, …> = SpecificChannels::rgba(|Vec2(x: usize,y: usize)| colors(x,y));
43 Image::from_channels((width, height), channels).write().to_file(path)
44}
45
46/// An oversimplified function for "just write the damn file already" use cases.
47/// Have a look at the examples to see how you can write an image with more flexibility (it's not that hard).
48/// Use `write_rgb_file` if you do not need an alpha channel.
49///
50/// Each of `R`, `G`, and `B` can be either `f16`, `f32`, `u32`, or `Sample`.
51// TODO explain pixel tuple f32,f16,u32
52pub fn write_rgb_file<R,G,B>(
53 path: impl AsRef<std::path::Path>, width: usize, height: usize,
54 colors: impl Sync + Fn(usize, usize) -> (R, G, B)
55) -> UnitResult
56 where R: IntoSample, G: IntoSample, B: IntoSample
57{
58 let channels: SpecificChannels …, …> = SpecificChannels::rgb(|Vec2(x: usize,y: usize)| colors(x,y));
59 Image::from_channels((width, height), channels).write().to_file(path)
60}
61
62
63
64/// Enables an image to be written to a file. Call `image.write()` where this trait is implemented.
65pub trait WritableImage<'img, WritableLayers>: Sized {
66
67 /// Create a temporary writer which can be configured and used to write the image to a file.
68 fn write(self) -> WriteImageWithOptions<'img, WritableLayers, fn(f64)>;
69}
70
71impl<'img, WritableLayers> WritableImage<'img, WritableLayers> for &'img Image<WritableLayers> {
72 fn write(self) -> WriteImageWithOptions<'img, WritableLayers, fn(f64)> {
73 WriteImageWithOptions {
74 image: self,
75 check_compatibility: true,
76 parallel: true,
77 on_progress: ignore_progress
78 }
79 }
80}
81
82/// A temporary writer which can be configured and used to write an image to a file.
83// temporary writer with options
84#[derive(Debug, Clone, PartialEq)]
85pub struct WriteImageWithOptions<'img, Layers, OnProgress> {
86 image: &'img Image<Layers>,
87 on_progress: OnProgress,
88 check_compatibility: bool,
89 parallel: bool,
90}
91
92
93impl<'img, L, F> WriteImageWithOptions<'img, L, F>
94 where L: WritableLayers<'img>, F: FnMut(f64)
95{
96 /// Generate file meta data for this image. The meta data structure is close to the data in the file.
97 pub fn infer_meta_data(&self) -> Headers { // TODO this should perform all validity checks? and none after that?
98 self.image.layer_data.infer_headers(&self.image.attributes)
99 }
100
101 /// Do not compress multiple pixel blocks on multiple threads at once.
102 /// Might use less memory and synchronization, but will be slower in most situations.
103 pub fn non_parallel(self) -> Self { Self { parallel: false, ..self } }
104
105 /// Skip some checks that ensure a file can be opened by other exr software.
106 /// For example, it is no longer checked that no two headers or two attributes have the same name,
107 /// which might be an expensive check for images with an exorbitant number of headers.
108 ///
109 /// If you write an uncompressed file and need maximum speed, it might save a millisecond to disable the checks,
110 /// if you know that your file is not invalid any ways. I do not recommend this though,
111 /// as the file might not be readably by any other exr library after that.
112 /// __You must care for not producing an invalid file yourself.__
113 pub fn skip_compatibility_checks(self) -> Self { Self { check_compatibility: false, ..self } }
114
115 /// Specify a function to be called regularly throughout the writing process.
116 /// Replaces all previously specified progress functions in this reader.
117 pub fn on_progress<OnProgress>(self, on_progress: OnProgress) -> WriteImageWithOptions<'img, L, OnProgress>
118 where OnProgress: FnMut(f64)
119 {
120 WriteImageWithOptions {
121 on_progress,
122 image: self.image,
123 check_compatibility: self.check_compatibility,
124 parallel: self.parallel
125 }
126 }
127
128 /// Write the exr image to a file.
129 /// Use `to_unbuffered` instead, if you do not have a file.
130 /// If an error occurs, attempts to delete the partially written file.
131 #[inline]
132 #[must_use]
133 pub fn to_file(self, path: impl AsRef<std::path::Path>) -> UnitResult {
134 crate::io::attempt_delete_file_on_write_error(path.as_ref(), move |write|
135 self.to_unbuffered(write)
136 )
137 }
138
139 /// Buffer the writer and then write the exr image to it.
140 /// Use `to_buffered` instead, if your writer is an in-memory buffer.
141 /// Use `to_file` instead, if you have a file path.
142 /// If your writer cannot seek, you can write to an in-memory vector of bytes first, using `to_buffered`.
143 #[inline]
144 #[must_use]
145 pub fn to_unbuffered(self, unbuffered: impl Write + Seek) -> UnitResult {
146 self.to_buffered(BufWriter::new(unbuffered))
147 }
148
149 /// Write the exr image to a writer.
150 /// Use `to_file` instead, if you have a file path.
151 /// Use `to_unbuffered` instead, if this is not an in-memory writer.
152 /// If your writer cannot seek, you can write to an in-memory vector of bytes first.
153 #[must_use]
154 pub fn to_buffered(self, write: impl Write + Seek) -> UnitResult {
155 let headers = self.infer_meta_data();
156 let layers = self.image.layer_data.create_writer(&headers);
157
158 crate::block::write(
159 write, headers, self.check_compatibility,
160 move |meta, chunk_writer|{
161
162 let blocks = meta.collect_ordered_block_data(|block_index|
163 layers.extract_uncompressed_block(&meta.headers, block_index)
164 );
165
166 let chunk_writer = chunk_writer.on_progress(self.on_progress);
167 if self.parallel { chunk_writer.compress_all_blocks_parallel(&meta, blocks)?; }
168 else { chunk_writer.compress_all_blocks_sequential(&meta, blocks)?; }
169 /*let blocks_writer = chunk_writer.as_blocks_writer(&meta);
170
171 // TODO propagate send requirement further upwards
172 if self.parallel {
173 blocks_writer.compress_all_blocks_parallel(blocks)?;
174 }
175 else {
176 blocks_writer.compress_all_blocks_sequential(blocks)?;
177 }*/
178
179 Ok(())
180 }
181 )
182 }
183}
184
185