Skip to main content

jxl_encoder/headers/
extra_channels.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//! Extra channel definitions for JPEG XL.
6
7use crate::bit_writer::BitWriter;
8use crate::error::Result;
9
10use super::file_header::BitDepth;
11
12/// Type of extra channel.
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
14#[repr(u8)]
15pub enum ExtraChannelType {
16    /// Alpha (transparency) channel.
17    #[default]
18    Alpha = 0,
19    /// Depth map.
20    Depth = 1,
21    /// Spot color.
22    SpotColor = 2,
23    /// Selection mask.
24    SelectionMask = 3,
25    /// Black channel (for CMYK).
26    Black = 4,
27    /// CFA (Color Filter Array) channel.
28    Cfa = 5,
29    /// Thermal channel.
30    Thermal = 6,
31    /// Reserved for future use.
32    Reserved0 = 7,
33    Reserved1 = 8,
34    Reserved2 = 9,
35    Reserved3 = 10,
36    Reserved4 = 11,
37    Reserved5 = 12,
38    Reserved6 = 13,
39    Reserved7 = 14,
40    /// Optional extra channel.
41    Optional = 15,
42}
43
44/// Information about an extra channel.
45#[derive(Debug, Clone)]
46pub struct ExtraChannelInfo {
47    /// Type of extra channel.
48    pub ec_type: ExtraChannelType,
49    /// Bit depth of this channel.
50    pub bit_depth: BitDepth,
51    /// Dimension shift (log2 of downsampling factor).
52    pub dim_shift: u32,
53    /// Name of the channel (optional).
54    pub name: String,
55    /// Whether alpha is premultiplied.
56    pub alpha_associated: bool,
57    /// Spot color values (for SpotColor type).
58    pub spot_color: [f32; 4],
59    /// CFA index (for CFA type).
60    pub cfa_channel: u32,
61}
62
63impl Default for ExtraChannelInfo {
64    fn default() -> Self {
65        Self {
66            ec_type: ExtraChannelType::Alpha,
67            bit_depth: BitDepth::default(),
68            dim_shift: 0,
69            name: String::new(),
70            alpha_associated: false,
71            spot_color: [0.0; 4],
72            cfa_channel: 0,
73        }
74    }
75}
76
77impl ExtraChannelInfo {
78    /// Creates an alpha channel with default settings.
79    pub fn alpha() -> Self {
80        Self {
81            ec_type: ExtraChannelType::Alpha,
82            ..Default::default()
83        }
84    }
85
86    /// Creates a depth channel.
87    pub fn depth() -> Self {
88        Self {
89            ec_type: ExtraChannelType::Depth,
90            ..Default::default()
91        }
92    }
93
94    /// Creates a spot color channel.
95    pub fn spot_color(color: [f32; 4]) -> Self {
96        Self {
97            ec_type: ExtraChannelType::SpotColor,
98            spot_color: color,
99            ..Default::default()
100        }
101    }
102
103    /// Writes the extra channel info to the bitstream.
104    pub fn write(&self, writer: &mut BitWriter) -> Result<()> {
105        // d_alpha flag (true if this is default alpha)
106        let d_alpha = self.is_default_alpha();
107        writer.write_bit(d_alpha)?;
108
109        if d_alpha {
110            return Ok(());
111        }
112
113        // type
114        writer.write_u32_coder(self.ec_type as u32, 0, 1, 2, 3, 4)?;
115
116        // bit_depth
117        self.bit_depth.write(writer)?;
118
119        // dim_shift
120        writer.write_u32_coder(self.dim_shift, 0, 3, 4, 1, 3)?;
121
122        // name_len
123        let name_len = self.name.len() as u32;
124        writer.write_u32_coder(name_len, 0, 0, 0, 0, 10)?;
125        for byte in self.name.bytes() {
126            writer.write_u8(byte)?;
127        }
128
129        // alpha_associated (only for alpha channels)
130        if self.ec_type == ExtraChannelType::Alpha {
131            writer.write_bit(self.alpha_associated)?;
132        }
133
134        // spot_color (only for spot color channels)
135        if self.ec_type == ExtraChannelType::SpotColor {
136            for &value in &self.spot_color {
137                writer.write_u32(value.to_bits())?;
138            }
139        }
140
141        // cfa_channel (only for CFA channels)
142        if self.ec_type == ExtraChannelType::Cfa {
143            writer.write_u32_coder(self.cfa_channel, 1, 0, 2, 3, 4)?;
144        }
145
146        Ok(())
147    }
148
149    /// Returns true if this is a default alpha channel.
150    fn is_default_alpha(&self) -> bool {
151        self.ec_type == ExtraChannelType::Alpha
152            && self.bit_depth.bits_per_sample == 8
153            && !self.bit_depth.float_sample
154            && self.dim_shift == 0
155            && self.name.is_empty()
156            && !self.alpha_associated
157    }
158}
159
160#[cfg(test)]
161mod tests {
162    use super::*;
163
164    #[test]
165    fn test_default_alpha() {
166        let alpha = ExtraChannelInfo::alpha();
167        assert!(alpha.is_default_alpha());
168    }
169
170    #[test]
171    fn test_write_default_alpha() {
172        let alpha = ExtraChannelInfo::alpha();
173        let mut writer = BitWriter::new();
174        alpha.write(&mut writer).unwrap();
175        writer.zero_pad_to_byte();
176
177        // Default alpha should write just d_alpha=true (1 bit)
178        assert_eq!(writer.bits_written(), 8); // Padded
179    }
180
181    #[test]
182    fn test_non_default_alpha() {
183        let mut alpha = ExtraChannelInfo::alpha();
184        alpha.alpha_associated = true; // Makes it non-default
185        assert!(!alpha.is_default_alpha());
186
187        let mut writer = BitWriter::new();
188        alpha.write(&mut writer).unwrap();
189        // Should write d_alpha=false, type, bit_depth, dim_shift, name_len, alpha_associated
190        assert!(writer.bits_written() > 1);
191    }
192
193    #[test]
194    fn test_alpha_with_name() {
195        let mut alpha = ExtraChannelInfo::alpha();
196        alpha.name = "MyAlpha".to_string();
197        assert!(!alpha.is_default_alpha());
198
199        let mut writer = BitWriter::new();
200        alpha.write(&mut writer).unwrap();
201        // Should include name bytes
202        assert!(writer.bits_written() > 8);
203    }
204
205    #[test]
206    fn test_depth_channel() {
207        let depth = ExtraChannelInfo::depth();
208        assert_eq!(depth.ec_type, ExtraChannelType::Depth);
209
210        let mut writer = BitWriter::new();
211        depth.write(&mut writer).unwrap();
212        // Not default alpha, so writes more data
213        assert!(writer.bits_written() > 1);
214    }
215
216    #[test]
217    fn test_spot_color_channel() {
218        let spot = ExtraChannelInfo::spot_color([1.0, 0.5, 0.25, 1.0]);
219        assert_eq!(spot.ec_type, ExtraChannelType::SpotColor);
220        assert_eq!(spot.spot_color, [1.0, 0.5, 0.25, 1.0]);
221
222        let mut writer = BitWriter::new();
223        spot.write(&mut writer).unwrap();
224        // Should include spot color values (4 x 32 bits = 128 bits)
225        assert!(writer.bits_written() >= 128);
226    }
227
228    #[test]
229    fn test_cfa_channel() {
230        let cfa = ExtraChannelInfo {
231            ec_type: ExtraChannelType::Cfa,
232            cfa_channel: 2,
233            ..Default::default()
234        };
235
236        let mut writer = BitWriter::new();
237        cfa.write(&mut writer).unwrap();
238        assert!(writer.bits_written() > 1);
239    }
240
241    #[test]
242    fn test_extra_channel_types() {
243        // Test that all channel types have expected values
244        assert_eq!(ExtraChannelType::Alpha as u8, 0);
245        assert_eq!(ExtraChannelType::Depth as u8, 1);
246        assert_eq!(ExtraChannelType::SpotColor as u8, 2);
247        assert_eq!(ExtraChannelType::SelectionMask as u8, 3);
248        assert_eq!(ExtraChannelType::Black as u8, 4);
249        assert_eq!(ExtraChannelType::Cfa as u8, 5);
250        assert_eq!(ExtraChannelType::Thermal as u8, 6);
251        assert_eq!(ExtraChannelType::Optional as u8, 15);
252    }
253
254    #[test]
255    fn test_dim_shift() {
256        let mut alpha = ExtraChannelInfo::alpha();
257        alpha.dim_shift = 2; // Downsampled by 4x
258        assert!(!alpha.is_default_alpha());
259
260        let mut writer = BitWriter::new();
261        alpha.write(&mut writer).unwrap();
262        assert!(writer.bits_written() > 1);
263    }
264}