| 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 |
|
| 15 | pub mod layers;
|
| 16 | pub mod samples;
|
| 17 | pub mod channels;
|
| 18 |
|
| 19 |
|
| 20 |
|
| 21 | use crate::meta::Headers;
|
| 22 | use crate::error::UnitResult;
|
| 23 | use std::io::{Seek, BufWriter};
|
| 24 | use crate::io::Write;
|
| 25 | use crate::image::{Image, ignore_progress, SpecificChannels, IntoSample};
|
| 26 | use crate::image::write::layers::{WritableLayers, LayersWriter};
|
| 27 | use crate::math::Vec2;
|
| 28 | use 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
|
| 36 | pub 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
|
| 52 | pub 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.
|
| 65 | pub 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 |
|
| 71 | impl<'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)]
|
| 85 | pub struct WriteImageWithOptions<'img, Layers, OnProgress> {
|
| 86 | image: &'img Image<Layers>,
|
| 87 | on_progress: OnProgress,
|
| 88 | check_compatibility: bool,
|
| 89 | parallel: bool,
|
| 90 | }
|
| 91 |
|
| 92 |
|
| 93 | impl<'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 | |