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