Skip to main content

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