Skip to main content

jxl_encoder/modular/
channel.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//! Modular channel and image types.
6//!
7//! In modular mode, each channel stores signed 32-bit integers representing
8//! either raw pixel values or prediction residuals.
9
10use crate::error::{Error, Result};
11
12/// A single channel in a modular image.
13///
14/// Channels store i32 values, which can represent:
15/// - Raw pixel values (0-255 for 8-bit, 0-65535 for 16-bit)
16/// - Prediction residuals (can be negative)
17/// - Transformed values (after RCT, Squeeze, etc.)
18#[derive(Debug, Clone)]
19pub struct Channel {
20    /// Pixel data stored in row-major order.
21    data: Vec<i32>,
22    /// Channel width.
23    width: usize,
24    /// Channel height.
25    height: usize,
26    /// Horizontal subsampling shift (0 = no subsampling).
27    pub hshift: u32,
28    /// Vertical subsampling shift (0 = no subsampling).
29    pub vshift: u32,
30}
31
32impl Channel {
33    /// Creates a new channel filled with zeros.
34    pub fn new(width: usize, height: usize) -> Result<Self> {
35        if width == 0 || height == 0 {
36            return Err(Error::InvalidImageDimensions(width, height));
37        }
38
39        let size = width
40            .checked_mul(height)
41            .ok_or(Error::InvalidImageDimensions(width, height))?;
42
43        let mut data = Vec::new();
44        data.try_reserve_exact(size)?;
45        data.resize(size, 0);
46
47        Ok(Self {
48            data,
49            width,
50            height,
51            hshift: 0,
52            vshift: 0,
53        })
54    }
55
56    /// Creates a channel from existing data.
57    pub fn from_vec(data: Vec<i32>, width: usize, height: usize) -> Result<Self> {
58        if width == 0 || height == 0 {
59            return Err(Error::InvalidImageDimensions(width, height));
60        }
61        if data.len() != width * height {
62            return Err(Error::InvalidImageDimensions(width, height));
63        }
64        Ok(Self {
65            data,
66            width,
67            height,
68            hshift: 0,
69            vshift: 0,
70        })
71    }
72
73    /// Returns the width of the channel.
74    #[inline]
75    pub fn width(&self) -> usize {
76        self.width
77    }
78
79    /// Returns the height of the channel.
80    #[inline]
81    pub fn height(&self) -> usize {
82        self.height
83    }
84
85    /// Returns the total number of pixels.
86    #[inline]
87    pub fn len(&self) -> usize {
88        self.data.len()
89    }
90
91    /// Returns true if the channel has no pixels.
92    #[inline]
93    pub fn is_empty(&self) -> bool {
94        self.data.is_empty()
95    }
96
97    /// Returns a reference to the pixel at (x, y).
98    #[inline]
99    pub fn get(&self, x: usize, y: usize) -> i32 {
100        debug_assert!(x < self.width && y < self.height);
101        self.data[y * self.width + x]
102    }
103
104    /// Sets the pixel at (x, y).
105    #[inline]
106    pub fn set(&mut self, x: usize, y: usize, value: i32) {
107        debug_assert!(x < self.width && y < self.height);
108        self.data[y * self.width + x] = value;
109    }
110
111    /// Returns a reference to a row.
112    #[inline]
113    pub fn row(&self, y: usize) -> &[i32] {
114        debug_assert!(y < self.height);
115        let start = y * self.width;
116        &self.data[start..start + self.width]
117    }
118
119    /// Returns a mutable reference to a row.
120    #[inline]
121    pub fn row_mut(&mut self, y: usize) -> &mut [i32] {
122        debug_assert!(y < self.height);
123        let start = y * self.width;
124        &mut self.data[start..start + self.width]
125    }
126
127    /// Returns a reference to the underlying data.
128    #[inline]
129    pub fn data(&self) -> &[i32] {
130        &self.data
131    }
132
133    /// Returns a mutable reference to the underlying data.
134    #[inline]
135    pub fn data_mut(&mut self) -> &mut [i32] {
136        &mut self.data
137    }
138
139    /// Gets a pixel with boundary handling (returns 0 outside bounds).
140    #[inline]
141    pub fn get_clamped(&self, x: isize, y: isize) -> i32 {
142        if x < 0 || y < 0 || x >= self.width as isize || y >= self.height as isize {
143            0
144        } else {
145            self.data[y as usize * self.width + x as usize]
146        }
147    }
148
149    /// Gets a pixel, clamping coordinates to valid range.
150    #[inline]
151    pub fn get_clamped_to_edge(&self, x: isize, y: isize) -> i32 {
152        let x = x.clamp(0, self.width as isize - 1) as usize;
153        let y = y.clamp(0, self.height as isize - 1) as usize;
154        self.data[y * self.width + x]
155    }
156}
157
158/// A modular image consisting of multiple channels.
159///
160/// For RGB images, this typically contains 3 channels.
161/// For RGBA, 4 channels (with alpha stored last).
162#[derive(Debug, Clone)]
163pub struct ModularImage {
164    /// The channels in this image.
165    pub channels: Vec<Channel>,
166    /// Bit depth of the original image.
167    pub bit_depth: u32,
168    /// Whether the image is originally grayscale.
169    pub is_grayscale: bool,
170    /// Whether the image has alpha.
171    pub has_alpha: bool,
172}
173
174impl ModularImage {
175    /// Creates a new modular image from 8-bit RGB data.
176    pub fn from_rgb8(data: &[u8], width: usize, height: usize) -> Result<Self> {
177        if data.len() != width * height * 3 {
178            return Err(Error::InvalidImageDimensions(width, height));
179        }
180
181        let mut channels = Vec::with_capacity(3);
182        for c in 0..3 {
183            let mut channel = Channel::new(width, height)?;
184            for y in 0..height {
185                for x in 0..width {
186                    let idx = (y * width + x) * 3 + c;
187                    channel.set(x, y, data[idx] as i32);
188                }
189            }
190            channels.push(channel);
191        }
192
193        Ok(Self {
194            channels,
195            bit_depth: 8,
196            is_grayscale: false,
197            has_alpha: false,
198        })
199    }
200
201    /// Creates a new modular image from 8-bit RGBA data.
202    pub fn from_rgba8(data: &[u8], width: usize, height: usize) -> Result<Self> {
203        if data.len() != width * height * 4 {
204            return Err(Error::InvalidImageDimensions(width, height));
205        }
206
207        let mut channels = Vec::with_capacity(4);
208        for c in 0..4 {
209            let mut channel = Channel::new(width, height)?;
210            for y in 0..height {
211                for x in 0..width {
212                    let idx = (y * width + x) * 4 + c;
213                    channel.set(x, y, data[idx] as i32);
214                }
215            }
216            channels.push(channel);
217        }
218
219        Ok(Self {
220            channels,
221            bit_depth: 8,
222            is_grayscale: false,
223            has_alpha: true,
224        })
225    }
226
227    /// Creates a new modular image from 8-bit grayscale data.
228    pub fn from_gray8(data: &[u8], width: usize, height: usize) -> Result<Self> {
229        if data.len() != width * height {
230            return Err(Error::InvalidImageDimensions(width, height));
231        }
232
233        let mut channel = Channel::new(width, height)?;
234        for (i, &val) in data.iter().enumerate() {
235            let x = i % width;
236            let y = i / width;
237            channel.set(x, y, val as i32);
238        }
239
240        Ok(Self {
241            channels: vec![channel],
242            bit_depth: 8,
243            is_grayscale: true,
244            has_alpha: false,
245        })
246    }
247
248    /// Creates a new modular image from 16-bit RGB data (big-endian).
249    pub fn from_rgb16(data: &[u8], width: usize, height: usize) -> Result<Self> {
250        if data.len() != width * height * 6 {
251            return Err(Error::InvalidImageDimensions(width, height));
252        }
253
254        let mut channels = Vec::with_capacity(3);
255        for c in 0..3 {
256            let mut channel = Channel::new(width, height)?;
257            for y in 0..height {
258                for x in 0..width {
259                    let idx = (y * width + x) * 6 + c * 2;
260                    let val = u16::from_be_bytes([data[idx], data[idx + 1]]);
261                    channel.set(x, y, val as i32);
262                }
263            }
264            channels.push(channel);
265        }
266
267        Ok(Self {
268            channels,
269            bit_depth: 16,
270            is_grayscale: false,
271            has_alpha: false,
272        })
273    }
274
275    /// Creates a new modular image from native-endian 16-bit RGB data.
276    ///
277    /// Input is a byte slice interpreted as `&[u16]` in native endian order
278    /// (6 bytes per pixel: R_lo, R_hi, G_lo, G_hi, B_lo, B_hi on little-endian).
279    pub fn from_rgb16_native(data: &[u8], width: usize, height: usize) -> Result<Self> {
280        if data.len() != width * height * 6 {
281            return Err(Error::InvalidImageDimensions(width, height));
282        }
283        let pixels: &[u16] = bytemuck::cast_slice(data);
284        let mut channels = Vec::with_capacity(3);
285        for c in 0..3 {
286            let mut channel = Channel::new(width, height)?;
287            for y in 0..height {
288                for x in 0..width {
289                    let idx = (y * width + x) * 3 + c;
290                    channel.set(x, y, pixels[idx] as i32);
291                }
292            }
293            channels.push(channel);
294        }
295        Ok(Self {
296            channels,
297            bit_depth: 16,
298            is_grayscale: false,
299            has_alpha: false,
300        })
301    }
302
303    /// Creates a new modular image from native-endian 16-bit RGBA data.
304    ///
305    /// Input is 8 bytes per pixel (R, G, B, A as native-endian u16).
306    pub fn from_rgba16_native(data: &[u8], width: usize, height: usize) -> Result<Self> {
307        if data.len() != width * height * 8 {
308            return Err(Error::InvalidImageDimensions(width, height));
309        }
310        let pixels: &[u16] = bytemuck::cast_slice(data);
311        let mut channels = Vec::with_capacity(4);
312        for c in 0..4 {
313            let mut channel = Channel::new(width, height)?;
314            for y in 0..height {
315                for x in 0..width {
316                    let idx = (y * width + x) * 4 + c;
317                    channel.set(x, y, pixels[idx] as i32);
318                }
319            }
320            channels.push(channel);
321        }
322        Ok(Self {
323            channels,
324            bit_depth: 16,
325            is_grayscale: false,
326            has_alpha: true,
327        })
328    }
329
330    /// Creates a new modular image from native-endian 16-bit grayscale data.
331    ///
332    /// Input is 2 bytes per pixel (native-endian u16).
333    pub fn from_gray16_native(data: &[u8], width: usize, height: usize) -> Result<Self> {
334        if data.len() != width * height * 2 {
335            return Err(Error::InvalidImageDimensions(width, height));
336        }
337        let pixels: &[u16] = bytemuck::cast_slice(data);
338        let mut channel = Channel::new(width, height)?;
339        for (i, &val) in pixels.iter().enumerate() {
340            let x = i % width;
341            let y = i / width;
342            channel.set(x, y, val as i32);
343        }
344        Ok(Self {
345            channels: vec![channel],
346            bit_depth: 16,
347            is_grayscale: true,
348            has_alpha: false,
349        })
350    }
351
352    /// Returns the width of the image.
353    pub fn width(&self) -> usize {
354        self.channels.first().map_or(0, |c| c.width())
355    }
356
357    /// Returns the height of the image.
358    pub fn height(&self) -> usize {
359        self.channels.first().map_or(0, |c| c.height())
360    }
361
362    /// Returns the number of channels.
363    pub fn num_channels(&self) -> usize {
364        self.channels.len()
365    }
366
367    /// Returns a reference to a channel.
368    pub fn channel(&self, idx: usize) -> &Channel {
369        &self.channels[idx]
370    }
371
372    /// Returns a mutable reference to a channel.
373    pub fn channel_mut(&mut self, idx: usize) -> &mut Channel {
374        &mut self.channels[idx]
375    }
376
377    /// Extracts a rectangular region from the image.
378    ///
379    /// Creates a new ModularImage containing only the pixels within the
380    /// specified bounds. Used for multi-group encoding.
381    pub fn extract_region(
382        &self,
383        x_start: usize,
384        y_start: usize,
385        x_end: usize,
386        y_end: usize,
387    ) -> Result<Self> {
388        let region_width = x_end.saturating_sub(x_start);
389        let region_height = y_end.saturating_sub(y_start);
390
391        if region_width == 0 || region_height == 0 {
392            return Err(Error::InvalidImageDimensions(region_width, region_height));
393        }
394
395        let mut channels = Vec::with_capacity(self.channels.len());
396        for src_channel in &self.channels {
397            let mut dst_channel = Channel::new(region_width, region_height)?;
398
399            for dy in 0..region_height {
400                let sy = y_start + dy;
401                if sy >= src_channel.height() {
402                    continue;
403                }
404
405                for dx in 0..region_width {
406                    let sx = x_start + dx;
407                    if sx >= src_channel.width() {
408                        continue;
409                    }
410
411                    dst_channel.set(dx, dy, src_channel.get(sx, sy));
412                }
413            }
414
415            channels.push(dst_channel);
416        }
417
418        Ok(Self {
419            channels,
420            bit_depth: self.bit_depth,
421            is_grayscale: self.is_grayscale,
422            has_alpha: self.has_alpha,
423        })
424    }
425}
426
427#[cfg(test)]
428mod tests {
429    use super::*;
430
431    #[test]
432    fn test_channel_creation() {
433        let channel = Channel::new(100, 100).unwrap();
434        assert_eq!(channel.width(), 100);
435        assert_eq!(channel.height(), 100);
436        assert_eq!(channel.len(), 10000);
437    }
438
439    #[test]
440    fn test_channel_access() {
441        let mut channel = Channel::new(10, 10).unwrap();
442        channel.set(5, 5, 42);
443        assert_eq!(channel.get(5, 5), 42);
444    }
445
446    #[test]
447    fn test_channel_clamped() {
448        let mut channel = Channel::new(10, 10).unwrap();
449        channel.set(0, 0, 100);
450        assert_eq!(channel.get_clamped(-1, -1), 0);
451        assert_eq!(channel.get_clamped(0, 0), 100);
452        assert_eq!(channel.get_clamped(100, 100), 0);
453    }
454
455    #[test]
456    fn test_modular_image_rgb8() {
457        let data = vec![
458            255, 0, 0, // Red pixel
459            0, 255, 0, // Green pixel
460            0, 0, 255, // Blue pixel
461            255, 255, 0, // Yellow pixel
462        ];
463        let img = ModularImage::from_rgb8(&data, 2, 2).unwrap();
464
465        assert_eq!(img.num_channels(), 3);
466        assert_eq!(img.width(), 2);
467        assert_eq!(img.height(), 2);
468
469        // Check R channel
470        assert_eq!(img.channel(0).get(0, 0), 255);
471        assert_eq!(img.channel(0).get(1, 0), 0);
472
473        // Check G channel
474        assert_eq!(img.channel(1).get(0, 0), 0);
475        assert_eq!(img.channel(1).get(1, 0), 255);
476
477        // Check B channel
478        assert_eq!(img.channel(2).get(0, 0), 0);
479        assert_eq!(img.channel(2).get(0, 1), 255);
480    }
481}