blob: 0589aabf84af86cc3d0f4b9b8c688c33bcfea42b [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
use "std/vp8"
pub status "#bad Huffman code (over-subscribed)"
pub status "#bad Huffman code (under-subscribed)"
pub status "#bad Huffman code"
pub status "#bad back-reference"
pub status "#bad color cache"
pub status "#bad header"
pub status "#bad transform"
pub status "#short chunk"
pub status "#truncated input"
pub status "#unsupported number of Huffman groups"
pub status "#unsupported transform after color indexing transform"
pub status "#unsupported WebP file"
pri status "#internal error: inconsistent Huffman code"
pri status "#internal error: inconsistent dst buffer"
pri status "#internal error: inconsistent n_bits"
pub const DECODER_WORKBUF_LEN_MAX_INCL_WORST_CASE : base.u64 = 0
pub struct decoder? implements base.image_decoder(
pixfmt : base.u32,
width : base.u32[..= 0x4000],
height : base.u32[..= 0x4000],
// The call sequence state machine is discussed in
// (/doc/std/image-decoders-call-sequence.md).
call_sequence : base.u8,
code_length_code_lengths : array[19] base.u8[..= 7],
sub_chunk_has_padding : base.bool,
is_vp8_lossy : base.bool,
frame_config_io_position : base.u64,
riff_chunk_length : base.u32,
sub_chunk_length : base.u32,
bits : base.u32,
n_bits : base.u32[..= 31],
seen_transform : array[4] base.bool,
transform_type : array[4] base.u8[..= 3],
transform_tile_size_log2 : array[4] base.u8[..= 9],
n_transforms : base.u32[..= 4],
color_cache_bits : base.u32[..= 11],
overall_color_cache_bits : base.u32[..= 11],
overall_tile_size_log2 : base.u32[..= 9],
overall_n_huffman_groups : base.u32[..= 256],
ht_n_symbols : base.u32[..= 2328],
ht_code_lengths_remaining : base.u32,
color_indexing_palette_size : base.u32[..= 256],
color_indexing_width : base.u32[..= 0x4000],
// The 0th element is for the Meta (Huffman Group) selectors.
// The 1st element is for the Predictor transform.
// The 2nd element is for the Cross Color transform.
// The 3rd element is overall workbuf length.
workbuf_offset_for_transform : array[4] base.u32[..= 0x4C00_0000],
// If the Color Indexing transform is present then workbuf[i .. j]
// holds the pre-transformed pixel data (the Green component of the
// pixel's bgra data holds color indexes) and workbuf[0 .. j] holds the
// post-transformed data:
//
// - i is workbuf_offset_for_color_indexing
// - j is workbuf_offset_for_transform[0]
//
// i is calculated as:
//
// - if there are 8 bits per color index then i is 0.
// - if there are 4 bits per color index then i is j*1/2, roughly.
// - if there are 2 bits per color index then i is j*3/4, roughly.
// - if there are 1 bits per color index then i is j*7/8, roughly.
//
// The two slices (workbuf[i .. j] and workbuf[0 .. j]) overlap.
// apply_transform_color_indexing modifies the pixel buffer in-place.
workbuf_offset_for_color_indexing : base.u32[..= 0x4000_0000],
vp8 : vp8.decoder,
swizzler : base.pixel_swizzler,
util : base.utility,
) + (
palette : array[4 * 256] base.u8,
color_cache : array[2048] base.u32,
codes : array[2328] base.u16,
code_lengths : array[2328] base.u16,
// Each node is a base.u16 whose bit patterns are:
//
// - 0000_0000_0000_0000 Invalid.
// - 0CCC_CCCC_CCCC_CCCC Branch node (children_offset is CCC).
// - 1SSS_SSSS_SSSS_SSSS Leaf node (symbol is SSS, it may be zero).
code_lengths_huffman_nodes : array[37] base.u16,
// A Huffman group has five (5) Huffman trees.
//
// Start .. End Size
// 0x064C .. 0x187B 0x122F (1) Green, back-ref length, color cache.
// 0x0000 .. 0x01FF 0x01FF (2) Red.
// 0x01FF .. 0x03FE 0x01FF (3) Blue.
// 0x03FE .. 0x05FD 0x01FF (4) Alpha.
// 0x05FD .. 0x064C 0x004F (5) Back-ref distance.
//
// The Green+etc tree is last (in terms of start offset) because it's
// by far the largest (worst case) and its color cache component has
// variable length.
//
// 0x122F = 4655 = ((2 * 2328) - 1) and the same 2328 turns up in func
// decoder.decode_huffman_tree.
//
// 2328 is (256 + 24 + 2048), combining 256 Green values, 24 back-ref
// lengths and up to 2048 = (1 << 11) color cache keys.
//
// 0x01FF = 511 = ((2 * 256) - 1) is for 256 Red (Blue, Alpha) values.
//
// 0x004F = 79 = ((2 * 40) - 1) is for 40 back-ref distances.
//
// 0x187B = 6267 and (0x187B * sizeof(u16)) = 12534. Overall, this
// field takes 0x30_F600 = 3_208704 bytes of memory.
//
// The base.u16's bits are the same as for code_lengths_huffman_nodes.
huffman_nodes : array[256] array[0x187B] base.u16,
)
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 r_mark : base.u64
var status : base.status
if this.call_sequence <> 0x00 {
return base."#bad call sequence"
}
c32 = args.src.read_u32le?()
if c32 <> 'RIFF'le {
return "#bad header"
}
this.riff_chunk_length = args.src.read_u32le?()
if (this.riff_chunk_length & 1) <> 0 {
return "#bad header"
}
while true {
io_limit (io: args.src, limit: this.riff_chunk_length as base.u64) {
r_mark = args.src.mark()
status =? this.do_decode_image_config_limited?(dst: args.dst, src: args.src)
this.riff_chunk_length ~sat-=
(args.src.count_since(mark: r_mark) & 0xFFFF_FFFF) as base.u32
}
if status.is_ok() {
break
} else if not status.is_suspension() {
return status
} else if (status == base."$short read") and (this.riff_chunk_length == 0) {
return "#short chunk"
}
yield? status
}
this.frame_config_io_position = args.src.position()
if (not this.is_vp8_lossy) and (args.dst <> nullptr) {
args.dst.set!(
pixfmt: this.pixfmt,
pixsub: 0,
width: this.width,
height: this.height,
first_frame_io_position: this.frame_config_io_position,
first_frame_is_opaque: false)
}
this.call_sequence = 0x20
}
pri func decoder.do_decode_image_config_limited?(dst: nptr base.image_config, src: base.io_reader) {
var c32 : base.u32
var r_mark : base.u64
var status : base.status
c32 = args.src.read_u32le?()
if c32 <> 'WEBP'le {
return "#bad header"
}
c32 = args.src.read_u32le?()
if c32 == 'VP8 'le {
this.is_vp8_lossy = true
} else if c32 == 'VP8L'le {
// No-op.
} else if c32 == 'VP8X'le {
return "#unsupported WebP file"
} else {
return "#bad header"
}
this.sub_chunk_length = args.src.read_u32le?()
if this.sub_chunk_length < 4 {
return "#bad header"
}
this.sub_chunk_has_padding = (this.sub_chunk_length & 1) <> 0
while true {
io_limit (io: args.src, limit: this.sub_chunk_length as base.u64) {
r_mark = args.src.mark()
if this.is_vp8_lossy {
status =? this.vp8.decode_image_config?(dst: args.dst, src: args.src)
} else {
status =? this.do_decode_image_config_limited_vp8l?(src: args.src)
}
this.sub_chunk_length ~sat-=
(args.src.count_since(mark: r_mark) & 0xFFFF_FFFF) as base.u32
}
if status.is_ok() {
break
} else if not status.is_suspension() {
return status
} else if (status == base."$short read") and (this.sub_chunk_length == 0) {
return "#short chunk"
}
yield? status
}
}
pri func decoder.do_decode_image_config_limited_vp8l?(src: base.io_reader) {
var c32 : base.u32
var transform_size : base.u32[..= 0x400_0000]
c32 = args.src.read_u8_as_u32?()
if c32 <> 0x2F {
return "#bad header"
}
c32 = args.src.read_u32le?()
this.width = (c32 & 0x3FFF) + 1
c32 >>= 14
this.height = (c32 & 0x3FFF) + 1
c32 >>= 14
this.pixfmt = base.PIXEL_FORMAT__BGRX
if (c32 & 1) <> 0 {
this.pixfmt = base.PIXEL_FORMAT__BGRA_NONPREMUL
}
c32 >>= 1
if c32 <> 0 {
return "#bad header"
}
transform_size = 4 * ((this.width + 3) >> 2) * ((this.height + 3) >> 2)
this.workbuf_offset_for_transform[0] = (4 * this.width * this.height) + (0 * transform_size)
this.workbuf_offset_for_transform[1] = (4 * this.width * this.height) + (1 * transform_size)
this.workbuf_offset_for_transform[2] = (4 * this.width * this.height) + (2 * transform_size)
this.workbuf_offset_for_transform[3] = (4 * this.width * this.height) + (3 * transform_size)
}
pub func decoder.decode_frame_config?(dst: nptr base.frame_config, src: base.io_reader) {
var status : base.status
while true {
if this.is_vp8_lossy {
status =? this.vp8.decode_frame_config?(dst: args.dst, src: args.src)
} else {
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) {
var pixfmt : base.pixel_format
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 <> 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 {
pixfmt = this.util.make_pixel_format(repr: this.pixfmt)
args.dst.set!(bounds: this.util.make_rect_ie_u32(
min_incl_x: 0,
min_incl_y: 0,
max_excl_x: this.width,
max_excl_y: this.height),
duration: 0,
index: 0,
io_position: this.frame_config_io_position,
disposal: 0,
opaque_within_bounds: false,
overwrite_instead_of_blend: false,
background_color: pixfmt.default_background_color())
}
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 {
if this.is_vp8_lossy {
status =? this.vp8.decode_frame?(dst: args.dst, src: args.src, blend: args.blend, workbuf: args.workbuf, opts: args.opts)
} else {
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 c8 : base.u8
var has_more : base.u32[..= 1]
var width : base.u32[..= 0x4000]
var dst : slice base.u8
var tile_data : roslice base.u8
var status : base.status
var pix : slice base.u8
var which : base.u32[..= 4]
var transform_type : base.u32[..= 3]
var ti : base.u64
var tj : base.u64
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"
}
this.seen_transform[0] = false
this.seen_transform[1] = false
this.seen_transform[2] = false
this.seen_transform[3] = false
this.n_transforms = 0
while true {
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
}
has_more = this.bits & 1
this.bits >>= 1
this.n_bits -= 1
if has_more == 0 {
break
}
this.decode_transform?(src: args.src, workbuf: args.workbuf)
}
width = this.width
if this.seen_transform[3] {
width = this.color_indexing_width
}
this.decode_color_cache_parameters?(src: args.src)
this.overall_color_cache_bits = this.color_cache_bits
this.decode_hg_table?(src: args.src, width: width, workbuf: args.workbuf)
this.color_cache_bits = this.overall_color_cache_bits
this.decode_huffman_groups?(src: args.src, n_huffman_groups: this.overall_n_huffman_groups)
while true {
if ((this.workbuf_offset_for_color_indexing as base.u64) > (this.workbuf_offset_for_transform[0] as base.u64)) or
((this.workbuf_offset_for_transform[0] as base.u64) > (this.workbuf_offset_for_transform[1] as base.u64)) or
((this.workbuf_offset_for_transform[1] as base.u64) > args.workbuf.length()) or
((this.workbuf_offset_for_transform[0] as base.u64) > args.workbuf.length()) {
return base."#bad workbuf length"
}
dst = args.workbuf[
(this.workbuf_offset_for_color_indexing as base.u64) ..
(this.workbuf_offset_for_transform[0] as base.u64)]
tile_data = args.workbuf[
(this.workbuf_offset_for_transform[0] as base.u64) ..
(this.workbuf_offset_for_transform[1] as base.u64)]
status =? this.decode_pixels?(
dst: dst,
src: args.src,
width: width,
height: this.height,
tile_data: tile_data,
tile_size_log2: this.overall_tile_size_log2)
if status.is_ok() {
break
}
yield? status
}
if (this.workbuf_offset_for_transform[0] as base.u64) > args.workbuf.length() {
return base."#bad workbuf length"
}
pix = args.workbuf[.. (this.workbuf_offset_for_transform[0] as base.u64)]
which = this.n_transforms
while which > 0 {
which -= 1
transform_type = this.transform_type[which] as base.u32
tile_data = this.util.empty_slice_u8()
if transform_type < 2 {
ti = this.workbuf_offset_for_transform[transform_type + 1] as base.u64
tj = this.workbuf_offset_for_transform[transform_type + 2] as base.u64
if (ti <= tj) and (tj <= args.workbuf.length()) {
tile_data = args.workbuf[ti .. tj]
}
}
if transform_type == 0 {
this.apply_transform_predictor!(pix: pix, tile_data: tile_data)
} else if transform_type == 1 {
this.apply_transform_cross_color!(pix: pix, tile_data: tile_data)
} else if transform_type == 2 {
this.apply_transform_subtract_green!(pix: pix)
} else {
this.apply_transform_color_indexing!(pix: pix)
width = this.width
}
}
status = this.swizzle!(
dst: args.dst,
src: pix,
blend: args.blend)
if not status.is_ok() {
return status
}
this.call_sequence = 0x60
}
pri func decoder.decode_transform?(src: base.io_reader, workbuf: slice base.u8) {
var status : base.status
var c8 : base.u8
var transform_type : base.u32[..= 3]
var tile_size_log2 : base.u32[..= 9]
var p : slice base.u8
if this.n_bits < 2 {
c8 = args.src.read_u8?()
if this.n_bits >= 2 {
return "#internal error: inconsistent n_bits"
}
this.bits |= (c8 as base.u32) << this.n_bits
this.n_bits += 8
assert this.n_bits >= 2
}
transform_type = this.bits & 3
this.bits >>= 2
this.n_bits -= 2
if this.seen_transform[transform_type] or (this.n_transforms >= 4) {
return "#bad transform"
} else if this.seen_transform[3] {
return "#unsupported transform after color indexing transform"
}
this.seen_transform[transform_type] = true
this.transform_type[this.n_transforms] = transform_type as base.u8
this.n_transforms += 1
if transform_type < 2 { // Predictor, Cross Color transform.
if this.n_bits < 3 {
c8 = args.src.read_u8?()
if this.n_bits >= 3 {
return "#internal error: inconsistent n_bits"
}
this.bits |= (c8 as base.u32) << this.n_bits
this.n_bits += 8
assert this.n_bits >= 3
}
tile_size_log2 = (this.bits & 7) + 2
this.transform_tile_size_log2[transform_type] = tile_size_log2 as base.u8
this.bits >>= 3
this.n_bits -= 3
this.decode_color_cache_parameters?(src: args.src)
this.decode_huffman_groups?(src: args.src, n_huffman_groups: 1)
while true,
inv transform_type < 2,
inv tile_size_log2 >= 2,
{
if ((this.workbuf_offset_for_transform[transform_type + 1] as base.u64) > (this.workbuf_offset_for_transform[transform_type + 2] as base.u64)) or
((this.workbuf_offset_for_transform[transform_type + 2] as base.u64) > args.workbuf.length()) {
return base."#bad workbuf length"
}
status =? this.decode_pixels?(
dst: args.workbuf[
(this.workbuf_offset_for_transform[transform_type + 1] as base.u64) ..
(this.workbuf_offset_for_transform[transform_type + 2] as base.u64)],
src: args.src,
width: (this.width + (((1 as base.u32) << tile_size_log2) - 1)) >> tile_size_log2,
height: (this.height + (((1 as base.u32) << tile_size_log2) - 1)) >> tile_size_log2,
tile_data: this.util.empty_slice_u8(),
tile_size_log2: 0)
if status.is_ok() {
break
}
yield? status
}
} else if transform_type == 2 { // Subtract Green transform.
// No-op.
} else { // Color Indexing transform.
if this.n_bits < 8 {
c8 = args.src.read_u8?()
if this.n_bits >= 8 {
return "#internal error: inconsistent n_bits"
}
this.bits |= (c8 as base.u32) << this.n_bits
this.n_bits += 8
assert this.n_bits >= 8
}
this.color_indexing_palette_size = (this.bits & 0xFF) + 1
this.bits >>= 8
this.n_bits -= 8
if this.color_indexing_palette_size <= 2 {
this.color_indexing_width = (this.width + 7) / 8
this.transform_tile_size_log2[3] = 3
} else if this.color_indexing_palette_size <= 4 {
this.color_indexing_width = (this.width + 3) / 4
this.transform_tile_size_log2[3] = 2
} else if this.color_indexing_palette_size <= 16 {
this.color_indexing_width = (this.width + 1) / 2
this.transform_tile_size_log2[3] = 1
} else {
this.color_indexing_width = this.width
this.transform_tile_size_log2[3] = 0
}
if this.width >= this.color_indexing_width {
this.workbuf_offset_for_color_indexing = 4 * (this.width - this.color_indexing_width) * this.height
}
this.decode_color_cache_parameters?(src: args.src)
this.decode_huffman_groups?(src: args.src, n_huffman_groups: 1)
this.decode_pixels?(
dst: this.palette[.. 4 * this.color_indexing_palette_size],
src: args.src,
width: this.color_indexing_palette_size,
height: 1,
tile_data: this.util.empty_slice_u8(),
tile_size_log2: 0)
this.palette[4 * this.color_indexing_palette_size .. 1024].bulk_memset!(byte_value: 0)
p = this.palette[.. 4 * this.color_indexing_palette_size]
while p.length() >= 8 {
p[4] ~mod+= p[0]
p[5] ~mod+= p[1]
p[6] ~mod+= p[2]
p[7] ~mod+= p[3]
p = p[4 ..]
}
}
}
pri func decoder.decode_color_cache_parameters?(src: base.io_reader) {
var c8 : base.u8
var use_color_cache : base.u32[..= 1]
var color_cache_bits : base.u32[..= 15]
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
}
use_color_cache = this.bits & 1
this.bits >>= 1
this.n_bits -= 1
this.color_cache_bits = 0
if use_color_cache <> 0 {
if this.n_bits < 4 {
c8 = args.src.read_u8?()
if this.n_bits >= 4 {
return "#internal error: inconsistent n_bits"
}
this.bits |= (c8 as base.u32) << this.n_bits
this.n_bits += 8
assert this.n_bits >= 4
}
color_cache_bits = this.bits & 15
this.bits >>= 4
this.n_bits -= 4
if (color_cache_bits < 1) or (11 < color_cache_bits) {
return "#bad color cache"
}
this.color_cache_bits = color_cache_bits
}
}
pri func decoder.decode_hg_table?(src: base.io_reader, width: base.u32[..= 0x4000], workbuf: slice base.u8) {
var status : base.status
var c8 : base.u8
var use_hg_table : base.u32[..= 1]
var tile_size_log2 : base.u32[..= 9]
var hg_pixels : slice base.u8
var n : base.u64
var p : roslice base.u8
var hg_plus_1 : base.u32[..= 256]
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
}
use_hg_table = this.bits & 1
this.bits >>= 1
this.n_bits -= 1
if use_hg_table == 0 {
this.overall_n_huffman_groups = 1
this.overall_tile_size_log2 = 0
if ((this.workbuf_offset_for_transform[0] as base.u64) > (this.workbuf_offset_for_transform[1] as base.u64)) or
((this.workbuf_offset_for_transform[1] as base.u64) > args.workbuf.length()) {
return base."#bad workbuf length"
}
hg_pixels = args.workbuf[
this.workbuf_offset_for_transform[0] as base.u64 ..
this.workbuf_offset_for_transform[1] as base.u64]
if hg_pixels.length() >= 4 {
hg_pixels[0] = 0x00
hg_pixels[1] = 0x00
hg_pixels[2] = 0x00
hg_pixels[3] = 0x00
}
return ok
}
if this.n_bits < 3 {
c8 = args.src.read_u8?()
if this.n_bits >= 3 {
return "#internal error: inconsistent n_bits"
}
this.bits |= (c8 as base.u32) << this.n_bits
this.n_bits += 8
assert this.n_bits >= 3
}
tile_size_log2 = (this.bits & 7) + 2
this.bits >>= 3
this.n_bits -= 3
this.overall_tile_size_log2 = tile_size_log2
this.decode_color_cache_parameters?(src: args.src)
this.decode_huffman_groups?(src: args.src, n_huffman_groups: 1)
while true,
inv tile_size_log2 >= 2,
{
if ((this.workbuf_offset_for_transform[0] as base.u64) > (this.workbuf_offset_for_transform[1] as base.u64)) or
((this.workbuf_offset_for_transform[1] as base.u64) > args.workbuf.length()) {
return base."#bad workbuf length"
}
status =? this.decode_pixels?(
dst: args.workbuf[
(this.workbuf_offset_for_transform[0] as base.u64) ..
(this.workbuf_offset_for_transform[1] as base.u64)],
src: args.src,
width: (args.width + (((1 as base.u32) << tile_size_log2) - 1)) >> tile_size_log2,
height: (this.height + (((1 as base.u32) << tile_size_log2) - 1)) >> tile_size_log2,
tile_data: this.util.empty_slice_u8(),
tile_size_log2: 0)
if status.is_ok() {
break
}
yield? status
}
this.overall_n_huffman_groups = 1
if ((this.workbuf_offset_for_transform[0] as base.u64) > (this.workbuf_offset_for_transform[1] as base.u64)) or
((this.workbuf_offset_for_transform[1] as base.u64) > args.workbuf.length()) {
return base."#bad workbuf length"
}
hg_pixels = args.workbuf[
this.workbuf_offset_for_transform[0] as base.u64 ..
this.workbuf_offset_for_transform[1] as base.u64]
n = (((args.width + (((1 as base.u32) << tile_size_log2) - 1)) >> tile_size_log2) *
((this.height + (((1 as base.u32) << tile_size_log2) - 1)) >> tile_size_log2) *
4) as base.u64
if n > hg_pixels.length() {
return base."#bad workbuf length"
}
p = hg_pixels[.. n]
while p.length() >= 4 {
if p[2] <> 0 {
return "#unsupported number of Huffman groups"
}
hg_plus_1 = (p[1] as base.u32) + 1
if this.overall_n_huffman_groups < hg_plus_1 {
this.overall_n_huffman_groups = hg_plus_1
}
p = p[4 ..]
}
}
pri func decoder.decode_pixels?(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 i : base.u32
var n : base.u32[..= 2048]
i = 0
n = (1 as base.u32) << this.color_cache_bits
while i < n {
assert i < 2048 via "a < b: a < c; c <= b"(c: n)
this.color_cache[i] = 0
i += 1
}
this.decode_pixels_slow?(
dst: args.dst,
src: args.src,
width: args.width,
height: args.height,
tile_data: args.tile_data,
tile_size_log2: args.tile_size_log2)
}
pri func decoder.swizzle!(dst: ptr base.pixel_buffer, src: roslice base.u8, blend: base.pixel_blend) base.status {
var status : base.status
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 dst_palette : slice base.u8
var tab : table base.u8
var src_bytes_per_row : base.u64
var dst : slice base.u8
var y : base.u32
status = this.swizzler.prepare!(
dst_pixfmt: args.dst.pixel_format(),
dst_palette: args.dst.palette_or_else(fallback: this.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
}
// 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 = (this.width * dst_bytes_per_pixel) as base.u64
dst_palette = args.dst.palette_or_else(fallback: this.palette[..])
tab = args.dst.plane(p: 0)
src_bytes_per_row = (this.width * 4) as base.u64
while src_bytes_per_row <= args.src.length() {
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: dst_palette,
src: args.src[.. src_bytes_per_row])
args.src = args.src[src_bytes_per_row ..]
y ~mod+= 1
}
return ok
}
pub func decoder.frame_dirty_rect() base.rect_ie_u32 {
if this.is_vp8_lossy {
return this.vp8.frame_dirty_rect()
}
return this.util.make_rect_ie_u32(
min_incl_x: 0,
min_incl_y: 0,
max_excl_x: this.width,
max_excl_y: this.height)
}
pub func decoder.num_animation_loops() base.u32 {
return 0
}
pub func decoder.num_decoded_frame_configs() base.u64 {
if this.is_vp8_lossy {
return this.vp8.num_decoded_frame_configs()
}
if this.call_sequence > 0x20 {
return 1
}
return 0
}
pub func decoder.num_decoded_frames() base.u64 {
if this.is_vp8_lossy {
return this.vp8.num_decoded_frames()
}
if this.call_sequence > 0x40 {
return 1
}
return 0
}
pub func decoder.restart_frame!(index: base.u64, io_position: base.u64) base.status {
var status : base.status
if this.is_vp8_lossy {
status = this.vp8.restart_frame!(index: args.index, io_position: args.io_position)
return status
}
if this.call_sequence < 0x20 {
return base."#bad call sequence"
}
if (args.index <> 0) or (args.io_position <> this.frame_config_io_position) {
return base."#bad argument"
}
this.call_sequence = 0x28
return ok
}
pub func decoder.set_report_metadata!(fourcc: base.u32, report: base.bool) {
// No-op.
}
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 {
if this.is_vp8_lossy {
return this.vp8.workbuf_len()
}
return this.util.make_range_ii_u64(
min_incl: this.workbuf_offset_for_transform[3] as base.u64,
max_incl: this.workbuf_offset_for_transform[3] as base.u64)
}
pri const DISTANCE_MAP : roarray[120] base.u8 = [
0x18, 0x07, 0x17, 0x19, 0x28, 0x06, 0x27, 0x29, 0x16, 0x1A,
0x26, 0x2A, 0x38, 0x05, 0x37, 0x39, 0x15, 0x1B, 0x36, 0x3A,
0x25, 0x2B, 0x48, 0x04, 0x47, 0x49, 0x14, 0x1C, 0x35, 0x3B,
0x46, 0x4A, 0x24, 0x2C, 0x58, 0x45, 0x4B, 0x34, 0x3C, 0x03,
0x57, 0x59, 0x13, 0x1D, 0x56, 0x5A, 0x23, 0x2D, 0x44, 0x4C,
0x55, 0x5B, 0x33, 0x3D, 0x68, 0x02, 0x67, 0x69, 0x12, 0x1E,
0x66, 0x6A, 0x22, 0x2E, 0x54, 0x5C, 0x43, 0x4D, 0x65, 0x6B,
0x32, 0x3E, 0x78, 0x01, 0x77, 0x79, 0x53, 0x5D, 0x11, 0x1F,
0x64, 0x6C, 0x42, 0x4E, 0x76, 0x7A, 0x21, 0x2F, 0x75, 0x7B,
0x31, 0x3F, 0x63, 0x6D, 0x52, 0x5E, 0x00, 0x74, 0x7C, 0x41,
0x4F, 0x10, 0x20, 0x62, 0x6E, 0x30, 0x73, 0x7D, 0x51, 0x5F,
0x40, 0x72, 0x7E, 0x61, 0x6F, 0x50, 0x71, 0x7F, 0x60, 0x70,
]