std/thumbhash: add QUIRK_JUST_RAW_THUMBHASH
diff --git a/doc/note/quirks.md b/doc/note/quirks.md
index 006631b..8ebdf18 100644
--- a/doc/note/quirks.md
+++ b/doc/note/quirks.md
@@ -81,5 +81,6 @@
- [JSON decoder quirks](/std/json/decode_quirks.wuffs)
- [LZMA decoder quirks](/std/lzma/decode_quirks.wuffs)
- [LZW decoder quirks](/std/lzw/decode_quirks.wuffs)
+- [TH decoder quirks](/std/thumbhash/decode_quirks.wuffs)
- [XZ decoder quirks](/std/xz/decode_quirks.wuffs)
- [ZLIB decoder quirks](/std/zlib/decode_quirks.wuffs)
diff --git a/doc/std/image-decoders.md b/doc/std/image-decoders.md
index 8b44eba..4683baf 100644
--- a/doc/std/image-decoders.md
+++ b/doc/std/image-decoders.md
@@ -158,6 +158,8 @@
## [Quirks](/doc/note/quirks.md)
- [GIF decoder quirks](/std/gif/decode_quirks.wuffs)
+- [JPEG decoder quirks](/std/jpeg/decode_quirks.wuffs)
+- [TH decoder quirks](/std/thumbhash/decode_quirks.wuffs)
## Related Documentation
diff --git a/release/c/wuffs-unsupported-snapshot.c b/release/c/wuffs-unsupported-snapshot.c
index b31ec0e..fd0f26f 100644
--- a/release/c/wuffs-unsupported-snapshot.c
+++ b/release/c/wuffs-unsupported-snapshot.c
@@ -14107,6 +14107,8 @@
// ---------------- Public Consts
+#define WUFFS_THUMBHASH__QUIRK_JUST_RAW_THUMBHASH 1712283648u
+
#define WUFFS_THUMBHASH__DECODER_WORKBUF_LEN_MAX_INCL_WORST_CASE 0u
// ---------------- Struct Declarations
@@ -14281,6 +14283,7 @@
uint64_t f_p_dc;
uint64_t f_q_dc;
uint64_t f_a_dc;
+ bool f_quirk_just_raw_thumbhash;
uint8_t f_l_scale;
uint8_t f_p_scale;
uint8_t f_q_scale;
@@ -73313,6 +73316,8 @@
// ---------------- Private Consts
+#define WUFFS_THUMBHASH__QUIRKS_BASE 1712283648u
+
static const uint8_t
WUFFS_THUMBHASH__DIMENSIONS_FROM_DIMENSION_CODES[8] WUFFS_BASE__POTENTIALLY_UNUSED = {
0u, 14u, 18u, 19u, 23u, 26u, 27u, 32u,
@@ -73855,6 +73860,9 @@
return 0;
}
+ if ((a_key == 1712283648u) && self->private_impl.f_quirk_just_raw_thumbhash) {
+ return 1u;
+ }
return 0u;
}
@@ -73876,6 +73884,10 @@
: wuffs_base__error__initialize_not_called);
}
+ if (a_key == 1712283648u) {
+ self->private_impl.f_quirk_just_raw_thumbhash = (a_value > 0u);
+ return wuffs_base__make_status(NULL);
+ }
return wuffs_base__make_status(wuffs_base__error__unsupported_option);
}
@@ -73977,38 +73989,40 @@
status = wuffs_base__make_status(wuffs_base__error__bad_call_sequence);
goto exit;
}
- {
- WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1);
- uint32_t t_0;
- if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 3)) {
- t_0 = ((uint32_t)(wuffs_base__peek_u24le__no_bounds_check(iop_a_src)));
- iop_a_src += 3;
- } else {
- self->private_data.s_do_decode_image_config.scratch = 0;
- WUFFS_BASE__COROUTINE_SUSPENSION_POINT(2);
- while (true) {
- if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
- status = wuffs_base__make_status(wuffs_base__suspension__short_read);
- goto suspend;
+ if ( ! self->private_impl.f_quirk_just_raw_thumbhash) {
+ {
+ WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1);
+ uint32_t t_0;
+ if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 3)) {
+ t_0 = ((uint32_t)(wuffs_base__peek_u24le__no_bounds_check(iop_a_src)));
+ iop_a_src += 3;
+ } else {
+ self->private_data.s_do_decode_image_config.scratch = 0;
+ WUFFS_BASE__COROUTINE_SUSPENSION_POINT(2);
+ while (true) {
+ if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
+ status = wuffs_base__make_status(wuffs_base__suspension__short_read);
+ goto suspend;
+ }
+ uint64_t* scratch = &self->private_data.s_do_decode_image_config.scratch;
+ uint32_t num_bits_0 = ((uint32_t)(*scratch >> 56));
+ *scratch <<= 8;
+ *scratch >>= 8;
+ *scratch |= ((uint64_t)(*iop_a_src++)) << num_bits_0;
+ if (num_bits_0 == 16) {
+ t_0 = ((uint32_t)(*scratch));
+ break;
+ }
+ num_bits_0 += 8u;
+ *scratch |= ((uint64_t)(num_bits_0)) << 56;
}
- uint64_t* scratch = &self->private_data.s_do_decode_image_config.scratch;
- uint32_t num_bits_0 = ((uint32_t)(*scratch >> 56));
- *scratch <<= 8;
- *scratch >>= 8;
- *scratch |= ((uint64_t)(*iop_a_src++)) << num_bits_0;
- if (num_bits_0 == 16) {
- t_0 = ((uint32_t)(*scratch));
- break;
- }
- num_bits_0 += 8u;
- *scratch |= ((uint64_t)(num_bits_0)) << 56;
}
+ v_c32 = t_0;
}
- v_c32 = t_0;
- }
- if (v_c32 != 16694979u) {
- status = wuffs_base__make_status(wuffs_thumbhash__error__bad_header);
- goto exit;
+ if (v_c32 != 16694979u) {
+ status = wuffs_base__make_status(wuffs_thumbhash__error__bad_header);
+ goto exit;
+ }
}
{
WUFFS_BASE__COROUTINE_SUSPENSION_POINT(3);
@@ -74093,7 +74107,6 @@
}
self->private_impl.f_frame_config_io_position = 8u;
if (self->private_impl.f_has_alpha != 0u) {
- self->private_impl.f_frame_config_io_position = 9u;
{
WUFFS_BASE__COROUTINE_SUSPENSION_POINT(7);
if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
@@ -74105,6 +74118,17 @@
}
self->private_impl.f_a_dc = (((uint64_t)(((v_c32 >> 0u) & 15u))) << 42u);
self->private_impl.f_a_scale = ((uint8_t)(((v_c32 >> 4u) & 15u)));
+ self->private_impl.f_frame_config_io_position = 9u;
+ }
+ if (self->private_impl.f_quirk_just_raw_thumbhash) {
+#if defined(__GNUC__)
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wconversion"
+#endif
+ self->private_impl.f_frame_config_io_position -= 3u;
+#if defined(__GNUC__)
+#pragma GCC diagnostic pop
+#endif
}
self->private_impl.f_pixfmt = 2415954056u;
if (self->private_impl.f_has_alpha != 0u) {
diff --git a/std/thumbhash/decode_quirks.wuffs b/std/thumbhash/decode_quirks.wuffs
new file mode 100644
index 0000000..f5f4287
--- /dev/null
+++ b/std/thumbhash/decode_quirks.wuffs
@@ -0,0 +1,26 @@
+// 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 "th.." is 0x19_83D8. Left shifting by 10 gives
+// 0x660F_6000.
+pri const QUIRKS_BASE : base.u32 = 0x660F_6000
+
+// --------
+
+// When this quirk is enabled, the decoder speaks just raw thumbhash, without
+// the "\xC3\xBE\xFE" magic identifier to start the file.
+//
+// Enabling this quirk matches the behavior of the original JavaScript
+// reference implementation at https://evanw.github.io/thumbhash/
+pub const QUIRK_JUST_RAW_THUMBHASH : base.u32 = 0x660F_6000 | 0x00
diff --git a/std/thumbhash/decode_thumbhash.wuffs b/std/thumbhash/decode_thumbhash.wuffs
index 7556ef6..487ea1c 100644
--- a/std/thumbhash/decode_thumbhash.wuffs
+++ b/std/thumbhash/decode_thumbhash.wuffs
@@ -71,6 +71,8 @@
q_dc : base.u64, // Fixed-point denominator is (LDENOM << 28).
a_dc : base.u64, // Fixed-point denominator is (ADENOM << 28).
+ quirk_just_raw_thumbhash : base.bool,
+
l_scale : base.u8[..= 31],
p_scale : base.u8[..= 63],
q_scale : base.u8[..= 63],
@@ -185,10 +187,17 @@
)
pub func decoder.get_quirk(key: base.u32) base.u64 {
+ if (args.key == QUIRK_JUST_RAW_THUMBHASH) and this.quirk_just_raw_thumbhash {
+ return 1
+ }
return 0
}
pub func decoder.set_quirk!(key: base.u32, value: base.u64) base.status {
+ if args.key == QUIRK_JUST_RAW_THUMBHASH {
+ this.quirk_just_raw_thumbhash = args.value > 0
+ return ok
+ }
return base."#unsupported option"
}
@@ -212,10 +221,11 @@
return base."#bad call sequence"
}
- // TODO: implement QUIRK_JUST_RAW_THUMBHASH.
- c32 = args.src.read_u24le_as_u32?()
- if c32 <> '\xC3\xBE\xFE'le {
- return "#bad header"
+ if not this.quirk_just_raw_thumbhash {
+ c32 = args.src.read_u24le_as_u32?()
+ if c32 <> '\xC3\xBE\xFE'le {
+ return "#bad header"
+ }
}
c32 = args.src.read_u24le_as_u32?()
@@ -247,11 +257,17 @@
}
this.frame_config_io_position = 8
+ assert this.frame_config_io_position >= 8
if this.has_alpha <> 0 {
- this.frame_config_io_position = 9
c32 = args.src.read_u8_as_u32?()
this.a_dc = (((c32 >> 0x00) & 15) as base.u64) << 42
this.a_scale = ((c32 >> 0x04) & 15) as base.u8
+ this.frame_config_io_position = 9
+ assert this.frame_config_io_position >= 8
+ }
+
+ if this.quirk_just_raw_thumbhash {
+ this.frame_config_io_position -= 3
}
this.pixfmt = base.PIXEL_FORMAT__BGRX
diff --git a/test/c/std/thumbhash.c b/test/c/std/thumbhash.c
index 9715dfc..908bce3 100644
--- a/test/c/std/thumbhash.c
+++ b/test/c/std/thumbhash.c
@@ -108,8 +108,7 @@
}
const char* //
-test_wuffs_thumbhash_decode_frame_config() {
- CHECK_FOCUS(__func__);
+do_test_wuffs_thumbhash_decode_frame_config(bool raw) {
wuffs_thumbhash__decoder dec;
CHECK_STATUS("initialize",
wuffs_thumbhash__decoder__initialize(
@@ -122,9 +121,25 @@
});
CHECK_STRING(read_file(
&src, "test/data/artificial-thumbhash/3OcRJYB4d3h_iIeHeEh3eIhw-j3A.th"));
+
+ if (raw) {
+ if ((src.meta.wi <= src.meta.ri) || ((src.meta.wi - src.meta.ri) < 3)) {
+ return "could not skip 3 bytes";
+ }
+ src.meta.ri += 3; // The number of bytes in "\xC3\xBE\xFE".
+ wuffs_thumbhash__decoder__set_quirk(
+ &dec, WUFFS_THUMBHASH__QUIRK_JUST_RAW_THUMBHASH, 1);
+ }
+
CHECK_STATUS("decode_frame_config #0",
wuffs_thumbhash__decoder__decode_frame_config(&dec, &fc, &src));
+ uint32_t have = wuffs_base__frame_config__height(&fc);
+ uint32_t want = 23;
+ if (have != want) {
+ RETURN_FAIL("height: have %u, want %u", have, want);
+ }
+
wuffs_base__status status =
wuffs_thumbhash__decoder__decode_frame_config(&dec, &fc, &src);
if (status.repr != wuffs_base__note__end_of_data) {
@@ -134,6 +149,18 @@
return NULL;
}
+const char* //
+test_wuffs_thumbhash_decode_frame_config_cooked() {
+ CHECK_FOCUS(__func__);
+ return do_test_wuffs_thumbhash_decode_frame_config(false);
+}
+
+const char* //
+test_wuffs_thumbhash_decode_frame_config_raw() {
+ CHECK_FOCUS(__func__);
+ return do_test_wuffs_thumbhash_decode_frame_config(true);
+}
+
// ---------------- Mimic Tests
#ifdef WUFFS_MIMIC
@@ -158,7 +185,8 @@
proc g_tests[] = {
- test_wuffs_thumbhash_decode_frame_config,
+ test_wuffs_thumbhash_decode_frame_config_cooked,
+ test_wuffs_thumbhash_decode_frame_config_raw,
test_wuffs_thumbhash_decode_interface,
test_wuffs_thumbhash_decode_truncated_input,