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