blob: 7556ef61328561691cbd1e79663197260653d7a6 [file] [log] [blame]
// 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
// --------
// Thumbhash is a tiny image format (maximum image dimensions are 32×32 pixels;
// maximum image file size is 29 bytes or 32 bytes with a 3-byte header), based
// on the Discrete Cosine Transform.
//
// https://evanw.github.io/thumbhash/ has the original description and
// implementation. This implementation assumes a 3-byte magic identifier is
// prepended, unless QUIRK_JUST_RAW_THUMBHASH is enabled, so that /usr/bin/file
// or similar programs can identify the data as thumbhash-formatted data.
//
// That 3-byte magic string is "\xC3\xBE\xFE", which is arbitrary and not part
// of the original thumbhash description or implementation. But "\xC3\xBE" is
// the UTF-8 encoding of 'þ' (U+00FE LATIN SMALL LETTER THORN) and "\xFE" is
// invalid UTF-8 but is the ISO-8859-1 encoding of 'þ'. The Old English letter
// 'þ' is pronounced like the "th" that starts "thumbhash".
//
// This implementation also uses fixed point math instead of floating point.
pub status "#bad header"
pub status "#truncated input"
pub const DECODER_WORKBUF_LEN_MAX_INCL_WORST_CASE : base.u64 = 0
pub struct decoder? implements base.image_decoder(
pixfmt : base.u32,
w_dimension_code : base.u8[..= 7],
h_dimension_code : base.u8[..= 7],
// The call sequence state machine is discussed in
// (/doc/std/image-decoders-call-sequence.md).
call_sequence : base.u8,
frame_config_io_position : base.u8[..= 9],
// The L, P and Q DC values occupy 6 bits in the file format.
// Conceptually, they range from 0 to 1 (for L) or from -1 to +1 (for P
// and Q). That's what values are calculated in the original JavaScript
// reference implementation, which uses floating point.
//
// This implementation uses fixed point. The fx and fy cosine tables
// and the FROM_4_BITS_TO_PLUS_MINUS_ETC tables are all stored as 2.14
// signed fixed point (represented in this code as a base.u16, since
// Wuffs as of 2024 only speaks unsigned integers). Multiplying two of
// those uses 4.28 (as a base.u32). Multiplying three of those uses
// 22.42 (as a base.u64).
//
// We eventually combine L, P and Q values so we need them to use the
// same denominator. l_scale is out of 31 and p_scale and q_scale are
// out of 63, so the common denominator is ((63 * 31) << 42) =
// 8589_384836_186112 = 0x001E_8400_0000_0000.
//
// The A channel is not combined with L, P or Q and a_scale is out of
// 15, so we can keep the a_dc denominator at (15 << 42).
//
// Let LDENOM = ((63 * 31) << 14) = 0x01E8_4000.
//
// Let ADENOM = ( 15 << 14) = 0x0003_C000.
l_dc : base.u64, // Fixed-point denominator is (LDENOM << 28).
p_dc : base.u64, // Fixed-point denominator is (LDENOM << 28).
q_dc : base.u64, // Fixed-point denominator is (LDENOM << 28).
a_dc : base.u64, // Fixed-point denominator is (ADENOM << 28).
l_scale : base.u8[..= 31],
p_scale : base.u8[..= 63],
q_scale : base.u8[..= 63],
a_scale : base.u8[..= 15],
// The first three fields determine lx and ly: the exclusive max of the
// (cx, cy) pairs indexing two-dimensional AC coefficients. The (0, 0)
// pair is the DC coefficient. (lx / ly) is also the image's aspect
// ratio, after rounding width and height to a whole number of pixels
// such that the longest dimension is 32.
//
// Let lminor and lmajor alias lx and ly if is_landscape is 0 (false).
// If is_landscape is 1 (true) then these are swapped.
//
// lminor equals max(3, l_count). lmajor equals 7 or 5, depending on
// whether has_alpha is 0 or 1. For example, if has_alpha = 0, l_count
// = 5 and is_landscape = 1 then lx = 7 and ly = 5, so that width = 32
// and height = 23.
//
// lx and ly then detemine the number of L AC coefficients (the number
// of lac elements that are used), ranging in 10 ..= 27. The number for
// P and Q is always 5 and the number for A is 0 or 14, depending on
// has_alpha. Total file size is an optional 3-byte magic identifier
// plus (5 + has_alpha) bytes of header plus 4 bits per AC coefficient.
// As a table:
//
// When has_alpha = 0 and is_landscape = 0:
// l_count: 3 lx / ly = 3 / 7 w = 14 h = 32 #lac = 14 #pac = #qac = 5 #aac = 0 file_size = 3 + 17
// l_count: 4 lx / ly = 4 / 7 w = 18 h = 32 #lac = 18 #pac = #qac = 5 #aac = 0 file_size = 3 + 19
// l_count: 5 lx / ly = 5 / 7 w = 23 h = 32 #lac = 22 #pac = #qac = 5 #aac = 0 file_size = 3 + 21
// l_count: 6 lx / ly = 6 / 7 w = 27 h = 32 #lac = 26 #pac = #qac = 5 #aac = 0 file_size = 3 + 23
// l_count: 7 lx / ly = 7 / 7 w = 32 h = 32 #lac = 27 #pac = #qac = 5 #aac = 0 file_size = 3 + 24
//
// When has_alpha = 1 and is_landscape = 0:
// l_count: 3 lx / ly = 3 / 5 w = 19 h = 32 #lac = 10 #pac = #qac = 5 #aac = 14 file_size = 3 + 23
// l_count: 4 lx / ly = 4 / 5 w = 26 h = 32 #lac = 13 #pac = #qac = 5 #aac = 14 file_size = 3 + 25
// l_count: 5 lx / ly = 5 / 5 w = 32 h = 32 #lac = 14 #pac = #qac = 5 #aac = 14 file_size = 3 + 25
// l_count: 6 lx / ly = 6 / 5 w = 32 h = 27 #lac = 19 #pac = #qac = 5 #aac = 14 file_size = 3 + 28
// l_count: 7 lx / ly = 7 / 5 w = 32 h = 23 #lac = 22 #pac = #qac = 5 #aac = 14 file_size = 3 + 29
//
// A "(cx * ly) < (lx * (ly - cy))" constraint determines which L AC
// coefficients are used. Here are some visualizations, where 'D' is
// the DC coefficient. When has_alpha = 0:
// - the 14 '3' coefficients are always used,
// - the (18 - 14) '4' coefficients are also used when (l_count >= 4),
// - the (22 - 18) '5' coefficients are also used when (l_count >= 5),
// - the (26 - 22) '6' coefficients are also used when (l_count >= 6),
// - the (27 - 26) '7' coefficients are also used when (l_count >= 7),
//
// When has_alpha = 0:
// D334567
// 333456
// 33356
// 3346
// 335
// 34
// 3
//
// When has_alpha = 1:
// D334567
// 333467
// 33467
// 336
// 36
//
// The same visualization for P and Q:
// D33
// 33
// 3
//
// The same visualization for A (when has_alpha = 1):
// D3333
// 3333
// 333
// 33
// 3
has_alpha : base.u8[..= 1],
l_count : base.u8[..= 7],
is_landscape : base.u8[..= 1],
lx : base.u32[..= 7],
ly : base.u32[..= 7],
swizzler : base.pixel_swizzler,
util : base.utility,
) + (
// AC coefficients, doubled (†) when stored here to simplify the later
// (fx[cx] * fy[cy]) calculation in from_coeffs_to_pixels.
//
// Before doubling, these conceptually range from -1 to +1 (for L and
// A) or from -1.25 to +1.25 (for P and Q, boosted "to compensate for
// quantization" in the original JavaScript reference implementation).
//
// These AC values will eventually be multiplied by fx[cx] and fy[cy],
// both of which are 2.14 signed fixed point, so these fixed point
// values use the DC value denominators right-shifted by 28: LDENOM =
// ((63 * 31) << 14) for L, P and Q and ADENOM = (15 << 14) for A.
//
// For example, if lac[10] was 23998464 as a base.u32 here in Wuffs
// code, the corresponding undoubled floating point value in JavaScript
// code would be (23998464.0 / (2 * LDENOM)) = 0.375.
//
// Not every element is used, only the first up-to-27 (lac), 5 (pac and
// qac) or 14 (aac). But we round up the array sizes up to a power of 2
// (and bitwise-and the array indexes) to simplify bounds checking.
lac : array[32] base.u32, // Fixed-point denominator is LDENOM.
pac : array[8] base.u32, // Fixed-point denominator is LDENOM.
qac : array[8] base.u32, // Fixed-point denominator is LDENOM.
aac : array[16] base.u32, // Fixed-point denominator is ADENOM.
// 32 rows, 32 BGRA pixels per row.
pixels : array[32] array[128] base.u8,
)
pub func decoder.get_quirk(key: base.u32) base.u64 {
return 0
}
pub func decoder.set_quirk!(key: base.u32, value: base.u64) base.status {
return base."#unsupported option"
}
pub func decoder.decode_image_config?(dst: nptr base.image_config, src: base.io_reader) {
var status : base.status
while true {
status =? this.do_decode_image_config?(dst: args.dst, src: args.src)
if (status == base."$short read") and args.src.is_closed() {
return "#truncated input"
}
yield? status
}
}
pri func decoder.do_decode_image_config?(dst: nptr base.image_config, src: base.io_reader) {
var c32 : base.u32
var swap : base.u8[..= 7]
if this.call_sequence <> 0x00 {
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"
}
c32 = args.src.read_u24le_as_u32?()
this.l_dc = (((c32 >> 0x00) & 63) as base.u64) * (31 << 42)
this.p_dc = ((((c32 >> 0x06) & 63) as base.u64) * (31 << 43)) ~mod- ((63 * 31) << 42)
this.q_dc = ((((c32 >> 0x0C) & 63) as base.u64) * (31 << 43)) ~mod- ((63 * 31) << 42)
this.l_scale = ((c32 >> 0x12) & 31) as base.u8
this.has_alpha = ((c32 >> 0x17) & 1) as base.u8
c32 = args.src.read_u16le_as_u32?()
this.l_count = ((c32 >> 0x00) & 7) as base.u8
this.p_scale = ((c32 >> 0x03) & 63) as base.u8
this.q_scale = ((c32 >> 0x09) & 63) as base.u8
this.is_landscape = ((c32 >> 0x0F) & 1) as base.u8
this.w_dimension_code = (DIMENSION_CODES_FROM_HAS_ALPHA_AND_L_COUNT[this.has_alpha][this.l_count] >> 4) & 7
this.h_dimension_code = (DIMENSION_CODES_FROM_HAS_ALPHA_AND_L_COUNT[this.has_alpha][this.l_count] >> 0) & 7
if this.is_landscape <> 0 {
swap = this.w_dimension_code
this.w_dimension_code = this.h_dimension_code
this.h_dimension_code = swap
}
if this.is_landscape <> 0 {
this.lx = (7 - (2 * this.has_alpha)) as base.u32
this.ly = (this.l_count.max(no_less_than: 3)) as base.u32
} else {
this.lx = (this.l_count.max(no_less_than: 3)) as base.u32
this.ly = (7 - (2 * this.has_alpha)) as base.u32
}
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.pixfmt = base.PIXEL_FORMAT__BGRX
if this.has_alpha <> 0 {
this.pixfmt = base.PIXEL_FORMAT__BGRA_NONPREMUL
}
if args.dst <> nullptr {
args.dst.set!(
pixfmt: this.pixfmt,
pixsub: 0,
width: DIMENSIONS_FROM_DIMENSION_CODES[this.w_dimension_code] as base.u32,
height: DIMENSIONS_FROM_DIMENSION_CODES[this.h_dimension_code] as base.u32,
first_frame_io_position: this.frame_config_io_position as base.u64,
first_frame_is_opaque: this.has_alpha == 0)
}
this.call_sequence = 0x20
}
pub func decoder.decode_frame_config?(dst: nptr base.frame_config, src: base.io_reader) {
var status : base.status
while true {
status =? this.do_decode_frame_config?(dst: args.dst, src: args.src)
if (status == base."$short read") and args.src.is_closed() {
return "#truncated input"
}
yield? status
}
}
pri func decoder.do_decode_frame_config?(dst: nptr base.frame_config, src: base.io_reader) {
if this.call_sequence == 0x20 {
// No-op.
} else if this.call_sequence < 0x20 {
this.do_decode_image_config?(dst: nullptr, src: args.src)
} else if this.call_sequence == 0x28 {
if (this.frame_config_io_position as base.u64) <> args.src.position() {
return base."#bad restart"
}
} else if this.call_sequence == 0x40 {
this.call_sequence = 0x60
return base."@end of data"
} else {
return base."@end of data"
}
if args.dst <> nullptr {
args.dst.set!(bounds: this.util.make_rect_ie_u32(
min_incl_x: 0,
min_incl_y: 0,
max_excl_x: DIMENSIONS_FROM_DIMENSION_CODES[this.w_dimension_code] as base.u32,
max_excl_y: DIMENSIONS_FROM_DIMENSION_CODES[this.h_dimension_code] as base.u32),
duration: 0,
index: 0,
io_position: this.frame_config_io_position as base.u64,
disposal: 0,
opaque_within_bounds: this.has_alpha == 0,
overwrite_instead_of_blend: false,
background_color: 0x0000_0000)
}
this.call_sequence = 0x40
}
pub func decoder.decode_frame?(dst: ptr base.pixel_buffer, src: base.io_reader, blend: base.pixel_blend, workbuf: slice base.u8, opts: nptr base.decode_frame_options) {
var status : base.status
while true {
status =? this.do_decode_frame?(dst: args.dst, src: args.src, blend: args.blend, workbuf: args.workbuf, opts: args.opts)
if (status == base."$short read") and args.src.is_closed() {
return "#truncated input"
}
yield? status
}
}
pri func decoder.do_decode_frame?(dst: ptr base.pixel_buffer, src: base.io_reader, blend: base.pixel_blend, workbuf: slice base.u8, opts: nptr base.decode_frame_options) {
var status : base.status
if this.call_sequence == 0x40 {
// No-op.
} else if this.call_sequence < 0x40 {
this.do_decode_frame_config?(dst: nullptr, src: args.src)
} else {
return base."@end of data"
}
status = this.swizzler.prepare!(
dst_pixfmt: args.dst.pixel_format(),
dst_palette: args.dst.palette(),
src_pixfmt: this.util.make_pixel_format(repr: this.pixfmt),
src_palette: this.util.empty_slice_u8(),
blend: args.blend)
if not status.is_ok() {
return status
}
this.from_src_to_coeffs?(src: args.src)
this.from_coeffs_to_pixels!()
status = this.from_pixels_to_dst!(dst: args.dst)
if not status.is_ok() {
return status
}
this.call_sequence = 0x60
}
pri func decoder.from_src_to_coeffs?(src: base.io_reader) {
var c8 : base.u8
var cy : base.u32
var cx : base.u32
var i : base.u32
var has_bits : base.bool
// Read L AC coefficients.
i = 0
cy = 0
while cy < this.ly {
cx = 0
if cy == 0 {
cx = 1
}
while (cx ~mod* this.ly) < (this.lx ~mod* (this.ly ~mod- cy)) {
if has_bits {
has_bits = false
c8 >>= 4
} else {
has_bits = true
c8 = args.src.read_u8?()
}
// Multiply by (63 * 2) to double (†) and get to LDENOM.
this.lac[i & 31] = ((this.l_scale as base.u32) ~mod* 126) ~mod*
this.util.sign_extend_convert_u16_u32(a: FROM_4_BITS_TO_PLUS_MINUS_1_00[c8 & 15])
i ~mod+= 1
cx ~mod+= 1
}
cy ~mod+= 1
}
// Read P AC coefficients.
i = 0
cx = 0
while cx < 5 {
if has_bits {
has_bits = false
c8 >>= 4
} else {
has_bits = true
c8 = args.src.read_u8?()
}
// Multiply by (31 * 2) to double (†) and get to LDENOM.
this.pac[i & 7] = ((this.p_scale as base.u32) ~mod* 62) ~mod*
this.util.sign_extend_convert_u16_u32(a: FROM_4_BITS_TO_PLUS_MINUS_1_25[c8 & 15])
i ~mod+= 1
cx ~mod+= 1
}
// Read Q AC coefficients.
i = 0
cx = 0
while cx < 5 {
if has_bits {
has_bits = false
c8 >>= 4
} else {
has_bits = true
c8 = args.src.read_u8?()
}
// Multiply by (31 * 2) to double (†) and get to LDENOM.
this.qac[i & 7] = ((this.q_scale as base.u32) ~mod* 62) ~mod*
this.util.sign_extend_convert_u16_u32(a: FROM_4_BITS_TO_PLUS_MINUS_1_25[c8 & 15])
i ~mod+= 1
cx ~mod+= 1
}
// Read A AC coefficients.
if this.has_alpha == 0 {
return ok
}
i = 0
cx = 0
while cx < 14 {
if has_bits {
has_bits = false
c8 >>= 4
} else {
has_bits = true
c8 = args.src.read_u8?()
}
// Multiply by 2 to double (†). We're already at ADENOM.
this.aac[i & 15] = ((this.a_scale as base.u32) ~mod* 2) ~mod*
this.util.sign_extend_convert_u16_u32(a: FROM_4_BITS_TO_PLUS_MINUS_1_00[c8 & 15])
i ~mod+= 1
cx ~mod+= 1
}
}
pri func decoder.from_coeffs_to_pixels!() {
var h : base.u32[..= 32]
var w : base.u32[..= 32]
// fx[7] and fy[7] are unused but we round up the array sizes up to a power
// of 2 (and bitwise-and the array indexes) to simplify bounds checking.
var fy : array[8] base.u32
var fx : array[8] base.u32
var cosines_base_y : base.u32[..= 127]
var cosines_base_x : base.u32[..= 127]
var y : base.u32
var x : base.u32
var f : base.u32
var l : base.u64
var p : base.u64
var q : base.u64
var b : base.u64
var g : base.u64
var r : base.u64
var a : base.u64
var i : base.u32
var cy : base.u32
var cx : base.u32
h = DIMENSIONS_FROM_DIMENSION_CODES[this.h_dimension_code] as base.u32
w = DIMENSIONS_FROM_DIMENSION_CODES[this.w_dimension_code] as base.u32
fy[0] = 0x4000 // This is always cos(0), scaled by 0x4000 = (1 << 14).
fx[0] = 0x4000 // This is always cos(0), scaled by 0x4000 = (1 << 14).
a = 0xFF
y = 0
while y < h {
assert y < 32 via "a < b: a < c; c <= b"(c: h)
cosines_base_y = CUMULATIVE_DIMENSIONS[this.h_dimension_code] as base.u32
fy[1] = this.util.sign_extend_convert_u16_u32(a: COSINES[cosines_base_y + y][0])
fy[2] = this.util.sign_extend_convert_u16_u32(a: COSINES[cosines_base_y + y][1])
fy[3] = this.util.sign_extend_convert_u16_u32(a: COSINES[cosines_base_y + y][2])
fy[4] = this.util.sign_extend_convert_u16_u32(a: COSINES[cosines_base_y + y][3])
fy[5] = this.util.sign_extend_convert_u16_u32(a: COSINES[cosines_base_y + y][4])
fy[6] = this.util.sign_extend_convert_u16_u32(a: COSINES[cosines_base_y + y][5])
// The original JavaScript reference implementation also multiplied
// fy[cy] by 2 but we have already adjusted for that (†).
x = 0
while x < w,
inv y < 32,
{
assert x < 32 via "a < b: a < c; c <= b"(c: w)
cosines_base_x = CUMULATIVE_DIMENSIONS[this.w_dimension_code] as base.u32
fx[1] = this.util.sign_extend_convert_u16_u32(a: COSINES[cosines_base_x + x][0])
fx[2] = this.util.sign_extend_convert_u16_u32(a: COSINES[cosines_base_x + x][1])
fx[3] = this.util.sign_extend_convert_u16_u32(a: COSINES[cosines_base_x + x][2])
fx[4] = this.util.sign_extend_convert_u16_u32(a: COSINES[cosines_base_x + x][3])
fx[5] = this.util.sign_extend_convert_u16_u32(a: COSINES[cosines_base_x + x][4])
fx[6] = this.util.sign_extend_convert_u16_u32(a: COSINES[cosines_base_x + x][5])
// Accumulate L.
l = this.l_dc
i = 0
cy = 0
while cy < this.ly,
inv y < 32,
inv x < 32,
{
cx = 0
if cy == 0 {
cx = 1
}
while (cx ~mod* this.ly) < (this.lx ~mod* (this.ly ~mod- cy)),
inv y < 32,
inv x < 32,
{
f = fx[cx & 7] ~mod* fy[cy & 7]
l ~mod+= this.util.sign_extend_convert_u32_u64(a: f) ~mod*
this.util.sign_extend_convert_u32_u64(a: this.lac[i & 31])
i ~mod+= 1
cx ~mod+= 1
}
cy ~mod+= 1
}
// Accumulate P and Q.
p = this.p_dc
q = this.q_dc
i = 0
cy = 0
while cy < 3,
inv y < 32,
inv x < 32,
{
cx = 0
if cy == 0 {
cx = 1
}
while cx < (3 - cy),
inv y < 32,
inv x < 32,
inv cy < 3,
{
assert cx < 3 via "a < b: a < c; c <= b"(c: 3 - cy)
f = fx[cx] ~mod* fy[cy]
p ~mod+= this.util.sign_extend_convert_u32_u64(a: f) ~mod*
this.util.sign_extend_convert_u32_u64(a: this.pac[i & 7])
q ~mod+= this.util.sign_extend_convert_u32_u64(a: f) ~mod*
this.util.sign_extend_convert_u32_u64(a: this.qac[i & 7])
i ~mod+= 1
cx ~mod+= 1
}
cy ~mod+= 1
}
// Convert LPQ to RGB. The code is the i64 equivalents of:
// b = l - ((2 * p) / 3)
// r = (((3 * l) + q) - b) / 2
// g = r - q
b = l ~mod- this.util.i64_divide(a: 2 ~mod* p, b: 3)
r = this.util.sign_extend_rshift_u64(a: ((3 ~mod* l) ~mod+ q) ~mod- b, n: 1)
g = r ~mod- q
// Rescale from (LDENOM << 28) to 255 and clamp.
//
// (LDENOM << 28) = 8589_384836_186112 = (33_683862_102690 * 255) + 162
//
// Rounding down (instead of rounding to nearest) more closely
// matches the original JavaScript reference implementation. The
// two implementations aren't a 100% match, due to floating point
// versus fixed point rounding errors, but they're pretty close.
if (b >> 63) <> 0 {
b = 0
} else if b >= (33_683862_102690 * 255) {
b = 255
} else {
b /= 33_683862_102690
}
if (g >> 63) <> 0 {
g = 0
} else if g >= (33_683862_102690 * 255) {
g = 255
} else {
g /= 33_683862_102690
}
if (r >> 63) <> 0 {
r = 0
} else if r >= (33_683862_102690 * 255) {
r = 255
} else {
r /= 33_683862_102690
}
// Accumulate, rescale from (ADENOM << 28) and clamp A.
if this.has_alpha <> 0 {
a = this.a_dc
i = 0
cy = 0
while cy < 5,
inv y < 32,
inv x < 32,
{
cx = 0
if cy == 0 {
cx = 1
}
while cx < (5 - cy),
inv y < 32,
inv x < 32,
inv cy < 5,
{
assert cx < 5 via "a < b: a < c; c <= b"(c: 5 - cy)
f = fx[cx] ~mod* fy[cy]
a ~mod+= this.util.sign_extend_convert_u32_u64(a: f) ~mod*
this.util.sign_extend_convert_u32_u64(a: this.aac[i & 15])
i ~mod+= 1
cx ~mod+= 1
}
cy ~mod+= 1
}
// (ADENOM << 28) = 65_970697_666560 = (258708_618300 * 255) + 60
if (a >> 63) <> 0 {
a = 0
} else if a >= (258708_618300 * 255) {
a = 255
} else {
a /= 258708_618300
}
}
// Store the BGRA pixel.
this.pixels[y][(4 * x) + 0] = (b & 0xFF) as base.u8
this.pixels[y][(4 * x) + 1] = (g & 0xFF) as base.u8
this.pixels[y][(4 * x) + 2] = (r & 0xFF) as base.u8
this.pixels[y][(4 * x) + 3] = (a & 0xFF) as base.u8
x += 1
}
y += 1
}
}
pri func decoder.from_pixels_to_dst!(dst: ptr base.pixel_buffer) base.status {
var h : base.u32[..= 32]
var w : base.u32[..= 32]
var dst_pixfmt : base.pixel_format
var dst_bits_per_pixel : base.u32[..= 256]
var dst_bytes_per_pixel : base.u32[..= 32]
var dst_bytes_per_row : base.u64
var tab : table base.u8
var y : base.u32
var dst : slice base.u8
var src : slice base.u8
h = DIMENSIONS_FROM_DIMENSION_CODES[this.h_dimension_code] as base.u32
w = DIMENSIONS_FROM_DIMENSION_CODES[this.w_dimension_code] as base.u32
// TODO: the dst_pixfmt variable shouldn't be necessary. We should be able
// to chain the two calls: "args.dst.pixel_format().bits_per_pixel()".
dst_pixfmt = args.dst.pixel_format()
dst_bits_per_pixel = dst_pixfmt.bits_per_pixel()
if (dst_bits_per_pixel & 7) <> 0 {
return base."#unsupported option"
}
dst_bytes_per_pixel = dst_bits_per_pixel / 8
dst_bytes_per_row = (w * dst_bytes_per_pixel) as base.u64
tab = args.dst.plane(p: 0)
while y < h {
assert y < 32 via "a < b: a < c; c <= b"(c: h)
src = this.pixels[y][.. w * 4]
dst = tab.row_u32(y: y)
if dst_bytes_per_row < dst.length() {
dst = dst[.. dst_bytes_per_row]
}
this.swizzler.swizzle_interleaved_from_slice!(
dst: dst,
dst_palette: args.dst.palette(),
src: src)
y += 1
}
return ok
}
pub func decoder.frame_dirty_rect() base.rect_ie_u32 {
return this.util.make_rect_ie_u32(
min_incl_x: 0,
min_incl_y: 0,
max_excl_x: DIMENSIONS_FROM_DIMENSION_CODES[this.w_dimension_code] as base.u32,
max_excl_y: DIMENSIONS_FROM_DIMENSION_CODES[this.h_dimension_code] as base.u32)
}
pub func decoder.num_animation_loops() base.u32 {
return 0
}
pub func decoder.num_decoded_frame_configs() base.u64 {
if this.call_sequence > 0x20 {
return 1
}
return 0
}
pub func decoder.num_decoded_frames() base.u64 {
if this.call_sequence > 0x40 {
return 1
}
return 0
}
pub func decoder.restart_frame!(index: base.u64, io_position: base.u64) base.status {
if this.call_sequence < 0x20 {
return base."#bad call sequence"
}
if (args.index <> 0) or (args.io_position <> (this.frame_config_io_position as base.u64)) {
return base."#bad argument"
}
this.call_sequence = 0x28
return ok
}
pub func decoder.set_report_metadata!(fourcc: base.u32, report: base.bool) {
// No-op. Thumbhash doesn't support metadata.
}
pub func decoder.tell_me_more?(dst: base.io_writer, minfo: nptr base.more_information, src: base.io_reader) {
return base."#no more information"
}
pub func decoder.workbuf_len() base.range_ii_u64 {
return this.util.make_range_ii_u64(min_incl: 0, max_incl: 0)
}
// DIMENSIONS_FROM_DIMENSION_CODES enumerates all possible widths and heights.
pri const DIMENSIONS_FROM_DIMENSION_CODES : roarray[8] base.u8[..= 32] = [
0, 14, 18, 19, 23, 26, 27, 32,
]
// CUMULATIVE_DIMENSIONS is the sum-table of DIMENSIONS_FROM_DIMENSION_CODES.
pri const CUMULATIVE_DIMENSIONS : roarray[8] base.u8[..= 127] = [
0, 0, 14, 32, 51, 74, 100, 127,
]
// DIMENSION_CODES_FROM_HAS_ALPHA_AND_L_COUNT holds the width (high 4 bits) and
// height (low 4 bits) codes. For example, if has_alpha is 0 and l_count is 5
// then the 0x47 value means that the image is 23×32 (so the aspect ratio is
// 0.71875), because:
// - the width is DIMENSIONS_FROM_DIMENSION_CODES[4] = 23
// - the height is DIMENSIONS_FROM_DIMENSION_CODES[7] = 32
//
// If is_landscape is 1 (true) then the width and height are swapped.
pri const DIMENSION_CODES_FROM_HAS_ALPHA_AND_L_COUNT : roarray[2] roarray[8] base.u8 = [
[0x17, 0x17, 0x17, 0x17, 0x27, 0x47, 0x67, 0x77],
[0x37, 0x37, 0x37, 0x37, 0x57, 0x77, 0x76, 0x74],
]
// FROM_4_BITS_TO_PLUS_MINUS_1_00 converts AC coefficients to 2.14 fixed point.
pri const FROM_4_BITS_TO_PLUS_MINUS_1_00 : roarray[16] base.u16 = [
0xC000, // -1.000
0xC889, // -0.867
0xD111, // -0.733
0xD99A, // -0.600
0xE222, // -0.467
0xEAAB, // -0.333
0xF333, // -0.200
0xFBBC, // -0.067
0x0444, // +0.067
0x0CCD, // +0.200
0x1555, // +0.333
0x1DDE, // +0.467
0x2666, // +0.600
0x2EEF, // +0.733
0x3777, // +0.867
0x4000, // +1.000
]
// FROM_4_BITS_TO_PLUS_MINUS_1_25 converts AC coefficients to 2.14 fixed point.
pri const FROM_4_BITS_TO_PLUS_MINUS_1_25 : roarray[16] base.u16 = [
0xB000, // -1.250
0xBAAB, // -1.083
0xC555, // -0.917
0xD000, // -0.750
0xDAAB, // -0.583
0xE555, // -0.417
0xF000, // -0.250
0xFAAB, // -0.083
0x0555, // +0.083
0x1000, // +0.250
0x1AAB, // +0.417
0x2555, // +0.583
0x3000, // +0.750
0x3AAB, // +0.917
0x4555, // +1.083
0x5000, // +1.250
]
// COSINES[etc][cx - 1] holds cos(π * cx * (x + 0.5) / w) as 2.14 fixed point.
// Each 6-element row is copied to fx[1 .. 7] and fy[1 .. 7]. fx[0] and fy[0]
// are always equal to 0x4000, which is cos(0) as 2.14 fixed point. fx[7] and
// fy[7] are unused.
//
// (x + 0.5) ranges from (0 + 0.5) to (w - 0.5). Valid w values are given by
// the positive elements of DIMENSIONS_FROM_DIMENSION_CODES.
pri const COSINES : roarray[159] roarray[6] base.u16 = [
// w = 14
[0x3F99, 0x3E65, 0x3C69, 0x39A9, 0x3631, 0x320A], // x = 0
[0x3C69, 0x320A, 0x220D, 0x0E3E, 0xF8D6, 0xE43B], // x = 1
[0x3631, 0x1BC5, 0xF8D6, 0xD819, 0xC397, 0xC19B], // x = 2
[0x2D41, 0x0000, 0xD2BF, 0xC000, 0xD2BF, 0x0000], // x = 3
[0x220D, 0xE43B, 0xC067, 0xD819, 0x1523, 0x3E65], // x = 4
[0x1523, 0xCDF6, 0xC9CF, 0x0E3E, 0x3F99, 0x1BC5], // x = 5
[0x072A, 0xC19B, 0xEADD, 0x39A9, 0x220D, 0xCDF6], // x = 6
[0xF8D6, 0xC19B, 0x1523, 0x39A9, 0xDDF3, 0xCDF6], // x = 7
[0xEADD, 0xCDF6, 0x3631, 0x0E3E, 0xC067, 0x1BC5], // x = 8
[0xDDF3, 0xE43B, 0x3F99, 0xD819, 0xEADD, 0x3E65], // x = 9
[0xD2BF, 0x0000, 0x2D41, 0xC000, 0x2D41, 0x0000], // x = 10
[0xC9CF, 0x1BC5, 0x072A, 0xD819, 0x3C69, 0xC19B], // x = 11
[0xC397, 0x320A, 0xDDF3, 0x0E3E, 0x072A, 0xE43B], // x = 12
[0xC067, 0x3E65, 0xC397, 0x39A9, 0xC9CF, 0x320A], // x = 13
// w = 18
[0x3FC2, 0x3F07, 0x3DD2, 0x3C24, 0x3A01, 0x376D], // x = 0
[0x3DD2, 0x376D, 0x2D41, 0x2000, 0x1090, 0x0000], // x = 1
[0x3A01, 0x2923, 0x1090, 0xF4E3, 0xDB4B, 0xC893], // x = 2
[0x346D, 0x15E4, 0xEF70, 0xCEF9, 0xC03E, 0xC893], // x = 3
[0x2D41, 0x0000, 0xD2BF, 0xC000, 0xD2BF, 0x0000], // x = 4
[0x24B5, 0xEA1C, 0xC22E, 0xCEF9, 0x0594, 0x376D], // x = 5
[0x1B0C, 0xD6DD, 0xC22E, 0xF4E3, 0x346D, 0x376D], // x = 6
[0x1090, 0xC893, 0xD2BF, 0x2000, 0x3DD2, 0x0000], // x = 7
[0x0594, 0xC0F9, 0xEF70, 0x3C24, 0x1B0C, 0xC893], // x = 8
[0xFA6C, 0xC0F9, 0x1090, 0x3C24, 0xE4F4, 0xC893], // x = 9
[0xEF70, 0xC893, 0x2D41, 0x2000, 0xC22E, 0x0000], // x = 10
[0xE4F4, 0xD6DD, 0x3DD2, 0xF4E3, 0xCB93, 0x376D], // x = 11
[0xDB4B, 0xEA1C, 0x3DD2, 0xCEF9, 0xFA6C, 0x376D], // x = 12
[0xD2BF, 0x0000, 0x2D41, 0xC000, 0x2D41, 0x0000], // x = 13
[0xCB93, 0x15E4, 0x1090, 0xCEF9, 0x3FC2, 0xC893], // x = 14
[0xC5FF, 0x2923, 0xEF70, 0xF4E3, 0x24B5, 0xC893], // x = 15
[0xC22E, 0x376D, 0xD2BF, 0x2000, 0xEF70, 0x0000], // x = 16
[0xC03E, 0x3F07, 0xC22E, 0x3C24, 0xC5FF, 0x376D], // x = 17
// w = 19
[0x3FC8, 0x3F21, 0x3E0B, 0x3C88, 0x3A9C, 0x3849], // x = 0
[0x3E0B, 0x3849, 0x2F16, 0x2301, 0x14C8, 0x0549], // x = 1
[0x3A9C, 0x2B59, 0x14C8, 0xFAB7, 0xE18A, 0xCD7F], // x = 2
[0x3594, 0x19B5, 0xF577, 0xD4A7, 0xC1F5, 0xC378], // x = 3
[0x2F16, 0x0549, 0xD8B1, 0xC0DF, 0xCA6C, 0xF04A], // x = 4
[0x274F, 0xF04A, 0xC564, 0xC7B7, 0xF577, 0x2B59], // x = 5
[0x1E76, 0xDCFF, 0xC038, 0xE64B, 0x274F, 0x3F21], // x = 6
[0x14C8, 0xCD7F, 0xCA6C, 0x0FB6, 0x3FC8, 0x19B5], // x = 7
[0x0A89, 0xC378, 0xE18A, 0x3281, 0x2F16, 0xDCFF], // x = 8
[0x0000, 0xC000, 0x0000, 0x4000, 0x0000, 0xC000], // x = 9
[0xF577, 0xC378, 0x1E76, 0x3281, 0xD0EA, 0xDCFF], // x = 10
[0xEB38, 0xCD7F, 0x3594, 0x0FB6, 0xC038, 0x19B5], // x = 11
[0xE18A, 0xDCFF, 0x3FC8, 0xE64B, 0xD8B1, 0x3F21], // x = 12
[0xD8B1, 0xF04A, 0x3A9C, 0xC7B7, 0x0A89, 0x2B59], // x = 13
[0xD0EA, 0x0549, 0x274F, 0xC0DF, 0x3594, 0xF04A], // x = 14
[0xCA6C, 0x19B5, 0x0A89, 0xD4A7, 0x3E0B, 0xC378], // x = 15
[0xC564, 0x2B59, 0xEB38, 0xFAB7, 0x1E76, 0xCD7F], // x = 16
[0xC1F5, 0x3849, 0xD0EA, 0x2301, 0xEB38, 0x0549], // x = 17
[0xC038, 0x3F21, 0xC1F5, 0x3C88, 0xC564, 0x3849], // x = 18
// w = 23
[0x3FDA, 0x3F67, 0x3EA9, 0x3DA0, 0x3C4E, 0x3AB4], // x = 0
[0x3EA9, 0x3AB4, 0x3449, 0x2BAF, 0x2141, 0x156F], // x = 1
[0x3C4E, 0x31A5, 0x2141, 0x0D05, 0xF749, 0xE28E], // x = 2
[0x38D3, 0x24E8, 0x08B7, 0xEA91, 0xD13A, 0xC260], // x = 3
[0x3449, 0x156F, 0xEEBC, 0xCE5B, 0xC026, 0xC951], // x = 4
[0x2EC6, 0x045E, 0xD79C, 0xC099, 0xCBB7, 0xF2FB], // x = 5
[0x2864, 0xF2FB, 0xC72D, 0xC54C, 0xEEBC, 0x24E8], // x = 6
[0x2141, 0xE28E, 0xC026, 0xDB18, 0x197F, 0x3F67], // x = 7
[0x197F, 0xD451, 0xC3B2, 0xFBA2, 0x38D3, 0x31A5], // x = 8
[0x1144, 0xC951, 0xD13A, 0x1D72, 0x3EA9, 0x045E], // x = 9
[0x08B7, 0xC260, 0xE681, 0x36AF, 0x2864, 0xD451], // x = 10
[0x0000, 0xC000, 0x0000, 0x4000, 0x0000, 0xC000], // x = 11
[0xF749, 0xC260, 0x197F, 0x36AF, 0xD79C, 0xD451], // x = 12
[0xEEBC, 0xC951, 0x2EC6, 0x1D72, 0xC157, 0x045E], // x = 13
[0xE681, 0xD451, 0x3C4E, 0xFBA2, 0xC72D, 0x31A5], // x = 14
[0xDEBF, 0xE28E, 0x3FDA, 0xDB18, 0xE681, 0x3F67], // x = 15
[0xD79C, 0xF2FB, 0x38D3, 0xC54C, 0x1144, 0x24E8], // x = 16
[0xD13A, 0x045E, 0x2864, 0xC099, 0x3449, 0xF2FB], // x = 17
[0xCBB7, 0x156F, 0x1144, 0xCE5B, 0x3FDA, 0xC951], // x = 18
[0xC72D, 0x24E8, 0xF749, 0xEA91, 0x2EC6, 0xC260], // x = 19
[0xC3B2, 0x31A5, 0xDEBF, 0x0D05, 0x08B7, 0xE28E], // x = 20
[0xC157, 0x3AB4, 0xCBB7, 0x2BAF, 0xDEBF, 0x156F], // x = 21
[0xC026, 0x3F67, 0xC157, 0x3DA0, 0xC3B2, 0x3AB4], // x = 22
// w = 26
[0x3FE2, 0x3F89, 0x3EF4, 0x3E24, 0x3D1A, 0x3BD7], // x = 0
[0x3EF4, 0x3BD7, 0x36C5, 0x2FE8, 0x2778, 0x1DBE], // x = 1
[0x3D1A, 0x34AC, 0x2778, 0x16B2, 0x03DD, 0xF0AF], // x = 2
[0x3A5D, 0x2A71, 0x130A, 0xF849, 0xDEE4, 0xCB54], // x = 3
[0x36C5, 0x1DBE, 0xFC23, 0xDBA5, 0xC5A3, 0xC077], // x = 4
[0x3261, 0x0F51, 0xE5BC, 0xC755, 0xC10C, 0xD58F], // x = 5
[0x2D41, 0x0000, 0xD2BF, 0xC000, 0xD2BF, 0x0000], // x = 6
[0x2778, 0xF0AF, 0xC5A3, 0xC755, 0xF477, 0x2A71], // x = 7
[0x211C, 0xE242, 0xC01E, 0xDBA5, 0x1A44, 0x3F89], // x = 8
[0x1A44, 0xD58F, 0xC2E6, 0xF849, 0x36C5, 0x34AC], // x = 9
[0x130A, 0xCB54, 0xCD9F, 0x16B2, 0x3FE2, 0x0F51], // x = 10
[0x0B89, 0xC429, 0xDEE4, 0x2FE8, 0x3261, 0xE242], // x = 11
[0x03DD, 0xC077, 0xF477, 0x3E24, 0x130A, 0xC429], // x = 12
[0xFC23, 0xC077, 0x0B89, 0x3E24, 0xECF6, 0xC429], // x = 13
[0xF477, 0xC429, 0x211C, 0x2FE8, 0xCD9F, 0xE242], // x = 14
[0xECF6, 0xCB54, 0x3261, 0x16B2, 0xC01E, 0x0F51], // x = 15
[0xE5BC, 0xD58F, 0x3D1A, 0xF849, 0xC93B, 0x34AC], // x = 16
[0xDEE4, 0xE242, 0x3FE2, 0xDBA5, 0xE5BC, 0x3F89], // x = 17
[0xD888, 0xF0AF, 0x3A5D, 0xC755, 0x0B89, 0x2A71], // x = 18
[0xD2BF, 0x0000, 0x2D41, 0xC000, 0x2D41, 0x0000], // x = 19
[0xCD9F, 0x0F51, 0x1A44, 0xC755, 0x3EF4, 0xD58F], // x = 20
[0xC93B, 0x1DBE, 0x03DD, 0xDBA5, 0x3A5D, 0xC077], // x = 21
[0xC5A3, 0x2A71, 0xECF6, 0xF849, 0x211C, 0xCB54], // x = 22
[0xC2E6, 0x34AC, 0xD888, 0x16B2, 0xFC23, 0xF0AF], // x = 23
[0xC10C, 0x3BD7, 0xC93B, 0x2FE8, 0xD888, 0x1DBE], // x = 24
[0xC01E, 0x3F89, 0xC10C, 0x3E24, 0xC2E6, 0x3BD7], // x = 25
// w = 27
[0x3FE4, 0x3F91, 0x3F07, 0x3E46, 0x3D50, 0x3C24], // x = 0
[0x3F07, 0x3C24, 0x376D, 0x3107, 0x2923, 0x2000], // x = 1
[0x3D50, 0x3579, 0x2923, 0x1959, 0x076E, 0xF4E3], // x = 2
[0x3AC4, 0x2BEB, 0x15E4, 0xFC47, 0xE347, 0xCEF9], // x = 3
[0x376D, 0x2000, 0x0000, 0xE000, 0xC893, 0xC000], // x = 4
[0x3356, 0x125B, 0xEA1C, 0xCA87, 0xC01C, 0xCEF9], // x = 5
[0x2E8D, 0x03B9, 0xD6DD, 0xC06F, 0xCCAA, 0xF4E3], // x = 6
[0x2923, 0xF4E3, 0xC893, 0xC3DC, 0xEA1C, 0x2000], // x = 7
[0x232B, 0xE6A7, 0xC0F9, 0xD415, 0x0EC2, 0x3C24], // x = 8
[0x1CB9, 0xD9C8, 0xC0F9, 0xEDA5, 0x2E8D, 0x3C24], // x = 9
[0x15E4, 0xCEF9, 0xC893, 0x0B1D, 0x3F07, 0x2000], // x = 10
[0x0EC2, 0xC6CF, 0xD6DD, 0x2638, 0x3AC4, 0xF4E3], // x = 11
[0x076E, 0xC1BA, 0xEA1C, 0x3931, 0x232B, 0xCEF9], // x = 12
[0x0000, 0xC000, 0x0000, 0x4000, 0x0000, 0xC000], // x = 13
[0xF892, 0xC1BA, 0x15E4, 0x3931, 0xDCD5, 0xCEF9], // x = 14
[0xF13E, 0xC6CF, 0x2923, 0x2638, 0xC53C, 0xF4E3], // x = 15
[0xEA1C, 0xCEF9, 0x376D, 0x0B1D, 0xC0F9, 0x2000], // x = 16
[0xE347, 0xD9C8, 0x3F07, 0xEDA5, 0xD173, 0x3C24], // x = 17
[0xDCD5, 0xE6A7, 0x3F07, 0xD415, 0xF13E, 0x3C24], // x = 18
[0xD6DD, 0xF4E3, 0x376D, 0xC3DC, 0x15E4, 0x2000], // x = 19
[0xD173, 0x03B9, 0x2923, 0xC06F, 0x3356, 0xF4E3], // x = 20
[0xCCAA, 0x125B, 0x15E4, 0xCA87, 0x3FE4, 0xCEF9], // x = 21
[0xC893, 0x2000, 0x0000, 0xE000, 0x376D, 0xC000], // x = 22
[0xC53C, 0x2BEB, 0xEA1C, 0xFC47, 0x1CB9, 0xCEF9], // x = 23
[0xC2B0, 0x3579, 0xD6DD, 0x1959, 0xF892, 0xF4E3], // x = 24
[0xC0F9, 0x3C24, 0xC893, 0x3107, 0xD6DD, 0x2000], // x = 25
[0xC01C, 0x3F91, 0xC0F9, 0x3E46, 0xC2B0, 0x3C24], // x = 26
// w = 32
[0x3FEC, 0x3FB1, 0x3F4F, 0x3EC5, 0x3E15, 0x3D3F], // x = 0
[0x3F4F, 0x3D3F, 0x39DB, 0x3537, 0x2F6C, 0x289A], // x = 1
[0x3E15, 0x3871, 0x2F6C, 0x238E, 0x1590, 0x0646], // x = 2
[0x3C42, 0x3179, 0x20E7, 0x0C7C, 0xF69C, 0xE1D5], // x = 3
[0x39DB, 0x289A, 0x0F8D, 0xF384, 0xD9E0, 0xC78F], // x = 4
[0x36E5, 0x1E2B, 0xFCDC, 0xDC72, 0xC625, 0xC04F], // x = 5
[0x3368, 0x1294, 0xEA70, 0xCAC9, 0xC014, 0xCE87], // x = 6
[0x2F6C, 0x0646, 0xD9E0, 0xC13B, 0xC91B, 0xED6C], // x = 7
[0x2AFB, 0xF9BA, 0xCC98, 0xC13B, 0xDF19, 0x1294], // x = 8
[0x2620, 0xED6C, 0xC3BE, 0xCAC9, 0xFCDC, 0x3179], // x = 9
[0x20E7, 0xE1D5, 0xC014, 0xDC72, 0x1B5D, 0x3FB1], // x = 10
[0x1B5D, 0xD766, 0xC1EB, 0xF384, 0x3368, 0x3871], // x = 11
[0x1590, 0xCE87, 0xC91B, 0x0C7C, 0x3F4F, 0x1E2B], // x = 12
[0x0F8D, 0xC78F, 0xD505, 0x238E, 0x3C42, 0xF9BA], // x = 13
[0x0964, 0xC2C1, 0xE4A3, 0x3537, 0x2AFB, 0xD766], // x = 14
[0x0324, 0xC04F, 0xF69C, 0x3EC5, 0x0F8D, 0xC2C1], // x = 15
[0xFCDC, 0xC04F, 0x0964, 0x3EC5, 0xF073, 0xC2C1], // x = 16
[0xF69C, 0xC2C1, 0x1B5D, 0x3537, 0xD505, 0xD766], // x = 17
[0xF073, 0xC78F, 0x2AFB, 0x238E, 0xC3BE, 0xF9BA], // x = 18
[0xEA70, 0xCE87, 0x36E5, 0x0C7C, 0xC0B1, 0x1E2B], // x = 19
[0xE4A3, 0xD766, 0x3E15, 0xF384, 0xCC98, 0x3871], // x = 20
[0xDF19, 0xE1D5, 0x3FEC, 0xDC72, 0xE4A3, 0x3FB1], // x = 21
[0xD9E0, 0xED6C, 0x3C42, 0xCAC9, 0x0324, 0x3179], // x = 22
[0xD505, 0xF9BA, 0x3368, 0xC13B, 0x20E7, 0x1294], // x = 23
[0xD094, 0x0646, 0x2620, 0xC13B, 0x36E5, 0xED6C], // x = 24
[0xCC98, 0x1294, 0x1590, 0xCAC9, 0x3FEC, 0xCE87], // x = 25
[0xC91B, 0x1E2B, 0x0324, 0xDC72, 0x39DB, 0xC04F], // x = 26
[0xC625, 0x289A, 0xF073, 0xF384, 0x2620, 0xC78F], // x = 27
[0xC3BE, 0x3179, 0xDF19, 0x0C7C, 0x0964, 0xE1D5], // x = 28
[0xC1EB, 0x3871, 0xD094, 0x238E, 0xEA70, 0x0646], // x = 29
[0xC0B1, 0x3D3F, 0xC625, 0x3537, 0xD094, 0x289A], // x = 30
[0xC014, 0x3FB1, 0xC0B1, 0x3EC5, 0xC1EB, 0x3D3F], // x = 31
]