Skip to main content

jxl_encoder/image/
mod.rs

1// Copyright (c) Imazen LLC and the JPEG XL Project Authors.
2// Algorithms and constants derived from libjxl (BSD-3-Clause).
3// Licensed under AGPL-3.0-or-later. Commercial licenses at https://www.imazen.io/pricing
4
5//! Image buffer types for the JPEG XL encoder.
6
7use crate::error::{Error, Result};
8
9/// A 2D image buffer with a single channel.
10#[derive(Debug, Clone)]
11pub struct Image<T> {
12    data: Vec<T>,
13    width: usize,
14    height: usize,
15}
16
17impl<T: Clone + Default> Image<T> {
18    /// Creates a new image filled with the default value.
19    pub fn new(width: usize, height: usize) -> Result<Self> {
20        if width == 0 || height == 0 {
21            return Err(Error::InvalidImageDimensions(width, height));
22        }
23
24        let size = width
25            .checked_mul(height)
26            .ok_or(Error::InvalidImageDimensions(width, height))?;
27
28        let mut data = Vec::new();
29        data.try_reserve_exact(size)?;
30        data.resize(size, T::default());
31
32        Ok(Self {
33            data,
34            width,
35            height,
36        })
37    }
38
39    /// Creates a new image from existing data.
40    pub fn from_vec(data: Vec<T>, width: usize, height: usize) -> Result<Self> {
41        if width == 0 || height == 0 {
42            return Err(Error::InvalidImageDimensions(width, height));
43        }
44        if data.len() != width * height {
45            return Err(Error::InvalidImageDimensions(width, height));
46        }
47        Ok(Self {
48            data,
49            width,
50            height,
51        })
52    }
53}
54
55impl<T> Image<T> {
56    /// Returns the width of the image.
57    #[inline]
58    pub fn width(&self) -> usize {
59        self.width
60    }
61
62    /// Returns the height of the image.
63    #[inline]
64    pub fn height(&self) -> usize {
65        self.height
66    }
67
68    /// Returns the total number of pixels.
69    #[inline]
70    pub fn len(&self) -> usize {
71        self.data.len()
72    }
73
74    /// Returns true if the image has no pixels.
75    #[inline]
76    pub fn is_empty(&self) -> bool {
77        self.data.is_empty()
78    }
79
80    /// Returns a reference to the pixel at (x, y).
81    #[inline]
82    pub fn get(&self, x: usize, y: usize) -> &T {
83        debug_assert!(x < self.width && y < self.height);
84        &self.data[y * self.width + x]
85    }
86
87    /// Returns a mutable reference to the pixel at (x, y).
88    #[inline]
89    pub fn get_mut(&mut self, x: usize, y: usize) -> &mut T {
90        debug_assert!(x < self.width && y < self.height);
91        &mut self.data[y * self.width + x]
92    }
93
94    /// Returns a reference to a row.
95    #[inline]
96    pub fn row(&self, y: usize) -> &[T] {
97        debug_assert!(y < self.height);
98        let start = y * self.width;
99        &self.data[start..start + self.width]
100    }
101
102    /// Returns a mutable reference to a row.
103    #[inline]
104    pub fn row_mut(&mut self, y: usize) -> &mut [T] {
105        debug_assert!(y < self.height);
106        let start = y * self.width;
107        &mut self.data[start..start + self.width]
108    }
109
110    /// Returns a reference to the underlying data.
111    #[inline]
112    pub fn data(&self) -> &[T] {
113        &self.data
114    }
115
116    /// Returns a mutable reference to the underlying data.
117    #[inline]
118    pub fn data_mut(&mut self) -> &mut [T] {
119        &mut self.data
120    }
121
122    /// Consumes the image and returns the underlying data.
123    #[inline]
124    pub fn into_vec(self) -> Vec<T> {
125        self.data
126    }
127}
128
129/// A multi-channel image (e.g., RGB, RGBA).
130#[derive(Debug, Clone)]
131pub struct ImageBundle<T> {
132    /// Individual channel images.
133    pub channels: Vec<Image<T>>,
134    /// Image width.
135    pub width: usize,
136    /// Image height.
137    pub height: usize,
138}
139
140impl<T: Clone + Default> ImageBundle<T> {
141    /// Creates a new image bundle with the specified number of channels.
142    pub fn new(width: usize, height: usize, num_channels: usize) -> Result<Self> {
143        let mut channels = Vec::with_capacity(num_channels);
144        for _ in 0..num_channels {
145            channels.push(Image::new(width, height)?);
146        }
147        Ok(Self {
148            channels,
149            width,
150            height,
151        })
152    }
153
154    /// Returns the number of channels.
155    pub fn num_channels(&self) -> usize {
156        self.channels.len()
157    }
158
159    /// Returns a reference to a specific channel.
160    pub fn channel(&self, idx: usize) -> &Image<T> {
161        &self.channels[idx]
162    }
163
164    /// Returns a mutable reference to a specific channel.
165    pub fn channel_mut(&mut self, idx: usize) -> &mut Image<T> {
166        &mut self.channels[idx]
167    }
168}
169
170/// Pixel format for input images.
171#[derive(Debug, Clone, Copy, PartialEq, Eq)]
172pub enum PixelFormat {
173    /// Grayscale, 8-bit.
174    Gray8,
175    /// Grayscale with alpha, 8-bit.
176    GrayA8,
177    /// RGB, 8-bit per channel.
178    Rgb8,
179    /// RGBA, 8-bit per channel.
180    Rgba8,
181    /// Grayscale, 16-bit.
182    Gray16,
183    /// Grayscale with alpha, 16-bit.
184    GrayA16,
185    /// RGB, 16-bit per channel.
186    Rgb16,
187    /// RGBA, 16-bit per channel.
188    Rgba16,
189    /// RGB, 32-bit float per channel.
190    RgbF32,
191    /// RGBA, 32-bit float per channel.
192    RgbaF32,
193}
194
195impl PixelFormat {
196    /// Returns the number of channels.
197    pub fn num_channels(self) -> usize {
198        match self {
199            Self::Gray8 | Self::Gray16 => 1,
200            Self::GrayA8 | Self::GrayA16 => 2,
201            Self::Rgb8 | Self::Rgb16 | Self::RgbF32 => 3,
202            Self::Rgba8 | Self::Rgba16 | Self::RgbaF32 => 4,
203        }
204    }
205
206    /// Returns the bytes per sample.
207    pub fn bytes_per_sample(self) -> usize {
208        match self {
209            Self::Gray8 | Self::GrayA8 | Self::Rgb8 | Self::Rgba8 => 1,
210            Self::Gray16 | Self::GrayA16 | Self::Rgb16 | Self::Rgba16 => 2,
211            Self::RgbF32 | Self::RgbaF32 => 4,
212        }
213    }
214
215    /// Returns the total bytes per pixel.
216    pub fn bytes_per_pixel(self) -> usize {
217        self.num_channels() * self.bytes_per_sample()
218    }
219
220    /// Returns true if this format has an alpha channel.
221    pub fn has_alpha(self) -> bool {
222        matches!(
223            self,
224            Self::GrayA8 | Self::GrayA16 | Self::Rgba8 | Self::Rgba16 | Self::RgbaF32
225        )
226    }
227
228    /// Returns true if this is a grayscale format.
229    pub fn is_grayscale(self) -> bool {
230        matches!(
231            self,
232            Self::Gray8 | Self::Gray16 | Self::GrayA8 | Self::GrayA16
233        )
234    }
235}
236
237#[cfg(test)]
238mod tests {
239    use super::*;
240
241    #[test]
242    fn test_image_creation() {
243        let img: Image<f32> = Image::new(100, 100).unwrap();
244        assert_eq!(img.width(), 100);
245        assert_eq!(img.height(), 100);
246        assert_eq!(img.len(), 10000);
247        assert!(!img.is_empty());
248    }
249
250    #[test]
251    fn test_image_access() {
252        let mut img: Image<u8> = Image::new(10, 10).unwrap();
253        *img.get_mut(5, 5) = 42;
254        assert_eq!(*img.get(5, 5), 42);
255    }
256
257    #[test]
258    fn test_image_bundle() {
259        let bundle: ImageBundle<f32> = ImageBundle::new(100, 100, 3).unwrap();
260        assert_eq!(bundle.num_channels(), 3);
261        assert_eq!(bundle.width, 100);
262        assert_eq!(bundle.height, 100);
263    }
264
265    #[test]
266    fn test_pixel_format() {
267        assert_eq!(PixelFormat::Rgba8.num_channels(), 4);
268        assert_eq!(PixelFormat::Rgba8.bytes_per_pixel(), 4);
269        assert!(PixelFormat::Rgba8.has_alpha());
270        assert!(!PixelFormat::Rgb8.has_alpha());
271    }
272
273    #[test]
274    fn test_image_zero_width() {
275        let result: Result<Image<u8>> = Image::new(0, 10);
276        assert!(result.is_err());
277    }
278
279    #[test]
280    fn test_image_zero_height() {
281        let result: Result<Image<u8>> = Image::new(10, 0);
282        assert!(result.is_err());
283    }
284
285    #[test]
286    fn test_image_from_vec() {
287        let data = vec![1u8, 2, 3, 4, 5, 6];
288        let img = Image::from_vec(data, 3, 2).unwrap();
289        assert_eq!(img.width(), 3);
290        assert_eq!(img.height(), 2);
291        assert_eq!(*img.get(0, 0), 1);
292        assert_eq!(*img.get(2, 1), 6);
293    }
294
295    #[test]
296    fn test_image_from_vec_wrong_size() {
297        let data = vec![1u8, 2, 3, 4, 5];
298        let result = Image::from_vec(data, 3, 2);
299        assert!(result.is_err());
300    }
301
302    #[test]
303    fn test_image_from_vec_zero_dims() {
304        let data = vec![1u8, 2, 3];
305        assert!(Image::from_vec(data.clone(), 0, 3).is_err());
306        assert!(Image::from_vec(data, 3, 0).is_err());
307    }
308
309    #[test]
310    fn test_image_row_access() {
311        let data = vec![1u8, 2, 3, 4, 5, 6];
312        let img = Image::from_vec(data, 3, 2).unwrap();
313        assert_eq!(img.row(0), &[1, 2, 3]);
314        assert_eq!(img.row(1), &[4, 5, 6]);
315    }
316
317    #[test]
318    fn test_image_row_mut() {
319        let data = vec![1u8, 2, 3, 4, 5, 6];
320        let mut img = Image::from_vec(data, 3, 2).unwrap();
321        img.row_mut(0)[1] = 99;
322        assert_eq!(*img.get(1, 0), 99);
323    }
324
325    #[test]
326    fn test_image_data_access() {
327        let data = vec![1u8, 2, 3, 4];
328        let mut img = Image::from_vec(data, 2, 2).unwrap();
329        assert_eq!(img.data(), &[1, 2, 3, 4]);
330        img.data_mut()[0] = 100;
331        assert_eq!(img.data()[0], 100);
332    }
333
334    #[test]
335    fn test_image_into_vec() {
336        let data = vec![1u8, 2, 3, 4];
337        let img = Image::from_vec(data.clone(), 2, 2).unwrap();
338        let recovered = img.into_vec();
339        assert_eq!(recovered, data);
340    }
341
342    #[test]
343    fn test_image_bundle_channel_access() {
344        let mut bundle: ImageBundle<u8> = ImageBundle::new(10, 10, 3).unwrap();
345        *bundle.channel_mut(1).get_mut(5, 5) = 42;
346        assert_eq!(*bundle.channel(1).get(5, 5), 42);
347    }
348
349    #[test]
350    fn test_pixel_format_all_variants() {
351        // Test all pixel format variants
352        let formats = [
353            (PixelFormat::Gray8, 1, 1, false, true),
354            (PixelFormat::GrayA8, 2, 1, true, true),
355            (PixelFormat::Rgb8, 3, 1, false, false),
356            (PixelFormat::Rgba8, 4, 1, true, false),
357            (PixelFormat::Gray16, 1, 2, false, true),
358            (PixelFormat::GrayA16, 2, 2, true, true),
359            (PixelFormat::Rgb16, 3, 2, false, false),
360            (PixelFormat::Rgba16, 4, 2, true, false),
361            (PixelFormat::RgbF32, 3, 4, false, false),
362            (PixelFormat::RgbaF32, 4, 4, true, false),
363        ];
364
365        for (format, channels, bytes_per_sample, has_alpha, is_gray) in formats {
366            assert_eq!(format.num_channels(), channels, "{:?}", format);
367            assert_eq!(format.bytes_per_sample(), bytes_per_sample, "{:?}", format);
368            assert_eq!(
369                format.bytes_per_pixel(),
370                channels * bytes_per_sample,
371                "{:?}",
372                format
373            );
374            assert_eq!(format.has_alpha(), has_alpha, "{:?}", format);
375            assert_eq!(format.is_grayscale(), is_gray, "{:?}", format);
376        }
377    }
378}