blob: 668c30acc0d246ca676a72de5674ed382b14ceaa [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
pri func decoder.decode_pixels_slow?(dst: slice base.u8, src: base.io_reader, width: base.u32[..= 0x4000], height: base.u32[..= 0x4000], tile_data: roslice base.u8, tile_size_log2: base.u32[..= 9]) {
var c8 : base.u8
var p : base.u64
var p_max : base.u64[..= 0x4000_0000]
var tile_size_log2 : base.u32[..= 31]
var width_in_tiles : base.u32[..= 0x20FF]
var x : base.u32
var y : base.u32
var i : base.u32
var hg : base.u32[..= 0xFF]
var h : base.u32[..= 0x187A]
var node : base.u16
var pixel_g : base.u32[..= 0x7FFF]
var color : base.u32 // u32 0xAARR_GGBB, non-premultiplied alpha.
var dst_pixel : slice base.u8
var back_ref_len_n_bits : base.u32[..= 11]
var back_ref_len_minus_1 : base.u32[..= 0x1FFF] // 0x1FFF = 8191.
var back_ref_dist_n_bits : base.u32[..= 18]
var back_ref_dist_sym : base.u32[..= 0x7FFF]
var back_ref_dist_premap_minus_1 : base.u32[..= 0xF_FFFF] // 0xF_FFFF = 1048575.
var back_ref_dist_minus_1 : base.u32
var dm : base.u32[..= 0xFF]
var dx : base.u32
var dy : base.u32
var p_end : base.u64[..= 0x4000_8000]
var dist4 : base.u64
var q : base.u64
var color_cache_pixels : slice base.u8
var color_cache_p : base.u64
var color_cache_shift : base.u32[..= 31]
p_max = (4 * args.width * args.height) as base.u64
if args.dst.length() < p_max {
return "#internal error: inconsistent dst buffer"
}
if args.tile_size_log2 <> 0 {
tile_size_log2 = args.tile_size_log2
width_in_tiles = (args.width + (((1 as base.u32) << tile_size_log2) - 1)) >> tile_size_log2
} else {
tile_size_log2 = 31
width_in_tiles = 1
}
while p < p_max {
// The "~mod+ 1" selects the green pixel of the BGRA 4-byte group.
i = ((((y >> tile_size_log2) ~mod* width_in_tiles) ~mod+ (x >> tile_size_log2)) ~mod* 4) ~mod+ 1
if (i as base.u64) < args.tile_data.length() {
hg = args.tile_data[i as base.u64] as base.u32
}
// Decode the Green+etc symbol.
h = HUFFMAN_TABLE_BASE_OFFSETS[0] as base.u32
while true,
inv p < p_max,
{
node = this.huffman_nodes[hg][h]
if node >= 0x8000 {
break
} else if node > 0x1879 {
return "#internal error: inconsistent Huffman code"
}
if this.n_bits < 1 {
c8 = args.src.read_u8?()
this.bits = (c8 as base.u32)
this.n_bits = 8
assert this.n_bits >= 1
}
h = (node as base.u32) + (this.bits & 1)
this.bits >>= 1
this.n_bits -= 1
}
pixel_g = (node & 0x7FFF) as base.u32
if pixel_g < 0x100 { // Literal pixel.
color = pixel_g << 8
// Decode the Red symbol.
h = HUFFMAN_TABLE_BASE_OFFSETS[1] as base.u32
while true,
inv p < p_max,
{
node = this.huffman_nodes[hg][h]
if node >= 0x8000 {
break
}
if this.n_bits < 1 {
c8 = args.src.read_u8?()
this.bits = (c8 as base.u32)
this.n_bits = 8
assert this.n_bits >= 1
}
h = ((node as base.u32) & 0xFFF) + (this.bits & 1)
this.bits >>= 1
this.n_bits -= 1
}
color |= ((node & 0xFF) as base.u32) << 16
// Decode the Blue symbol.
h = HUFFMAN_TABLE_BASE_OFFSETS[2] as base.u32
while true,
inv p < p_max,
{
node = this.huffman_nodes[hg][h]
if node >= 0x8000 {
break
}
if this.n_bits < 1 {
c8 = args.src.read_u8?()
this.bits = (c8 as base.u32)
this.n_bits = 8
assert this.n_bits >= 1
}
h = ((node as base.u32) & 0xFFF) + (this.bits & 1)
this.bits >>= 1
this.n_bits -= 1
}
color |= ((node & 0xFF) as base.u32) << 0
// Decode the Alpha symbol.
h = HUFFMAN_TABLE_BASE_OFFSETS[3] as base.u32
while true,
inv p < p_max,
{
node = this.huffman_nodes[hg][h]
if node >= 0x8000 {
break
}
if this.n_bits < 1 {
c8 = args.src.read_u8?()
this.bits = (c8 as base.u32)
this.n_bits = 8
assert this.n_bits >= 1
}
h = ((node as base.u32) & 0xFFF) + (this.bits & 1)
this.bits >>= 1
this.n_bits -= 1
}
color |= ((node & 0xFF) as base.u32) << 24
} else if pixel_g < 0x118 { // Back-ref pixel.
// Decode the back-ref length.
if pixel_g < 0x104 {
back_ref_len_minus_1 = pixel_g - 0x100
} else {
back_ref_len_n_bits = (pixel_g - 0x102) >> 1
back_ref_len_minus_1 = ((2 as base.u32) + (pixel_g & 1)) << back_ref_len_n_bits
assert back_ref_len_minus_1 <= 6144
while this.n_bits < back_ref_len_n_bits,
inv p < p_max,
inv back_ref_len_minus_1 <= 6144,
post this.n_bits >= back_ref_len_n_bits,
{
c8 = args.src.read_u8?()
if this.n_bits >= back_ref_len_n_bits {
return "#internal error: inconsistent n_bits"
}
assert this.n_bits < 12 via "a < b: a < c; c <= b"(c: back_ref_len_n_bits)
this.bits |= (c8 as base.u32) << this.n_bits
this.n_bits += 8
}
back_ref_len_minus_1 += this.bits & (((1 as base.u32) << back_ref_len_n_bits) - 1)
this.bits >>= back_ref_len_n_bits
this.n_bits -= back_ref_len_n_bits
}
// Decode the back-ref distance.
h = HUFFMAN_TABLE_BASE_OFFSETS[4] as base.u32
while true,
inv p < p_max,
{
node = this.huffman_nodes[hg][h]
if node >= 0x8000 {
break
}
if this.n_bits < 1 {
c8 = args.src.read_u8?()
this.bits = (c8 as base.u32)
this.n_bits = 8
assert this.n_bits >= 1
}
h = ((node as base.u32) & 0xFFF) + (this.bits & 1)
this.bits >>= 1
this.n_bits -= 1
}
back_ref_dist_sym = (node & 0x7FFF) as base.u32
if back_ref_dist_sym < 4 {
back_ref_dist_premap_minus_1 = back_ref_dist_sym
} else if back_ref_dist_sym < 40 {
back_ref_dist_n_bits = (back_ref_dist_sym - 2) >> 1
back_ref_dist_premap_minus_1 = ((2 as base.u32) + (back_ref_dist_sym & 1)) << back_ref_dist_n_bits
assert back_ref_dist_premap_minus_1 <= 786432
while this.n_bits < back_ref_dist_n_bits,
inv p < p_max,
inv back_ref_dist_premap_minus_1 <= 786432,
post this.n_bits >= back_ref_dist_n_bits,
{
c8 = args.src.read_u8?()
if this.n_bits >= back_ref_dist_n_bits {
return "#internal error: inconsistent n_bits"
}
assert this.n_bits < 24 via "a < b: a < c; c <= b"(c: back_ref_dist_n_bits)
this.bits |= (c8 as base.u32) << this.n_bits
this.n_bits += 8
}
back_ref_dist_premap_minus_1 += this.bits & (((1 as base.u32) << back_ref_dist_n_bits) - 1)
this.bits >>= back_ref_dist_n_bits
this.n_bits -= back_ref_dist_n_bits
}
if back_ref_dist_premap_minus_1 >= 120 {
back_ref_dist_minus_1 = back_ref_dist_premap_minus_1 - 120
} else {
dm = DISTANCE_MAP[back_ref_dist_premap_minus_1] as base.u32
dy = dm >> 4
dx = 7 ~mod- (dm & 15)
back_ref_dist_minus_1 = (args.width * dy) ~mod+ dx
}
// Apply the (back_ref_len_minus_1, back_ref_dist_minus_1) pair.
assert p < 0x4000_0000 via "a < b: a < c; c <= b"(c: p_max)
p_end = p + (((back_ref_len_minus_1 + 1) * 4) as base.u64)
dist4 = ((back_ref_dist_minus_1 as base.u64) * 4) + 4
if (p_end > p_max) or (p_end > args.dst.length()) or (p < dist4) {
return "#bad back-reference"
}
q = p - dist4
while (q < p) and (p < p_end),
inv p_end <= args.dst.length(),
{
assert q < p_end via "a < b: a < c; c < b"(c: p)
assert p < 0x4000_8000 via "a < b: a < c; c <= b"(c: p_end)
assert q < 0x4000_8000 via "a < b: a < c; c <= b"(c: p_end)
assert p < args.dst.length() via "a < b: a < c; c <= b"(c: p_end)
assert q < args.dst.length() via "a < b: a < c; c <= b"(c: p_end)
args.dst[p] = args.dst[q]
p += 1
q += 1
}
// Update (x, y).
x ~mod+= back_ref_len_minus_1 + 1
while x >= args.width {
x -= args.width
y ~mod+= 1
}
continue
} else { // Color cache pixel.
// Insert previous pixels into this.color_cache.
if (color_cache_p > p) or (p > args.dst.length()) {
return "#internal error: inconsistent dst buffer"
}
color_cache_pixels = args.dst[color_cache_p .. p]
color_cache_p = p
color_cache_shift = (32 - this.color_cache_bits) & 31
while color_cache_pixels.length() >= 4,
inv pixel_g >= 0x118,
{
color = color_cache_pixels.peek_u32le()
this.color_cache[((color ~mod* 0x1E35_A7BD) >> color_cache_shift) & 2047] = color
color_cache_pixels = color_cache_pixels[4 ..]
}
// Look up this.color_cache.
color = this.color_cache[(pixel_g - 0x118) & 2047]
}
// Set the dst pixel to the color.
if p > args.dst.length() {
return "#internal error: inconsistent dst buffer"
}
dst_pixel = args.dst[p ..]
if dst_pixel.length() < 4 {
return "#internal error: inconsistent dst buffer"
}
dst_pixel.poke_u32le!(a: color)
p ~mod+= 4
// Update (x, y).
x ~mod+= 1
if x == args.width {
x = 0
y ~mod+= 1
}
}
}