jpegli/
types.rs

1//! Core types for jpegli.
2
3use crate::consts::DCT_BLOCK_SIZE;
4
5/// Color space representation.
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
7#[non_exhaustive]
8#[repr(u8)]
9pub enum ColorSpace {
10    /// Unknown or unspecified color space
11    #[default]
12    Unknown = 0,
13    /// Grayscale (single channel)
14    Grayscale = 1,
15    /// RGB color space
16    Rgb = 2,
17    /// YCbCr color space (typical JPEG)
18    YCbCr = 3,
19    /// CMYK color space
20    Cmyk = 4,
21    /// YCCK color space (CMYK encoded as YCbCr + K)
22    Ycck = 5,
23    /// XYB color space (jpegli's perceptual color space)
24    Xyb = 6,
25}
26
27impl ColorSpace {
28    /// Returns the number of components for this color space.
29    #[must_use]
30    pub const fn num_components(self) -> usize {
31        match self {
32            Self::Unknown => 0,
33            Self::Grayscale => 1,
34            Self::Rgb | Self::YCbCr | Self::Xyb => 3,
35            Self::Cmyk | Self::Ycck => 4,
36        }
37    }
38
39    /// Returns true if this color space uses chroma subsampling by default.
40    #[must_use]
41    pub const fn default_subsampling(self) -> bool {
42        matches!(self, Self::YCbCr | Self::Ycck)
43    }
44}
45
46/// Pixel format for input/output data.
47#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
48#[non_exhaustive]
49pub enum PixelFormat {
50    /// Grayscale, 1 byte per pixel
51    Gray,
52    /// RGB, 3 bytes per pixel
53    #[default]
54    Rgb,
55    /// RGBA, 4 bytes per pixel (alpha is ignored for encoding)
56    Rgba,
57    /// BGR, 3 bytes per pixel
58    Bgr,
59    /// BGRA, 4 bytes per pixel
60    Bgra,
61    /// CMYK, 4 bytes per pixel
62    Cmyk,
63}
64
65impl PixelFormat {
66    /// Returns the number of bytes per pixel.
67    #[must_use]
68    pub const fn bytes_per_pixel(self) -> usize {
69        match self {
70            Self::Gray => 1,
71            Self::Rgb | Self::Bgr => 3,
72            Self::Rgba | Self::Bgra | Self::Cmyk => 4,
73        }
74    }
75
76    /// Returns the number of color channels (excluding alpha).
77    #[must_use]
78    pub const fn num_channels(self) -> usize {
79        match self {
80            Self::Gray => 1,
81            Self::Rgb | Self::Bgr | Self::Rgba | Self::Bgra => 3,
82            Self::Cmyk => 4,
83        }
84    }
85
86    /// Returns the corresponding color space.
87    #[must_use]
88    pub const fn color_space(self) -> ColorSpace {
89        match self {
90            Self::Gray => ColorSpace::Grayscale,
91            Self::Rgb | Self::Rgba | Self::Bgr | Self::Bgra => ColorSpace::Rgb,
92            Self::Cmyk => ColorSpace::Cmyk,
93        }
94    }
95}
96
97/// Sample bit depth.
98#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
99#[non_exhaustive]
100pub enum SampleDepth {
101    /// 8-bit samples (0-255)
102    #[default]
103    Bits8,
104    /// 16-bit samples (0-65535)
105    Bits16,
106    /// 32-bit floating point samples (0.0-1.0)
107    Float32,
108}
109
110impl SampleDepth {
111    /// Returns the number of bytes per sample.
112    #[must_use]
113    pub const fn bytes_per_sample(self) -> usize {
114        match self {
115            Self::Bits8 => 1,
116            Self::Bits16 => 2,
117            Self::Float32 => 4,
118        }
119    }
120}
121
122/// Output data type for decoder.
123///
124/// Controls the precision and format of decoded pixel data.
125/// jpegli uses float internally for 12-bit precision, so Float32 output
126/// preserves the full internal precision without conversion loss.
127#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
128#[non_exhaustive]
129pub enum OutputDataType {
130    /// 8-bit unsigned integer (0-255), standard JPEG output
131    #[default]
132    Uint8,
133    /// 16-bit unsigned integer (0-65535), scaled from internal precision
134    Uint16,
135    /// 32-bit float (0.0-1.0), preserves full internal precision
136    Float32,
137}
138
139/// Chroma subsampling mode.
140#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
141#[non_exhaustive]
142pub enum Subsampling {
143    /// 4:4:4 - No subsampling
144    #[default]
145    S444,
146    /// 4:2:2 - Horizontal subsampling only
147    S422,
148    /// 4:2:0 - Both horizontal and vertical subsampling
149    S420,
150    /// 4:4:0 - Vertical subsampling only (rare)
151    S440,
152}
153
154impl Subsampling {
155    /// Returns the horizontal sampling factor for luma.
156    #[must_use]
157    pub const fn h_samp_factor_luma(self) -> u8 {
158        match self {
159            Self::S444 | Self::S440 => 1,
160            Self::S422 | Self::S420 => 2,
161        }
162    }
163
164    /// Returns the vertical sampling factor for luma.
165    #[must_use]
166    pub const fn v_samp_factor_luma(self) -> u8 {
167        match self {
168            Self::S444 | Self::S422 => 1,
169            Self::S420 | Self::S440 => 2,
170        }
171    }
172}
173
174/// JPEG encoding mode.
175#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
176#[non_exhaustive]
177pub enum JpegMode {
178    /// Baseline sequential DCT (most compatible)
179    #[default]
180    Baseline,
181    /// Extended sequential DCT (12-bit precision)
182    Extended,
183    /// Progressive DCT (multiple scans)
184    Progressive,
185    /// Lossless (not implemented)
186    Lossless,
187}
188
189/// A single component in a JPEG image.
190#[derive(Debug, Clone)]
191pub struct Component {
192    /// Component ID (1-255, typically 1=Y, 2=Cb, 3=Cr)
193    pub id: u8,
194    /// Horizontal sampling factor (1-4)
195    pub h_samp_factor: u8,
196    /// Vertical sampling factor (1-4)
197    pub v_samp_factor: u8,
198    /// Quantization table index (0-3)
199    pub quant_table_idx: u8,
200    /// DC Huffman table index (0-1)
201    pub dc_huffman_idx: u8,
202    /// AC Huffman table index (0-1)
203    pub ac_huffman_idx: u8,
204}
205
206impl Default for Component {
207    fn default() -> Self {
208        Self {
209            id: 0,
210            h_samp_factor: 1,
211            v_samp_factor: 1,
212            quant_table_idx: 0,
213            dc_huffman_idx: 0,
214            ac_huffman_idx: 0,
215        }
216    }
217}
218
219/// A quantization table.
220#[derive(Debug, Clone)]
221pub struct QuantTable {
222    /// Quantization values in zigzag order (1-255 for baseline, 1-65535 for extended)
223    pub values: [u16; DCT_BLOCK_SIZE],
224    /// Precision: 0 = 8-bit, 1 = 16-bit
225    pub precision: u8,
226}
227
228impl Default for QuantTable {
229    fn default() -> Self {
230        Self {
231            values: [16; DCT_BLOCK_SIZE], // Default to flat table
232            precision: 0,
233        }
234    }
235}
236
237impl QuantTable {
238    /// Creates a new quantization table from values in natural (row-major) order.
239    #[must_use]
240    pub fn from_natural_order(values: &[u16; DCT_BLOCK_SIZE]) -> Self {
241        let mut zigzag = [0u16; DCT_BLOCK_SIZE];
242        for (i, &v) in values.iter().enumerate() {
243            let zi = crate::consts::JPEG_ZIGZAG_ORDER[i] as usize;
244            zigzag[zi] = v;
245        }
246        Self {
247            values: zigzag,
248            precision: if values.iter().any(|&v| v > 255) {
249                1
250            } else {
251                0
252            },
253        }
254    }
255
256    /// Returns values in natural (row-major) order.
257    #[must_use]
258    pub fn to_natural_order(&self) -> [u16; DCT_BLOCK_SIZE] {
259        let mut natural = [0u16; DCT_BLOCK_SIZE];
260        for (i, &zi) in crate::consts::JPEG_NATURAL_ORDER[..DCT_BLOCK_SIZE]
261            .iter()
262            .enumerate()
263        {
264            natural[zi as usize] = self.values[i];
265        }
266        natural
267    }
268}
269
270/// A Huffman table.
271#[derive(Debug, Clone)]
272pub struct HuffmanTable {
273    /// Number of codes of each length (1-16 bits)
274    pub bits: [u8; 16],
275    /// Symbol values (up to 256)
276    pub values: Vec<u8>,
277    /// True if this is a DC table, false for AC
278    pub is_dc: bool,
279}
280
281impl Default for HuffmanTable {
282    fn default() -> Self {
283        Self {
284            bits: [0; 16],
285            values: Vec::new(),
286            is_dc: true,
287        }
288    }
289}
290
291/// DCT coefficient type (after quantization).
292pub type Coeff = i16;
293
294/// A single 8x8 block of DCT coefficients.
295pub type CoeffBlock = [Coeff; DCT_BLOCK_SIZE];
296
297/// Image dimensions.
298#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
299pub struct Dimensions {
300    /// Width in pixels
301    pub width: u32,
302    /// Height in pixels
303    pub height: u32,
304}
305
306impl Dimensions {
307    /// Creates new dimensions.
308    #[must_use]
309    pub const fn new(width: u32, height: u32) -> Self {
310        Self { width, height }
311    }
312
313    /// Returns the number of 8x8 blocks horizontally.
314    #[must_use]
315    pub const fn width_in_blocks(self) -> u32 {
316        (self.width + 7) / 8
317    }
318
319    /// Returns the number of 8x8 blocks vertically.
320    #[must_use]
321    pub const fn height_in_blocks(self) -> u32 {
322        (self.height + 7) / 8
323    }
324
325    /// Returns the total number of pixels.
326    #[must_use]
327    pub const fn num_pixels(self) -> u64 {
328        self.width as u64 * self.height as u64
329    }
330}
331
332/// Scan parameters for progressive JPEG.
333#[derive(Debug, Clone, Copy, PartialEq, Eq)]
334pub struct ScanSpec {
335    /// First component index in this scan
336    pub comp_start: u8,
337    /// Number of components in this scan
338    pub num_comps: u8,
339    /// Spectral selection start (0-63)
340    pub ss: u8,
341    /// Spectral selection end (0-63)
342    pub se: u8,
343    /// Successive approximation high bit
344    pub ah: u8,
345    /// Successive approximation low bit
346    pub al: u8,
347}
348
349impl Default for ScanSpec {
350    fn default() -> Self {
351        Self {
352            comp_start: 0,
353            num_comps: 3,
354            ss: 0,
355            se: 63,
356            ah: 0,
357            al: 0,
358        }
359    }
360}
361
362/// Restart interval (number of MCUs between restart markers).
363#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
364pub struct RestartInterval(pub u16);
365
366#[cfg(test)]
367mod tests {
368    use super::*;
369
370    #[test]
371    fn test_color_space_components() {
372        assert_eq!(ColorSpace::Grayscale.num_components(), 1);
373        assert_eq!(ColorSpace::Rgb.num_components(), 3);
374        assert_eq!(ColorSpace::YCbCr.num_components(), 3);
375        assert_eq!(ColorSpace::Cmyk.num_components(), 4);
376    }
377
378    #[test]
379    fn test_pixel_format_bytes() {
380        assert_eq!(PixelFormat::Gray.bytes_per_pixel(), 1);
381        assert_eq!(PixelFormat::Rgb.bytes_per_pixel(), 3);
382        assert_eq!(PixelFormat::Rgba.bytes_per_pixel(), 4);
383    }
384
385    #[test]
386    fn test_quant_table_order_conversion() {
387        let natural = [
388            1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
389            25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46,
390            47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64,
391        ];
392        let table = QuantTable::from_natural_order(&natural);
393        let recovered = table.to_natural_order();
394        assert_eq!(natural, recovered);
395    }
396}