blob: 469e37fb8fc5388b64bdc4c4d4d0ceed10176fdd [file] [log] [blame]
// Copyright 2017 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.
use "std/lzw"
pub status "?bad block"
pub status "?bad extension label"
pub status "?bad graphic control"
pub status "?bad header"
pub status "?bad literal width"
pub status "?not enough pixel data"
pub status "?too much pixel data"
pri status "?internal error: inconsistent ri/wi"
// TODO: reference lzw.decoder_workbuf_len_max_incl_worst_case.
pub const decoder_workbuf_len_max_incl_worst_case base.u64 = 0xFFFF
// See the spec appendix E "Interlaced Images" on page 29. The first element
// represents either that the frame was non-interlaced, or that all interlace
// stages are complete. Otherwise, the four interlace stages are elements 4, 3,
// 2 and 1 in descending order. For example, the start and delta for the first
// interlace stage is 0 and 8, for the second interlace stage is 4 and 8, etc.
// For interlaced frames, the decoder.interlace field starts at 4 and is
// decremented towards 0.
//
// interlace_start[0] is a special case. For non-interlaced frames, that
// element is never accessed. For interlaced frames, that element is only
// accessed after all interlace stages are complete. Being the maximum base.u32
// value means that, after all interlace stages are complete, dst_y will be set
// to that maximum value (and therefore outside the frame rect).
pri const interlace_start array[5] base.u32 = [0xFFFFFFFF, 1, 2, 4, 0]
pri const interlace_delta array[5] base.u8 = [1, 2, 4, 8, 8]
pub struct decoder?(
width base.u32,
height base.u32,
// Call sequence states:
// - 0: initial state.
// - 1: image config decoded, including the first frame's bounds, but not
// the first frame's pixels.
// - 2: frame config decoded.
// - 3: frame decoded.
//
// State transitions:
//
// - 0 -> 1: via IC
// - 0 -> 2: via FC with implicit IC
// - 0 -> 3: via F with implicit IC and FC
//
// - 1 -> 2: via FC
// - 1 -> 3: via F with implicit FC
//
// - 2 -> 2: via FC with implicit F
// - 2 -> 3: via F
//
// - 3 -> 2: via FC
// - 3 -> 3: via F with implicit FC
//
// Where:
// - F is decode_frame, implicit means skip_frame
// - FC is decode_frame_config, implicit means nullptr args.dst
// - IC is decode_image_config, implicit means nullptr args.dst
call_sequence base.u8,
end_of_data base.bool,
restarted base.bool,
previous_lzw_decode_ended_abruptly base.bool,
which_palette base.u8[..1],
// interlace indexes the interlace_start and interlace_delta arrays.
interlace base.u8[..4],
// Absent an ANIMEXTS1.0 or NETSCAPE2.0 extension, the implicit number of
// animation loops is 1.
seen_num_loops base.bool,
num_loops base.u32,
seen_graphic_control base.bool,
gc_has_transparent_index base.bool,
gc_transparent_index base.u8,
gc_disposal base.u8,
// There are 7056000 flicks per centisecond.
gc_duration base.u64[..0xFFFF * 7056000],
frame_config_io_position base.u64,
num_decoded_frame_configs_value base.u64,
num_decoded_frames_value base.u64,
frame_rect_x0 base.u32,
frame_rect_y0 base.u32,
frame_rect_x1 base.u32,
frame_rect_y1 base.u32,
// The dst_etc fields are the output cursor during copy_to_image_buffer.
dst_x base.u32,
dst_y base.u32,
dirty_y base.range_ie_u32,
// Indexes into the compressed array, defined below.
compressed_ri base.u64,
compressed_wi base.u64,
swizzler base.pixel_swizzler,
util base.utility,
lzw lzw.decoder,
compressed array[4096] base.u8,
// palettes[0] and palettes[1] are the Global and Local Color Table.
palettes array[2] array[4 * 256] base.u8,
// dst_palette is the swizzled color table.
dst_palette array[4 * 256] base.u8,
)
pub func decoder.decode_image_config?(dst nptr base.image_config, src base.io_reader) {
var ffio base.bool
if this.call_sequence >= 1 {
return base."?bad call sequence"
}
this.dirty_y.reset!()
this.decode_header?(src:args.src)
this.decode_lsd?(src:args.src)
this.decode_up_to_id_part1?(src:args.src)
// TODO: if this.end_of_data, return an error and/or set dst to zero?
ffio = (not this.gc_has_transparent_index) and
(this.frame_rect_x0 == 0) and
(this.frame_rect_y0 == 0) and
(this.frame_rect_x1 == this.width) and
(this.frame_rect_y1 == this.height)
if args.dst != nullptr {
// TODO: a Wuffs (not just C) name for the
// WUFFS_BASE__PIXEL_FORMAT__INDEXED__BGRA_BINARY magic pixfmt constant.
args.dst.set!(
pixfmt:0x47040008,
pixsub:0,
width:this.width,
height:this.height,
first_frame_io_position:this.frame_config_io_position,
first_frame_is_opaque:ffio)
}
this.call_sequence = 1
}
pub func decoder.num_animation_loops() base.u32 {
if this.seen_num_loops {
return this.num_loops
}
return 1
}
pub func decoder.num_decoded_frame_configs() base.u64 {
return this.num_decoded_frame_configs_value
}
pub func decoder.num_decoded_frames() base.u64 {
return this.num_decoded_frames_value
}
pub func decoder.frame_dirty_rect() base.rect_ie_u32 {
return this.util.make_rect_ie_u32(
min_incl_x:this.frame_rect_x0,
min_incl_y:this.dirty_y.get_min_incl(),
max_excl_x:this.frame_rect_x1,
max_excl_y:this.dirty_y.get_max_excl())
}
pub func decoder.workbuf_len() base.range_ii_u64 {
return this.util.make_range_ii_u64(
min_incl:this.width as base.u64,
max_incl:this.width as base.u64)
}
pub func decoder.restart_frame!(index base.u64, io_position base.u64) base.status {
if this.call_sequence == 0 {
return base."?bad call sequence"
}
this.end_of_data = false
this.restarted = true
this.frame_config_io_position = args.io_position
this.num_decoded_frame_configs_value = args.index
this.num_decoded_frames_value = args.index
this.reset_gc!()
return ok
}
pub func decoder.decode_frame_config?(dst nptr base.frame_config, src base.io_reader) {
var blend base.u8
if not this.end_of_data {
if this.call_sequence == 0 {
this.decode_image_config?(dst:nullptr, src:args.src)
} else if this.call_sequence != 1 {
if this.call_sequence == 2 {
this.skip_frame?(src:args.src)
}
this.decode_up_to_id_part1?(src:args.src)
}
}
// This is a new "if", not an "else", because the calls above can modify
// this.end_of_data.
if this.end_of_data {
return base."~end of data"
}
blend = 0
if not this.gc_has_transparent_index {
blend = 2 // 2 is WUFFS_BASE__ANIMATION_BLEND__OPAQUE.
}
if args.dst != nullptr {
args.dst.update!(bounds:this.util.make_rect_ie_u32(
min_incl_x:this.frame_rect_x0.min(x:this.width),
min_incl_y:this.frame_rect_y0.min(x:this.height),
max_excl_x:this.frame_rect_x1.min(x:this.width),
max_excl_y:this.frame_rect_y1.min(x:this.height)),
duration:this.gc_duration,
index:this.num_decoded_frame_configs_value,
io_position:this.frame_config_io_position,
blend:blend,
disposal:this.gc_disposal)
}
this.num_decoded_frame_configs_value ~sat+= 1
this.call_sequence = 2
}
pri func decoder.skip_frame?(src base.io_reader) {
var flags base.u8
// Skip the optional Local Color Table, 3 bytes (RGB) per entry.
flags = args.src.read_u8?()
if (flags & 0x80) != 0 {
args.src.skip?(n:(3 as base.u32) << (1 + (flags & 0x07)))
}
// Skip the literal width.
args.src.skip?(n:1)
// Skip the blocks of LZW-compressed data.
this.skip_blocks?(src:args.src)
this.num_decoded_frames_value ~sat+= 1
this.reset_gc!()
}
// TODO: honor args.opts.
pub func decoder.decode_frame?(dst ptr base.pixel_buffer, src base.io_reader, workbuf slice base.u8, opts nptr base.decode_frame_options) {
if this.call_sequence != 2 {
this.decode_frame_config?(dst:nullptr, src:args.src)
}
this.decode_id_part1?(dst:args.dst, src:args.src)
this.decode_id_part2?(dst:args.dst, src:args.src, workbuf:args.workbuf)
this.num_decoded_frames_value ~sat+= 1
this.reset_gc!()
}
pri func decoder.reset_gc!() {
this.call_sequence = 3
// The Image Descriptor is mandatory, but the Graphic Control extension is
// optional. Reset the GC related fields for the next decode_frame call.
this.seen_graphic_control = false
this.gc_has_transparent_index = false
this.gc_transparent_index = 0
this.gc_disposal = 0
this.gc_duration = 0
}
pri func decoder.decode_up_to_id_part1?(src base.io_reader) {
var block_type base.u8
if not this.restarted {
this.frame_config_io_position = args.src.position()
} else if this.frame_config_io_position != args.src.position() {
return base."?bad restart"
} else {
this.restarted = false
}
while true {
block_type = args.src.read_u8?()
if block_type == 0x21 { // The spec calls 0x21 the "Extension Introducer".
this.decode_extension?(src:args.src)
} else if block_type == 0x2C { // The spec calls 0x2C the "Image Separator".
this.decode_id_part0?(src:args.src)
break
} else if block_type == 0x3B { // The spec calls 0x3B the "Trailer".
this.end_of_data = true
break
} else {
return "?bad block"
}
}
}
// decode_header reads either "GIF87a" or "GIF89a".
//
// See the spec section 17 "Header" on page 7.
pri func decoder.decode_header?(src base.io_reader) {
var c array[6] base.u8
var i base.u32
while i < 6 {
c[i] = args.src.read_u8?()
i += 1
}
if (c[0] != 0x47) or (c[1] != 0x49) or (c[2] != 0x46) or (c[3] != 0x38) or
((c[4] != 0x37) and (c[4] != 0x39)) or (c[5] != 0x61) {
return "?bad header"
}
}
// decode_lsd reads the Logical Screen Descriptor.
//
// See the spec section 18 "Logical Screen Descriptor" on page 8.
pri func decoder.decode_lsd?(src base.io_reader) {
var flags base.u8
var num_palette_entries base.u32[..256]
var i base.u32
var argb base.u32
this.width = args.src.read_u16le_as_u32?()
this.height = args.src.read_u16le_as_u32?()
flags = args.src.read_u8?()
// Ignore the Background Color Index and Pixel Aspect Ratio bytes.
args.src.skip?(n:2)
// Read the optional Global Color Table.
if (flags & 0x80) != 0 {
num_palette_entries = (1 as base.u32) << (1 + (flags & 0x07))
i = 0
while i < num_palette_entries {
assert i < 256 via "a < b: a < c; c <= b"(c:num_palette_entries)
// Convert from RGB (in memory order) to ARGB (in native u32 order)
// to BGRA (in memory order).
argb = args.src.read_u24be_as_u32?()
argb |= 0xFF000000
this.palettes[0][(4 * i) + 0] = ((argb >> 0) & 0xFF) as base.u8
this.palettes[0][(4 * i) + 1] = ((argb >> 8) & 0xFF) as base.u8
this.palettes[0][(4 * i) + 2] = ((argb >> 16) & 0xFF) as base.u8
this.palettes[0][(4 * i) + 3] = ((argb >> 24) & 0xFF) as base.u8
i += 1
}
// Set the remaining palette entries to opaque black.
while i < 256 {
this.palettes[0][(4 * i) + 0] = 0x00
this.palettes[0][(4 * i) + 1] = 0x00
this.palettes[0][(4 * i) + 2] = 0x00
this.palettes[0][(4 * i) + 3] = 0xFF
i += 1
}
}
}
// decode_extension reads an extension. The Extension Introducer byte has
// already been read.
//
// See the spec:
// - section 23 "Graphic Control Extension" on page 15.
// - section 24 "Comment Extension" on page 17.
// - section 25 "Plain Text Extension" on page 18.
// - section 26 "Application Extension" on page 21.
pri func decoder.decode_extension?(src base.io_reader) {
var label base.u8
label = args.src.read_u8?()
if label == 0xF9 { // The spec calls 0xF9 the "Graphic Control Label".
this.decode_gc?(src:args.src)
return ok
} else if label == 0xFF { // The spec calls 0xFF the "Application Extension Label".
this.decode_ae?(src:args.src)
return ok
}
// We skip over all other extensions, including 0x01 "Plain Text Label" and
// 0xFE "Comment Label".
this.skip_blocks?(src:args.src)
}
pri func decoder.skip_blocks?(src base.io_reader) {
var block_size base.u8
while true {
block_size = args.src.read_u8?()
if block_size == 0 {
return ok
}
args.src.skip?(n:block_size as base.u32)
}
}
// animexts1dot0 is "ANIMEXTS1.0" as bytes.
pri const animexts1dot0 array[11] base.u8 = [
0x41, 0x4E, 0x49, 0x4D, 0x45, 0x58, 0x54, 0x53, 0x31, 0x2E, 0x30,
]
// netscape2dot0 is "NETSCAPE2.0" as bytes.
pri const netscape2dot0 array[11] base.u8 = [
0x4E, 0x45, 0x54, 0x53, 0x43, 0x41, 0x50, 0x45, 0x32, 0x2E, 0x30,
]
// decode_ae reads an Application Extension.
pri func decoder.decode_ae?(src base.io_reader) {
var c base.u8
var block_size base.u8
var not_animexts base.bool
var not_netscape base.bool
// This "while true" always executes exactly once, as it ends with a
// "break", but using "break"s throughout simplifies the control flow.
while true {
block_size = args.src.read_u8?()
if block_size == 0 {
return ok
}
// Look only for an 11 byte "ANIMEXTS1.0" or "NETSCAPE2.0" extension,
// as per:
// - http://www.vurdalakov.net/misc/gif/animexts-looping-application-extension
// - http://www.vurdalakov.net/misc/gif/netscape-looping-application-extension
if block_size != 11 {
args.src.skip?(n:block_size as base.u32)
break
}
not_animexts = false
not_netscape = false
block_size = 0 // Re-purpose the block_size variable as a counter.
while block_size < 11 {
c = args.src.read_u8?()
not_animexts = not_animexts or (c != animexts1dot0[block_size])
not_netscape = not_netscape or (c != netscape2dot0[block_size])
block_size += 1
}
if not_animexts and not_netscape {
break
}
// Those 11 bytes should be followed by 0x03, 0x01 and then the loop
// count.
block_size = args.src.read_u8?()
if block_size != 3 {
args.src.skip?(n:block_size as base.u32)
break
}
c = args.src.read_u8?()
if c != 0x01 {
args.src.skip?(n:2)
break
}
this.num_loops = args.src.read_u16le_as_u32?()
this.seen_num_loops = true
// A loop count of N, in the wire format, actually means "repeat N
// times after the first play", if N is positive. A zero N means to
// loop forever. Playing the frames exactly once is denoted by the
// *absence* of this NETSCAPE2.0 application extension.
//
// For example, if there are four frames: A, B, C, D, and N is 2, then
// each frame is actually played N+1 or 3 times: ABCDABCDABCD.
//
// Thus, we increment N if it is positive. The comparison against
// 0xFFFF will never fail, but is necessary for the overflow checker.
if (0 < this.num_loops) and (this.num_loops <= 0xFFFF) {
this.num_loops += 1
}
break
}
this.skip_blocks?(src:args.src)
}
// decode_gc reads a Graphic Control.
pri func decoder.decode_gc?(src base.io_reader) {
var c base.u8
var flags base.u8
var gc_duration_centiseconds base.u16
if this.seen_graphic_control {
return "?bad graphic control"
}
c = args.src.read_u8?()
if c != 4 {
return "?bad graphic control"
}
flags = args.src.read_u8?()
this.gc_has_transparent_index = (flags & 0x01) != 0
// Convert the disposal method from GIF's wire format to Wuffs constants.
//
// The GIF spec discusses the 3-bit flag value being 0, 1, 2 or 3. Values
// in the range [4..7] are "to be defined". In practice, some encoders also
// use 4 for "restore previous". See
// https://cs.chromium.org/chromium/src/third_party/blink/renderer/platform/image-decoders/gif/gif_image_reader.cc?rcl=5161173c43324da2b13e1aa45bbe69901daa1279&l=625
//
// TODO: named constants instead of assigning 1 for
// WUFFS_BASE__ANIMATION_DISPOSAL__RESTORE_BACKGROUND, etc.
flags = (flags >> 2) & 0x07
if flags == 2 {
this.gc_disposal = 1 // 1 is WUFFS_BASE__ANIMATION_DISPOSAL__RESTORE_BACKGROUND
} else if (flags == 3) or (flags == 4) {
this.gc_disposal = 2 // 2 is WUFFS_BASE__ANIMATION_DISPOSAL__RESTORE_PREVIOUS
} else {
this.gc_disposal = 0
}
// There are 7056000 flicks per centisecond.
gc_duration_centiseconds = args.src.read_u16le?()
this.gc_duration = (gc_duration_centiseconds as base.u64) * 7056000
this.gc_transparent_index = args.src.read_u8?()
c = args.src.read_u8?()
if c != 0 {
return "?bad graphic control"
}
this.seen_graphic_control = true
}
// decode_id_partX reads an Image Descriptor. The Image Separator byte has
// already been read.
//
// See the spec section 20 "Image Descriptor" on page 11.
//
// The code is split into three parts (part0, part 1 and part12 because
// determining the overall image's width and height also requires decoding the
// first frame's bounds (part0), but doesn't require decoding the first frame's
// pixels (the other two parts). Decoding the actual pixels is split into two
// (part1 and part2) not out of necessity, just for the general programming
// principle that smaller functions are easier to understand.
pri func decoder.decode_id_part0?(src base.io_reader) {
this.frame_rect_x0 = args.src.read_u16le_as_u32?()
this.frame_rect_y0 = args.src.read_u16le_as_u32?()
this.frame_rect_x1 = args.src.read_u16le_as_u32?()
this.frame_rect_x1 ~mod+= this.frame_rect_x0
this.frame_rect_y1 = args.src.read_u16le_as_u32?()
this.frame_rect_y1 ~mod+= this.frame_rect_y0
this.dst_x = this.frame_rect_x0
this.dst_y = this.frame_rect_y0
// Set the image's overall width and height to be the maximum of the
// nominal image width and height (given in the Logical Screen Descriptor)
// and the bottom right extent of the first frame. See
// test/data/artificial/gif-frame-out-of-bounds.gif.make-artificial.txt for
// more discussion.
if this.call_sequence == 0 {
this.width = this.width.max(x:this.frame_rect_x1)
this.height = this.height.max(x:this.frame_rect_y1)
}
}
pri func decoder.decode_id_part1?(dst ptr base.pixel_buffer, src base.io_reader) {
var flags base.u8
var num_palette_entries base.u32[..256]
var i base.u32
var argb base.u32
var dst_palette slice base.u8
var lw base.u8
flags = args.src.read_u8?()
if (flags & 0x40) != 0 {
this.interlace = 4
} else {
this.interlace = 0
}
// Read the optional Local Color Table.
this.which_palette = 1
if (flags & 0x80) != 0 {
num_palette_entries = (1 as base.u32) << (1 + (flags & 0x07))
i = 0
while i < num_palette_entries {
assert i < 256 via "a < b: a < c; c <= b"(c:num_palette_entries)
// Convert from RGB (in memory order) to ARGB (in native u32 order)
// to BGRA (in memory order).
argb = args.src.read_u24be_as_u32?()
argb |= 0xFF000000
this.palettes[1][(4 * i) + 0] = ((argb >> 0) & 0xFF) as base.u8
this.palettes[1][(4 * i) + 1] = ((argb >> 8) & 0xFF) as base.u8
this.palettes[1][(4 * i) + 2] = ((argb >> 16) & 0xFF) as base.u8
this.palettes[1][(4 * i) + 3] = ((argb >> 24) & 0xFF) as base.u8
i += 1
}
// Set the remaining palette entries to opaque black.
while i < 256 {
this.palettes[1][(4 * i) + 0] = 0x00
this.palettes[1][(4 * i) + 1] = 0x00
this.palettes[1][(4 * i) + 2] = 0x00
this.palettes[1][(4 * i) + 3] = 0xFF
i += 1
}
} else if this.gc_has_transparent_index {
this.palettes[1][:].copy_from_slice!(s:this.palettes[0][:])
} else {
this.which_palette = 0
}
// Set the gc_transparent_index palette entry to transparent black.
if this.gc_has_transparent_index {
this.palettes[1][(4 * (this.gc_transparent_index as base.u32)) + 0] = 0x00
this.palettes[1][(4 * (this.gc_transparent_index as base.u32)) + 1] = 0x00
this.palettes[1][(4 * (this.gc_transparent_index as base.u32)) + 2] = 0x00
this.palettes[1][(4 * (this.gc_transparent_index as base.u32)) + 3] = 0x00
}
dst_palette = args.dst.palette()
if dst_palette.length() == 0 {
dst_palette = this.dst_palette[:]
}
// TODO: a Wuffs (not just C) name for the
// WUFFS_BASE__PIXEL_FORMAT__INDEXED__BGRA_BINARY magic pixfmt constant.
this.swizzler.prepare!(
dst_pixfmt:args.dst.pixel_format(),
dst_palette:dst_palette,
src_pixfmt:0x47040008,
src_palette:this.palettes[this.which_palette][:])
// Other GIF implementations accept GIF files that aren't completely spec
// compliant. For example, the test/data/gifplayer-muybridge.gif file
// (created by the gifsicle program) is accepted by other GIF decoders.
// However, in that file, frame #61's embedded LZW data is truncated,
// finishing with only 8 of the 9 bits required of the LZW end code. The
// end code itself, 0x81, is representable in only 8 bits, but the number
// of bits for the decoder to read has ticked over from 8 to 9 just before
// that end code is encountered.
//
// To accommodate such malformed GIFs, we detect when the previous frame's
// LZW decoding ended abruptly. The previous LZW decode 'works', in that it
// decodes as much pixel data as is available, but without seeing that end
// code (i.e. returning the "ok" status code), the LZW decoder is stuck in
// a coroutine-in-progress lzw_decoder.decode call, still waiting for that
// end code. To cancel that coroutine, we reset the LZW decoder.
if this.previous_lzw_decode_ended_abruptly {
this.lzw.reset!()
}
lw = args.src.read_u8?()
if (lw < 2) or (8 < lw) {
return "?bad literal width"
}
this.lzw.set_literal_width!(lw:lw as base.u32)
this.previous_lzw_decode_ended_abruptly = true
}
pri func decoder.decode_id_part2?(dst ptr base.pixel_buffer, src base.io_reader, workbuf slice base.u8) {
var block_size base.u64[..255]
var need_block_size base.bool
var n_compressed base.u64
var compressed slice base.u8
var r base.io_reader
var lzw_status base.status
var copy_status base.status
var uncompressed slice base.u8
need_block_size = true
while:outer true {
if need_block_size {
need_block_size = false
block_size = args.src.read_u8_as_u64?()
}
if block_size == 0 {
break
}
while args.src.available() == 0 {
yield base."$short read"
}
if this.compressed_ri == this.compressed_wi {
this.compressed_ri = 0
this.compressed_wi = 0
}
while this.compressed_wi <= (4096 - 255) {
n_compressed = block_size.min(x:args.src.available())
if n_compressed <= 0 {
break
}
compressed = args.src.take!(n:n_compressed)
this.compressed[this.compressed_wi:].copy_from_slice!(s:compressed)
this.compressed_wi ~sat+= n_compressed
block_size ~sat-= n_compressed
if block_size > 0 {
break
}
if args.src.available() <= 0 {
need_block_size = true
break
}
block_size = args.src.peek_u8_as_u64()
args.src.skip_fast!(actual:1, worst_case:1)
}
if (this.width as base.u64) < args.workbuf.length() {
args.workbuf = args.workbuf[:this.width as base.u64]
} else if (this.width as base.u64) > args.workbuf.length() {
return base."?bad workbuf length"
}
while:inner true {
if (this.compressed_ri > this.compressed_wi) or (this.compressed_wi > 4096) {
return "?internal error: inconsistent ri/wi"
}
io_bind (io:r, data:this.compressed[this.compressed_ri:this.compressed_wi]) {
lzw_status =? this.lzw.decode_io_writer?(
dst:this.util.null_io_writer(), src:r, workbuf:this.util.null_slice_u8())
this.compressed_ri ~sat+= r.since_mark().length()
}
uncompressed = this.lzw.flush!()
if uncompressed.length() > 0 {
copy_status = this.copy_to_image_buffer!(pb:args.dst, src:uncompressed)
if copy_status.is_error() {
return copy_status
}
}
if lzw_status.is_ok() {
this.previous_lzw_decode_ended_abruptly = false
// Skip any trailing blocks.
if need_block_size or (block_size > 0) {
args.src.skip?(n:block_size as base.u32)
this.skip_blocks?(src:args.src)
}
break:outer
} else if lzw_status == base."$short read" {
continue:outer
} else if lzw_status == base."$short write" {
continue:inner
}
return lzw_status
}
}
this.compressed_ri = 0
this.compressed_wi = 0
// TODO: check for "not enough pixel data".
}
pri func decoder.copy_to_image_buffer!(pb ptr base.pixel_buffer, src slice base.u8) base.status {
// TODO: don't assume a packed pixel format.
var dst slice base.u8
var src slice base.u8
var n base.u64
var src_ri base.u64
var bytes_per_pixel base.u32[..64]
var pixfmt base.u32
var tab table base.u8
var i base.u64
var j base.u64
// TODO: a Wuffs (not just C) name for the WUFFS_BASE__PIXEL_FORMAT__ETC
// magic pixfmt constants. Also, support more formats.
bytes_per_pixel = 1
pixfmt = args.pb.pixel_format()
if (pixfmt == 0x45008888) or (pixfmt == 0x55008888) or
(pixfmt == 0x46008888) or (pixfmt == 0x56008888) or
(pixfmt == 0x47008888) or (pixfmt == 0x57008888) {
bytes_per_pixel = 4
}
tab = args.pb.plane(p:0)
while src_ri < args.src.length() {
src = args.src[src_ri:]
if this.dst_y >= this.frame_rect_y1 {
return "?too much pixel data"
}
// First, copy from src to that part of the frame rect that is inside
// args.pb's bounds.
dst = tab.row(y:this.dst_y)
i = (this.dst_x as base.u64) * (bytes_per_pixel as base.u64)
if i < dst.length() {
j = (this.frame_rect_x1 as base.u64) * (bytes_per_pixel as base.u64)
if (i <= j) and (j <= dst.length()) {
dst = dst[i:j]
} else {
dst = dst[i:]
}
n = this.swizzler.swizzle_packed!(dst:dst, dst_palette:this.dst_palette[:], src:src)
src_ri ~sat+= n
this.dst_x ~sat+= (n & 0xFFFFFFFF) as base.u32
this.dirty_y = this.dirty_y.unite(r:this.util.make_range_ie_u32(
min_incl:this.dst_y,
max_excl:this.dst_y ~sat+ 1))
}
if this.frame_rect_x1 <= this.dst_x {
this.dst_x = this.frame_rect_x0
this.dst_y ~sat+= interlace_delta[this.interlace] as base.u32
while (this.interlace > 0) and (this.dst_y >= this.frame_rect_y1) {
this.interlace -= 1
this.dst_y = this.frame_rect_y0 ~sat+ interlace_start[this.interlace]
}
continue
}
if args.src.length() == src_ri {
break
} else if args.src.length() < src_ri {
return "?internal error: inconsistent ri/wi"
}
// Second, skip over src for that part of the frame rect that is
// outside args.pb's bounds. This second step should be infrequent.
// Set n to the number of pixels (i.e. the number of bytes) to skip.
n = (this.frame_rect_x1 - this.dst_x) as base.u64
n = n.min(x:args.src.length() - src_ri)
src_ri ~sat+= n
this.dst_x ~sat+= (n & 0xFFFFFFFF) as base.u32
if this.frame_rect_x1 <= this.dst_x {
this.dst_x = this.frame_rect_x0
this.dst_y ~sat+= interlace_delta[this.interlace] as base.u32
while (this.interlace > 0) and (this.dst_y >= this.frame_rect_y1) {
this.interlace -= 1
this.dst_y = this.frame_rect_y0 ~sat+ interlace_start[this.interlace]
}
continue
}
if src_ri != args.src.length() {
return "?internal error: inconsistent ri/wi"
}
break
}
return ok
}