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    /// Original color component index (-1 = unset).
31    /// Used to look up per-component quantization tables in lossy modular encoding.
32    /// Set by LfFrame: Y=0, X=1, B-Y=2. Propagated through Squeeze transforms.
33    pub component: i32,
34}
35
36impl Channel {
37    /// Creates a new channel filled with zeros.
38    pub fn new(width: usize, height: usize) -> Result<Self> {
39        if width == 0 || height == 0 {
40            return Err(Error::InvalidImageDimensions(width, height));
41        }
42
43        let size = width
44            .checked_mul(height)
45            .ok_or(Error::InvalidImageDimensions(width, height))?;
46
47        let mut data = Vec::new();
48        data.try_reserve_exact(size)?;
49        data.resize(size, 0);
50
51        Ok(Self {
52            data,
53            width,
54            height,
55            hshift: 0,
56            vshift: 0,
57            component: -1,
58        })
59    }
60
61    /// Creates a channel from existing data.
62    pub fn from_vec(data: Vec<i32>, width: usize, height: usize) -> Result<Self> {
63        if width == 0 || height == 0 {
64            return Err(Error::InvalidImageDimensions(width, height));
65        }
66        if data.len() != width * height {
67            return Err(Error::InvalidImageDimensions(width, height));
68        }
69        Ok(Self {
70            data,
71            width,
72            height,
73            hshift: 0,
74            vshift: 0,
75            component: -1,
76        })
77    }
78
79    /// Returns the width of the channel.
80    #[inline]
81    pub fn width(&self) -> usize {
82        self.width
83    }
84
85    /// Returns the height of the channel.
86    #[inline]
87    pub fn height(&self) -> usize {
88        self.height
89    }
90
91    /// Returns the total number of pixels.
92    #[inline]
93    pub fn len(&self) -> usize {
94        self.data.len()
95    }
96
97    /// Returns true if the channel has no pixels.
98    #[inline]
99    pub fn is_empty(&self) -> bool {
100        self.data.is_empty()
101    }
102
103    /// Returns a reference to the pixel at (x, y).
104    #[inline]
105    pub fn get(&self, x: usize, y: usize) -> i32 {
106        debug_assert!(x < self.width && y < self.height);
107        self.data[y * self.width + x]
108    }
109
110    /// Sets the pixel at (x, y).
111    #[inline]
112    pub fn set(&mut self, x: usize, y: usize, value: i32) {
113        debug_assert!(x < self.width && y < self.height);
114        self.data[y * self.width + x] = value;
115    }
116
117    /// Returns a reference to a row.
118    #[inline]
119    pub fn row(&self, y: usize) -> &[i32] {
120        debug_assert!(y < self.height);
121        let start = y * self.width;
122        &self.data[start..start + self.width]
123    }
124
125    /// Returns a mutable reference to a row.
126    #[inline]
127    pub fn row_mut(&mut self, y: usize) -> &mut [i32] {
128        debug_assert!(y < self.height);
129        let start = y * self.width;
130        &mut self.data[start..start + self.width]
131    }
132
133    /// Returns a reference to the underlying data.
134    #[inline]
135    pub fn data(&self) -> &[i32] {
136        &self.data
137    }
138
139    /// Returns a mutable reference to the underlying data.
140    #[inline]
141    pub fn data_mut(&mut self) -> &mut [i32] {
142        &mut self.data
143    }
144
145    /// Gets a pixel with boundary handling (returns 0 outside bounds).
146    #[inline]
147    pub fn get_clamped(&self, x: isize, y: isize) -> i32 {
148        if x < 0 || y < 0 || x >= self.width as isize || y >= self.height as isize {
149            0
150        } else {
151            self.data[y as usize * self.width + x as usize]
152        }
153    }
154
155    /// Extracts a region from this channel, accounting for hshift/vshift.
156    ///
157    /// The rect is specified in full-resolution image coordinates. It is
158    /// downshifted by `hshift`/`vshift` and clamped to channel bounds.
159    /// Returns `None` if the shifted region has zero area.
160    pub fn extract_shifted_region(
161        &self,
162        rect_x0: usize,
163        rect_y0: usize,
164        rect_xsize: usize,
165        rect_ysize: usize,
166    ) -> Option<Channel> {
167        let x0 = rect_x0 >> self.hshift;
168        let y0 = rect_y0 >> self.vshift;
169        let xsize = (rect_xsize >> self.hshift).min(self.width.saturating_sub(x0));
170        let ysize = (rect_ysize >> self.vshift).min(self.height.saturating_sub(y0));
171
172        if xsize == 0 || ysize == 0 {
173            return None;
174        }
175
176        let mut data = Vec::with_capacity(xsize * ysize);
177        for y in 0..ysize {
178            for x in 0..xsize {
179                data.push(self.get(x0 + x, y0 + y));
180            }
181        }
182
183        let mut ch = Channel::from_vec(data, xsize, ysize).ok()?;
184        ch.hshift = self.hshift;
185        ch.vshift = self.vshift;
186        ch.component = self.component;
187        Some(ch)
188    }
189
190    /// Extracts a grid cell region matching the decoder's get_grid_rect logic.
191    ///
192    /// Given a group position (gx, gy) and group_dim (the image-level group size,
193    /// typically 256), computes the channel-space sub-region for this group.
194    ///
195    /// This matches jxl-rs `ModularChannel::get_grid_rect`:
196    ///   grid_dim = (group_dim >> hshift, group_dim >> vshift)
197    ///   bx = gx * grid_dim.0, by = gy * grid_dim.1
198    ///   size = (min(chan_w - bx, grid_dim.0), min(chan_h - by, grid_dim.1))
199    ///
200    /// Returns None if the sub-region has zero area.
201    pub fn extract_grid_cell(&self, gx: usize, gy: usize, group_dim: usize) -> Option<Channel> {
202        let grid_w = group_dim >> self.hshift;
203        let grid_h = group_dim >> self.vshift;
204
205        if grid_w == 0 || grid_h == 0 {
206            return None;
207        }
208
209        let bx = gx * grid_w;
210        let by = gy * grid_h;
211
212        if bx >= self.width || by >= self.height {
213            return None;
214        }
215
216        let xsize = (self.width - bx).min(grid_w);
217        let ysize = (self.height - by).min(grid_h);
218
219        if xsize == 0 || ysize == 0 {
220            return None;
221        }
222
223        let mut data = Vec::with_capacity(xsize * ysize);
224        for y in 0..ysize {
225            for x in 0..xsize {
226                data.push(self.get(bx + x, by + y));
227            }
228        }
229
230        let mut ch = Channel::from_vec(data, xsize, ysize).ok()?;
231        ch.hshift = self.hshift;
232        ch.vshift = self.vshift;
233        ch.component = self.component;
234        Some(ch)
235    }
236
237    /// Gets a pixel, clamping coordinates to valid range.
238    #[inline]
239    pub fn get_clamped_to_edge(&self, x: isize, y: isize) -> i32 {
240        let x = x.clamp(0, self.width as isize - 1) as usize;
241        let y = y.clamp(0, self.height as isize - 1) as usize;
242        self.data[y * self.width + x]
243    }
244}
245
246/// A modular image consisting of multiple channels.
247///
248/// For RGB images, this typically contains 3 channels.
249/// For RGBA, 4 channels (with alpha stored last).
250#[derive(Debug, Clone)]
251pub struct ModularImage {
252    /// The channels in this image.
253    pub channels: Vec<Channel>,
254    /// Bit depth of the original image.
255    pub bit_depth: u32,
256    /// Whether the image is originally grayscale.
257    pub is_grayscale: bool,
258    /// Whether the image has alpha.
259    pub has_alpha: bool,
260}
261
262impl ModularImage {
263    /// Creates a new modular image from 8-bit RGB data.
264    pub fn from_rgb8(data: &[u8], width: usize, height: usize) -> Result<Self> {
265        if data.len() != width * height * 3 {
266            return Err(Error::InvalidImageDimensions(width, height));
267        }
268
269        let mut channels = Vec::with_capacity(3);
270        for c in 0..3 {
271            let mut channel = Channel::new(width, height)?;
272            for y in 0..height {
273                for x in 0..width {
274                    let idx = (y * width + x) * 3 + c;
275                    channel.set(x, y, data[idx] as i32);
276                }
277            }
278            channels.push(channel);
279        }
280
281        Ok(Self {
282            channels,
283            bit_depth: 8,
284            is_grayscale: false,
285            has_alpha: false,
286        })
287    }
288
289    /// Creates a new modular image from 8-bit RGBA data.
290    pub fn from_rgba8(data: &[u8], width: usize, height: usize) -> Result<Self> {
291        if data.len() != width * height * 4 {
292            return Err(Error::InvalidImageDimensions(width, height));
293        }
294
295        let mut channels = Vec::with_capacity(4);
296        for c in 0..4 {
297            let mut channel = Channel::new(width, height)?;
298            for y in 0..height {
299                for x in 0..width {
300                    let idx = (y * width + x) * 4 + c;
301                    channel.set(x, y, data[idx] as i32);
302                }
303            }
304            channels.push(channel);
305        }
306
307        Ok(Self {
308            channels,
309            bit_depth: 8,
310            is_grayscale: false,
311            has_alpha: true,
312        })
313    }
314
315    /// Creates a new modular image from 8-bit grayscale data.
316    pub fn from_gray8(data: &[u8], width: usize, height: usize) -> Result<Self> {
317        if data.len() != width * height {
318            return Err(Error::InvalidImageDimensions(width, height));
319        }
320
321        let mut channel = Channel::new(width, height)?;
322        for (i, &val) in data.iter().enumerate() {
323            let x = i % width;
324            let y = i / width;
325            channel.set(x, y, val as i32);
326        }
327
328        Ok(Self {
329            channels: vec![channel],
330            bit_depth: 8,
331            is_grayscale: true,
332            has_alpha: false,
333        })
334    }
335
336    /// Creates a new modular image from 16-bit RGB data (big-endian).
337    pub fn from_rgb16(data: &[u8], width: usize, height: usize) -> Result<Self> {
338        if data.len() != width * height * 6 {
339            return Err(Error::InvalidImageDimensions(width, height));
340        }
341
342        let mut channels = Vec::with_capacity(3);
343        for c in 0..3 {
344            let mut channel = Channel::new(width, height)?;
345            for y in 0..height {
346                for x in 0..width {
347                    let idx = (y * width + x) * 6 + c * 2;
348                    let val = u16::from_be_bytes([data[idx], data[idx + 1]]);
349                    channel.set(x, y, val as i32);
350                }
351            }
352            channels.push(channel);
353        }
354
355        Ok(Self {
356            channels,
357            bit_depth: 16,
358            is_grayscale: false,
359            has_alpha: false,
360        })
361    }
362
363    /// Creates a new modular image from native-endian 16-bit RGB data.
364    ///
365    /// Input is a byte slice interpreted as `&[u16]` in native endian order
366    /// (6 bytes per pixel: R_lo, R_hi, G_lo, G_hi, B_lo, B_hi on little-endian).
367    pub fn from_rgb16_native(data: &[u8], width: usize, height: usize) -> Result<Self> {
368        if data.len() != width * height * 6 {
369            return Err(Error::InvalidImageDimensions(width, height));
370        }
371        let pixels: &[u16] = bytemuck::cast_slice(data);
372        let mut channels = Vec::with_capacity(3);
373        for c in 0..3 {
374            let mut channel = Channel::new(width, height)?;
375            for y in 0..height {
376                for x in 0..width {
377                    let idx = (y * width + x) * 3 + c;
378                    channel.set(x, y, pixels[idx] as i32);
379                }
380            }
381            channels.push(channel);
382        }
383        Ok(Self {
384            channels,
385            bit_depth: 16,
386            is_grayscale: false,
387            has_alpha: false,
388        })
389    }
390
391    /// Creates a new modular image from native-endian 16-bit RGBA data.
392    ///
393    /// Input is 8 bytes per pixel (R, G, B, A as native-endian u16).
394    pub fn from_rgba16_native(data: &[u8], width: usize, height: usize) -> Result<Self> {
395        if data.len() != width * height * 8 {
396            return Err(Error::InvalidImageDimensions(width, height));
397        }
398        let pixels: &[u16] = bytemuck::cast_slice(data);
399        let mut channels = Vec::with_capacity(4);
400        for c in 0..4 {
401            let mut channel = Channel::new(width, height)?;
402            for y in 0..height {
403                for x in 0..width {
404                    let idx = (y * width + x) * 4 + c;
405                    channel.set(x, y, pixels[idx] as i32);
406                }
407            }
408            channels.push(channel);
409        }
410        Ok(Self {
411            channels,
412            bit_depth: 16,
413            is_grayscale: false,
414            has_alpha: true,
415        })
416    }
417
418    /// Creates a new modular image from 8-bit grayscale + alpha data (2 bytes per pixel).
419    pub fn from_grayalpha8(data: &[u8], width: usize, height: usize) -> Result<Self> {
420        if data.len() != width * height * 2 {
421            return Err(Error::InvalidImageDimensions(width, height));
422        }
423        let mut gray = Channel::new(width, height)?;
424        let mut alpha = Channel::new(width, height)?;
425        for y in 0..height {
426            for x in 0..width {
427                let idx = (y * width + x) * 2;
428                gray.set(x, y, data[idx] as i32);
429                alpha.set(x, y, data[idx + 1] as i32);
430            }
431        }
432        Ok(Self {
433            channels: vec![gray, alpha],
434            bit_depth: 8,
435            is_grayscale: true,
436            has_alpha: true,
437        })
438    }
439
440    /// Creates a new modular image from native-endian 16-bit grayscale data.
441    ///
442    /// Input is 2 bytes per pixel (native-endian u16).
443    pub fn from_gray16_native(data: &[u8], width: usize, height: usize) -> Result<Self> {
444        if data.len() != width * height * 2 {
445            return Err(Error::InvalidImageDimensions(width, height));
446        }
447        let pixels: &[u16] = bytemuck::cast_slice(data);
448        let mut channel = Channel::new(width, height)?;
449        for (i, &val) in pixels.iter().enumerate() {
450            let x = i % width;
451            let y = i / width;
452            channel.set(x, y, val as i32);
453        }
454        Ok(Self {
455            channels: vec![channel],
456            bit_depth: 16,
457            is_grayscale: true,
458            has_alpha: false,
459        })
460    }
461
462    /// Creates a new modular image from native-endian 16-bit grayscale + alpha data.
463    ///
464    /// Input is 4 bytes per pixel (native-endian u16 gray, u16 alpha).
465    pub fn from_grayalpha16_native(data: &[u8], width: usize, height: usize) -> Result<Self> {
466        if data.len() != width * height * 4 {
467            return Err(Error::InvalidImageDimensions(width, height));
468        }
469        let pixels: &[u16] = bytemuck::cast_slice(data);
470        let mut gray = Channel::new(width, height)?;
471        let mut alpha = Channel::new(width, height)?;
472        for y in 0..height {
473            for x in 0..width {
474                let idx = (y * width + x) * 2;
475                gray.set(x, y, pixels[idx] as i32);
476                alpha.set(x, y, pixels[idx + 1] as i32);
477            }
478        }
479        Ok(Self {
480            channels: vec![gray, alpha],
481            bit_depth: 16,
482            is_grayscale: true,
483            has_alpha: true,
484        })
485    }
486
487    /// Returns the width of the image.
488    pub fn width(&self) -> usize {
489        self.channels.first().map_or(0, |c| c.width())
490    }
491
492    /// Returns the height of the image.
493    pub fn height(&self) -> usize {
494        self.channels.first().map_or(0, |c| c.height())
495    }
496
497    /// Returns the number of channels.
498    pub fn num_channels(&self) -> usize {
499        self.channels.len()
500    }
501
502    /// Returns a reference to a channel.
503    pub fn channel(&self, idx: usize) -> &Channel {
504        &self.channels[idx]
505    }
506
507    /// Returns a mutable reference to a channel.
508    pub fn channel_mut(&mut self, idx: usize) -> &mut Channel {
509        &mut self.channels[idx]
510    }
511
512    /// Extracts a rectangular region from the image.
513    ///
514    /// Creates a new ModularImage containing only the pixels within the
515    /// specified bounds. Used for multi-group encoding.
516    pub fn extract_region(
517        &self,
518        x_start: usize,
519        y_start: usize,
520        x_end: usize,
521        y_end: usize,
522    ) -> Result<Self> {
523        let region_width = x_end.saturating_sub(x_start);
524        let region_height = y_end.saturating_sub(y_start);
525
526        if region_width == 0 || region_height == 0 {
527            return Err(Error::InvalidImageDimensions(region_width, region_height));
528        }
529
530        let mut channels = Vec::with_capacity(self.channels.len());
531        for src_channel in &self.channels {
532            let mut dst_channel = Channel::new(region_width, region_height)?;
533
534            for dy in 0..region_height {
535                let sy = y_start + dy;
536                if sy >= src_channel.height() {
537                    continue;
538                }
539
540                for dx in 0..region_width {
541                    let sx = x_start + dx;
542                    if sx >= src_channel.width() {
543                        continue;
544                    }
545
546                    dst_channel.set(dx, dy, src_channel.get(sx, sy));
547                }
548            }
549
550            channels.push(dst_channel);
551        }
552
553        Ok(Self {
554            channels,
555            bit_depth: self.bit_depth,
556            is_grayscale: self.is_grayscale,
557            has_alpha: self.has_alpha,
558        })
559    }
560}
561
562#[cfg(test)]
563mod tests {
564    use super::*;
565
566    #[test]
567    fn test_channel_creation() {
568        let channel = Channel::new(100, 100).unwrap();
569        assert_eq!(channel.width(), 100);
570        assert_eq!(channel.height(), 100);
571        assert_eq!(channel.len(), 10000);
572    }
573
574    #[test]
575    fn test_channel_access() {
576        let mut channel = Channel::new(10, 10).unwrap();
577        channel.set(5, 5, 42);
578        assert_eq!(channel.get(5, 5), 42);
579    }
580
581    #[test]
582    fn test_channel_clamped() {
583        let mut channel = Channel::new(10, 10).unwrap();
584        channel.set(0, 0, 100);
585        assert_eq!(channel.get_clamped(-1, -1), 0);
586        assert_eq!(channel.get_clamped(0, 0), 100);
587        assert_eq!(channel.get_clamped(100, 100), 0);
588    }
589
590    #[test]
591    fn test_modular_image_rgb8() {
592        let data = vec![
593            255, 0, 0, // Red pixel
594            0, 255, 0, // Green pixel
595            0, 0, 255, // Blue pixel
596            255, 255, 0, // Yellow pixel
597        ];
598        let img = ModularImage::from_rgb8(&data, 2, 2).unwrap();
599
600        assert_eq!(img.num_channels(), 3);
601        assert_eq!(img.width(), 2);
602        assert_eq!(img.height(), 2);
603
604        // Check R channel
605        assert_eq!(img.channel(0).get(0, 0), 255);
606        assert_eq!(img.channel(0).get(1, 0), 0);
607
608        // Check G channel
609        assert_eq!(img.channel(1).get(0, 0), 0);
610        assert_eq!(img.channel(1).get(1, 0), 255);
611
612        // Check B channel
613        assert_eq!(img.channel(2).get(0, 0), 0);
614        assert_eq!(img.channel(2).get(0, 1), 255);
615    }
616}