blob: c3bcaae03ee0a5a9e2fea6c742c7536ca2c6f4f6 [file] [log] [blame]
// Copyright 2023 The Wuffs Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
pub status "#bad DHT marker"
pub status "#bad DQT marker"
pub status "#bad DRI marker"
pub status "#bad SOF marker"
pub status "#bad SOS marker"
pub status "#bad header"
pub status "#bad marker"
pub status "#missing Huffman table"
pub status "#missing Quantization table"
pub status "#truncated input"
pub status "#unsupported DQT after SOF markers"
pub status "#unsupported arithmetic coding"
pub status "#unsupported fractional sampling"
pub status "#unsupported hierarchical coding"
pub status "#unsupported implicit height"
pub status "#unsupported lossless coding"
pub status "#unsupported marker"
pub status "#unsupported precision (12 bits)"
pub status "#unsupported precision (16 bits)"
pub status "#unsupported precision"
pri status "#internal error: inconsistent decoder state"
pub const DECODER_WORKBUF_LEN_MAX_INCL_WORST_CASE : base.u64 = 0x4_0040_0100
pub struct decoder? implements base.image_decoder(
width : base.u32[..= 0xFFFF],
height : base.u32[..= 0xFFFF],
width_in_mcus : base.u32[..= 0x2000],
height_in_mcus : base.u32[..= 0x2000],
// The call sequence state machine is discussed in
// (/doc/std/image-decoders-call-sequence.md).
call_sequence : base.u8,
sof_marker : base.u8,
next_restart_marker : base.u8[..= 7],
max_incl_components_h : base.u8[..= 4],
max_incl_components_v : base.u8[..= 4],
num_components : base.u32[..= 4],
components_c : array[4] base.u8,
components_h : array[4] base.u8[..= 4],
components_v : array[4] base.u8[..= 4],
components_tq : array[4] base.u8[..= 3],
// components_workbuf_offsets, indexed by (b) and (b + 1), is the i and
// j bounds in the workbuf[i .. j] slice holding the post-IDCT values.
// components_workbuf_widths[b] is the row width, which is also the row
// stride (there is no slack).
//
// For example, a 4:2:0 chroma-subsampled, 36 pixels wide × 28 pixels
// high image has 6 blocks (4 Y, 1 Cb, 1 Cr; 8×8 samples each) per MCU.
// Each MCU is 16×16 pixels. The image is 3 MCUs wide × 2 MCUs high.
// The post-IDCT image dimensions round up to 48 × 32 pixels.
//
// The components_workbuf_widths array is:
// 0: 0x30 = 48 // Y
// 1: 0x18 = 24 // Cb
// 2: 0x18 = 24 // Cr
// 3: 0x00 = 0 // Unused
//
// The components_workbuf_offsets array is:
// 0: 0x000 = 0
// 1: 0x600 = 1536 = previous + (48 * 32)
// 2: 0x790 = 1920 = previous + (24 * 16)
// 3: 0x900 = 2304 = previous + (24 * 16)
// 4: 0x900 = 2304 = previous + ( 0 * 0)
components_workbuf_widths : array[4] base.u32[..= 0x1_0008],
components_workbuf_heights : array[4] base.u32[..= 0x1_0008],
components_workbuf_offsets : array[5] base.u64[..= 0x4_0040_0100], // 4 * 0x1_0008 * 0x1_0008.
scan_num_components : base.u32[..= 4],
scan_comps_cselector : array[4] base.u8[..= 3],
scan_comps_td : array[4] base.u8[..= 3],
scan_comps_ta : array[4] base.u8[..= 3],
scan_ss : base.u8[..= 63],
scan_se : base.u8[..= 63],
scan_ah : base.u8[..= 13],
scan_al : base.u8[..= 13],
scan_width_in_mcus : base.u32[..= 0x2000],
scan_height_in_mcus : base.u32[..= 0x2000],
scan_comps_bx_offset : array[16] base.u8[..= 3],
scan_comps_by_offset : array[16] base.u8[..= 3],
mcu_num_blocks : base.u32[..= 10],
mcu_current_block : base.u32[..= 10],
mcu_blocks_sselector : array[16] base.u8[..= 3],
mcu_zig_index : base.u32[..= 63],
mcu_previous_dc_values : array[4] base.u16,
restart_interval : base.u16,
saved_restart_interval : base.u16,
restarts_remaining : base.u16,
frame_config_io_position : base.u64,
payload_length : base.u32[..= 0xFFFF],
seen_dqt : array[4] base.bool,
seen_dht : array[8] base.bool,
// These fields yield bitstream bits in Most Significant Bits order.
bitstream_bits : base.u64,
bitstream_n_bits : base.u32,
bitstream_ri : base.u32[..= 0x800],
bitstream_wi : base.u32[..= 0x800],
quant_tables : array[4] array[64] base.u8,
// huff_tables_symbols[(tc*4)|th][i] is the i'th Huffman code's symbol.
huff_tables_symbols : array[8] array[256] base.u8,
// huff_tables_slow[(tc*4)|th][n] is a u32 such that:
// - the high 24 bits hold the maximum (exclusive) bit-string of the
// codes of bit-length (n+1).
// - the low 8 bits hold a huff_tables_symbols[(tc*4)|th] index bias.
huff_tables_slow : array[8] array[16] base.u32,
// huff_tables_fast[(tc*4)|th][b] is a u16 that decodes the bit-length
// and symbol when the MSB-first bit-stream starts with b.
// - the high 8 bits hold the bit-length. Zero means no fast path.
// - the low 8 bits hold the symbol.
huff_tables_fast : array[8] array[256] base.u16,
swizzler : base.pixel_swizzler,
util : base.utility,
) + (
bitstream_buffer : array[0x800] base.u8, // 0x800 = 2048.
mcu_blocks : array[10] array[64] base.u16,
// The dht_temp_etc fields are decode_dht temporary values.
// dht_temp_counts[n] is the number of codes with bit-length (n+1).
dht_temp_counts : array[16] base.u8,
// dht_temp_bit_lengths[i] is the bit-length of the i'th code.
dht_temp_bit_lengths : array[256] base.u8,
// dht_temp_bit_strings[i] is the bit-string of the i'th code.
dht_temp_bit_strings : array[256] base.u16,
dst_palette : array[4 * 256] base.u8,
)
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
} endwhile
}
pri func decoder.do_decode_image_config?(dst: nptr base.image_config, src: base.io_reader) {
var c : base.u8
var marker : base.u8
var pixfmt : base.u32
if this.call_sequence <> 0x00 {
return base."#bad call sequence"
}
c = args.src.read_u8?()
if c <> 0xFF {
return "#bad header"
}
c = args.src.read_u8?()
if c <> 0xD8 { // SOI (Start Of Image).
return "#bad header"
}
// Process chunks (markers and their payloads).
while true {
// Read the marker (a two-byte 0xFF 0x?? sequence).
while true {
c = args.src.read_u8?()
if c == 0xFF {
break
}
// Getting here is invalid according to the JPEG spec, but libjpeg
// treats this as a warning (JWRN_EXTRANEOUS_DATA), not an error.
} endwhile
while true {
c = args.src.read_u8?()
if c <> 0xFF {
marker = c
break
}
// Section B.1.1.2: "Any marker may optionally be preceded by any
// number of [0xFF] fill bytes".
} endwhile
if marker == 0x00 {
// Ignore byte stuffing.
continue
} else if (0xD0 <= marker) and (marker <= 0xD7) {
// RSTn (Restart) markers have no payload.
continue
}
// Payload length includes the 2 bytes for the u16be payload length.
this.payload_length = args.src.read_u16be_as_u32?()
if this.payload_length < 2 {
return "#bad marker"
}
this.payload_length -= 2
// Switch on the marker.
if marker < 0xC0 {
return "#unsupported marker"
} else if marker < 0xD0 { // SOFn (Start of Frame) and friends.
if marker <= 0xC2 {
if this.sof_marker <> 0 {
return "#bad SOF marker"
}
this.sof_marker = marker
this.decode_sof?(src: args.src)
break
} else if marker == 0xC3 {
return "#unsupported lossless coding"
} else if marker == 0xC4 { // DHT (Define Huffman Table).
// We shouldn't see DHT before SOF.
return "#bad DHT marker"
} else if (0xC5 <= marker) and (marker <= 0xC7) {
return "#unsupported hierarchical coding"
} else if marker == 0xC8 { // JPG (JPEG extension).
return "#unsupported marker"
} else {
return "#unsupported arithmetic coding"
}
} else if marker < 0xE0 {
if marker < 0xDA {
// RSTn markers are already handled above. We either have 0xD8
// (SOI: Start Of Image) or 0xD9 (EOI: End Of Image), neither
// of which are valid here.
return "#bad marker"
} else if marker == 0xDA { // SOS (Start Of Scan).
// We shouldn't see SOS before SOF.
return "#bad SOS marker"
} else if marker == 0xDB { // DQT (Define Quantization Table).
this.decode_dqt?(src: args.src)
continue
} else if marker == 0xDD { // DRI (Define Restart Interval).
this.decode_dri?(src: args.src)
continue
} else {
// 0xDC (DNL: Define Number of Lines).
// 0xDE (DHP: Define Hierarchical Progression).
// 0xDF (EXP: Expand Reference Components).
return "#unsupported marker"
}
} else if marker < 0xF0 { // APPn (Application specific).
// No-op.
} else {
if marker == 0xFE { // COM (Comment).
// No-op.
} else {
return "#unsupported marker"
}
}
args.src.skip_u32?(n: this.payload_length)
this.payload_length = 0
} endwhile
this.frame_config_io_position = args.src.position()
if args.dst <> nullptr {
pixfmt = base.PIXEL_FORMAT__Y
if this.num_components > 1 {
// TODO: base.PIXEL_FORMAT__YCBCR is probably more correct,
// although possibly less convenient for the caller.
pixfmt = base.PIXEL_FORMAT__BGRX
}
args.dst.set!(
pixfmt: pixfmt,
pixsub: 0,
width: this.width,
height: this.height,
first_frame_io_position: this.frame_config_io_position,
first_frame_is_opaque: true)
}
this.call_sequence = 0x20
}
pri func decoder.decode_dqt?(src: base.io_reader) {
var c : base.u8
var q : base.u8[..= 3]
var i : base.u32
while this.payload_length > 0 {
this.payload_length -= 1
c = args.src.read_u8?()
if (c & 0x0F) > 3 {
return "#bad DQT marker"
}
q = c & 0x0F
if (c >> 4) == 1 {
return "#unsupported precision"
} else if ((c >> 4) > 1) or (this.payload_length < 64) {
return "#bad DQT marker"
}
this.payload_length -= 64
i = 0
while i < 64 {
this.quant_tables[q][UNZIG[i]] = args.src.read_u8?()
i += 1
} endwhile
this.seen_dqt[q] = true
} endwhile
}
pri func decoder.decode_dri?(src: base.io_reader) {
if this.payload_length <> 2 {
return "#bad DRI marker"
}
this.payload_length = 0
this.restart_interval = args.src.read_u16be?()
if this.sof_marker == 0 {
this.saved_restart_interval = this.restart_interval
}
}
pri func decoder.decode_sof?(src: base.io_reader) {
var c : base.u8
var comp_h : base.u8
var comp_v : base.u8
var i : base.u32
var j : base.u32
var has_h24 : base.bool
var has_h3 : base.bool
var has_v24 : base.bool
var has_v3 : base.bool
var upper_bound : base.u32[..= 0x1_0008]
if this.payload_length < 6 {
return "#bad SOF marker"
}
this.payload_length -= 6
c = args.src.read_u8?()
if c == 8 {
// No-op.
} else if c == 12 {
return "#unsupported precision (12 bits)"
} else if c == 16 {
return "#unsupported precision (16 bits)"
} else {
return "#unsupported precision"
}
this.height = args.src.read_u16be_as_u32?()
if this.height == 0 {
return "#unsupported implicit height"
}
this.width = args.src.read_u16be_as_u32?()
if this.width == 0 {
return base."#unsupported image dimension"
}
c = args.src.read_u8?()
if (c == 0) or (c > 4) {
return "#bad SOF marker"
}
this.num_components = c as base.u32
if this.payload_length <> (3 * this.num_components) {
return "#bad SOF marker"
}
this.payload_length = 0
i = 0
while i < this.num_components {
assert i < 4 via "a < b: a < c; c <= b"(c: this.num_components)
this.components_c[i] = args.src.read_u8?()
c = args.src.read_u8?()
comp_h = c >> 4
comp_v = c & 0x0F
if (comp_h == 0) or (comp_h > 4) or (comp_v == 0) or (comp_v > 4) {
return "#bad SOF marker"
}
this.components_h[i] = comp_h
if this.max_incl_components_h < this.components_h[i] {
this.max_incl_components_h = this.components_h[i]
}
this.components_v[i] = comp_v
if this.max_incl_components_v < this.components_v[i] {
this.max_incl_components_v = this.components_v[i]
}
c = args.src.read_u8?()
if c >= 4 {
return "#bad SOF marker"
}
this.components_tq[i] = c
// Section B.2.2: "the value of C_i shall be different from the values
// of C_1 through C_(i-1)".
j = 0
while j < i,
inv i < 4,
{
assert j < 4 via "a < b: a < c; c < b"(c: i)
if this.components_c[j] == this.components_c[i] {
return "#bad SOF marker"
}
j += 1
} endwhile
i += 1
} endwhile
if this.num_components == 1 {
// The first (and only) component's H and V factors are effectively
// always 1. Section A.2.2: "When Ns = 1 (where Ns is the number of
// components in a scan), the order of data units [8x8 blocks] within a
// scan shall be left-to-right and top-to-bottom... regardless of the
// values of H1 and V1.".
this.max_incl_components_h = 1
this.max_incl_components_v = 1
this.components_h[0] = 1
this.components_v[0] = 1
} else {
has_h24 = false
has_h3 = false
has_v24 = false
has_v3 = false
i = 0
while i < this.num_components {
assert i < 4 via "a < b: a < c; c <= b"(c: this.num_components)
has_h24 = has_h24 or (this.components_h[i] == 2) or (this.components_h[i] == 4)
has_h3 = has_h3 or (this.components_h[i] == 3)
has_v24 = has_v24 or (this.components_v[i] == 2) or (this.components_v[i] == 4)
has_v3 = has_v3 or (this.components_v[i] == 3)
i += 1
} endwhile
if (has_h24 and has_h3) or (has_v24 and has_v3) {
return "#unsupported fractional sampling"
}
}
if this.max_incl_components_h == 1 {
this.width_in_mcus = (this.width + 0x07) / 0x08
} else if this.max_incl_components_h == 2 {
this.width_in_mcus = (this.width + 0x0F) / 0x10
} else if this.max_incl_components_h == 3 {
this.width_in_mcus = (this.width + 0x17) / 0x18
} else {
this.width_in_mcus = (this.width + 0x1F) / 0x20
}
if this.max_incl_components_v == 1 {
this.height_in_mcus = (this.height + 0x07) / 0x08
} else if this.max_incl_components_v == 2 {
this.height_in_mcus = (this.height + 0x0F) / 0x10
} else if this.max_incl_components_v == 3 {
this.height_in_mcus = (this.height + 0x17) / 0x18
} else {
this.height_in_mcus = (this.height + 0x1F) / 0x20
}
upper_bound = 0x1_0008
this.components_workbuf_widths[0] = upper_bound.min(no_more_than:
8 * this.width_in_mcus * (this.components_h[0] as base.u32))
this.components_workbuf_widths[1] = upper_bound.min(no_more_than:
8 * this.width_in_mcus * (this.components_h[1] as base.u32))
this.components_workbuf_widths[2] = upper_bound.min(no_more_than:
8 * this.width_in_mcus * (this.components_h[2] as base.u32))
this.components_workbuf_widths[3] = upper_bound.min(no_more_than:
8 * this.width_in_mcus * (this.components_h[3] as base.u32))
this.components_workbuf_heights[0] = upper_bound.min(no_more_than:
8 * this.height_in_mcus * (this.components_v[0] as base.u32))
this.components_workbuf_heights[1] = upper_bound.min(no_more_than:
8 * this.height_in_mcus * (this.components_v[1] as base.u32))
this.components_workbuf_heights[2] = upper_bound.min(no_more_than:
8 * this.height_in_mcus * (this.components_v[2] as base.u32))
this.components_workbuf_heights[3] = upper_bound.min(no_more_than:
8 * this.height_in_mcus * (this.components_v[3] as base.u32))
this.components_workbuf_offsets[0] = 0
this.components_workbuf_offsets[1] = this.components_workbuf_offsets[0] +
((this.components_workbuf_widths[0] as base.u64) * (this.components_workbuf_heights[0] as base.u64))
this.components_workbuf_offsets[2] = this.components_workbuf_offsets[1] +
((this.components_workbuf_widths[1] as base.u64) * (this.components_workbuf_heights[1] as base.u64))
this.components_workbuf_offsets[3] = this.components_workbuf_offsets[2] +
((this.components_workbuf_widths[2] as base.u64) * (this.components_workbuf_heights[2] as base.u64))
this.components_workbuf_offsets[4] = this.components_workbuf_offsets[3] +
((this.components_workbuf_widths[3] as base.u64) * (this.components_workbuf_heights[3] as base.u64))
}
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
} endwhile
}
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 <> 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: this.width,
max_excl_y: this.height),
duration: 0,
index: 0,
io_position: this.frame_config_io_position,
disposal: 0,
opaque_within_bounds: true,
overwrite_instead_of_blend: false,
background_color: 0xFF00_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
} endwhile
}
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 pixfmt : base.u32
var status : base.status
var c : base.u8
var marker : base.u8
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"
}
pixfmt = base.PIXEL_FORMAT__Y
if this.num_components > 1 {
pixfmt = base.PIXEL_FORMAT__BGRX
}
status = this.swizzler.prepare!(
dst_pixfmt: args.dst.pixel_format(),
dst_palette: args.dst.palette_or_else(fallback: this.dst_palette[..]),
src_pixfmt: this.util.make_pixel_format(repr: pixfmt),
src_palette: this.util.empty_slice_u8(),
blend: args.blend)
if not status.is_ok() {
return status
}
// Process chunks (markers and their payloads).
while true {
// Read the marker (a two-byte 0xFF 0x?? sequence).
while true {
c = args.src.read_u8?()
if c == 0xFF {
break
}
// Getting here is invalid according to the JPEG spec, but libjpeg
// treats this as a warning (JWRN_EXTRANEOUS_DATA), not an error.
} endwhile
while true {
c = args.src.read_u8?()
if c <> 0xFF {
marker = c
break
}
// Section B.1.1.2: "Any marker may optionally be preceded by any
// number of [0xFF] fill bytes".
} endwhile
if marker == 0x00 {
// Ignore byte stuffing.
continue
} else if (0xD0 <= marker) and (marker <= 0xD7) {
// RSTn (Restart) markers have no payload.
continue
}
// Payload length includes the 2 bytes for the u16be payload length.
this.payload_length = args.src.read_u16be_as_u32?()
if this.payload_length < 2 {
return "#bad marker"
}
this.payload_length -= 2
// Switch on the marker.
if marker < 0xC0 {
return "#unsupported marker"
} else if marker < 0xD0 { // SOFn (Start of Frame) and friends.
if marker == 0xC4 { // DHT (Define Huffman Table).
this.decode_dht?(src: args.src)
continue
} else if marker == 0xC8 { // JPG (JPEG extension).
return "#unsupported marker"
}
return "#bad SOF marker"
} else if marker < 0xE0 {
if marker < 0xDA {
// RSTn markers are already handled above. We either have 0xD8
// (SOI: Start Of Image) or 0xD9 (EOI: End Of Image), neither
// of which are valid here.
return "#bad marker"
} else if marker == 0xDA { // SOS (Start Of Scan).
this.decode_sos?(dst: args.dst, src: args.src, workbuf: args.workbuf)
break
} else if marker == 0xDB { // DQT (Define Quantization Table).
return "#unsupported DQT after SOF markers"
} else if marker == 0xDD { // DRI (Define Restart Interval).
this.decode_dri?(src: args.src)
continue
} else {
// 0xDC (DNL: Define Number of Lines).
// 0xDE (DHP: Define Hierarchical Progression).
// 0xDF (EXP: Expand Reference Components).
return "#unsupported marker"
}
} else if marker < 0xF0 { // APPn (Application specific).
// No-op.
} else {
if marker == 0xFE { // COM (Comment).
// No-op.
} else {
return "#unsupported marker"
}
}
args.src.skip_u32?(n: this.payload_length)
this.payload_length = 0
} endwhile
this.call_sequence = 0x60
}
pri func decoder.decode_dht?(src: base.io_reader) {
var c : base.u8
var tc : base.u8[..= 1]
var th : base.u8[..= 3]
var tc4_th : base.u8[..= 7]
var working_total_count : base.u32[..= 0xFFFF]
var total_count : base.u32[..= 256]
var i : base.u32
var failed : base.bool
if this.sof_marker == 0 {
return "#bad DHT marker"
}
while this.payload_length > 0 {
if this.payload_length < 17 {
return "#bad DHT marker"
}
this.payload_length -= 17
c = args.src.read_u8?()
if ((c >> 4) > 1) or ((c & 0x0F) > 3) {
return "#bad DHT marker"
}
tc = c >> 4
th = c & 0x0F
tc4_th = ((tc * 4) | th) as base.u8
// SOF0 (which is 0xC0) means a Baseline JPEG. The Baseline (th <= 1)
// restriction is specified in table B.5.
if (this.sof_marker == 0xC0) and ((tc4_th & 3) > 1) {
return "#bad DHT marker"
}
// Read dht_temp_counts.
i = 0
while i < 16 {
this.dht_temp_counts[i] = args.src.read_u8?()
i += 1
} endwhile
working_total_count = 0
i = 0
while i < 16 {
working_total_count =
(working_total_count + (this.dht_temp_counts[i] as base.u32)) & 0xFFFF
i += 1
} endwhile
if (working_total_count <= 0) or (256 < working_total_count) {
return "#bad DHT marker"
}
total_count = working_total_count
// Read huff_tables_symbols[tc4_th].
if this.payload_length < total_count {
return "#bad DHT marker"
}
this.payload_length -= total_count
i = 0
while i < total_count {
assert i < 256 via "a < b: a < c; c <= b"(c: total_count)
this.huff_tables_symbols[tc4_th][i] = args.src.read_u8?()
i += 1
} endwhile
while i < 256 {
this.huff_tables_symbols[tc4_th][i] = 0
i += 1
} endwhile
// For tc == 0 (DC, not AC tables), the symbols must not exceed 15 (for
// lossy JPEG) or 16 (for lossless JPEG). We only support lossy.
if (tc4_th & 4) == 0 {
i = 0
while i < total_count {
assert i < 256 via "a < b: a < c; c <= b"(c: total_count)
if this.huff_tables_symbols[tc4_th][i] > 15 {
return "#bad DHT marker"
}
i += 1
} endwhile
}
failed = this.calculate_huff_tables!(tc4_th: tc4_th, total_count: total_count)
if failed {
return "#bad DHT marker"
}
this.seen_dht[tc4_th] = true
} endwhile
}
pri func decoder.calculate_huff_tables!(tc4_th: base.u8[..= 7], total_count: base.u32[..= 256]) base.bool {
var i : base.u32
var j : base.u8
var k : base.u8
var bit_length_minus_one : base.u32[..= 15]
var bit_length : base.u8[..= 16]
var bit_string : base.u32
var slow : base.u32
var prefix : base.u8
var fast : base.u16
var reps : base.u32
// Calculate dht_temp_bit_lengths.
i = 0
k = 0
bit_length_minus_one = 0
while i < args.total_count {
assert i < 256 via "a < b: a < c; c <= b"(c: args.total_count)
while k >= this.dht_temp_counts[bit_length_minus_one],
inv i < 256,
post k < this.dht_temp_counts[bit_length_minus_one],
{
k = 0
bit_length_minus_one = (bit_length_minus_one + 1) & 15
} endwhile
assert k < 255 via "a < b: a < c; c <= b"(c: this.dht_temp_counts[bit_length_minus_one])
k += 1
this.dht_temp_bit_lengths[i] = (bit_length_minus_one + 1) as base.u8
i += 1
} endwhile
// Calculate dht_temp_bit_strings.
bit_length = 0
bit_string = 0
i = 0
while i < args.total_count {
assert i < 256 via "a < b: a < c; c <= b"(c: args.total_count)
while bit_length < this.dht_temp_bit_lengths[i],
inv i < 256,
{
if bit_length >= 16 {
return true
}
bit_length += 1
bit_string ~mod<<= 1
} endwhile
this.dht_temp_bit_strings[i] = (bit_string & 0xFFFF) as base.u16
bit_string ~mod+= 1
// Check the bit_string just assigned to dht_temp_bit_strings[i].
// Section C: "the codes shall be generated such that the
// all-1-bits code word of any length is reserved".
if (bit_string >> bit_length) > 0 {
return true
}
i += 1
} endwhile
// Calculate huff_tables_slow[args.tc4_th].
k = 0
bit_length_minus_one = 0
while true {
if this.dht_temp_counts[bit_length_minus_one] == 0 {
this.huff_tables_slow[args.tc4_th][bit_length_minus_one] = 0
} else {
slow = 0xFF & ((k as base.u32) ~mod-
(this.dht_temp_bit_strings[k] as base.u32))
k ~mod+= this.dht_temp_counts[bit_length_minus_one]
this.huff_tables_slow[args.tc4_th][bit_length_minus_one] = slow |
(((this.dht_temp_bit_strings[k ~mod- 1] as base.u32) + 1) << 8)
}
bit_length_minus_one = (bit_length_minus_one + 1) & 15
if bit_length_minus_one == 0 {
break
}
} endwhile
// Calculate huff_tables_fast[args.tc4_th].
i = 0
while i < 256 {
this.huff_tables_fast[args.tc4_th][i] = 0xFFFF
i += 1
} endwhile
j = 0
bit_length_minus_one = 0
while bit_length_minus_one < 8 {
k = 0
while k < this.dht_temp_counts[bit_length_minus_one],
inv bit_length_minus_one < 8,
{
assert k < 255 via "a < b: a < c; c <= b"(c: this.dht_temp_counts[bit_length_minus_one])
prefix = (((this.dht_temp_bit_strings[j] as base.u32) <<
(7 - bit_length_minus_one)) & 0xFF) as base.u8
fast = ((((bit_length_minus_one + 1) as base.u32) << 8) |
(this.huff_tables_symbols[args.tc4_th][j] as base.u32)) as base.u16
reps = (1 as base.u32) << (7 - bit_length_minus_one)
while reps > 0,
inv bit_length_minus_one < 8,
inv k < 255,
{
this.huff_tables_fast[args.tc4_th][prefix] = fast
prefix ~mod+= 1
reps -= 1
} endwhile
k += 1
j ~mod+= 1
} endwhile
bit_length_minus_one += 1
} endwhile
return false
}
pri func decoder.decode_sos?(dst: ptr base.pixel_buffer, src: base.io_reader, workbuf: slice base.u8) {
var my : base.u32
var mx : base.u32
var decode_mcu_result : base.u32
var bitstream_length : base.u32
var b : base.u32
var i : base.u32
var csel : base.u8[..= 3]
var h : base.u64[..= 4]
var v : base.u64[..= 4]
var stride : base.u64[..= 0x4_0000]
var offset : base.u64
var status : base.status
if args.workbuf.length() < this.components_workbuf_offsets[4] {
return base."#bad workbuf length"
}
this.prepare_scan?(src: args.src)
// Reset.
this.next_restart_marker = 0
this.restarts_remaining = this.restart_interval
this.mcu_previous_dc_values[0] = 0
this.mcu_previous_dc_values[1] = 0
this.mcu_previous_dc_values[2] = 0
this.mcu_previous_dc_values[3] = 0
this.bitstream_bits = 0
this.bitstream_n_bits = 0
this.bitstream_ri = 0
this.bitstream_wi = 0
this.fill_bitstream!(src: args.src)
my = 0
while my < this.scan_height_in_mcus {
assert my < 0x2000 via "a < b: a < c; c <= b"(c: this.scan_height_in_mcus)
mx = 0
while mx < this.scan_width_in_mcus,
inv my < 0x2000,
{
assert mx < 0x2000 via "a < b: a < c; c <= b"(c: this.scan_width_in_mcus)
this.mcu_current_block = 0
this.mcu_zig_index = 0
while.decode_mcu true,
inv my < 0x2000,
inv mx < 0x2000,
{
decode_mcu_result = this.decode_mcu!()
if decode_mcu_result == 0 {
break.decode_mcu
} else if decode_mcu_result <> 1 {
return "#internal error: inconsistent decoder state"
}
while.fill_bitstream true,
inv my < 0x2000,
inv mx < 0x2000,
{
bitstream_length = this.bitstream_wi ~mod- this.bitstream_ri
this.fill_bitstream!(src: args.src)
if bitstream_length < (this.bitstream_wi ~mod- this.bitstream_ri) {
break.fill_bitstream
}
yield? base."$short read"
} endwhile.fill_bitstream
} endwhile.decode_mcu
// Apply IDCT.
b = 0
while b < this.mcu_num_blocks,
inv my < 0x2000,
inv mx < 0x2000,
{
assert b < 10 via "a < b: a < c; c <= b"(c: this.mcu_num_blocks)
csel = this.scan_comps_cselector[this.mcu_blocks_sselector[b]]
h = this.components_h[csel] as base.u64
v = this.components_v[csel] as base.u64
stride = this.components_workbuf_widths[csel] as base.u64
offset = this.components_workbuf_offsets[csel] + (8 *
((((h * (mx as base.u64)) + (this.scan_comps_bx_offset[b] as base.u64))) +
((((v * (my as base.u64)) + (this.scan_comps_by_offset[b] as base.u64))) * stride)))
if offset <= args.workbuf.length() {
this.decode_idct!(
dst_buffer: args.workbuf[offset ..],
dst_stride: stride,
b: b,
q: this.components_tq[csel] as base.u32)
}
b += 1
} endwhile
// Clear this.mcu_blocks.
b = 0
while b < this.mcu_num_blocks,
inv my < 0x2000,
inv mx < 0x2000,
{
assert b < 10 via "a < b: a < c; c <= b"(c: this.mcu_num_blocks)
i = 0
while i < 64,
inv my < 0x2000,
inv mx < 0x2000,
inv b < 10,
{
this.mcu_blocks[b][i] = 0
i += 1
} endwhile
b += 1
} endwhile
// Check for a restart.
if this.restarts_remaining > 0 {
this.restarts_remaining -= 1
if this.restarts_remaining == 0 {
this.skip_past_the_next_restart_marker?(src: args.src)
// Reset.
this.restarts_remaining = this.restart_interval
this.mcu_previous_dc_values[0] = 0
this.mcu_previous_dc_values[1] = 0
this.mcu_previous_dc_values[2] = 0
this.mcu_previous_dc_values[3] = 0
this.bitstream_bits = 0
this.bitstream_n_bits = 0
this.bitstream_ri = 0
this.bitstream_wi = 0
}
}
mx += 1
} endwhile
my += 1
} endwhile
if this.num_components == 1 {
status = this.swizzle_gray!(dst: args.dst, workbuf: args.workbuf)
return status
}
status = this.swizzle_colorful!(dst: args.dst, workbuf: args.workbuf)
return status
}
pri func decoder.prepare_scan?(src: base.io_reader) {
var c : base.u8
var i : base.u32
var j : base.u32
var h : base.u32[..= 4]
var v : base.u32[..= 4]
var hv : base.u32[..= 16]
var total_hv : base.u32
var b : base.u32
var bx_offset : base.u32
var by_offset : base.u32
if (this.payload_length < 6) or (this.payload_length > 12) {
return "#bad SOS marker"
}
c = args.src.read_u8?()
if (c < 1) or (c > 4) {
return "#bad SOS marker"
}
this.scan_num_components = c as base.u32
if (this.scan_num_components > this.num_components) or
((this.scan_num_components < this.num_components) and (this.sof_marker < 0xC2)) or
(this.payload_length <> (4 + (2 * this.scan_num_components))) {
return "#bad SOS marker"
}
this.payload_length = 0
i = 0
while i < this.scan_num_components {
assert i < 4 via "a < b: a < c; c <= b"(c: this.scan_num_components)
c = args.src.read_u8?()
j = 0
while true,
inv i < 4,
{
if j >= this.num_components {
return "#bad SOS marker"
}
assert j < 4 via "a < b: a < c; c <= b"(c: this.num_components)
if c <> this.components_c[j] {
j += 1
continue
}
if i > 0 {
if j <= (this.scan_comps_cselector[i - 1] as base.u32) {
return "#bad SOS marker"
}
}
if not this.seen_dqt[this.components_tq[j]] {
return "#missing Quantization table"
}
this.scan_comps_cselector[i] = j as base.u8
break
} endwhile
c = args.src.read_u8?()
if ((c >> 4) > 3) or ((c & 0x0F) > 3) {
return "#bad SOS marker"
}
this.scan_comps_td[i] = c >> 4
this.scan_comps_ta[i] = c & 0x0F
// SOF0 (which is 0xC0) means a Baseline JPEG. The Baseline (tx <= 1)
// restriction is specified in table B.5.
if this.sof_marker == 0xC0 {
if (this.scan_comps_td[i] > 1) or (this.scan_comps_ta[i] > 1) {
return "#bad SOS marker"
}
}
if (not this.seen_dht[0 | this.scan_comps_td[i]]) or
(not this.seen_dht[4 | this.scan_comps_ta[i]]) {
// TODO: while not required by the spec, we could fall back to
// implicit tables? See section K.3 "Typical Huffman tables for
// 8-bit precision luminance and chrominance" and
// https://github.com/libjpeg-turbo/libjpeg-turbo/commit/a113506d175d03ae0e40965c3d3d21a5d561e119
return "#missing Huffman table"
}
i += 1
} endwhile
c = args.src.read_u8?()
if c > 63 {
return "#bad SOS marker"
}
this.scan_ss = c
c = args.src.read_u8?()
if (c > 63) or (c < this.scan_ss) {
return "#bad SOS marker"
}
this.scan_se = c
c = args.src.read_u8?()
if ((c >> 4) > 13) or ((c & 0x0F) > 13) {
return "#bad SOS marker"
}
this.scan_ah = c >> 4
this.scan_al = c & 0x0F
// At this point, for sequential (not progressive) JPEGs, we should check
// that (Ss, Se, Ah, Al) is (0, 63, 0, 0) but libjpeg treats otherwise as a
// warning (JWRN_NOT_SEQUENTIAL), not an error.
this.scan_width_in_mcus = this.width_in_mcus
this.scan_height_in_mcus = this.height_in_mcus
if this.scan_num_components == 1 {
this.scan_comps_bx_offset[0] = 0
this.scan_comps_by_offset[0] = 0
this.mcu_num_blocks = 1
this.mcu_blocks_sselector[0] = 0
} else {
total_hv = 0
i = 0
b = 0
bx_offset = 0
by_offset = 0
while i < this.scan_num_components {
assert i < 4 via "a < b: a < c; c <= b"(c: this.scan_num_components)
h = this.components_h[this.scan_comps_cselector[i]] as base.u32
v = this.components_v[this.scan_comps_cselector[i]] as base.u32
hv = ((this.components_h[this.scan_comps_cselector[i]] as base.u32) *
(this.components_v[this.scan_comps_cselector[i]] as base.u32))
total_hv ~mod+= hv
while hv > 0,
inv i < 4,
{
this.scan_comps_bx_offset[b & 15] = (bx_offset & 3) as base.u8
this.scan_comps_by_offset[b & 15] = (by_offset & 3) as base.u8
this.mcu_blocks_sselector[b & 15] = i as base.u8
b ~mod+= 1
bx_offset ~mod+= 1
if bx_offset == h {
bx_offset = 0
by_offset ~mod+= 1
if by_offset == v {
by_offset = 0
}
}
hv -= 1
} endwhile
i += 1
} endwhile
if total_hv > 10 {
return "#bad SOS marker"
}
this.mcu_num_blocks = total_hv
}
}
pri func decoder.fill_bitstream!(src: base.io_reader) {
var i : base.u32[..= 0x800]
var wi : base.u32[..= 0x800]
var c : base.u8
var n : base.u32[..= 0x800]
// Compact unread bytes to the start of the buffer.
if this.bitstream_ri <= 0 {
// No-op.
} else if this.bitstream_ri == this.bitstream_wi {
this.bitstream_ri = 0
this.bitstream_wi = 0
} else {
// TODO: have a built-in (calling memmove) to compact a slice?
i = 0
assert i < this.bitstream_ri via "a < b: a == c; c < b"(c: 0)
while this.bitstream_ri < this.bitstream_wi,
pre i < this.bitstream_ri,
{
assert this.bitstream_ri < 0x800 via "a < b: a < c; c <= b"(c: this.bitstream_wi)
assert i < 0x800 via "a < b: a < c; c < b"(c: this.bitstream_ri)
this.bitstream_buffer[i] = this.bitstream_buffer[this.bitstream_ri]
this.bitstream_ri += 1
i += 1
// TODO: this if statement should be unnecessary. Instead, we
// should be able to "assert i < this.bitstream_ri".
if i >= this.bitstream_ri {
break // Unreachable.
}
} endwhile
this.bitstream_ri = 0
this.bitstream_wi = i
}
wi = this.bitstream_wi
while (wi < 0x800) and (args.src.length() > 0) {
c = args.src.peek_u8()
if c < 0xFF {
this.bitstream_buffer[wi] = c
wi += 1
args.src.skip_u32_fast!(actual: 1, worst_case: 1)
} else if args.src.length() <= 1 {
break
} else if (args.src.peek_u16le() >> 8) > 0 {
break
} else {
this.bitstream_buffer[wi] = 0xFF
wi += 1
args.src.skip_u32_fast!(actual: 2, worst_case: 2)
}
} endwhile
// If we hit a "\xFF\x??" marker then pad the bitstream. In this method
// invocation, insert up to 32 bytes of implicit zeroes. Repeated calls to
// this method can insert even more.
if args.src.length() > 1 {
if (args.src.peek_u8() >= 0xFF) and ((args.src.peek_u16le() >> 8) > 0) {
n = wi.min(no_more_than: 0x800 - 32) + 32
while wi < n {
assert wi < 0x800 via "a < b: a < c; c <= b"(c: n)
this.bitstream_buffer[wi] = 0x00
wi += 1
} endwhile
}
}
this.bitstream_wi = wi
}
pri func decoder.skip_past_the_next_restart_marker?(src: base.io_reader) {
var c : base.u8
while true {
if args.src.length() < 2 {
yield? base."$short read"
continue
} else if args.src.peek_u8() < 0xFF {
args.src.skip_u32_fast!(actual: 1, worst_case: 1)
continue
}
c = (args.src.peek_u16le() >> 8) as base.u8
if c < 0xC0 {
// Either a reserved marker or "\xFF\x00" byte stuffing. Consume it
// and continue.
args.src.skip_u32_fast!(actual: 2, worst_case: 2)
continue
} else if (c < 0xD0) or (0xD7 < c) {
// Not an RSTn (Restart) marker. Leave it and break.
break
}
c &= 7
if (this.next_restart_marker == ((c + 1) & 7)) or
(this.next_restart_marker == ((c + 2) & 7)) {
// A leading RSTn marker. Leave it and break.
break
} else if (this.next_restart_marker == ((c + 7) & 7)) or
(this.next_restart_marker == ((c + 6) & 7)) {
// A lagging RSTn marker. Consume it and continue.
args.src.skip_u32_fast!(actual: 2, worst_case: 2)
continue
} else {
// The RSTn marker is either the expected one or far away from the
// expected one (neither leading or lagging). Consume it and break.
args.src.skip_u32_fast!(actual: 2, worst_case: 2)
break
}
} endwhile
this.next_restart_marker = (this.next_restart_marker ~mod+ 1) & 7
}
pri func decoder.swizzle_gray!(dst: ptr base.pixel_buffer, workbuf: slice base.u8) 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_length : base.u64
var tab : table base.u8
var dst : slice base.u8
var y : base.u32
var stride : base.u64[..= 0x4_0000]
// 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_length = (dst_bytes_per_pixel * this.width) as base.u64
tab = args.dst.plane(p: 0)
y = 0
while y < this.height {
assert y < 0xFFFF via "a < b: a < c; c <= b"(c: this.height)
dst = tab.row_u32(y: y)
if dst_length < dst.length() {
dst = dst[.. dst_length]
}
this.swizzler.swizzle_interleaved_from_slice!(
dst: dst,
dst_palette: args.dst.palette_or_else(fallback: this.dst_palette[..]),
src: args.workbuf)
stride = this.components_workbuf_widths[0] as base.u64
if stride <= args.workbuf.length() {
args.workbuf = args.workbuf[stride ..]
} else {
args.workbuf = this.util.empty_slice_u8()
}
y += 1
} endwhile
return ok
}
pri func decoder.swizzle_colorful!(dst: ptr base.pixel_buffer, workbuf: slice base.u8) base.status {
var src0 : slice base.u8
var src1 : slice base.u8
var src2 : slice base.u8
var src3 : slice base.u8
var status : base.status
if (this.components_workbuf_offsets[0] <= this.components_workbuf_offsets[1]) and
(this.components_workbuf_offsets[1] <= args.workbuf.length()) {
src0 = args.workbuf[this.components_workbuf_offsets[0] .. this.components_workbuf_offsets[1]]
}
if (this.components_workbuf_offsets[1] <= this.components_workbuf_offsets[2]) and
(this.components_workbuf_offsets[2] <= args.workbuf.length()) {
src1 = args.workbuf[this.components_workbuf_offsets[1] .. this.components_workbuf_offsets[2]]
}
if (this.components_workbuf_offsets[2] <= this.components_workbuf_offsets[3]) and
(this.components_workbuf_offsets[3] <= args.workbuf.length()) {
src2 = args.workbuf[this.components_workbuf_offsets[2] .. this.components_workbuf_offsets[3]]
}
if (this.components_workbuf_offsets[3] <= this.components_workbuf_offsets[4]) and
(this.components_workbuf_offsets[4] <= args.workbuf.length()) {
src3 = args.workbuf[this.components_workbuf_offsets[3] .. this.components_workbuf_offsets[4]]
}
status = this.swizzler.swizzle_ycck!(
dst: args.dst,
dst_palette: args.dst.palette_or_else(fallback: this.dst_palette[..]),
width: this.width,
height: this.height,
src0: src0,
src1: src1,
src2: src2,
src3: src3,
width0: this.components_workbuf_widths[0],
width1: this.components_workbuf_widths[1],
width2: this.components_workbuf_widths[2],
width3: this.components_workbuf_widths[3],
height0: this.components_workbuf_heights[0],
height1: this.components_workbuf_heights[1],
height2: this.components_workbuf_heights[2],
height3: this.components_workbuf_heights[3],
stride0: this.components_workbuf_widths[0],
stride1: this.components_workbuf_widths[1],
stride2: this.components_workbuf_widths[2],
stride3: this.components_workbuf_widths[3],
h0: this.components_h[0],
h1: this.components_h[1],
h2: this.components_h[2],
h3: this.components_h[3],
v0: this.components_v[0],
v1: this.components_v[1],
v2: this.components_v[2],
v3: this.components_v[3],
triangle_filter_for_2to1: true,
scratch_buffer_2k: this.bitstream_buffer[..])
return status
}
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: 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.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 {
var i : base.u32
if this.call_sequence < 0x20 {
return base."#bad call sequence"
}
if args.index <> 0 {
return base."#bad argument"
}
this.call_sequence = 0x28
this.frame_config_io_position = args.io_position
this.restart_interval = this.saved_restart_interval
while i < 8 {
this.seen_dht[i] = false
i += 1
} endwhile
return ok
}
pub func decoder.set_report_metadata!(fourcc: base.u32, report: base.bool) {
// TODO: implement.
}
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: this.components_workbuf_offsets[4],
max_incl: this.components_workbuf_offsets[4])
}