Skip to main content

otf_pixels_codec_jpeg/
encoder.rs

1//! The JPEG encoder: baseline, or progressive on request.
2//!
3//! # Memory
4//!
5//! Encoding is streaming, at one MCU row — the same band structure the
6//! decoder uses, run the other way. Rows arrive one at a time, accumulate
7//! until a whole MCU row is available (8 or 16 of them), and are then
8//! colour-converted, transformed, quantized, entropy-coded and written
9//! straight to the sink. Nothing accumulates across bands, so peak memory is
10//! a band and not the image.
11//!
12//! That is why the standard Huffman tables are used rather than tables
13//! derived from the image's own statistics: optimal tables need a counting
14//! pass over every coefficient before the first byte can be written, which
15//! means buffering the whole image. The few percent it would save is not
16//! worth trading ADR-0005's streaming contract for.
17//!
18//! Progressive output ([`JpegEncoder::with_progressive`]) has no such
19//! choice: no scan can be written before every block is known. It keeps the
20//! image's quantized coefficients, as the progressive decoder does, and
21//! writes its scans, with tables optimized for each, in
22//! [`Encoder::finish`]; see `progressive_encode`.
23//!
24//! # What is written
25//!
26//! Baseline sequential JPEG: `SOI`, `APP0` (JFIF), `DQT`, `SOF0`, `DHT`,
27//! `SOS`, one interleaved scan, `EOI`. Progressive JPEG writes `SOF2` and
28//! then libjpeg's default ten scans (six for grey), each after its own `DHT`. No restart markers — they cost bytes
29//! and buy resynchronization after corruption, which matters for broadcast
30//! and not for a file a pipeline just produced.
31
32use crate::fdct;
33use crate::format::{ICC_CHUNK, ZIGZAG, icc_segments, marker};
34use crate::huffman::HuffmanEncoder;
35use crate::progressive_encode::Coefficients;
36use crate::tables;
37use otf_pixels_core::{
38    ChromaSubsampling, Density, EncodeOptions, Encoder, ImageDescriptor, PixelFormat, PixelsError,
39    ResolutionUnit, Result, Sink,
40};
41
42/// How much the chroma channels are subsampled relative to luma.
43///
44/// Chroma carries far less perceptible detail than luma, so discarding three
45/// quarters of it is nearly free visually and saves a substantial fraction of
46/// the bytes. It is not free for synthetic images with hard colour edges,
47/// which is why it can be turned off.
48#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
49#[non_exhaustive]
50pub enum Subsampling {
51    /// 4:4:4 — full-resolution chroma.
52    None,
53    /// 4:2:2 — chroma at half width.
54    Horizontal,
55    /// 4:2:0 — chroma at half width and half height.
56    #[default]
57    Both,
58}
59
60impl Subsampling {
61    /// The luma sampling factors this implies. Chroma is always 1x1.
62    const fn factors(self) -> (u32, u32) {
63        match self {
64            Self::None => (1, 1),
65            Self::Horizontal => (2, 1),
66            Self::Both => (2, 2),
67        }
68    }
69}
70
71/// Encodes a baseline JPEG stream.
72#[derive(Debug)]
73pub struct JpegEncoder {
74    quality: u8,
75    subsampling: Subsampling,
76    /// Write a progressive (`SOF2`) file rather than a baseline one.
77    progressive: bool,
78    /// Set by `write_header`; its presence means the header was written.
79    state: Option<State>,
80    /// The ICC profile to embed after the JFIF header, if any.
81    icc: Option<Vec<u8>>,
82    /// The EXIF block to write as `APP1`, if any.
83    exif: Option<Vec<u8>>,
84    /// The XMP packet to write as `APP1`, if any.
85    xmp: Option<Vec<u8>>,
86    /// The resolution to write in the JFIF header, if any.
87    density: Option<Density>,
88}
89
90/// Everything fixed once the descriptor is known.
91#[derive(Debug)]
92struct State {
93    descriptor: ImageDescriptor,
94    /// Whether the image is coded as one luminance component or three.
95    grayscale: bool,
96    /// Luma sampling factors; chroma is always 1x1.
97    factors: (u32, u32),
98    luma_quant: [u16; 64],
99    chroma_quant: [u16; 64],
100    luma_dc: HuffmanEncoder,
101    luma_ac: HuffmanEncoder,
102    chroma_dc: HuffmanEncoder,
103    chroma_ac: HuffmanEncoder,
104    /// Input rows accumulated for the MCU row being built.
105    band: Vec<u8>,
106    /// How many rows of `band` hold real pixels.
107    band_rows: u32,
108    /// Pixel height of one MCU row.
109    band_height: u32,
110    mcus_per_line: u32,
111    /// Padded component planes, reused across bands.
112    luma: Plane,
113    chroma_blue: Plane,
114    chroma_red: Plane,
115    /// Full-resolution Cb/Cr for the band, interleaved, before box filtering.
116    chroma_full: Vec<u8>,
117    /// Per-component DC predictor.
118    predictors: [i32; 3],
119    writer: BitWriter,
120    rows_written: u32,
121    /// For progressive output, every quantized block, kept for the scans.
122    coefficients: Option<Coefficients>,
123    /// MCU rows already transformed.
124    mcu_row: u32,
125}
126
127/// One component's padded plane for the MCU row being encoded.
128#[derive(Debug, Default)]
129struct Plane {
130    stride: usize,
131    height: usize,
132    samples: Vec<u8>,
133}
134
135impl Plane {
136    fn new(stride: usize, height: usize) -> Self {
137        Self {
138            stride,
139            height,
140            samples: vec![128; stride * height],
141        }
142    }
143
144    /// Copy the 8x8 block whose top-left corner is at `(x, y)`.
145    fn block(&self, x: usize, y: usize, out: &mut [u8; 64]) {
146        for row in 0..8 {
147            let start = (y + row) * self.stride + x;
148            let source = self
149                .samples
150                .get(start..)
151                .and_then(|rest| rest.get(..8))
152                .unwrap_or(&[128; 8]);
153            if let Some(target) = out.get_mut(row * 8..row * 8 + 8) {
154                target.copy_from_slice(source);
155            }
156        }
157    }
158}
159
160/// A chroma plane, or an empty one for a greyscale image that has no chroma.
161fn chroma_plane(grayscale: bool, stride: usize, height: u32) -> Plane {
162    if grayscale {
163        Plane::default()
164    } else {
165        Plane::new(stride, height as usize)
166    }
167}
168
169/// Accumulates entropy-coded bits, stuffing `0xFF` bytes as it goes.
170#[derive(Debug, Default)]
171pub(crate) struct BitWriter {
172    /// Completed bytes, drained to the sink after each MCU row.
173    pub(crate) bytes: Vec<u8>,
174    accumulator: u32,
175    bits: u32,
176}
177
178impl BitWriter {
179    /// Append `length` low bits of `code`, most significant first.
180    pub(crate) fn write(&mut self, code: u32, length: u32) {
181        if length == 0 || length > 32 {
182            return;
183        }
184        let mask = if length >= 32 {
185            u32::MAX
186        } else {
187            (1_u32 << length) - 1
188        };
189        self.accumulator = (self.accumulator << length.min(31)) | (code & mask);
190        self.bits += length;
191
192        while self.bits >= 8 {
193            let byte = ((self.accumulator >> (self.bits - 8)) & 0xFF) as u8;
194            self.bytes.push(byte);
195            // `0xFF` introduces a marker, so entropy data escapes a literal
196            // one as `FF 00`. Omitting this is the classic encoder bug: the
197            // file decodes correctly until the first pixel that happens to
198            // produce an `0xFF`.
199            if byte == 0xFF {
200                self.bytes.push(0x00);
201            }
202            self.bits -= 8;
203        }
204        self.accumulator &= (1_u32 << self.bits) - 1;
205    }
206
207    /// Pad to a byte boundary with one bits.
208    ///
209    /// One bits rather than zero: a trailing run of zeros could be read as
210    /// the start of a valid code, where a run of ones cannot be — no standard
211    /// table assigns an all-ones code.
212    pub(crate) fn flush(&mut self) {
213        if self.bits > 0 {
214            let padding = 8 - self.bits;
215            self.write((1 << padding) - 1, padding);
216        }
217    }
218}
219
220/// The number of bits needed to code `value`, and the bits themselves.
221///
222/// JPEG codes a coefficient as a magnitude category plus that many raw bits,
223/// where a negative value is stored as its predecessor in the category's
224/// lower half — the mirror of `receive_extend` on the decoding side.
225pub(crate) fn magnitude(value: i32) -> (u32, u32) {
226    if value == 0 {
227        return (0, 0);
228    }
229    let size = 32 - value.unsigned_abs().leading_zeros();
230    let bits = if value < 0 {
231        // `value - 1` in `size` bits: -1 becomes 0, -2 and -3 become 1 and 2.
232        (value - 1) as u32 & ((1_u32 << size) - 1)
233    } else {
234        value as u32
235    };
236    (size, bits)
237}
238
239impl JpegEncoder {
240    /// An encoder at the default quality.
241    #[must_use]
242    pub const fn new() -> Self {
243        Self {
244            quality: EncodeOptions::DEFAULT_QUALITY,
245            subsampling: Subsampling::Both,
246            progressive: false,
247            state: None,
248            icc: None,
249            exif: None,
250            xmp: None,
251            density: None,
252        }
253    }
254
255    /// An encoder at an explicit quality, 1..=100.
256    ///
257    /// Chroma subsampling follows quality, as it does in every encoder that
258    /// exposes one number: 4:4:4 from 90 up, 4:2:0 below. Above 90 the
259    /// subsampling, not the quantization, becomes the dominant loss, so
260    /// keeping it on would make the quality number stop meaning anything.
261    ///
262    /// # Errors
263    ///
264    /// Returns [`PixelsError::InvalidArgument`] unless `quality` is 1..=100.
265    pub fn with_quality(quality: u8) -> Result<Self> {
266        if !(1..=100).contains(&quality) {
267            return Err(PixelsError::invalid_argument(
268                "quality",
269                format!("must be in 1..=100, got {quality}"),
270            ));
271        }
272        Ok(Self {
273            quality,
274            subsampling: if quality >= 90 {
275                Subsampling::None
276            } else {
277                Subsampling::Both
278            },
279            progressive: false,
280            state: None,
281            icc: None,
282            exif: None,
283            xmp: None,
284            density: None,
285        })
286    }
287
288    /// Override the chroma subsampling chosen by quality.
289    #[must_use]
290    pub const fn with_subsampling(mut self, subsampling: Subsampling) -> Self {
291        self.subsampling = subsampling;
292        self
293    }
294
295    /// Write a progressive file (sharp's `progressive`): a viewer shows a
296    /// coarse picture first and sharpens it as the rest arrives, and the
297    /// per-scan tables usually make it a few percent smaller. The whole
298    /// image's coefficients are held until [`Encoder::finish`].
299    #[must_use]
300    pub const fn with_progressive(mut self, progressive: bool) -> Self {
301        self.progressive = progressive;
302        self
303    }
304
305    /// Whether this encoder writes a progressive file.
306    #[must_use]
307    pub const fn progressive(&self) -> bool {
308        self.progressive
309    }
310
311    /// An encoder configured from generic encode options: `quality`,
312    /// `progressive`, and `chroma_subsampling` in place of the one quality
313    /// implies.
314    #[must_use]
315    pub fn from_options(options: &EncodeOptions) -> Self {
316        let encoder = Self::with_quality(options.quality)
317            .unwrap_or_else(|_| Self::new())
318            .with_progressive(options.progressive);
319        match options.chroma_subsampling {
320            Some(ChromaSubsampling::Yuv444) => encoder.with_subsampling(Subsampling::None),
321            Some(ChromaSubsampling::Yuv420) => encoder.with_subsampling(Subsampling::Both),
322            _ => encoder,
323        }
324    }
325
326    /// The chosen chroma subsampling.
327    #[must_use]
328    pub const fn subsampling(&self) -> Subsampling {
329        self.subsampling
330    }
331}
332
333impl Default for JpegEncoder {
334    fn default() -> Self {
335        Self::new()
336    }
337}
338
339/// How many channels a row of `format` carries, and whether JPEG can take it.
340fn channels_of(format: PixelFormat) -> Result<usize> {
341    match format {
342        PixelFormat::Gray8 => Ok(1),
343        PixelFormat::GrayA8 => Ok(2),
344        PixelFormat::Rgb8 => Ok(3),
345        PixelFormat::Rgba8 => Ok(4),
346        // JPEG is 8-bit by definition at baseline, and has no alpha at all.
347        other => Err(PixelsError::unsupported(format!(
348            "JPEG encoding needs an 8-bit format; got {other}. Convert first."
349        ))),
350    }
351}
352
353/// Read one pixel as RGB, compositing any alpha against black.
354///
355/// Alpha is composited rather than dropped, for the reason the GIF encoder
356/// gives: JPEG has no transparency, so a translucent pixel has to become
357/// *something*, and blending against black is what a viewer shows for a
358/// flattened image.
359fn pixel_rgb(row: &[u8], index: usize, format: PixelFormat) -> [u8; 3] {
360    let channels = format.channels();
361    let at = index * channels;
362    let get = |offset: usize| u32::from(row.get(at + offset).copied().unwrap_or(0));
363    let blend = |value: u32, alpha: u32| ((value * alpha + 127) / 255) as u8;
364
365    match format {
366        PixelFormat::Gray8 => {
367            let value = get(0) as u8;
368            [value, value, value]
369        }
370        PixelFormat::GrayA8 => {
371            let value = blend(get(0), get(1));
372            [value, value, value]
373        }
374        PixelFormat::Rgba8 => {
375            let alpha = get(3);
376            [
377                blend(get(0), alpha),
378                blend(get(1), alpha),
379                blend(get(2), alpha),
380            ]
381        }
382        // Rgb8 and anything `channels_of` already rejected.
383        _ => [get(0) as u8, get(1) as u8, get(2) as u8],
384    }
385}
386
387/// Convert one RGB triple to YCbCr in fixed point.
388///
389/// The inverse of the decoder's transform, with JFIF's coefficients scaled by
390/// 2^16, and fixed point for the same reason (ADR-0011).
391fn rgb_to_ycbcr([r, g, b]: [u8; 3]) -> [u8; 3] {
392    const HALF: i32 = 1 << 15;
393    let (r, g, b) = (i32::from(r), i32::from(g), i32::from(b));
394
395    let y = (19_595 * r + 38_470 * g + 7_471 * b + HALF) >> 16;
396    let cb = ((-11_056 * r - 21_712 * g + 32_768 * b + HALF) >> 16) + 128;
397    let cr = ((32_768 * r - 27_440 * g - 5_328 * b + HALF) >> 16) + 128;
398    [
399        y.clamp(0, 255) as u8,
400        cb.clamp(0, 255) as u8,
401        cr.clamp(0, 255) as u8,
402    ]
403}
404
405impl State {
406    /// Fill the component planes from the rows accumulated in `band`.
407    ///
408    /// Padding is edge replication in both axes: the format codes whole
409    /// blocks, so an image whose width or height is not a multiple of the MCU
410    /// size has samples that must be invented. Replicating the edge invents
411    /// the ones that cost the fewest bits — a constant would put an artificial
412    /// edge inside the last block, and the DCT would spend high-frequency
413    /// coefficients describing it.
414    fn fill_planes(&mut self) {
415        let width = self.descriptor.width as usize;
416        let format = self.descriptor.pixel;
417        let row_bytes = self.descriptor.row_bytes();
418        let (h, v) = (self.factors.0 as usize, self.factors.1 as usize);
419        let stride = self.luma.stride;
420        // The last row that holds real pixels; everything below it repeats.
421        let last = (self.band_rows.max(1) - 1) as usize;
422
423        for y in 0..self.luma.height {
424            let row = self
425                .band
426                .get(y.min(last) * row_bytes..)
427                .and_then(|rest| rest.get(..row_bytes))
428                .unwrap_or(&[]);
429
430            for x in 0..stride {
431                let rgb = pixel_rgb(row, x.min(width - 1), format);
432                let ycbcr = if self.grayscale {
433                    [rgb[0], 128, 128]
434                } else {
435                    rgb_to_ycbcr(rgb)
436                };
437                if let Some(slot) = self.luma.samples.get_mut(y * stride + x) {
438                    *slot = ycbcr[0];
439                }
440                if self.grayscale {
441                    continue;
442                }
443                // Full-resolution chroma is kept only until it is averaged
444                // down, which is why it lives in scratch rather than a plane.
445                if let Some(slot) = self.chroma_full.get_mut((y * stride + x) * 2..) {
446                    if let Some(pair) = slot.get_mut(..2) {
447                        pair.copy_from_slice(&[ycbcr[1], ycbcr[2]]);
448                    }
449                }
450            }
451        }
452        if self.grayscale {
453            return;
454        }
455
456        // Box-filter chroma down to its own resolution. Averaging is what
457        // makes subsampling a resampling rather than a decimation: keeping
458        // every other sample instead would alias hard edges into the chroma.
459        let count = (h * v) as u32;
460        for cy in 0..self.chroma_blue.height {
461            for cx in 0..self.chroma_blue.stride {
462                let (mut blue, mut red) = (0_u32, 0_u32);
463                for dy in 0..v {
464                    for dx in 0..h {
465                        let y = (cy * v + dy).min(self.luma.height - 1);
466                        let x = (cx * h + dx).min(stride - 1);
467                        let at = (y * stride + x) * 2;
468                        blue += u32::from(self.chroma_full.get(at).copied().unwrap_or(128));
469                        red += u32::from(self.chroma_full.get(at + 1).copied().unwrap_or(128));
470                    }
471                }
472                let at = cy * self.chroma_blue.stride + cx;
473                if let Some(slot) = self.chroma_blue.samples.get_mut(at) {
474                    *slot = ((blue + count / 2) / count) as u8;
475                }
476                if let Some(slot) = self.chroma_red.samples.get_mut(at) {
477                    *slot = ((red + count / 2) / count) as u8;
478                }
479            }
480        }
481    }
482
483    /// Transform, quantize and entropy-code one MCU row.
484    fn encode_band(&mut self) {
485        let (h, v) = self.factors;
486        let mut samples = [0_u8; 64];
487        let mut coefficients = [0_i64; 64];
488        let mut quantized = [0_i32; 64];
489
490        let [luma_predictor, blue_predictor, red_predictor] = &mut self.predictors;
491
492        for mcu in 0..self.mcus_per_line {
493            // Luma blocks first, in raster order within the MCU: that is the
494            // interleave order the scan header declares.
495            for block_y in 0..v {
496                for block_x in 0..h {
497                    let x = ((mcu * h + block_x) * 8) as usize;
498                    let y = (block_y * 8) as usize;
499                    self.luma.block(x, y, &mut samples);
500                    fdct::block(&samples, &mut coefficients);
501                    fdct::quantize(&coefficients, &self.luma_quant, &mut quantized);
502                    if let Some(kept) = self.coefficients.as_mut() {
503                        let row = (self.mcu_row * v + block_y) as usize;
504                        kept.store(0, (mcu * h + block_x) as usize, row, &quantized);
505                        continue;
506                    }
507                    encode_block(
508                        &mut self.writer,
509                        &quantized,
510                        &self.luma_dc,
511                        &self.luma_ac,
512                        luma_predictor,
513                    );
514                }
515            }
516            if self.grayscale {
517                continue;
518            }
519
520            let x = (mcu * 8) as usize;
521            for (component, (plane, predictor)) in [
522                (&self.chroma_blue, &mut *blue_predictor),
523                (&self.chroma_red, &mut *red_predictor),
524            ]
525            .into_iter()
526            .enumerate()
527            {
528                plane.block(x, 0, &mut samples);
529                fdct::block(&samples, &mut coefficients);
530                fdct::quantize(&coefficients, &self.chroma_quant, &mut quantized);
531                if let Some(kept) = self.coefficients.as_mut() {
532                    kept.store(
533                        component + 1,
534                        mcu as usize,
535                        self.mcu_row as usize,
536                        &quantized,
537                    );
538                    continue;
539                }
540                encode_block(
541                    &mut self.writer,
542                    &quantized,
543                    &self.chroma_dc,
544                    &self.chroma_ac,
545                    predictor,
546                );
547            }
548        }
549        self.mcu_row += 1;
550    }
551}
552
553/// Entropy-code one quantized block.
554fn encode_block(
555    writer: &mut BitWriter,
556    block: &[i32; 64],
557    dc: &HuffmanEncoder,
558    ac: &HuffmanEncoder,
559    predictor: &mut i32,
560) {
561    let value = block.first().copied().unwrap_or(0);
562    // DC is coded as a difference from the previous block of the same
563    // component, because neighbouring blocks of a photograph have nearly the
564    // same average brightness.
565    let difference = value.wrapping_sub(*predictor);
566    *predictor = value;
567
568    let (size, bits) = magnitude(difference);
569    if let Some((code, length)) = dc.code(size as u8) {
570        writer.write(code, length);
571    }
572    writer.write(bits, size);
573
574    let mut run = 0_u32;
575    for index in 1..64 {
576        let value = ZIGZAG
577            .get(index)
578            .and_then(|&at| block.get(at))
579            .copied()
580            .unwrap_or(0);
581        if value == 0 {
582            run += 1;
583            continue;
584        }
585        // The run length field holds four bits, so longer runs are broken up
586        // with the zero-run-length code.
587        while run >= 16 {
588            if let Some((code, length)) = ac.code(0xF0) {
589                writer.write(code, length);
590            }
591            run -= 16;
592        }
593        let (size, bits) = magnitude(value);
594        if let Some((code, length)) = ac.code(((run as u8) << 4) | size as u8) {
595            writer.write(code, length);
596        }
597        writer.write(bits, size);
598        run = 0;
599    }
600    // A trailing run of zeros is collapsed into one end-of-block code, which
601    // is where most of JPEG's compression comes from.
602    if run > 0 {
603        if let Some((code, length)) = ac.code(0x00) {
604            writer.write(code, length);
605        }
606    }
607}
608
609/// Write a marker with no payload.
610fn write_marker(code: u8, sink: &mut dyn Sink) -> Result<()> {
611    sink.write_all(&[0xFF, code])
612}
613
614/// Write a marker segment, prefixing the length its payload implies.
615pub(crate) fn write_segment(code: u8, payload: &[u8], sink: &mut dyn Sink) -> Result<()> {
616    let Ok(length) = u16::try_from(payload.len() + 2) else {
617        return Err(PixelsError::unsupported(format!(
618            "a {code:#04x} segment of {} bytes does not fit a 16-bit length",
619            payload.len()
620        )));
621    };
622    sink.write_all(&[0xFF, code])?;
623    sink.write_all(&length.to_be_bytes())?;
624    sink.write_all(payload)
625}
626
627/// Write one quantization table, in the zigzag order `DQT` uses.
628fn write_quant_table(slot: u8, steps: &[u16; 64], payload: &mut Vec<u8>) {
629    // Precision 0 (8-bit) in the high nibble, slot in the low.
630    payload.push(slot & 0x0F);
631    for &position in &ZIGZAG {
632        payload.push(steps.get(position).copied().unwrap_or(1).clamp(1, 255) as u8);
633    }
634}
635
636/// The standard Huffman tables and the one interleaved scan's header, for
637/// baseline output.
638fn write_baseline_scan_header(grayscale: bool, sink: &mut dyn Sink) -> Result<()> {
639    let mut payload = Vec::new();
640    write_huffman_table(
641        0,
642        0,
643        &tables::LUMA_DC_COUNTS,
644        &tables::LUMA_DC_VALUES,
645        &mut payload,
646    );
647    write_huffman_table(
648        1,
649        0,
650        &tables::LUMA_AC_COUNTS,
651        &tables::LUMA_AC_VALUES,
652        &mut payload,
653    );
654    if !grayscale {
655        write_huffman_table(
656            0,
657            1,
658            &tables::CHROMA_DC_COUNTS,
659            &tables::CHROMA_DC_VALUES,
660            &mut payload,
661        );
662        write_huffman_table(
663            1,
664            1,
665            &tables::CHROMA_AC_COUNTS,
666            &tables::CHROMA_AC_VALUES,
667            &mut payload,
668        );
669    }
670    write_segment(marker::DHT, &payload, sink)?;
671
672    let mut payload = Vec::new();
673    if grayscale {
674        payload.push(1);
675        payload.extend_from_slice(&[1, 0x00]);
676    } else {
677        payload.push(3);
678        payload.extend_from_slice(&[1, 0x00, 2, 0x11, 3, 0x11]);
679    }
680    // Spectral selection 0..=63 with no successive approximation: the
681    // only shape a baseline scan may take.
682    payload.extend_from_slice(&[0, 63, 0]);
683    write_segment(marker::SOS, &payload, sink)
684}
685
686/// Write one Huffman table definition.
687fn write_huffman_table(
688    class: u8,
689    slot: u8,
690    counts: &[u8; 16],
691    values: &[u8],
692    payload: &mut Vec<u8>,
693) {
694    payload.push(((class & 0x0F) << 4) | (slot & 0x0F));
695    payload.extend_from_slice(counts);
696    payload.extend_from_slice(values);
697}
698
699/// The largest EXIF block an `APP1` segment holds: 65,535 less the length
700/// field and the identifier.
701const MAX_EXIF: usize = 65_535 - 2 - 6;
702
703/// The identifier an XMP `APP1` segment starts with.
704const XMP_ID: &[u8] = b"http://ns.adobe.com/xap/1.0/\0";
705
706/// The largest XMP packet one `APP1` segment holds.
707const MAX_XMP: usize = 65_535 - 2 - XMP_ID.len();
708
709impl Encoder for JpegEncoder {
710    fn set_icc_profile(&mut self, profile: Option<&[u8]>) -> Result<()> {
711        if self.state.is_some() {
712            return Err(PixelsError::invalid_argument(
713                "profile",
714                "the ICC profile must be set before write_header",
715            ));
716        }
717        // 255 segments is the most the sequence numbers can count.
718        if let Some(profile) = profile.filter(|p| p.len() > 255 * ICC_CHUNK) {
719            return Err(PixelsError::unsupported(format!(
720                "a {}-byte ICC profile needs more than 255 JPEG segments",
721                profile.len()
722            )));
723        }
724        self.icc = profile.map(<[u8]>::to_vec);
725        Ok(())
726    }
727
728    /// The block goes in an `APP1` segment, which holds at most 65,527
729    /// bytes after its `Exif\0\0` identifier. A larger block, which no
730    /// JPEG could have carried, is not written, as libvips drops it.
731    fn set_exif(&mut self, exif: Option<&[u8]>) -> Result<()> {
732        if self.state.is_some() {
733            return Err(PixelsError::invalid_argument(
734                "exif",
735                "the EXIF block must be set before write_header",
736            ));
737        }
738        self.exif = exif.filter(|e| e.len() <= MAX_EXIF).map(<[u8]>::to_vec);
739        Ok(())
740    }
741
742    /// The packet goes in one `APP1` segment, which holds at most 65,504
743    /// bytes after its identifier; a larger one is not written (extended XMP,
744    /// spread over several segments, is not).
745    fn set_xmp(&mut self, xmp: Option<&[u8]>) -> Result<()> {
746        if self.state.is_some() {
747            return Err(PixelsError::invalid_argument(
748                "xmp",
749                "the XMP packet must be set before write_header",
750            ));
751        }
752        self.xmp = xmp.filter(|x| x.len() <= MAX_XMP).map(<[u8]>::to_vec);
753        Ok(())
754    }
755
756    /// Written in the JFIF header, rounded to whole pixels per inch or
757    /// centimetre.
758    fn set_density(&mut self, density: Option<Density>) -> Result<()> {
759        if self.state.is_some() {
760            return Err(PixelsError::invalid_argument(
761                "density",
762                "the density must be set before write_header",
763            ));
764        }
765        self.density = density;
766        Ok(())
767    }
768
769    fn write_header(&mut self, desc: &ImageDescriptor, sink: &mut dyn Sink) -> Result<()> {
770        if self.state.is_some() {
771            return Err(PixelsError::invalid_argument(
772                "descriptor",
773                "write_header called more than once",
774            ));
775        }
776        let channels = channels_of(desc.pixel)?;
777        // JPEG dimensions are 16-bit; a larger image cannot be represented at
778        // all, so this is a format limit rather than a policy.
779        if desc.width > u32::from(u16::MAX) || desc.height > u32::from(u16::MAX) {
780            return Err(PixelsError::unsupported(format!(
781                "JPEG dimensions are 16-bit; {}x{} does not fit",
782                desc.width, desc.height
783            )));
784        }
785
786        let grayscale = channels <= 2;
787        // Subsampling chroma that does not exist would only pad the planes.
788        let factors = if grayscale {
789            (1, 1)
790        } else {
791            self.subsampling.factors()
792        };
793        let luma_quant = tables::scale_quant(&tables::LUMA_QUANT, self.quality);
794        let chroma_quant = tables::scale_quant(&tables::CHROMA_QUANT, self.quality);
795
796        let (h, v) = factors;
797        let band_height = v * 8;
798        let mcus_per_line = desc.width.div_ceil(h * 8);
799        let luma_stride = (mcus_per_line * h * 8) as usize;
800        let chroma_stride = (mcus_per_line * 8) as usize;
801
802        sink.write_all(&[0xFF, marker::SOI])?;
803
804        // A JFIF header. Nothing here needs it — without a density it says
805        // "no units", an aspect ratio of 1:1 — but its absence makes some
806        // consumers guess at the colour model rather than assume YCbCr.
807        let (unit, x, y) = match self.density {
808            None => (0, 1, 1),
809            Some(d) => {
810                let unit = if d.unit == ResolutionUnit::Centimetre {
811                    2
812                } else {
813                    1
814                };
815                let round = |v: f64| v.round().clamp(1.0, f64::from(u16::MAX)) as u16;
816                (unit, round(d.x), round(d.y))
817            }
818        };
819        let mut jfif = vec![b'J', b'F', b'I', b'F', 0, 1, 2, unit];
820        jfif.extend_from_slice(&x.to_be_bytes());
821        jfif.extend_from_slice(&y.to_be_bytes());
822        jfif.extend_from_slice(&[0, 0]); // no thumbnail
823        write_segment(marker::APP0, &jfif, sink)?;
824        if let Some(exif) = &self.exif {
825            let mut payload = b"Exif\0\0".to_vec();
826            payload.extend_from_slice(exif);
827            write_segment(marker::APP1, &payload, sink)?;
828        }
829        if let Some(xmp) = &self.xmp {
830            let mut payload = XMP_ID.to_vec();
831            payload.extend_from_slice(xmp);
832            write_segment(marker::APP1, &payload, sink)?;
833        }
834        // The profile, in as many APP2 segments as it takes.
835        if let Some(profile) = &self.icc {
836            for segment in icc_segments(profile) {
837                write_segment(marker::APP2, &segment, sink)?;
838            }
839        }
840
841        let mut payload = Vec::new();
842        write_quant_table(0, &luma_quant, &mut payload);
843        if !grayscale {
844            write_quant_table(1, &chroma_quant, &mut payload);
845        }
846        write_segment(marker::DQT, &payload, sink)?;
847
848        let mut payload = vec![8];
849        payload.extend_from_slice(&(desc.height as u16).to_be_bytes());
850        payload.extend_from_slice(&(desc.width as u16).to_be_bytes());
851        if grayscale {
852            payload.push(1);
853            payload.extend_from_slice(&[1, 0x11, 0]);
854        } else {
855            payload.push(3);
856            payload.extend_from_slice(&[1, ((h as u8) << 4) | v as u8, 0]);
857            payload.extend_from_slice(&[2, 0x11, 1]);
858            payload.extend_from_slice(&[3, 0x11, 1]);
859        }
860        let frame = if self.progressive {
861            marker::SOF2
862        } else {
863            marker::SOF0
864        };
865        write_segment(frame, &payload, sink)?;
866
867        // A progressive file's tables and scans come in `finish`, once every
868        // block is known.
869        if !self.progressive {
870            write_baseline_scan_header(grayscale, sink)?;
871        }
872
873        self.state = Some(State {
874            descriptor: *desc,
875            grayscale,
876            factors,
877            luma_quant,
878            chroma_quant,
879            luma_dc: HuffmanEncoder::new(&tables::LUMA_DC_COUNTS, &tables::LUMA_DC_VALUES)?,
880            luma_ac: HuffmanEncoder::new(&tables::LUMA_AC_COUNTS, &tables::LUMA_AC_VALUES)?,
881            chroma_dc: HuffmanEncoder::new(&tables::CHROMA_DC_COUNTS, &tables::CHROMA_DC_VALUES)?,
882            chroma_ac: HuffmanEncoder::new(&tables::CHROMA_AC_COUNTS, &tables::CHROMA_AC_VALUES)?,
883            band: vec![0; desc.row_bytes() * band_height as usize],
884            band_rows: 0,
885            band_height,
886            mcus_per_line,
887            luma: Plane::new(luma_stride, band_height as usize),
888            // A greyscale image has no chroma to hold, so none is allocated.
889            chroma_full: if grayscale {
890                Vec::new()
891            } else {
892                vec![128; luma_stride * band_height as usize * 2]
893            },
894            chroma_blue: chroma_plane(grayscale, chroma_stride, band_height / v),
895            chroma_red: chroma_plane(grayscale, chroma_stride, band_height / v),
896            predictors: [0; 3],
897            writer: BitWriter::default(),
898            rows_written: 0,
899            coefficients: self
900                .progressive
901                .then(|| Coefficients::new(desc.width, desc.height, factors, grayscale)),
902            mcu_row: 0,
903        });
904        Ok(())
905    }
906
907    fn write_row(&mut self, row: &[u8], sink: &mut dyn Sink) -> Result<()> {
908        let Some(state) = self.state.as_mut() else {
909            return Err(PixelsError::invalid_argument(
910                "row",
911                "write_row called before write_header",
912            ));
913        };
914        let expected = state.descriptor.row_bytes();
915        if row.len() != expected {
916            return Err(PixelsError::invalid_argument(
917                "row",
918                format!("row is {} bytes, expected {expected}", row.len()),
919            ));
920        }
921        if state.rows_written >= state.descriptor.height {
922            return Err(PixelsError::invalid_argument(
923                "row",
924                format!("more than {} rows written", state.descriptor.height),
925            ));
926        }
927
928        let at = state.band_rows as usize * expected;
929        if let Some(slot) = state
930            .band
931            .get_mut(at..)
932            .and_then(|rest| rest.get_mut(..expected))
933        {
934            slot.copy_from_slice(row);
935        }
936        state.band_rows += 1;
937        state.rows_written += 1;
938
939        if state.band_rows == state.band_height {
940            state.fill_planes();
941            state.encode_band();
942            state.band_rows = 0;
943            // Drained here rather than at the end: this is what makes the
944            // encoder streaming rather than merely incremental.
945            sink.write_all(&state.writer.bytes)?;
946            state.writer.bytes.clear();
947        }
948        Ok(())
949    }
950
951    fn finish(&mut self, sink: &mut dyn Sink) -> Result<()> {
952        let Some(state) = self.state.as_mut() else {
953            return Err(PixelsError::invalid_argument(
954                "sink",
955                "finish called before write_header",
956            ));
957        };
958        if state.rows_written < state.descriptor.height {
959            return Err(PixelsError::malformed(
960                "jpeg",
961                format!(
962                    "{} of {} rows were written",
963                    state.rows_written, state.descriptor.height
964                ),
965            ));
966        }
967
968        // A final partial MCU row still has to be coded: the format has no
969        // way to say "this image ends mid-block". `fill_planes` pads it by
970        // replicating the last real row.
971        if state.band_rows > 0 {
972            state.fill_planes();
973            state.encode_band();
974            state.band_rows = 0;
975        }
976        if let Some(kept) = state.coefficients.take() {
977            kept.write_scans(sink)?;
978        } else {
979            state.writer.flush();
980            sink.write_all(&state.writer.bytes)?;
981            state.writer.bytes.clear();
982        }
983
984        write_marker(marker::EOI, sink)?;
985        sink.flush()
986    }
987}
988
989#[cfg(test)]
990#[allow(
991    clippy::unwrap_used,
992    clippy::indexing_slicing,
993    reason = "tests operate on known-good values and assert shapes directly"
994)]
995mod tests {
996    use super::*;
997
998    /// Encode a raster with `encoder`.
999    fn encode_with(desc: &ImageDescriptor, raster: &[u8], mut encoder: JpegEncoder) -> Vec<u8> {
1000        let mut out = Vec::new();
1001        encoder.write_header(desc, &mut out).unwrap();
1002        for row in raster.chunks_exact(desc.row_bytes()) {
1003            encoder.write_row(row, &mut out).unwrap();
1004        }
1005        encoder.finish(&mut out).unwrap();
1006        out
1007    }
1008
1009    fn decode_all(bytes: &[u8]) -> (bool, Vec<u8>) {
1010        use otf_pixels_core::{Decoder, Limits};
1011        let mut decoder = crate::JpegDecoder::new(bytes, Limits::default()).unwrap();
1012        let mut row = vec![0_u8; decoder.descriptor().row_bytes()];
1013        let mut out = Vec::new();
1014        for _ in 0..decoder.descriptor().height {
1015            decoder.read_row(&mut row).unwrap();
1016            out.extend_from_slice(&row);
1017        }
1018        (decoder.is_progressive(), out)
1019    }
1020
1021    #[test]
1022    fn progressive_output_decodes_to_exactly_the_baseline_pixels() {
1023        // The same quantized coefficients, sent in scans rather than at once.
1024        for (width, height, format) in [
1025            (1, 1, PixelFormat::Rgb8),
1026            (17, 9, PixelFormat::Rgb8),
1027            (33, 47, PixelFormat::Rgba8),
1028            (64, 64, PixelFormat::Gray8),
1029            (23, 31, PixelFormat::GrayA8),
1030            (130, 66, PixelFormat::Rgb8),
1031        ] {
1032            let desc = ImageDescriptor::new(width, height, format).unwrap();
1033            let raster: Vec<u8> = (0..desc.byte_len().unwrap())
1034                .map(|i| ((i * 37 + i / 101 * 11) % 256) as u8)
1035                .collect();
1036            for subsampling in [
1037                Subsampling::Both,
1038                Subsampling::Horizontal,
1039                Subsampling::None,
1040            ] {
1041                for quality in [20, 75, 100] {
1042                    let base = || {
1043                        JpegEncoder::with_quality(quality)
1044                            .unwrap()
1045                            .with_subsampling(subsampling)
1046                    };
1047                    let baseline = encode_with(&desc, &raster, base());
1048                    let progressive = encode_with(&desc, &raster, base().with_progressive(true));
1049                    let (was, pixels) = decode_all(&progressive);
1050                    assert!(was, "the file is progressive");
1051                    assert_eq!(
1052                        pixels,
1053                        decode_all(&baseline).1,
1054                        "{width}x{height} {format} {subsampling:?} q{quality}"
1055                    );
1056                }
1057            }
1058        }
1059    }
1060
1061    #[test]
1062    fn progressive_output_is_smaller_on_a_photograph() {
1063        let desc = ImageDescriptor::new(256, 192, PixelFormat::Rgb8).unwrap();
1064        let mut seed = 5_u32;
1065        let raster: Vec<u8> = (0..256 * 192)
1066            .flat_map(|i| {
1067                seed = seed.wrapping_mul(1_103_515_245).wrapping_add(12_345);
1068                let n = (seed >> 28) as usize;
1069                let (x, y) = (i % 256, i / 256);
1070                [(x + n) as u8, (y + n) as u8, ((x + y) / 2) as u8]
1071            })
1072            .collect();
1073        let base = || JpegEncoder::with_quality(80).unwrap();
1074        let baseline = encode_with(&desc, &raster, base());
1075        let progressive = encode_with(&desc, &raster, base().with_progressive(true));
1076        assert!(
1077            progressive.len() < baseline.len(),
1078            "{} against {}",
1079            progressive.len(),
1080            baseline.len()
1081        );
1082    }
1083
1084    #[test]
1085    fn progressive_follows_the_options() {
1086        let options = EncodeOptions::default().with_progressive(true);
1087        assert!(JpegEncoder::from_options(&options).progressive());
1088        assert!(!JpegEncoder::from_options(&EncodeOptions::default()).progressive());
1089    }
1090
1091    #[test]
1092    fn options_choose_the_subsampling_over_the_one_quality_implies() {
1093        let low = EncodeOptions::with_quality(50).unwrap();
1094        let high = EncodeOptions::with_quality(95).unwrap();
1095        assert_eq!(
1096            JpegEncoder::from_options(&low).subsampling(),
1097            Subsampling::Both
1098        );
1099        assert_eq!(
1100            JpegEncoder::from_options(&high).subsampling(),
1101            Subsampling::None
1102        );
1103        let full = low.with_chroma_subsampling(ChromaSubsampling::Yuv444);
1104        assert_eq!(
1105            JpegEncoder::from_options(&full).subsampling(),
1106            Subsampling::None
1107        );
1108        let sub = high.with_chroma_subsampling(ChromaSubsampling::Yuv420);
1109        assert_eq!(
1110            JpegEncoder::from_options(&sub).subsampling(),
1111            Subsampling::Both
1112        );
1113    }
1114
1115    #[test]
1116    fn magnitude_categories_mirror_the_decoder() {
1117        // The property: `magnitude` and the decoder's `receive_extend` are
1118        // inverses over every value a coefficient can take.
1119        for value in [-2047_i32, -255, -8, -3, -2, -1, 1, 2, 3, 8, 255, 2047] {
1120            let (size, bits) = magnitude(value);
1121            assert!(size <= 15, "{value}: size {size}");
1122            // Reproduce the decoder's extension.
1123            let threshold = 1_i32 << (size - 1);
1124            let raw = bits as i32;
1125            let decoded = if raw < threshold {
1126                raw - (1_i32 << size) + 1
1127            } else {
1128                raw
1129            };
1130            assert_eq!(decoded, value, "{value} round-tripped as {decoded}");
1131        }
1132        assert_eq!(magnitude(0), (0, 0));
1133    }
1134
1135    #[test]
1136    fn the_bit_writer_stuffs_ff_bytes() {
1137        let mut writer = BitWriter::default();
1138        writer.write(0xFF, 8);
1139        assert_eq!(
1140            writer.bytes,
1141            vec![0xFF, 0x00],
1142            "a literal FF must be stuffed"
1143        );
1144
1145        let mut writer = BitWriter::default();
1146        writer.write(0b1010, 4);
1147        writer.write(0b0101, 4);
1148        assert_eq!(writer.bytes, vec![0b1010_0101]);
1149    }
1150
1151    #[test]
1152    fn flushing_pads_with_one_bits() {
1153        let mut writer = BitWriter::default();
1154        writer.write(0b101, 3);
1155        writer.flush();
1156        // Five bits of padding, all ones.
1157        assert_eq!(writer.bytes, vec![0b1011_1111]);
1158
1159        // Already aligned: nothing is added.
1160        let mut writer = BitWriter::default();
1161        writer.write(0xAB, 8);
1162        writer.flush();
1163        assert_eq!(writer.bytes, vec![0xAB]);
1164    }
1165
1166    #[test]
1167    fn colour_conversion_round_trips_through_the_decoder() {
1168        // Grey stays grey and the primaries land where they should; the exact
1169        // values are checked against the decoder's inverse rather than
1170        // hardcoded, because the pair being inverses is the property.
1171        for rgb in [
1172            [0, 0, 0],
1173            [255, 255, 255],
1174            [128, 128, 128],
1175            [255, 0, 0],
1176            [0, 255, 0],
1177            [0, 0, 255],
1178            [37, 142, 201],
1179        ] {
1180            let ycbcr = rgb_to_ycbcr(rgb);
1181            if rgb[0] == rgb[1] && rgb[1] == rgb[2] {
1182                assert_eq!(ycbcr[0], rgb[0], "grey should map to its own luma");
1183                assert_eq!([ycbcr[1], ycbcr[2]], [128, 128], "grey has no chroma");
1184            }
1185            let back = crate::decoder::ycbcr_to_rgb(ycbcr);
1186            for channel in 0..3 {
1187                assert!(
1188                    back[channel].abs_diff(rgb[channel]) <= 2,
1189                    "{rgb:?} -> {ycbcr:?} -> {back:?}"
1190                );
1191            }
1192        }
1193    }
1194
1195    #[test]
1196    fn unsupported_pixel_formats_are_refused_at_the_header() {
1197        for format in [
1198            PixelFormat::Gray16,
1199            PixelFormat::Rgb16,
1200            PixelFormat::Rgba16,
1201            PixelFormat::RgbF32,
1202            PixelFormat::RgbaF32,
1203        ] {
1204            let descriptor = ImageDescriptor::new(8, 8, format).unwrap();
1205            let mut sink = Vec::new();
1206            let error = JpegEncoder::new()
1207                .write_header(&descriptor, &mut sink)
1208                .unwrap_err();
1209            assert_eq!(
1210                error.code(),
1211                otf_pixels_core::ErrorCode::Unsupported,
1212                "{format}"
1213            );
1214            assert!(sink.is_empty(), "{format}: bytes were written anyway");
1215        }
1216    }
1217
1218    #[test]
1219    fn quality_selects_subsampling_but_can_be_overridden() {
1220        assert_eq!(
1221            JpegEncoder::with_quality(80).unwrap().subsampling(),
1222            Subsampling::Both
1223        );
1224        assert_eq!(
1225            JpegEncoder::with_quality(95).unwrap().subsampling(),
1226            Subsampling::None
1227        );
1228        assert_eq!(
1229            JpegEncoder::with_quality(95)
1230                .unwrap()
1231                .with_subsampling(Subsampling::Horizontal)
1232                .subsampling(),
1233            Subsampling::Horizontal
1234        );
1235        assert!(JpegEncoder::with_quality(0).is_err());
1236        assert!(JpegEncoder::with_quality(101).is_err());
1237    }
1238}