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}