std/jpeg: add QUIRK_REJECT_PROGRESSIVE_JPEGS
diff --git a/doc/note/quirks.md b/doc/note/quirks.md index 4606c52..1afa484 100644 --- a/doc/note/quirks.md +++ b/doc/note/quirks.md
@@ -76,6 +76,7 @@ Package-specific quirks: - [GIF image decoder quirks](/std/gif/decode_quirks.wuffs) +- [JPEG decoder quirks](/std/jpeg/decode_quirks.wuffs) - [JSON decoder quirks](/std/json/decode_quirks.wuffs) - [LZMA decoder quirks](/std/lzma/decode_quirks.wuffs) - [LZW decoder quirks](/std/lzw/decode_quirks.wuffs)
diff --git a/release/c/wuffs-unsupported-snapshot.c b/release/c/wuffs-unsupported-snapshot.c index 8750fab..b3c4abb 100644 --- a/release/c/wuffs-unsupported-snapshot.c +++ b/release/c/wuffs-unsupported-snapshot.c
@@ -9664,6 +9664,7 @@ extern const char wuffs_jpeg__error__bad_scan_count[]; extern const char wuffs_jpeg__error__missing_huffman_table[]; extern const char wuffs_jpeg__error__missing_quantization_table[]; +extern const char wuffs_jpeg__error__rejected_progressive_jpeg[]; extern const char wuffs_jpeg__error__truncated_input[]; extern const char wuffs_jpeg__error__unsupported_arithmetic_coding[]; extern const char wuffs_jpeg__error__unsupported_color_model[]; @@ -9681,6 +9682,8 @@ #define WUFFS_JPEG__DECODER_WORKBUF_LEN_MAX_INCL_WORST_CASE 51552191232u +#define WUFFS_JPEG__QUIRK_REJECT_PROGRESSIVE_JPEGS 1220532224u + // ---------------- Struct Declarations typedef struct wuffs_jpeg__decoder__struct wuffs_jpeg__decoder; @@ -9908,6 +9911,7 @@ bool f_bitstream_is_closed; bool f_expect_multiple_scans; bool f_use_lower_quality; + bool f_reject_progressive_jpegs; bool f_swizzle_immediately; wuffs_base__status f_swizzle_immediately_status; uint32_t f_swizzle_immediately_b_offsets[10]; @@ -44300,6 +44304,7 @@ const char wuffs_jpeg__error__bad_scan_count[] = "#jpeg: bad scan count"; const char wuffs_jpeg__error__missing_huffman_table[] = "#jpeg: missing Huffman table"; const char wuffs_jpeg__error__missing_quantization_table[] = "#jpeg: missing Quantization table"; +const char wuffs_jpeg__error__rejected_progressive_jpeg[] = "#jpeg: rejected progressive JPEG"; const char wuffs_jpeg__error__truncated_input[] = "#jpeg: truncated input"; const char wuffs_jpeg__error__unsupported_arithmetic_coding[] = "#jpeg: unsupported arithmetic coding"; const char wuffs_jpeg__error__unsupported_color_model[] = "#jpeg: unsupported color model"; @@ -44538,6 +44543,8 @@ 248u, 249u, 250u, }; +#define WUFFS_JPEG__QUIRKS_BASE 1220532224u + // ---------------- Private Initializer Prototypes // ---------------- Private Function Prototypes @@ -46455,6 +46462,10 @@ if (self->private_impl.f_use_lower_quality) { return 18446744073709551615u; } + } else if (a_key == 1220532224u) { + if (self->private_impl.f_reject_progressive_jpegs) { + return 1u; + } } return 0u; } @@ -46480,6 +46491,9 @@ if (a_key == 2u) { self->private_impl.f_use_lower_quality = (a_value >= 9223372036854775808u); return wuffs_base__make_status(NULL); + } else if (a_key == 1220532224u) { + self->private_impl.f_reject_progressive_jpegs = (a_value != 0u); + return wuffs_base__make_status(NULL); } return wuffs_base__make_status(wuffs_base__error__unsupported_option); } @@ -46689,7 +46703,10 @@ goto exit; } else if (v_marker < 208u) { if (v_marker <= 194u) { - if (self->private_impl.f_sof_marker != 0u) { + if ((v_marker == 194u) && self->private_impl.f_reject_progressive_jpegs) { + status = wuffs_base__make_status(wuffs_jpeg__error__rejected_progressive_jpeg); + goto exit; + } else if (self->private_impl.f_sof_marker != 0u) { status = wuffs_base__make_status(wuffs_jpeg__error__bad_sof_marker); goto exit; }
diff --git a/std/jpeg/decode_jpeg.wuffs b/std/jpeg/decode_jpeg.wuffs index 5616ec1..12e7a8d 100644 --- a/std/jpeg/decode_jpeg.wuffs +++ b/std/jpeg/decode_jpeg.wuffs
@@ -18,6 +18,7 @@ pub status "#bad scan count" pub status "#missing Huffman table" pub status "#missing Quantization table" +pub status "#rejected progressive JPEG" pub status "#truncated input" pub status "#unsupported arithmetic coding" pub status "#unsupported color model" @@ -281,7 +282,8 @@ expect_multiple_scans : base.bool, - use_lower_quality : base.bool, + use_lower_quality : base.bool, + reject_progressive_jpegs : base.bool, swizzle_immediately : base.bool, swizzle_immediately_status : base.status, @@ -342,40 +344,21 @@ if this.use_lower_quality { return 0xFFFF_FFFF_FFFF_FFFF } + } else if args.key == QUIRK_REJECT_PROGRESSIVE_JPEGS { + if this.reject_progressive_jpegs { + return 1 + } } return 0 } -// If key is base.QUIRK_QUALITY then, when re-interpreting the value as a -// signed i64 value, a negative quirk value opts the decoder in to a slightly -// worse quality but leaner (less memory required) chroma upsampling algorithm. -// -// Negative (in i64 space) means >= 0x8000_0000_0000_0000 (in u64 space). -// -// Specifically, a negative quirk value uses a box filter. A non-negative quirk -// value (including the default value, zero) uses the same triangle filter that -// libjpeg-turbo also uses by default, which requires chroma upsampling on the -// edges of a MCU (Minimum Coded Unit) to remember adjacent MCU's values. -// -// Leaner means that for common (sequential) JPEG images, the range returned by -// decoder.workbuf_len can have a minimum value of zero, meaning that no work -// buffer memory needs to be allocated. Decoding rarer (progressive) JPEGs will -// still require a positive amount of work buffer memory. The exact amount -// required depends on factors like the JPEG image's width, height, component -// count and subsampling ratios. -// -// Regardless of the quirk value, as always, the workbuf_len method returns a -// range, whose min and max can be different. The caller is able to choose -// between providing the minimal amount of work buffer memory (for slightly -// slower decoding) or the maximal amount (for slightly faster decoding). -// -// Callers that never wish to allocate work buffer memory can simply reject -// progressive JPEGs (where workbuf_len().min_incl > 0 despite setting the -// base.QUIRK_QUALITY key to a negative quirk value) as unsupported. pub func decoder.set_quirk!(key: base.u32, value: base.u64) base.status { if args.key == base.QUIRK_QUALITY { this.use_lower_quality = args.value >= 0x8000_0000_0000_0000 return ok + } else if args.key == QUIRK_REJECT_PROGRESSIVE_JPEGS { + this.reject_progressive_jpegs = args.value <> 0 + return ok } return base."#unsupported option" } @@ -454,7 +437,9 @@ } else if marker < 0xD0 { // SOFn (Start of Frame) and friends. if marker <= 0xC2 { - if this.sof_marker <> 0 { + if (marker == 0xC2) and this.reject_progressive_jpegs { + return "#rejected progressive JPEG" + } else if this.sof_marker <> 0 { return "#bad SOF marker" } this.sof_marker = marker
diff --git a/std/jpeg/decode_quirks.wuffs b/std/jpeg/decode_quirks.wuffs new file mode 100644 index 0000000..1676947 --- /dev/null +++ b/std/jpeg/decode_quirks.wuffs
@@ -0,0 +1,77 @@ +// Copyright 2024 The Wuffs Authors. +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. +// +// SPDX-License-Identifier: Apache-2.0 OR MIT + +// -------- + +// Quirks are discussed in (/doc/note/quirks.md). +// +// The base38 encoding of "jpeg" is 0x122FF6. Left shifting by 10 gives +// 0x48BF_D800. +pri const QUIRKS_BASE : base.u32 = 0x48BF_D800 + +// -------- + +// When this quirk value is non-zero, the decoder will not support progressive +// JPEG images. When combined with setting base.QUIRK_QUALITY to "lower +// quality", this means that decoding will never require any temporary "work +// buffer" memory: decoder.workbuf_len will always return a range (a min and +// max pair) whose minimum value is zero. +// +// The maximum value can still be positive, as using more memory might lead to +// faster decoding, but a zero minimum means that it's valid for the caller to +// not allocate any memory for a work buffer. +// +// When opted into lower quality decoding, the workbuf_len minimum will be zero +// and positive for sequential and progressive JPEGs respectively, regardless +// of this QUIRK_REJECT_PROGRESSIVE_JPEGS key's value. But setting this quirk +// explicitly will produce a "#rejected progressive JPEG" error when +// encountering a progressive JPEG. This results in more readable calling code, +// instead of commenting about "positive work buffer minimum length implies a +// progressive JPEG". +pub const QUIRK_REJECT_PROGRESSIVE_JPEGS : base.u32 = 0x48BF_D800 | 0x00 + +// -------- + +// The base.QUIRK_QUALITY key is defined in the base package, not this package. +// Still, here's some documentation on how this package responds to that (key, +// value) quirk pair. +// +// If decoder.set_quirk is passed a base.QUIRK_QUALITY as the key argument +// then, when re-interpreting the u64 value argument as a signed i64 number, a +// negative number opts the decoder in to a slightly worse quality but leaner +// (less memory required) chroma upsampling algorithm. +// +// Negative (in i64 space) means >= 0x8000_0000_0000_0000 (in u64 space). +// base.QUIRK_QUALITY__VALUE__LOWER_QUALITY is one such value. +// +// Specifically, a negative quirk value uses a box filter. A non-negative quirk +// value (including the default value, zero) uses the same triangle filter that +// libjpeg-turbo also uses by default, which requires chroma upsampling on the +// edges of a MCU (Minimum Coded Unit) to remember adjacent MCU's values. +// +// Leaner means that for common (sequential) JPEG images, the range returned by +// decoder.workbuf_len will have a minimum value of zero, meaning that no work +// buffer memory needs to be allocated. Decoding rarer (progressive) JPEGs will +// still always require a positive amount of work buffer memory, for reasons +// unrelated to chroma upsampling. The exact amount required depends on factors +// like the JPEG image's width, height, component count and subsampling ratios. +// +// Regardless of the quirk value, as always, the workbuf_len method returns a +// range, whose min and max can be different. The caller is able to choose +// between providing the minimal amount of work buffer memory (for slightly +// slower decoding) or the maximal amount (for slightly faster decoding). +// +// Callers that never wish to allocate work buffer memory can simply reject +// progressive JPEGs (where "workbuf_len().min_incl > 0" despite setting the +// base.QUIRK_QUALITY key to a negative quirk value) as unsupported. See also +// QUIRK_REJECT_PROGRESSIVE_JPEGS. +// +// For some pictures comparing box and triangle filter decodings, see +// https://nigeltao.github.io/blog/2024/jpeg-chroma-upsampling.html