zune_image/
image.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
7//! This module represents a single image, an image can consists of one or more
8//!
9//! And that's how we represent images.
10//! Fully supported bit depths are 8 and 16 and float 32 which are expected to be in the range between 0.0 and 1.0,
11//! see [channel](crate::channel) documentation for how that happens
12//!
13use std::fmt::Debug;
14use std::mem::size_of;
15
16use bytemuck::{Pod, Zeroable};
17use zune_core::bit_depth::BitDepth;
18use zune_core::colorspace::ColorSpace;
19
20use crate::channel::{Channel, ChannelErrors};
21use crate::core_filters::colorspace::ColorspaceConv;
22use crate::core_filters::depth::Depth;
23use crate::deinterleave::{deinterleave_f32, deinterleave_u16, deinterleave_u8};
24use crate::errors::ImageErrors;
25use crate::frame::Frame;
26use crate::metadata::ImageMetadata;
27use crate::traits::{OperationsTrait, ZuneInts};
28
29/// Maximum supported color channels
30pub const MAX_CHANNELS: usize = 4;
31
32/// Represents a single image
33#[derive(Clone)]
34pub struct Image {
35    pub(crate) frames:   Vec<Frame>,
36    pub(crate) metadata: ImageMetadata
37}
38
39impl PartialEq<Self> for Image {
40    fn eq(&self, other: &Self) -> bool {
41        other.frames == self.frames
42    }
43}
44impl Eq for Image {}
45
46impl Image {
47    /// Create a new image instance
48    ///
49    /// This constructs a single image frame (non-animated) with the
50    /// configured dimensions,colorspace and depth
51    ///  
52    pub fn new(
53        channels: Vec<Channel>, depth: BitDepth, width: usize, height: usize,
54        colorspace: ColorSpace
55    ) -> Image {
56        // setup metadata information
57        let mut meta = ImageMetadata::default();
58
59        meta.set_dimensions(width, height);
60        meta.set_depth(depth);
61        meta.set_colorspace(colorspace);
62
63        Image {
64            frames:   vec![Frame::new(channels)],
65            metadata: meta
66        }
67    }
68    /// Create an image from multiple frames.
69    pub fn new_frames(
70        frames: Vec<Frame>, depth: BitDepth, width: usize, height: usize, colorspace: ColorSpace
71    ) -> Image {
72        // setup metadata information
73        let mut meta = ImageMetadata::default();
74
75        meta.set_dimensions(width, height);
76        meta.set_depth(depth);
77        meta.set_colorspace(colorspace);
78
79        Image {
80            frames,
81            metadata: meta
82        }
83    }
84
85    /// Return true if the current image contains more than
86    /// one frame indicating it is animated
87    ///
88    /// # Returns
89    ///  
90    /// - true : Image contains a series of frames which can be animated
91    /// - false: Image contains a single frame  
92    ///
93    pub fn is_animated(&self) -> bool {
94        self.frames.len() > 1
95    }
96    /// Get image dimensions as a tuple of (width,height)
97    pub const fn dimensions(&self) -> (usize, usize) {
98        self.metadata.get_dimensions()
99    }
100
101    /// Get the image depth of this image
102    pub const fn depth(&self) -> BitDepth {
103        self.metadata.get_depth()
104    }
105    /// Set image depth
106    pub fn set_depth(&mut self, depth: BitDepth) {
107        self.metadata.set_depth(depth)
108    }
109
110    pub const fn metadata(&self) -> &ImageMetadata {
111        &self.metadata
112    }
113
114    pub fn metadata_mut(&mut self) -> &mut ImageMetadata {
115        &mut self.metadata
116    }
117
118    /// Return an immutable reference to all image frames
119    ///
120    /// # Returns
121    /// All frames in the image
122    ///
123    ///
124    pub fn frames_ref(&self) -> &[Frame] {
125        &self.frames
126    }
127
128    pub fn frames_mut(&mut self) -> &mut [Frame] {
129        &mut self.frames
130    }
131    /// Return a reference to the underlying channels
132    pub fn channels_ref(&self, ignore_alpha: bool) -> Vec<&Channel> {
133        let colorspace = self.colorspace();
134
135        self.frames_ref()
136            .iter()
137            .flat_map(|x| x.channels_ref(colorspace, ignore_alpha))
138            .collect()
139    }
140
141    /// Return a mutable view into the image channels
142    ///
143    /// This gives mutable access to the chanel data allowing
144    /// single or multithreaded manipulation of images
145    pub fn channels_mut(&mut self, ignore_alpha: bool) -> Vec<&mut Channel> {
146        let colorspace = self.colorspace();
147
148        self.frames_mut()
149            .iter_mut()
150            .flat_map(|x| x.channels_mut(colorspace, ignore_alpha))
151            .collect()
152    }
153    /// Get the colorspace this image is stored
154    /// in
155    pub const fn colorspace(&self) -> ColorSpace {
156        self.metadata.colorspace
157    }
158    /// Flatten channels in this image.
159    ///
160    /// Flatten can be used to interleave all channels into one vector
161    pub fn flatten_frames<T: Default + Copy + 'static + Pod>(&self) -> Vec<Vec<T>> {
162        //
163        assert_eq!(self.metadata.get_depth().size_of(), size_of::<T>());
164        let colorspace = self.colorspace();
165
166        self.frames_ref()
167            .iter()
168            .map(|x| x.flatten(colorspace))
169            .collect()
170    }
171    /// Convert image to a byte representation interleaving
172    /// image pixels where necessary
173    ///
174    /// # Note
175    /// For images using anything larger than 8 bit,
176    /// u8 as native endian is used
177    /// i.e RGB data looks like `[R,R,G,G,G,B,B]`
178    #[allow(dead_code)]
179    pub(crate) fn to_u8(&self) -> Vec<Vec<u8>> {
180        let colorspace = self.colorspace();
181        if self.metadata.get_depth() == BitDepth::Eight {
182            self.flatten_frames::<u8>()
183        } else if self.metadata.get_depth() == BitDepth::Sixteen {
184            self.frames_ref()
185                .iter()
186                .map(|z| z.u16_to_native_endian(colorspace))
187                .collect()
188        } else {
189            todo!("Unimplemented")
190        }
191    }
192    pub fn flatten_to_u8(&self) -> Vec<Vec<u8>> {
193        if self.depth() == BitDepth::Eight {
194            self.flatten_frames::<u8>()
195        } else {
196            let mut im_clone = self.clone();
197            Depth::new(BitDepth::Eight).execute(&mut im_clone).unwrap();
198            im_clone.flatten_frames::<u8>()
199        }
200    }
201    #[allow(dead_code)]
202    pub(crate) fn to_u8_be(&self) -> Vec<Vec<u8>> {
203        let colorspace = self.colorspace();
204        if self.metadata.get_depth() == BitDepth::Eight {
205            self.flatten_frames::<u8>()
206        } else if self.metadata.get_depth() == BitDepth::Sixteen {
207            self.frames_ref()
208                .iter()
209                .map(|z| z.u16_to_big_endian(colorspace))
210                .collect()
211        } else {
212            todo!("Unimplemented")
213        }
214    }
215    /// Set new image dimensions
216    ///
217    /// # Warning
218    ///
219    /// This is potentially dangerous and should be used only when
220    /// the underlying channels have been modified.
221    ///
222    /// # Arguments:
223    /// - width: The new image width
224    /// - height: The new imag height.
225    ///
226    /// Modifies the image in place
227    pub fn set_dimensions(&mut self, width: usize, height: usize) {
228        self.metadata.set_dimensions(width, height);
229    }
230
231    pub(crate) fn set_colorspace(&mut self, colorspace: ColorSpace) {
232        self.metadata.set_colorspace(colorspace);
233    }
234
235    /// Create an image with a static color in it
236    ///
237    ///  # Arguments
238    /// - pixel: Value to fill the image with
239    /// - colorspace: The image colorspace
240    /// - width: Image width
241    /// - height: Image height
242    ///
243    ///  # Supported Types
244    /// - u8: BitDepth is treated as BitDepth::Eight
245    /// - u16: BitDepth is treated as BitDepth::Sixteen
246    /// - f32: BitDepth is treated as BitDepth::Float32
247    ///
248    /// # Example
249    /// - Create a 800 by 800 RGB image of type u8
250    /// ```
251    /// use zune_core::colorspace::ColorSpace;
252    /// use zune_image::image::Image;
253    /// let image = Image::fill::<u8>(212,ColorSpace::RGB,800,800);
254    /// ```
255    ///
256    pub fn fill<T>(pixel: T, colorspace: ColorSpace, width: usize, height: usize) -> Image
257    where
258        T: Copy + Clone + 'static + ZuneInts<T> + Zeroable + Pod
259    {
260        let dims = width * height;
261
262        let channels = vec![Channel::from_elm::<T>(dims, pixel); colorspace.num_components()];
263
264        
265
266        Image::new(channels, T::depth(), width, height, colorspace)
267    }
268    /// Create an image from a function
269    ///
270    /// The image width , height and colorspace need to be specified
271    ///
272    /// The function will receive two parameters, the first is the current x offset and y offset
273    /// and for each it's expected to return  an array with `MAX_CHANNELS`
274    ///
275    /// # Arguments
276    ///  - width : The width of the new image
277    ///  - height: The height of the new image
278    ///  - colorspace: The new colorspace of the image
279    ///  - func: A function which will be called for every pixel position
280    ///   the function is supposed to return pixels for that position
281    ///      - y: The position in the y-axis, starts at 0, ends at image height
282    ///      - x: The position in the x-axis, starts at 0, ends at image width
283    ///      - pixels: A mutable region where you can write pixels to. The results
284    ///         will be copied to the pixel positions at pixel x,y for image channels
285    ///     
286    /// # Limitations.
287    ///
288    /// Due to constrains imposed by the library, the response has to be an array containing
289    /// [MAX_CHANNELS], depending on the number of components the colorspace uses
290    /// some elements may be ignored.
291    ///
292    /// E.g for the following code
293    ///
294    /// ```
295    /// use zune_core::colorspace::ColorSpace;
296    /// use zune_image::image::{Image, MAX_CHANNELS};
297    ///
298    /// fn linear_gradient(y:usize,x:usize,pixels:&mut [u8;MAX_CHANNELS])
299    /// {    
300    ///     // this will create a linear band of colors from black to white and repeats
301    ///     // until the whole image is visited
302    ///     let luma = ((x+y) % 256) as u8;
303    ///     pixels[0] = luma;
304    ///      
305    /// }
306    /// let img  = Image::from_fn(30,20,ColorSpace::Luma,linear_gradient);
307    /// ```
308    /// We only set one element in our array but need to return an array with
309    /// [MAX_CHANNELS] elements
310    ///
311    /// [MAX_CHANNELS]:MAX_CHANNELS
312    pub fn from_fn<T, F>(width: usize, height: usize, colorspace: ColorSpace, func: F) -> Image
313    where
314        F: Fn(usize, usize, &mut [T; MAX_CHANNELS]),
315        T: ZuneInts<T> + Copy + Clone + 'static + Default + Debug + Zeroable + Pod
316    {
317        match colorspace.num_components() {
318            1 => Image::from_fn_inner::<_, _, 1>(width, height, func, colorspace),
319            2 => Image::from_fn_inner::<_, _, 2>(width, height, func, colorspace),
320            3 => Image::from_fn_inner::<_, _, 3>(width, height, func, colorspace),
321            4 => Image::from_fn_inner::<_, _, 4>(width, height, func, colorspace),
322            _ => unreachable!()
323        }
324    }
325
326    /// Template code to use with from_fn which engraves component number
327    /// as a constant in compile time.
328    ///
329    /// This allows further optimizations by the compiler
330    /// like removing bounds check in the inner loop
331    fn from_fn_inner<F, T, const COMPONENTS: usize>(
332        width: usize, height: usize, func: F, colorspace: ColorSpace
333    ) -> Image
334    where
335        F: Fn(usize, usize, &mut [T; MAX_CHANNELS]),
336        T: ZuneInts<T> + Copy + Clone + 'static + Default + Debug + Zeroable + Pod
337    {
338        let size = width * height * T::depth().size_of();
339
340        let mut channels = vec![Channel::new_with_length::<T>(size); COMPONENTS];
341
342        // convert the channels into mutable T's
343        //
344        // Iterate to number of components,
345        // map the channels to &mut [T], using reintepret_as_mut
346        // collect the items into a temporary vec
347        // convert that vec to a fixed size array
348        // panic if everything goes wrong
349        let channels_ref: [&mut [T]; COMPONENTS] = channels
350            .get_mut(0..COMPONENTS)
351            .unwrap()
352            .iter_mut()
353            .map(|x| x.reinterpret_as_mut().unwrap())
354            .collect::<Vec<&mut [T]>>()
355            .try_into()
356            .unwrap();
357
358        let mut pxs = [T::default(); MAX_CHANNELS];
359
360        for y in 0..height {
361            for x in 0..width {
362                (func)(y, x, &mut pxs);
363
364                let offset = y * height + x;
365
366                for i in 0..COMPONENTS {
367                    channels_ref[i][offset] = pxs[i];
368                }
369            }
370        }
371
372        Image::new(channels, T::depth(), width, height, colorspace)
373    }
374}
375
376/// Pixel constructors
377impl Image {
378    /// Create a new image from a raw pixels
379    ///
380    /// The image depth is treated as [BitDepth::U8](zune_core::bit_depth::BitDepth::Eight)
381    /// and formats which pack images into lower bit-depths are expected to expand them before
382    /// using this function
383    ///
384    /// Pixels are expected to be interleaved according to the colorspace
385    /// I.e if the image is RGB, pixel layout should be `[R,G,B,R,G,B]`
386    /// if it's Luma with alpha, pixel layout should be `[L,A,L,A]`
387    ///
388    /// # Returns
389    /// An [`Image`](crate::image::Image) struct
390    ///
391    /// # Panics
392    /// - In case calculating image dimensions overflows a [`usize`]
393    /// this indicates that the array cannot be indexed by usize,hence values are invalid
394    ///
395    /// - If the length of pixels doesn't match the expected length
396    pub fn from_u8(pixels: &[u8], width: usize, height: usize, colorspace: ColorSpace) -> Image {
397        let expected_len = checked_mul(width, height, 1, colorspace.num_components());
398
399        assert_eq!(
400            pixels.len(),
401            expected_len,
402            "Length mismatch, expected {expected_len} but found {} ",
403            pixels.len()
404        );
405
406        let pixels = deinterleave_u8(pixels, colorspace).unwrap();
407
408        Image::new(pixels, BitDepth::Eight, width, height, colorspace)
409    }
410    /// Create an image from raw u16 pixels
411    ///
412    /// Pixels are expected to be interleaved according to number of components in the colorspace
413    ///
414    /// e.g if image is RGBA, pixels should be in the form of `[R,G,B,A,R,G,B,A]`
415    ///
416    ///The bit depth will be treated as [BitDepth::Sixteen](zune_core::bit_depth::BitDepth::Sixteen)
417    ///
418    /// # Returns
419    /// An [`Image`](crate::image::Image) struct
420    ///
421    ///
422    /// # Panics
423    /// - If calculating image dimensions will overflow [`usize`]
424    ///
425    ///
426    /// - If pixels length is not equal to expected length
427    pub fn from_u16(pixels: &[u16], width: usize, height: usize, colorspace: ColorSpace) -> Image {
428        let expected_len = checked_mul(width, height, 1, colorspace.num_components());
429
430        assert_eq!(
431            pixels.len(),
432            expected_len,
433            "Length mismatch, expected {expected_len} but found {} ",
434            pixels.len()
435        );
436
437        let pixels = deinterleave_u16(pixels, colorspace).unwrap();
438
439        Image::new(pixels, BitDepth::Sixteen, width, height, colorspace)
440    }
441
442    /// Create an image from raw f32 pixels
443    ///
444    /// Pixels are expected to be interleaved according to number of components in the colorspace
445    ///
446    /// e.g if image is RGBA, pixels should be in the form of `[R,G,B,A,R,G,B,A]`
447    ///
448    ///The bit depth will be treated as [BitDepth::Float32](zune_core::bit_depth::BitDepth::Float32)
449    ///
450    /// # Returns
451    /// An [`Image`](crate::image::Image) struct
452    ///
453    ///
454    /// # Panics
455    /// - If calculating image dimensions will overflow [`usize`]
456    ///
457    /// - If pixels length is not equal to expected length
458    pub fn from_f32(pixels: &[f32], width: usize, height: usize, colorspace: ColorSpace) -> Image {
459        let expected_len = checked_mul(width, height, 1, colorspace.num_components());
460        assert_eq!(
461            pixels.len(),
462            expected_len,
463            "Length mismatch, expected {expected_len} but found {} ",
464            pixels.len()
465        );
466
467        let pixels = deinterleave_f32(pixels, colorspace).unwrap();
468
469        Image::new(pixels, BitDepth::Float32, width, height, colorspace)
470    }
471    pub fn frames_len(&self) -> usize {
472        self.frames.len()
473    }
474}
475
476/// Pixel manipulation methods
477impl Image {
478    /// Modify pixels in place using function `func`
479    ///
480    /// This iterates through all frames in the channel and calls
481    /// a function on each mutable pixel
482    ///
483    /// # Arguments
484    /// - func: Function which will modify the pixels
485    ///     The arguments used are
486    ///     - `y: usize`, the current position of the height we are currently in
487    ///     - `x: usize`, the current position on the x axis we are in
488    ///     - `[&mut T;MAX_CHANNELS]`, the pixels at `[y,x]` from the channels which
489    ///        can be modified.
490    ///        Even though it returns `MAX_CHANNELS`, only the image colorspace components
491    ///        considered, so for Luma colorspace, we only use the first element in the array and the rest are
492    ///         ignored
493    ///
494    /// # Returns
495    ///  - Ok(()): Successful manipulation of image
496    ///  - Err(ChannelErrors):  The channel could not be converted to type `T`
497    ///
498    /// # Example
499    /// Modify pixels creating a gradient
500    ///
501    /// ```
502    /// use zune_core::colorspace::ColorSpace;
503    /// use zune_image::image::Image;
504    /// // fill image with black pixel
505    /// let mut image = Image::fill(0_u8,ColorSpace::RGB,800,800);
506    ///
507    /// // then modify the pixels
508    /// // create a gradient
509    /// image.modify_pixels_mut(|x,y,pix|     
510    /// {
511    ///     let r = (0.3 * x as f32) as u8;
512    ///     let b = (0.3 * y as f32) as u8;
513    ///     // modify channels directly
514    ///     *pix[0] = r;
515    ///     *pix[2] = b;
516    /// }).unwrap();
517    ///
518    /// ```
519    pub fn modify_pixels_mut<T, F>(&mut self, func: F) -> Result<(), ChannelErrors>
520    where
521        T: ZuneInts<T> + Default + Copy + 'static + Pod,
522        F: Fn(usize, usize, [&mut T; MAX_CHANNELS])
523    {
524        let colorspace = self.colorspace();
525
526        let (width, height) = self.dimensions();
527
528        for frame in self.frames.iter_mut() {
529            let mut pixel_muts: Vec<&mut [T]> = vec![];
530
531            // convert all channels to type T
532            for channel in frame.channels_mut(colorspace, false) {
533                pixel_muts.push(channel.reinterpret_as_mut()?)
534            }
535            for y in 0..height {
536                for x in 0..width {
537                    let position = y * height + x;
538
539                    // This must be kept in sync with
540                    // MAX_CHANNELS, we can't do it another way
541                    // since they are references
542                    let mut output: [&mut T; MAX_CHANNELS] = [
543                        &mut T::default(),
544                        &mut T::default(),
545                        &mut T::default(),
546                        &mut T::default()
547                    ];
548                    // push pixels from channel to temporary output
549                    for (i, j) in (pixel_muts.iter_mut()).zip(output.iter_mut()) {
550                        *j = &mut i[position]
551                    }
552
553                    (func)(y, x, output);
554                }
555            }
556        }
557        Ok(())
558    }
559}
560
561/// Image conversion routines
562impl Image {
563    /// Convert an image from one colorspace to another
564    ///
565    ///  # Arguments
566    /// - to: The colorspace to convert image into
567    ///
568    pub fn convert_color(&mut self, to: ColorSpace) -> Result<(), ImageErrors> {
569        ColorspaceConv::new(to).execute(self)
570    }
571    /// Convert an image from one depth to another
572    ///
573    /// # Arguments
574    /// - to: The bit-depth to convert the image into
575    pub fn convert_depth(&mut self, to: BitDepth) -> Result<(), ImageErrors> {
576        Depth::new(to).execute(self)
577    }
578}
579
580pub(crate) fn checked_mul(
581    width: usize, height: usize, depth: usize, colorspace_components: usize
582) -> usize {
583    width
584        .checked_mul(height)
585        .unwrap()
586        .checked_mul(depth)
587        .unwrap()
588        .checked_mul(colorspace_components)
589        .unwrap()
590}