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}