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

//! Write an exr image to a file.
//!
//! First, call `my_image.write()`. The resulting value can be customized, like this:
//! ```no_run
//!     use exr::prelude::*;
//! #   let my_image: FlatImage = unimplemented!();
//!
//!     my_image.write()
//!            .on_progress(|progress| println!("progress: {:.1}", progress*100.0))
//!            .to_file("image.exr").unwrap();
//! ```
//!

pub mod layers;
pub mod samples;
pub mod channels;



use crate::meta::Headers;
use crate::error::UnitResult;
use std::io::{Seek, BufWriter};
use crate::io::Write;
use crate::image::{Image, ignore_progress, SpecificChannels, IntoSample};
use crate::image::write::layers::{WritableLayers, LayersWriter};
use crate::math::Vec2;
use crate::block::writer::ChunksWriter;

/// An oversimplified function for "just write the damn file already" use cases.
/// Have a look at the examples to see how you can write an image with more flexibility (it's not that hard).
/// Use `write_rgb_file` if you do not need an alpha channel.
///
/// Each of `R`, `G`, `B` and `A` can be either `f16`, `f32`, `u32`, or `Sample`.
// TODO explain pixel tuple f32,f16,u32
pub fn write_rgba_file<R,G,B,A>(
    path: impl AsRef<std::path::Path>, width: usize, height: usize,
    colors: impl Sync + Fn(usize, usize) -> (R, G, B, A)
) -> UnitResult
    where R: IntoSample, G: IntoSample, B: IntoSample, A: IntoSample,
{
    let channels = SpecificChannels::rgba(|Vec2(x,y)| colors(x,y));
    Image::from_channels((width, height), channels).write().to_file(path)
}

/// An oversimplified function for "just write the damn file already" use cases.
/// Have a look at the examples to see how you can write an image with more flexibility (it's not that hard).
/// Use `write_rgb_file` if you do not need an alpha channel.
///
/// Each of `R`, `G`, and `B` can be either `f16`, `f32`, `u32`, or `Sample`.
// TODO explain pixel tuple f32,f16,u32
pub fn write_rgb_file<R,G,B>(
    path: impl AsRef<std::path::Path>, width: usize, height: usize,
    colors: impl Sync + Fn(usize, usize) -> (R, G, B)
) -> UnitResult
    where R: IntoSample, G: IntoSample, B: IntoSample
{
    let channels = SpecificChannels::rgb(|Vec2(x,y)| colors(x,y));
    Image::from_channels((width, height), channels).write().to_file(path)
}



/// Enables an image to be written to a file. Call `image.write()` where this trait is implemented.
pub trait WritableImage<'img, WritableLayers>: Sized {

    /// Create a temporary writer which can be configured and used to write the image to a file.
    fn write(self) -> WriteImageWithOptions<'img, WritableLayers, fn(f64)>;
}

impl<'img, WritableLayers> WritableImage<'img, WritableLayers> for &'img Image<WritableLayers> {
    fn write(self) -> WriteImageWithOptions<'img, WritableLayers, fn(f64)> {
        WriteImageWithOptions {
            image: self,
            check_compatibility: true,
            parallel: true,
            on_progress: ignore_progress
        }
    }
}

/// A temporary writer which can be configured and used to write an image to a file.
// temporary writer with options
#[derive(Debug, Clone, PartialEq)]
pub struct WriteImageWithOptions<'img, Layers, OnProgress> {
    image: &'img Image<Layers>,
    on_progress: OnProgress,
    check_compatibility: bool,
    parallel: bool,
}


impl<'img, L, F> WriteImageWithOptions<'img, L, F>
    where L: WritableLayers<'img>, F: FnMut(f64)
{
    /// Generate file meta data for this image. The meta data structure is close to the data in the file.
    pub fn infer_meta_data(&self) -> Headers { // TODO this should perform all validity checks? and none after that?
        self.image.layer_data.infer_headers(&self.image.attributes)
    }

    /// Do not compress multiple pixel blocks on multiple threads at once.
    /// Might use less memory and synchronization, but will be slower in most situations.
    pub fn non_parallel(self) -> Self { Self { parallel: false, ..self } }

    /// Skip some checks that ensure a file can be opened by other exr software.
    /// For example, it is no longer checked that no two headers or two attributes have the same name,
    /// which might be an expensive check for images with an exorbitant number of headers.
    ///
    /// If you write an uncompressed file and need maximum speed, it might save a millisecond to disable the checks,
    /// if you know that your file is not invalid any ways. I do not recommend this though,
    /// as the file might not be readably by any other exr library after that.
    /// __You must care for not producing an invalid file yourself.__
    pub fn skip_compatibility_checks(self) -> Self { Self { check_compatibility: false, ..self } }

    /// Specify a function to be called regularly throughout the writing process.
    /// Replaces all previously specified progress functions in this reader.
    pub fn on_progress<OnProgress>(self, on_progress: OnProgress) -> WriteImageWithOptions<'img, L, OnProgress>
        where OnProgress: FnMut(f64)
    {
        WriteImageWithOptions {
            on_progress,
            image: self.image,
            check_compatibility: self.check_compatibility,
            parallel: self.parallel
        }
    }

    /// Write the exr image to a file.
    /// Use `to_unbuffered` instead, if you do not have a file.
    /// If an error occurs, attempts to delete the partially written file.
    #[inline]
    #[must_use]
    pub fn to_file(self, path: impl AsRef<std::path::Path>) -> UnitResult {
        crate::io::attempt_delete_file_on_write_error(path.as_ref(), move |write|
            self.to_unbuffered(write)
        )
    }

    /// Buffer the writer and then write the exr image to it.
    /// Use `to_buffered` instead, if your writer is an in-memory buffer.
    /// Use `to_file` instead, if you have a file path.
    /// If your writer cannot seek, you can write to an in-memory vector of bytes first, using `to_buffered`.
    #[inline]
    #[must_use]
    pub fn to_unbuffered(self, unbuffered: impl Write + Seek) -> UnitResult {
        self.to_buffered(BufWriter::new(unbuffered))
    }

    /// Write the exr image to a writer.
    /// Use `to_file` instead, if you have a file path.
    /// Use `to_unbuffered` instead, if this is not an in-memory writer.
    /// If your writer cannot seek, you can write to an in-memory vector of bytes first.
    #[must_use]
    pub fn to_buffered(self, write: impl Write + Seek) -> UnitResult {
        let headers = self.infer_meta_data();
        let layers = self.image.layer_data.create_writer(&headers);

        crate::block::write(
            write, headers, self.check_compatibility,
            move |meta, chunk_writer|{

                let blocks = meta.collect_ordered_block_data(|block_index|
                     layers.extract_uncompressed_block(&meta.headers, block_index)
                );

                let chunk_writer = chunk_writer.on_progress(self.on_progress);
                if self.parallel { chunk_writer.compress_all_blocks_parallel(&meta, blocks)?; }
                else { chunk_writer.compress_all_blocks_sequential(&meta, blocks)?; }
                /*let blocks_writer = chunk_writer.as_blocks_writer(&meta);

                // TODO propagate send requirement further upwards
                if self.parallel {
                    blocks_writer.compress_all_blocks_parallel(blocks)?;
                }
                else {
                    blocks_writer.compress_all_blocks_sequential(blocks)?;
                }*/

                Ok(())
            }
        )
    }
}