Skip to main content

zune_image/
pipelines.rs

1/*
2 * Copyright (c) 2023.
3 *
4 * This software is free software; You can redistribute it or modify it under terms of the MIT, Apache License or Zlib license
5 */
6//! Pipelines, Batch image processing support
7//!
8#![allow(unused_variables)]
9use std::time::Instant;
10
11use zune_core::log::Level::Trace;
12use zune_core::log::{log_enabled, trace};
13
14use crate::codecs::ImageFormat;
15use crate::errors::ImageErrors;
16use crate::image::Image;
17use crate::traits::{IntoImage, OperationsTrait};
18
19#[derive(Copy, Clone, Debug)]
20enum PipelineState {
21    /// Initial state, the struct has been defined
22    Initialized,
23    /// The pipeline is ready to carry out image decoding of various supported formats
24    Decode,
25    /// The pipeline is ready to carry out image processing routines
26    Operations,
27    /// The pipeline is ready to carry out image encoding
28    Encode,
29    /// The pipeline is done.
30    Finished
31}
32
33impl PipelineState {
34    pub fn next(self) -> Option<Self> {
35        match self {
36            PipelineState::Initialized => Some(PipelineState::Decode),
37            PipelineState::Decode => Some(PipelineState::Operations),
38            PipelineState::Operations => Some(PipelineState::Encode),
39            PipelineState::Encode => Some(PipelineState::Finished),
40            PipelineState::Finished => None
41        }
42    }
43}
44
45/// A struct holding the result of an encode operation
46///
47/// It contains the image format the data is in
48pub struct EncodeResult {
49    pub(crate) format: ImageFormat,
50    pub(crate) data:   Vec<u8>
51}
52
53impl EncodeResult {
54    /// Return the raw data of the encoded format
55    pub fn data(&self) -> &[u8] {
56        &self.data
57    }
58    /// Return the format for which the data
59    /// of this encode result is stored in
60    pub fn format(&self) -> ImageFormat {
61        self.format
62    }
63}
64
65/// Pipeline, batch image processing
66///
67/// A pipeline provides an idiomatic way to do batch image processing
68/// it can load multiple images (by queueing decoders) and batch apply an operation
69/// to all the images and then encode images to multiple specified format.
70///
71/// A pipeline accepts anything that implements [IntoImage](crate::traits::IntoImage) and
72/// it has to own the image for the duration of it's lifetime, but can return references to it
73/// via  [`images`](crate::pipelines::Pipeline::images) and
74///  [`images_mut`](crate::pipelines::Pipeline::images_mut)
75pub struct Pipeline {
76    state:      Option<PipelineState>,
77    decode:     Option<Box<dyn IntoImage>>,
78    image:      Vec<Image>,
79    operations: Vec<Box<dyn OperationsTrait>>
80}
81
82impl Pipeline {
83    /// Create a new pipeline that can be used for default
84    #[allow(clippy::new_without_default)]
85    pub fn new() -> Pipeline {
86        Pipeline {
87            image:      vec![],
88            state:      Some(PipelineState::Initialized),
89            decode:     None,
90            operations: vec![]
91        }
92    }
93
94    /// Add an image to this chain.
95    pub fn chain_image(&mut self, image: Image) {
96        self.image.push(image);
97    }
98
99    /// Override the decoder present in the pipeline with a different
100    /// decoder.
101    ///
102    /// There can only be one decoder in a pipeline, so the last decoder
103    /// is the one that will be considered.
104    pub fn chain_decoder(&mut self, decoder: Box<dyn IntoImage>) -> &mut Pipeline {
105        self.decode = Some(decoder);
106        self
107    }
108    /// Add a new operation to the workflow.
109    ///
110    /// This is used as a way to chain multiple operations in a builder
111    /// pattern style
112    ///
113    /// # Example
114    /// - This operation will decode a jpeg image pointed by buf,
115    /// which is added to the workflow via add_buffer, then
116    /// 1. It de-interleaves the image channels, separating them into
117    /// separate RGB channels
118    /// 2. Convert RGB data to grayscale
119    /// 3. Change the depth to be float32 (f32 in range 0..2)
120    /// ```no_run
121    /// #
122    /// use zune_image::core_filters::colorspace::ColorspaceConv;
123    /// use zune_image::image::Image;
124    ///
125    /// use zune_image::core_filters::depth::Depth;
126    /// use zune_image::pipelines::Pipeline;
127    ///
128    ///
129    /// let image = Pipeline::new()
130    ///     .chain_operations(Box::new(ColorspaceConv::new(zune_core::colorspace::ColorSpace::Luma)))
131    ///     .chain_operations(Box::new(Depth::new(zune_core::bit_depth::BitDepth::Float32)))   
132    ///     .advance_to_end();
133    /// ```
134    pub fn chain_operations(&mut self, operations: Box<dyn OperationsTrait>) -> &mut Pipeline {
135        self.operations.push(operations);
136        self
137    }
138    pub fn images(&self) -> &[Image] {
139        self.image.as_ref()
140    }
141    /// Return all images in the workflow as mutable references
142    pub fn images_mut(&mut self) -> &mut [Image] {
143        self.image.as_mut()
144    }
145    /// Advance the workflow one state forward
146    ///
147    /// The workflow advance is as follows
148    ///
149    /// 1. Decode
150    /// 2. One or more operations [ all ran at once]
151    /// 3. One or more encodes [all ran at once]
152    /// 4. Finish
153    ///
154    /// Calling `Workflow::advance()` will run one of this operation
155    pub fn advance(&mut self) -> Result<(), ImageErrors> {
156        if let Some(state) = self.state {
157            match state {
158                PipelineState::Decode => {
159                    let start = Instant::now();
160                    // do the actual decode
161                    if self.decode.is_none() {
162                        // we have an image, no need to decode a new one
163                        if self.image.is_empty() {
164                            trace!("Image already present, no need to decode");
165                            // move to the next state
166                            self.state = state.next();
167
168                            return Ok(());
169                        }
170                        return Err(ImageErrors::NoImageForOperations);
171                    }
172
173                    if log_enabled!(Trace) {
174                        println!();
175                        trace!("Current state: {:?}\n", state);
176                    }
177
178                    let mut decode_op = self.decode.take().unwrap();
179
180                    let img = decode_op.into_image()?;
181
182                    self.image.push(img);
183
184                    let stop = Instant::now();
185
186                    self.state = state.next();
187
188                    trace!("Finished decoding in {} ms", (stop - start).as_millis());
189                }
190                PipelineState::Operations => {
191                    if self.image.is_empty() {
192                        return Err(ImageErrors::NoImageForOperations);
193                    }
194
195                    if log_enabled!(Trace) && !self.operations.is_empty() {
196                        println!();
197                        trace!("Current state: {:?}\n", state);
198                    }
199
200                    for image in self.image.iter_mut() {
201                        for operation in &self.operations {
202                            let operation_name = operation.name();
203
204                            trace!("Running {}", operation_name);
205
206                            let start = Instant::now();
207
208                            operation.execute(image)?;
209
210                            let stop = Instant::now();
211
212                            trace!(
213                                "Finished running `{operation_name}` in {} ms",
214                                (stop - start).as_millis()
215                            );
216                        }
217                        self.state = state.next();
218                    }
219                }
220                PipelineState::Finished => {
221                    trace!("Finished operations for this workflow");
222
223                    self.state = state.next();
224                    return Ok(());
225                }
226                _ => {
227                    self.state = state.next();
228                }
229            }
230        }
231        Ok(())
232    }
233    /// Advance the operations in this workflow up until
234    /// we finish.
235    ///
236    /// This will run a decoder, all operations and all encoders
237    /// for this particular workflow
238    pub fn advance_to_end(&mut self) -> Result<(), ImageErrors> {
239        if self.state.is_some() {
240            while self.state.is_some() {
241                self.advance()?;
242            }
243        }
244        Ok(())
245    }
246}