| // 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 arithmetic coding" | |
| pub status "#unsupported color model" | |
| 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, | |
| saved_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, | |
| saved_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 | |
| if this.sof_marker == 0 { | |
| i = 0 | |
| while i < 64 { | |
| this.saved_quant_tables[q][i] = this.quant_tables[q][i] | |
| i += 1 | |
| } endwhile | |
| this.saved_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" | |
| } else if c == 2 { | |
| return "#unsupported color model" | |
| } | |
| 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). | |
| 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.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 | |
| var j : 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 < 4 { | |
| this.seen_dqt[i] = this.saved_seen_dqt[i] | |
| while j < 64, | |
| inv i < 4, | |
| { | |
| this.quant_tables[i][j] = this.saved_quant_tables[i][j] | |
| j += 1 | |
| } endwhile | |
| i += 1 | |
| } endwhile | |
| 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]) | |
| } |