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}