blob: b96ccee858bcb3180e5d5039f13144562065616b [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 frame size"
pub status "#bad graphic control"
pub status "#bad header"
pub status "#bad literal width"
pub status "#bad palette"
pri status "#internal error: inconsistent ri/wi"
// TODO: replace the placeholder 1 value with either 0 or 0xFFFF (plus
// lzw.decoder_workbuf_len_max_incl_worst_case), depending on whether we'll
// need a per-pixel-row workbuf.
pub const decoder_workbuf_len_max_incl_worst_case base.u64 = 1
// --------
// Quirks are discussed in (/doc/note/quirks.md).
//
// The base38 encoding of "gif " is 0xF8586.
// When this quirk is enabled, when skipping over frames, the number of frames
// visited isn't incremented when the last byte of the N'th frame is seen.
// Instead, it is incremented when the first byte of the N+1'th frame's header
// is seen. There may be zero or more GIF extensions between the N'th frame's
// payload and the N+1'th frame's header.
//
// For a well-formed GIF, this won't have much effect. For a malformed GIF,
// this can affect the number of valid frames, if there is an error detected in
// the extensions between one frame's payload and the next frame's header.
//
// Some other GIF decoders don't register the N'th frame as complete until they
// see the N+1'th frame's header (or the end-of-animation terminator), so that
// e.g. the API for visiting the N'th frame can also return whether it's the
// final frame. Enabling this quirk allows for matching that behavior.
pub const quirk_delay_num_decoded_frames base.u32 = (0xF8586 << 10) | 0
// When this quirk is enabled, the background color of the first frame is set
// to black whenever that first frame has a local (frame-specific) palette.
// That black can be either opaque black or transparent black, depending on
// whether or not that first frame is opaque: whether that local palette
// contains a transparent color.
//
// This has no effect unless quirk_honor_background_color is also enabled.
//
// There isn't really much of a rationale for this, other than it matches the
// behavior of another GIF implementation.
pub const quirk_first_frame_local_palette_means_black_background base.u32 = (0xF8586 << 10) | 1
// When this quirk is enabled, the background color is taken from the GIF
// instead of always being transparent black. If the background color index in
// the GIF header is non-zero but less than the global palette's size, the
// global background color is that global palette's entry. Otherwise, it is
// opaque black. A frame's background color is transparent if the frame palette
// contains a transparent color. Otherwise, it is the global background color.
// Note that different frames can have different background colors.
//
// Specifically, if the initial frame bounds is smaller than the image bounds,
// those pixels outside the initial frame bounds are assumed to start as that
// frame background color. The frame background color should also be used when
// processing WUFFS_BASE__ANIMATION_DISPOSAL__RESTORE_BACKGROUND. In both
// cases, the caller of Wuffs, not Wuffs itself, is responsible for filling the
// pixel buffer with that color.
pub const quirk_honor_background_color base.u32 = (0xF8586 << 10) | 2
// When this quirk is enabled, silently ignore e.g. a frame that reports a
// width and height of 6 pixels each, followed by 50 pixel values. In that
// case, we process the first 36 pixel values and discard the excess 14.
pub const quirk_ignore_too_much_pixel_data base.u32 = (0xF8586 << 10) | 3
// When this quirk is enabled, if the initial frame bounds extends beyond the
// image bounds, then the image bounds stay unchanged. By default (with this
// quirk disabled), the image bounds are adjusted to always contain the first
// frame's bounds (but not necessarily subsequent frame's bounds).
//
// For more discussion, see
// https://github.com/google/wuffs/blob/master/test/data/artificial/gif-frame-out-of-bounds.gif.make-artificial.txt
pub const quirk_image_bounds_are_strict base.u32 = (0xF8586 << 10) | 4
// When this quirk is enabled, a frame with zero width or height is rejected
// during decode_frame (but accepted during decode_frame_config).
pub const quirk_reject_empty_frame base.u32 = (0xF8586 << 10) | 5
// When this quirk is enabled, a frame with no explicit palette is rejected,
// instead of implicitly having a palette with every entry being opaque black.
pub const quirk_reject_empty_palette base.u32 = (0xF8586 << 10) | 6
// --------
// 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: metadata reported; image config decode is in progress.
// - 2: metadata finished; image config decode is in progress.
// - 3: image config decoded, including the first frame's bounds, but not
// the first frame's pixels.
// - 4: frame config decoded.
// - 5: frame decoded.
//
// State transitions:
//
// - 0 -> 1: via IC (metadata reported)
// - 0 -> 3: via IC (metadata not reported)
// - 0 -> 4: via FC with implicit IC
// - 0 -> 5: via F with implicit IC and FC
//
// - 1 -> 2: via AMC
//
// - 2 -> 1: via IC (metadata reported)
// - 2 -> 3: via IC (metadata not reported)
//
// - 3 -> 4: via FC
// - 3 -> 5: via F with implicit FC
//
// - 4 -> 4: via FC with implicit F
// - 4 -> 5: via F
//
// - 5 -> 4: via FC
// - 5 -> 5: via F with implicit FC
//
// Where:
// - AMC is ack_metadata_chunk
// - 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,
ignore_metadata base.bool,
report_metadata_iccp base.bool,
report_metadata_xmp base.bool,
metadata_fourcc_value base.u32,
metadata_chunk_length_value base.u64,
metadata_io_position base.u64,
quirk_enabled_delay_num_decoded_frames base.bool,
quirk_enabled_first_frame_local_palette_means_black_background base.bool,
quirk_enabled_honor_background_color base.bool,
quirk_enabled_ignore_too_much_pixel_data base.bool,
quirk_enabled_image_bounds_are_strict base.bool,
quirk_enabled_reject_empty_frame base.bool,
quirk_enabled_reject_empty_palette base.bool,
delayed_num_decoded_frames base.bool,
end_of_data base.bool,
restarted base.bool,
previous_lzw_decode_ended_abruptly base.bool,
has_global_palette base.bool,
// 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,
background_color_u32_argb_premul base.u32,
black_color_u32_argb_premul base.u32,
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,
)(
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,
lzw lzw.decoder,
)
pub func decoder.set_quirk_enabled!(quirk base.u32, enabled base.bool) {
if this.call_sequence == 0 {
if args.quirk == quirk_delay_num_decoded_frames {
this.quirk_enabled_delay_num_decoded_frames = args.enabled
} else if args.quirk == quirk_first_frame_local_palette_means_black_background {
this.quirk_enabled_first_frame_local_palette_means_black_background = args.enabled
} else if args.quirk == quirk_honor_background_color {
this.quirk_enabled_honor_background_color = args.enabled
} else if args.quirk == quirk_ignore_too_much_pixel_data {
this.quirk_enabled_ignore_too_much_pixel_data = args.enabled
} else if args.quirk == quirk_image_bounds_are_strict {
this.quirk_enabled_image_bounds_are_strict = args.enabled
} else if args.quirk == quirk_reject_empty_frame {
this.quirk_enabled_reject_empty_frame = args.enabled
} else if args.quirk == quirk_reject_empty_palette {
this.quirk_enabled_reject_empty_palette = args.enabled
}
}
}
pub func decoder.decode_image_config?(dst nptr base.image_config, src base.io_reader) {
var ffio base.bool
if this.call_sequence == 0 {
this.decode_header?(src:args.src)
this.decode_lsd?(src:args.src)
} else if this.call_sequence <> 2 {
return base."#bad call sequence"
}
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
if not this.quirk_enabled_honor_background_color {
ffio = ffio 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)
} else if ffio {
// Use opaque black, not transparent black.
this.black_color_u32_argb_premul = 0xFF000000
}
if this.background_color_u32_argb_premul == 77 {
this.background_color_u32_argb_premul = this.black_color_u32_argb_premul
}
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 = 3
}
pub func decoder.set_report_metadata!(fourcc base.u32, report base.bool) {
if args.fourcc == 0x49434350 { // "ICCP"
this.report_metadata_iccp = args.report
} else if args.fourcc == 0x584D5020 { // "XMP "
this.report_metadata_xmp = args.report
}
}
pub func decoder.ack_metadata_chunk?(src base.io_reader) {
if this.call_sequence <> 1 {
return base."#bad call sequence"
}
if args.src.position() <> this.metadata_io_position {
return base."#bad I/O position"
}
while args.src.available() <= 0,
post args.src.available() > 0,
{
yield? base."$short read"
}
if this.metadata_fourcc_value == 0x584D5020 { // "XMP "
// The +1 is because XMP metadata's encoding includes each block's leading
// byte (the block size) as part of the metadata passed to the caller.
this.metadata_chunk_length_value = args.src.peek_u8_as_u64() + 1
if this.metadata_chunk_length_value > 1 {
this.metadata_io_position = args.src.position() ~sat+ this.metadata_chunk_length_value
return base."@metadata reported"
}
} else {
this.metadata_chunk_length_value = args.src.peek_u8_as_u64()
if this.metadata_chunk_length_value > 0 {
args.src.skip_fast!(actual:1, worst_case:1)
this.metadata_io_position = args.src.position() ~sat+ this.metadata_chunk_length_value
return base."@metadata reported"
}
}
args.src.skip_fast!(actual:1, worst_case:1)
this.call_sequence = 2
this.metadata_fourcc_value = 0
this.metadata_io_position = 0
return ok
}
pub func decoder.metadata_fourcc() base.u32 {
return this.metadata_fourcc_value
}
pub func decoder.metadata_chunk_length() base.u64 {
return this.metadata_chunk_length_value
}
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 {
// TODO: intersect this with the frame_rect? In theory, that should be
// unnecessary, and could hide bugs, but it'd be a cheap way to ensure that
// the dirty_rect is inside the frame_rect.
//
// Note that this method is pure, so it cannot set a sticky error bit if
// the dirty_rect is too big.
return this.util.make_rect_ie_u32(
min_incl_x:this.frame_rect_x0.min(x:this.width),
min_incl_y:this.dirty_y.get_min_incl(),
max_excl_x:this.frame_rect_x1.min(x:this.width),
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:decoder_workbuf_len_max_incl_worst_case,
max_incl:decoder_workbuf_len_max_incl_worst_case)
}
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.delayed_num_decoded_frames = false
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
var background_color base.u32
var flags base.u8
this.ignore_metadata = true
this.dirty_y.reset!()
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 <> 3 {
if this.call_sequence == 4 {
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
background_color = this.black_color_u32_argb_premul
if not this.gc_has_transparent_index {
blend = 2 // 2 is WUFFS_BASE__ANIMATION_BLEND__OPAQUE.
background_color = this.background_color_u32_argb_premul
// If the quirk is enabled and the first frame has a local color
// palette, its background color is black.
if this.quirk_enabled_first_frame_local_palette_means_black_background and
(this.num_decoded_frame_configs_value == 0) {
while args.src.available() <= 0,
post args.src.available() > 0,
{
yield? base."$short read"
}
flags = args.src.peek_u8()
if (flags & 0x80) <> 0 {
background_color = this.black_color_u32_argb_premul
}
}
}
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,
background_color:background_color)
}
this.num_decoded_frame_configs_value ~sat+= 1
this.call_sequence = 4
}
pri func decoder.skip_frame?(src base.io_reader) {
var flags base.u8
var lw 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)))
}
// Process the LZW literal width.
lw = args.src.read_u8?()
if lw > 8 {
return "#bad literal width"
}
// Skip the blocks of LZW-compressed data.
this.skip_blocks?(src:args.src)
if this.quirk_enabled_delay_num_decoded_frames {
this.delayed_num_decoded_frames = true
} else {
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) {
this.ignore_metadata = true
if this.call_sequence <> 4 {
this.decode_frame_config?(dst:nullptr, src:args.src)
}
if this.quirk_enabled_reject_empty_frame and
((this.frame_rect_x0 == this.frame_rect_x1) or (this.frame_rect_y0 == this.frame_rect_y1)) {
return "#bad frame size"
}
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 = 5
// The Image Descriptor is mandatory, but the Graphic Control extension is
// optional. Reset the GC related fields for the next decode_frame call.
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 {
if this.call_sequence <> 2 {
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".
if this.delayed_num_decoded_frames {
this.delayed_num_decoded_frames = false
this.num_decoded_frames_value ~sat+= 1
}
this.decode_id_part0?(src:args.src)
break
} else if block_type == 0x3B { // The spec calls 0x3B the "Trailer".
if this.delayed_num_decoded_frames {
this.delayed_num_decoded_frames = false
this.num_decoded_frames_value ~sat+= 1
}
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 background_color_index base.u8
var num_palette_entries base.u32[..256]
var i base.u32
var j base.u32[..1020]
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?()
background_color_index = args.src.read_u8?()
// Ignore the Pixel Aspect Ratio byte.
args.src.skip?(n:1)
// Read the optional Global Color Table.
i = 0
this.has_global_palette = (flags & 0x80) <> 0
if this.has_global_palette {
num_palette_entries = (1 as base.u32) << (1 + (flags & 0x07))
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
}
if this.quirk_enabled_honor_background_color {
if (background_color_index <> 0) and
((background_color_index as base.u32) < num_palette_entries) {
j = 4 * (background_color_index as base.u32)
this.background_color_u32_argb_premul =
((this.palettes[0][j + 0] as base.u32) << 0) |
((this.palettes[0][j + 1] as base.u32) << 8) |
((this.palettes[0][j + 2] as base.u32) << 16) |
((this.palettes[0][j + 3] as base.u32) << 24)
} else {
// The background color is either opaque black or transparent
// black. We set it to an arbitrary nonsense value (77) for
// now, and set it to its real value later, once we know
// whether the first frame is opaque (the ffio value).
this.background_color_u32_argb_premul = 77
}
}
}
// 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,
]
// iccrgbg1012 is "ICCRGBG1012" as bytes.
pri const iccrgbg1012 array[11] base.u8 = [
0x49, 0x43, 0x43, 0x52, 0x47, 0x42, 0x47, 0x31, 0x30, 0x31, 0x32,
]
// xmpdataxmp is "XMP DataXMP" as bytes.
pri const xmpdataxmp array[11] base.u8 = [
0x58, 0x4D, 0x50, 0x20, 0x44, 0x61, 0x74, 0x61, 0x58, 0x4D, 0x50,
]
// 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 is_animexts base.bool
var is_netscape base.bool
var is_iccp base.bool
var is_xmp 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", "NETSCAPE2.0" or other
// extension, as per:
// - http://www.vurdalakov.net/misc/gif/animexts-looping-application-extension
// - http://www.vurdalakov.net/misc/gif/netscape-looping-application-extension
//
// Other extensions include XMP metadata.
if block_size <> 11 {
args.src.skip?(n:block_size as base.u32)
break
}
is_animexts = true
is_netscape = true
is_iccp = true
is_xmp = true
block_size = 0 // Re-purpose the block_size variable as a counter.
while block_size < 11 {
c = args.src.read_u8?()
is_animexts = is_animexts and (c == animexts1dot0[block_size])
is_netscape = is_netscape and (c == netscape2dot0[block_size])
is_iccp = is_iccp and (c == iccrgbg1012[block_size])
is_xmp = is_xmp and (c == xmpdataxmp[block_size])
block_size += 1
}
if is_animexts or is_netscape {
// 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
}
} else if this.ignore_metadata {
// No-op.
} else if is_iccp and this.report_metadata_iccp {
while args.src.available() <= 0,
post args.src.available() > 0,
{
yield? base."$short read"
}
this.metadata_chunk_length_value = args.src.peek_u8_as_u64()
args.src.skip_fast!(actual:1, worst_case:1)
this.metadata_fourcc_value = 0x49434350 // "ICCP"
this.metadata_io_position = args.src.position() ~sat+ this.metadata_chunk_length_value
this.call_sequence = 1
return base."@metadata reported"
} else if is_xmp and this.report_metadata_xmp {
while args.src.available() <= 0,
post args.src.available() > 0,
{
yield? base."$short read"
}
// The +1 is because XMP metadata's encoding includes each block's
// leading byte (the block size) as part of the metadata passed to
// the caller.
this.metadata_chunk_length_value = args.src.peek_u8_as_u64() + 1
this.metadata_fourcc_value = 0x584D5020 // "XMP "
this.metadata_io_position = args.src.position() ~sat+ this.metadata_chunk_length_value
this.call_sequence = 1
return base."@metadata reported"
}
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
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"
}
}
// 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) and (not this.quirk_enabled_image_bounds_are_strict) {
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 which_palette base.u8[..1]
var num_palette_entries base.u32[..256]
var i base.u32
var argb base.u32
var dst_palette slice base.u8
var status base.status
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.
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.quirk_enabled_reject_empty_palette and (not this.has_global_palette) {
return "#bad palette"
} else if this.gc_has_transparent_index {
this.palettes[1][:].copy_from_slice!(s:this.palettes[0][:])
} else {
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.
status = this.swizzler.prepare!(
dst_pixfmt:args.dst.pixel_format(),
dst_palette:dst_palette,
src_pixfmt:0x47040008,
src_palette:this.palettes[which_palette][:])
if not status.is_ok() {
return status
}
// 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!()
}
// Process the LZW literal width. The spec says that "images which have one
// color bit must be indicated as having a code size [i.e. literal width]
// of 2", but in practice, some encoders use a literal width of 1 or 0.
lw = args.src.read_u8?()
if lw > 8 {
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 decoder_workbuf_len_max_incl_worst_case > 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
if (this.dst_y < this.frame_rect_y1) and
(this.frame_rect_x0 <> this.frame_rect_x1) and
(this.frame_rect_y0 <> this.frame_rect_y1) {
return base."#not enough data"
}
}
pri func decoder.copy_to_image_buffer!(pb ptr base.pixel_buffer, src slice base.u8) base.status {
// TODO: don't assume an interleaved 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_channels 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.
pixfmt_channels = args.pb.pixel_format() & 0xFFFF
if (pixfmt_channels == 0x8888) {
bytes_per_pixel = 4
} else if (pixfmt_channels == 0x0888) {
bytes_per_pixel = 3
} else if (pixfmt_channels == 0x0008) {
bytes_per_pixel = 1
} else {
return base."#unsupported option"
}
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 {
if this.quirk_enabled_ignore_too_much_pixel_data {
return ok
}
return base."#too much 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_interleaved!(
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
}