blob: c198532dcc6f45b524e6a6b087802883cda84b06 [file]
// Copyright 2026 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.copy_partitions_to_workbuf?(src: base.io_reader, workbuf: slice base.u8) {
var i : base.u64
var j : base.u64
var n : base.u32
i = this.workbuf_yuv_v_end
j = this.workbuf_yuv_v_end + (this.partitioned_data_length as base.u64)
while i < j,
inv j <= 0x20FF_FFF7,
{
if j > args.workbuf.length() {
return base."#bad workbuf length"
}
assert j > i via "a > b: b < a"()
n = args.src.limited_copy_u32_to_slice!(
up_to: ((j - i) & 0xFFFF_FFFF) as base.u32,
s: args.workbuf[i .. j])
i ~mod+= n as base.u64
if i < j {
yield? base."$short read"
}
}
// Write eight 0x00 bytes to the end of the workbuf, past what we copy from
// args.src. Later on, we can then always peek_u64le from the workbuf.
j += 8
while i < j {
assert i < 0xFFFF_FFFF_FFFF_FFFF via "a < b: a < c; c <= b"(c: j)
if i >= args.workbuf.length() {
return base."#bad workbuf length"
}
args.workbuf[i] = 0x00
i += 1
}
}
pri func decoder.decode_header_partition!(workbuf: roslice base.u8) base.status {
var v1 : base.u32[..= 1]
var v32 : base.u32
// RFC 6386 Section 9.2. Color Space and Pixel Type (Key Frames Only).
if this.is_key_frame {
this.read_bit!(workbuf: args.workbuf, part: 0, prob: 128)
this.read_bit!(workbuf: args.workbuf, part: 0, prob: 128)
}
// RFC 6386 Section 9.3. Segment-Based Adjustments.
this.decode_segment_header!(workbuf: args.workbuf)
// RFC 6386 Section 9.4. Loop Filter Type and Levels.
this.decode_filter_header!(workbuf: args.workbuf)
this.calculate_loop_filters!()
// RFC 6386 Section 9.5. Token Partition and Partition Data Offsets.
v32 = this.read_unsigned!(workbuf: args.workbuf, part: 0, n: 2)
this.num_other_partitions_m1 = ((1 as base.u32) << (v32 & 3)) - 1
// RFC 6386 Section 9.6. Dequantization Indices.
v32 = this.read_unsigned!(workbuf: args.workbuf, part: 0, n: 7)
this.quant_yac_qi = v32 & 127
this.quant_ydc_delta = this.read_signed!(workbuf: args.workbuf, part: 0, n: 4)
this.quant_y2dc_delta = this.read_signed!(workbuf: args.workbuf, part: 0, n: 4)
this.quant_y2ac_delta = this.read_signed!(workbuf: args.workbuf, part: 0, n: 4)
this.quant_uvdc_delta = this.read_signed!(workbuf: args.workbuf, part: 0, n: 4)
this.quant_uvac_delta = this.read_signed!(workbuf: args.workbuf, part: 0, n: 4)
this.calculate_dequants!()
// RFC 6386 Section 9.7. Refresh Golden Frame and Altref Frame.
if not this.is_key_frame {
return "#unsupported VP8 file"
}
// RFC 6386 Section 9.8. Refresh Last Frame Buffer.
this.read_bit!(workbuf: args.workbuf, part: 0, prob: 128)
// RFC 6386 Section 9.9. DCT Coefficient Probability Update.
this.decode_coeff_probs!(workbuf: args.workbuf)
if not this.is_key_frame {
// RFC 6386 Section 9.10. Remaining Frame Header Data (Non-Key Frame).
return "#unsupported VP8 file"
} else {
// RFC 6386 Section 9.11. Remaining Frame Header Data (Key Frame).
v1 = this.read_bit!(workbuf: args.workbuf, part: 0, prob: 128)
if v1 <> 0 {
v32 = this.read_unsigned!(workbuf: args.workbuf, part: 0, n: 8)
this.prob_skip = (v32 & 0xFF) | 0x100
}
}
return ok
}
pri func decoder.decode_segment_header!(workbuf: roslice base.u8) {
var v1 : base.u32[..= 1]
var v32 : base.u32
var i : base.u32
v1 = this.read_bit!(workbuf: args.workbuf, part: 0, prob: 128)
this.seg_enabled = v1 <> 0
if not this.seg_enabled {
this.seg_update_map = false
return nothing
}
v1 = this.read_bit!(workbuf: args.workbuf, part: 0, prob: 128)
this.seg_update_map = v1 <> 0
v1 = this.read_bit!(workbuf: args.workbuf, part: 0, prob: 128)
if v1 <> 0 {
v1 = this.read_bit!(workbuf: args.workbuf, part: 0, prob: 128)
this.seg_absolute = v1 <> 0
i = 0
while i < 4 {
v32 = this.read_signed!(workbuf: args.workbuf, part: 0, n: 7)
this.seg_quants[i] = (v32 & 0xFF) as base.u8
i += 1
}
i = 0
while i < 4 {
v32 = this.read_signed!(workbuf: args.workbuf, part: 0, n: 6)
this.seg_strengths[i] = (v32 & 0xFF) as base.u8
i += 1
}
}
if this.seg_update_map {
i = 0
while i < 3 {
v1 = this.read_bit!(workbuf: args.workbuf, part: 0, prob: 128)
if v1 <> 0 {
v32 = this.read_unsigned!(workbuf: args.workbuf, part: 0, n: 8)
this.seg_probs[i] = (v32 & 0xFF) as base.u8
} else {
this.seg_probs[i] = 0xFF
}
i += 1
}
}
}
pri func decoder.decode_filter_header!(workbuf: roslice base.u8) {
var v1 : base.u32[..= 1]
var v32 : base.u32
var i : base.u32
v1 = this.read_bit!(workbuf: args.workbuf, part: 0, prob: 128)
this.filt_simple = v1 <> 0
v32 = this.read_unsigned!(workbuf: args.workbuf, part: 0, n: 6)
this.filt_level = (v32 & 0x3F) as base.u8
v32 = this.read_unsigned!(workbuf: args.workbuf, part: 0, n: 3)
this.filt_sharpness = (v32 & 0x07) as base.u8
v1 = this.read_bit!(workbuf: args.workbuf, part: 0, prob: 128)
this.filt_deltas_enabled = v1 <> 0
if not this.filt_deltas_enabled {
return nothing
}
v1 = this.read_bit!(workbuf: args.workbuf, part: 0, prob: 128)
if v1 == 0 {
return nothing
}
i = 0
while i < 8 {
v32 = this.read_signed!(workbuf: args.workbuf, part: 0, n: 6)
this.filt_deltas[i >> 2][i & 3] = (v32 & 0xFF) as base.u8
i += 1
}
}
pri func decoder.decode_coeff_probs!(workbuf: roslice base.u8) {
var v1 : base.u32[..= 1]
var v32 : base.u32
var i : base.u32
i = 0
while i < 1056 {
v1 = this.read_bit!(workbuf: args.workbuf, part: 0, prob: COEFF_UPDATE_PROBS[i] as base.u32)
if v1 <> 0 {
v32 = this.read_unsigned!(workbuf: args.workbuf, part: 0, n: 8)
this.coeff_probs[i] = (v32 & 0xFF) as base.u8
} else {
this.coeff_probs[i] = DEFAULT_COEFF_PROBS[i]
}
i += 1
}
}
pri func decoder.calculate_loop_filters!() {
var seg : base.u32
var inner : base.u32
var level : base.u32
var ilevel : base.u32
var hlevel : base.u32
seg = 0
while seg < 4 {
inner = 0
while inner < 2,
inv seg < 4,
{
level = this.filt_level as base.u32
if not this.seg_enabled {
// No-op.
} else if not this.filt_deltas_enabled {
level = this.util.sign_extend_convert_u8_u32(a: this.seg_strengths[seg])
} else {
// The (level as base.u32) is redundant, equivalent to level, but
// avoids a -Werror=sign-conversion false positive with gcc 12.2.0.
level = (level as base.u32) ~mod+
this.util.sign_extend_convert_u8_u32(a: this.seg_strengths[seg])
}
// Yes, filt_deltas[*][0], not filt_deltas[*][seg].
if this.filt_deltas_enabled {
level = (level as base.u32) ~mod+
this.util.sign_extend_convert_u8_u32(a: this.filt_deltas[0][0])
if inner <> 0 {
level = (level as base.u32) ~mod+
this.util.sign_extend_convert_u8_u32(a: this.filt_deltas[1][0])
}
}
if level >= 0x8000_0000 {
this.loop_filters[(2 * seg) + inner] = 0
inner += 1
continue
} else if level > 63 {
level = 63
}
ilevel = level
if this.filt_sharpness > 0 {
if this.filt_sharpness <= 4 {
ilevel >>= 1
} else {
ilevel >>= 2
}
ilevel = ilevel.max(no_less_than: (9 - this.filt_sharpness) as base.u32)
}
if (ilevel < 1) or (0x8000_0000 <= ilevel) {
ilevel = 1
}
// If we ever want to support (this.is_key_frame == false), note
// that the hlevel thresholds are a little different then.
if level < 15 {
hlevel = 0
} else if level < 40 {
hlevel = 1
} else {
hlevel = 2
}
level = (2 ~mod* level) ~mod+ ilevel
this.loop_filters[(2 * seg) + inner] =
level | (ilevel ~mod<< 8) | (hlevel ~mod<< 16) | (inner << 31)
inner += 1
}
seg += 1
}
}
pri func decoder.calculate_dequants!() {
var seg : base.u32
var q : base.u32
seg = 0
while seg < 4 {
q = this.quant_yac_qi
if not this.seg_enabled {
// No-op.
} else if this.seg_absolute {
q = this.util.sign_extend_convert_u8_u32(a: this.seg_quants[seg])
} else {
// The (q as base.u32) is redundant, equivalent to q, but avoids a
// -Werror=sign-conversion false positive with gcc 12.2.0.
q = (q as base.u32) ~mod+
this.util.sign_extend_convert_u8_u32(a: this.seg_quants[seg])
}
this.dequants[seg][0][0] = DEQUANTS[0][this.clip_127(a: q ~mod+ this.quant_ydc_delta)]
this.dequants[seg][0][1] = DEQUANTS[1][this.clip_127(a: q)]
this.dequants[seg][1][0] = DEQUANTS[0][this.clip_127(a: q ~mod+ this.quant_y2dc_delta)] * 2
this.dequants[seg][1][1] = (DEQUANTS[1][this.clip_127(a: q ~mod+ this.quant_y2ac_delta)] * 155) / 100
if this.dequants[seg][1][1] < 8 {
this.dequants[seg][1][1] = 8
}
this.dequants[seg][2][0] = DEQUANTS[0][this.clip_127(a: q ~mod+ this.quant_uvdc_delta)]
if this.dequants[seg][2][0] > 132 {
this.dequants[seg][2][0] = 132
}
this.dequants[seg][2][1] = DEQUANTS[1][this.clip_127(a: q ~mod+ this.quant_uvac_delta)]
seg += 1
}
}
pri func decoder.clip_127(a: base.u32) base.u32[..= 127] {
if args.a <= 127 {
return args.a
} else if args.a < 0x8000_0000 {
return 127
}
return 0
}
pri func decoder.decode_other_partition_lengths!(workbuf: roslice base.u8) base.status {
var v32 : base.u32
var remainder : base.u32
var i : base.u32
var o : base.u64
remainder = this.partitioned_data_length ~sat- this.part_lens[0]
remainder ~sat-= 3 * this.num_other_partitions_m1
o = this.workbuf_yuv_v_end + (this.part_lens[0] as base.u64)
i = 0
while i < this.num_other_partitions_m1 {
assert i < 7 via "a < b: a < c; c <= b"(c: this.num_other_partitions_m1)
v32 = 0
if o >= args.workbuf.length() {
return "#bad header"
}
assert o < 0xFFFF_FFFF_FFFF_FFFF via "a < b: a < c; c <= b"(c: args.workbuf.length())
v32 |= (args.workbuf[o] as base.u32) << 0
o += 1
if o >= args.workbuf.length() {
return "#bad header"
}
assert o < 0xFFFF_FFFF_FFFF_FFFF via "a < b: a < c; c <= b"(c: args.workbuf.length())
v32 |= (args.workbuf[o] as base.u32) << 8
o += 1
if o >= args.workbuf.length() {
return "#bad header"
}
assert o < 0xFFFF_FFFF_FFFF_FFFF via "a < b: a < c; c <= b"(c: args.workbuf.length())
v32 |= (args.workbuf[o] as base.u32) << 16
o += 1
if remainder < v32 {
return "#bad header"
}
remainder -= v32
this.part_lens[1 + i] = v32
i += 1
}
if remainder > 0xFF_FFFF {
return "#unsupported VP8 file"
}
this.part_lens[1 + this.num_other_partitions_m1] = remainder
i = 0
while i < (1 + this.num_other_partitions_m1) {
assert i < 8 via "a < b: a < c; c <= b"(c: (1 + this.num_other_partitions_m1))
this.part_workbuf_ris[1 + i] = o
o ~mod+= this.part_lens[1 + i] as base.u64
if o > args.workbuf.length() {
return "#bad header"
}
i += 1
}
return ok
}