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,