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/// Chroma subsampling mode.
123#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
124#[non_exhaustive]
125pub enum Subsampling {
126    /// 4:4:4 - No subsampling
127    #[default]
128    S444,
129    /// 4:2:2 - Horizontal subsampling only
130    S422,
131    /// 4:2:0 - Both horizontal and vertical subsampling
132    S420,
133    /// 4:4:0 - Vertical subsampling only (rare)
134    S440,
135}
136
137impl Subsampling {
138    /// Returns the horizontal sampling factor for luma.
139    #[must_use]
140    pub const fn h_samp_factor_luma(self) -> u8 {
141        match self {
142            Self::S444 | Self::S440 => 1,
143            Self::S422 | Self::S420 => 2,
144        }
145    }
146
147    /// Returns the vertical sampling factor for luma.
148    #[must_use]
149    pub const fn v_samp_factor_luma(self) -> u8 {
150        match self {
151            Self::S444 | Self::S422 => 1,
152            Self::S420 | Self::S440 => 2,
153        }
154    }
155}
156
157/// JPEG encoding mode.
158#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
159#[non_exhaustive]
160pub enum JpegMode {
161    /// Baseline sequential DCT (most compatible)
162    #[default]
163    Baseline,
164    /// Extended sequential DCT (12-bit precision)
165    Extended,
166    /// Progressive DCT (multiple scans)
167    Progressive,
168    /// Lossless (not implemented)
169    Lossless,
170}
171
172/// A single component in a JPEG image.
173#[derive(Debug, Clone)]
174pub struct Component {
175    /// Component ID (1-255, typically 1=Y, 2=Cb, 3=Cr)
176    pub id: u8,
177    /// Horizontal sampling factor (1-4)
178    pub h_samp_factor: u8,
179    /// Vertical sampling factor (1-4)
180    pub v_samp_factor: u8,
181    /// Quantization table index (0-3)
182    pub quant_table_idx: u8,
183    /// DC Huffman table index (0-1)
184    pub dc_huffman_idx: u8,
185    /// AC Huffman table index (0-1)
186    pub ac_huffman_idx: u8,
187}
188
189impl Default for Component {
190    fn default() -> Self {
191        Self {
192            id: 0,
193            h_samp_factor: 1,
194            v_samp_factor: 1,
195            quant_table_idx: 0,
196            dc_huffman_idx: 0,
197            ac_huffman_idx: 0,
198        }
199    }
200}
201
202/// A quantization table.
203#[derive(Debug, Clone)]
204pub struct QuantTable {
205    /// Quantization values in zigzag order (1-255 for baseline, 1-65535 for extended)
206    pub values: [u16; DCT_BLOCK_SIZE],
207    /// Precision: 0 = 8-bit, 1 = 16-bit
208    pub precision: u8,
209}
210
211impl Default for QuantTable {
212    fn default() -> Self {
213        Self {
214            values: [16; DCT_BLOCK_SIZE], // Default to flat table
215            precision: 0,
216        }
217    }
218}
219
220impl QuantTable {
221    /// Creates a new quantization table from values in natural (row-major) order.
222    #[must_use]
223    pub fn from_natural_order(values: &[u16; DCT_BLOCK_SIZE]) -> Self {
224        let mut zigzag = [0u16; DCT_BLOCK_SIZE];
225        for (i, &v) in values.iter().enumerate() {
226            let zi = crate::consts::JPEG_ZIGZAG_ORDER[i] as usize;
227            zigzag[zi] = v;
228        }
229        Self {
230            values: zigzag,
231            precision: if values.iter().any(|&v| v > 255) {
232                1
233            } else {
234                0
235            },
236        }
237    }
238
239    /// Returns values in natural (row-major) order.
240    #[must_use]
241    pub fn to_natural_order(&self) -> [u16; DCT_BLOCK_SIZE] {
242        let mut natural = [0u16; DCT_BLOCK_SIZE];
243        for (i, &zi) in crate::consts::JPEG_NATURAL_ORDER[..DCT_BLOCK_SIZE]
244            .iter()
245            .enumerate()
246        {
247            natural[zi as usize] = self.values[i];
248        }
249        natural
250    }
251}
252
253/// A Huffman table.
254#[derive(Debug, Clone)]
255pub struct HuffmanTable {
256    /// Number of codes of each length (1-16 bits)
257    pub bits: [u8; 16],
258    /// Symbol values (up to 256)
259    pub values: Vec<u8>,
260    /// True if this is a DC table, false for AC
261    pub is_dc: bool,
262}
263
264impl Default for HuffmanTable {
265    fn default() -> Self {
266        Self {
267            bits: [0; 16],
268            values: Vec::new(),
269            is_dc: true,
270        }
271    }
272}
273
274/// DCT coefficient type (after quantization).
275pub type Coeff = i16;
276
277/// A single 8x8 block of DCT coefficients.
278pub type CoeffBlock = [Coeff; DCT_BLOCK_SIZE];
279
280/// Image dimensions.
281#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
282pub struct Dimensions {
283    /// Width in pixels
284    pub width: u32,
285    /// Height in pixels
286    pub height: u32,
287}
288
289impl Dimensions {
290    /// Creates new dimensions.
291    #[must_use]
292    pub const fn new(width: u32, height: u32) -> Self {
293        Self { width, height }
294    }
295
296    /// Returns the number of 8x8 blocks horizontally.
297    #[must_use]
298    pub const fn width_in_blocks(self) -> u32 {
299        (self.width + 7) / 8
300    }
301
302    /// Returns the number of 8x8 blocks vertically.
303    #[must_use]
304    pub const fn height_in_blocks(self) -> u32 {
305        (self.height + 7) / 8
306    }
307
308    /// Returns the total number of pixels.
309    #[must_use]
310    pub const fn num_pixels(self) -> u64 {
311        self.width as u64 * self.height as u64
312    }
313}
314
315/// Scan parameters for progressive JPEG.
316#[derive(Debug, Clone, Copy, PartialEq, Eq)]
317pub struct ScanSpec {
318    /// First component index in this scan
319    pub comp_start: u8,
320    /// Number of components in this scan
321    pub num_comps: u8,
322    /// Spectral selection start (0-63)
323    pub ss: u8,
324    /// Spectral selection end (0-63)
325    pub se: u8,
326    /// Successive approximation high bit
327    pub ah: u8,
328    /// Successive approximation low bit
329    pub al: u8,
330}
331
332impl Default for ScanSpec {
333    fn default() -> Self {
334        Self {
335            comp_start: 0,
336            num_comps: 3,
337            ss: 0,
338            se: 63,
339            ah: 0,
340            al: 0,
341        }
342    }
343}
344
345/// Restart interval (number of MCUs between restart markers).
346#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
347pub struct RestartInterval(pub u16);
348
349#[cfg(test)]
350mod tests {
351    use super::*;
352
353    #[test]
354    fn test_color_space_components() {
355        assert_eq!(ColorSpace::Grayscale.num_components(), 1);
356        assert_eq!(ColorSpace::Rgb.num_components(), 3);
357        assert_eq!(ColorSpace::YCbCr.num_components(), 3);
358        assert_eq!(ColorSpace::Cmyk.num_components(), 4);
359    }
360
361    #[test]
362    fn test_pixel_format_bytes() {
363        assert_eq!(PixelFormat::Gray.bytes_per_pixel(), 1);
364        assert_eq!(PixelFormat::Rgb.bytes_per_pixel(), 3);
365        assert_eq!(PixelFormat::Rgba.bytes_per_pixel(), 4);
366    }
367
368    #[test]
369    fn test_quant_table_order_conversion() {
370        let natural = [
371            1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
372            25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46,
373            47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64,
374        ];
375        let table = QuantTable::from_natural_order(&natural);
376        let recovered = table.to_natural_order();
377        assert_eq!(natural, recovered);
378    }
379}