blob: 56c23da3a3f923f341d073d81c58f93c1170dab7 [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 "go run preprocess-wuffs.go" TO MAKE decode_config.wuffs
//#WHEN PREPROC000
//
// This file defines "struct decoder". It is also preprocessed (copied with
// automatic edits) to generate the decode_config.wuffs file that defines
// "struct config_decoder". See preprocess-wuffs.go for more detail about the
// preprocessor language (the //#FOOBAR comments).
//
// config_decoder does not implement decode_frame, only decode_frame_config and
// decode_image_config (and other miscellaneous methods). The sizeof the
// config_decoder can be substantially smaller than the sizeof the full
// decoder, as it does not need any internal buffers for understanding the
// underlying compression format.
//
//#WHEN PREPROC000 decode_config.wuffs
//#REPLACE "func decoder" WITH "func config_decoder"
//#REPLACE "struct decoder" WITH "struct config_decoder"
//#DONE PREPROC000
// --------
pub struct decoder? implements base.image_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 7_056000 flicks per centisecond.
gc_duration : base.u64[..= 0xFFFF * 7_056000],
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,
//#WHEN PREPROC100
// The dst_etc fields are the output cursor during copy_to_image_buffer.
dst_x : base.u32,
dst_y : base.u32,
dirty_max_excl_y : base.u32,
// Indexes into the compressed array, defined below.
compressed_ri : base.u64,
compressed_wi : base.u64,
swizzler : base.pixel_swizzler,
//#DONE PREPROC100
util : base.utility,
)(
//#WHEN PREPROC101
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,
//#WHEN PREPROC101 decode_config.wuffs
//## palettes : array[1] array[4 * 256] base.u8,
//#DONE PREPROC101
)
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 = 0xFF00_0000
}
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: 0x4704_0008,
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 == 'ICCP'be {
this.report_metadata_iccp = args.report
} else if args.fourcc == 'XMP 'be {
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"
}
if this.metadata_chunk_length_value > 0 {
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()
if this.metadata_chunk_length_value > 0 {
if this.metadata_fourcc_value == 'XMP 'be {
// 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 += 1
} else {
args.src.skip32_fast!(actual: 1, worst_case: 1)
}
this.metadata_io_position =
args.src.position() ~sat+ this.metadata_chunk_length_value
return base."@metadata reported"
}
// Consume the '\x00' that means a zero-length block.
args.src.skip32_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 {
//#WHEN PREPROC200
// The "foo.min(a:this.width_or_height)" calls clip the nominal frame_rect
// to the image_rect.
return this.util.make_rect_ie_u32(
min_incl_x: this.frame_rect_x0.min(a: this.width),
min_incl_y: this.frame_rect_y0.min(a: this.height),
max_excl_x: this.frame_rect_x1.min(a: this.width),
max_excl_y: this.dirty_max_excl_y.min(a: this.height))
//#WHEN PREPROC200 decode_config.wuffs
//## return this.util.empty_rect_ie_u32()
//#DONE PREPROC200
}
pub func decoder.workbuf_len() base.range_ii_u64 {
//#WHEN PREPROC201
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)
//#WHEN PREPROC201 decode_config.wuffs
//## return this.util.empty_range_ii_u64()
//#DONE PREPROC201
}
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 background_color : base.u32
var flags : base.u8
this.ignore_metadata = true
//#WHEN PREPROC202
this.dirty_max_excl_y = 0
//#DONE PREPROC202
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"
}
background_color = this.black_color_u32_argb_premul
if not this.gc_has_transparent_index {
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 {
// The "foo.min(a:this.width_or_height)" calls clip the nominal
// frame_rect to the image_rect.
args.dst.update!(bounds: this.util.make_rect_ie_u32(
min_incl_x: this.frame_rect_x0.min(a: this.width),
min_incl_y: this.frame_rect_y0.min(a: this.height),
max_excl_x: this.frame_rect_x1.min(a: this.width),
max_excl_y: this.frame_rect_y1.min(a: this.height)),
duration: this.gc_duration,
index: this.num_decoded_frame_configs_value,
io_position: this.frame_config_io_position,
disposal: this.gc_disposal,
opaque_within_bounds: not this.gc_has_transparent_index,
overwrite_instead_of_blend: false,
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.skip32?(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, blend: base.pixel_blend, workbuf: slice base.u8, opts: nptr base.decode_frame_options) {
//#WHEN PREPROC300
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, blend: args.blend)
this.decode_id_part2?(dst: args.dst, src: args.src, workbuf: args.workbuf)
this.num_decoded_frames_value ~sat+= 1
this.reset_gc!()
//#WHEN PREPROC300 decode_config.wuffs
//## return base."#unsupported method"
//#DONE PREPROC300
}
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] <> 'G') or (c[1] <> 'I') or (c[2] <> 'F') or (c[3] <> '8') or
((c[4] <> '7') and (c[4] <> '9')) or (c[5] <> 'a') {
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.skip32?(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 |= 0xFF00_0000
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.skip32?(n: block_size as base.u32)
}
}
// 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.skip32?(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.skip32?(n: block_size as base.u32)
break
}
c = args.src.read_u8?()
if c <> 0x01 {
args.src.skip32?(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.skip32_fast!(actual: 1, worst_case: 1)
this.metadata_fourcc_value = 'ICCP'be
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"
}
this.metadata_chunk_length_value = args.src.peek_u8_as_u64()
if this.metadata_chunk_length_value > 0 {
// 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 += 1
} else {
args.src.skip32_fast!(actual: 1, worst_case: 1)
}
this.metadata_fourcc_value = 'XMP 'be
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 7_056000 flicks per centisecond.
gc_duration_centiseconds = args.src.read_u16le?()
this.gc_duration = (gc_duration_centiseconds as base.u64) * 7_056000
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
//#WHEN PREPROC400
this.dst_x = this.frame_rect_x0
this.dst_y = this.frame_rect_y0
//#DONE PREPROC400
// 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(a: this.frame_rect_x1)
this.height = this.height.max(a: this.frame_rect_y1)
}
}
//#WHEN PREPROC900
pri func decoder.decode_id_part1?(dst: ptr base.pixel_buffer, src: base.io_reader, blend: base.pixel_blend) {
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 |= 0xFF00_0000
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: this.util.make_pixel_format(repr: 0x4704_0008),
src_palette: this.palettes[which_palette][..],
blend: args.blend)
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 mark : base.u64
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.outer
}
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(a: 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.skip32_fast!(actual: 1, worst_case: 1)
}
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]) {
mark = r.mark()
lzw_status =? this.lzw.transform_io?(
dst: this.util.empty_io_writer(), src: r, workbuf: this.util.empty_slice_u8())
this.compressed_ri ~sat+= r.count_since(mark: mark)
}
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.skip32?(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
} endwhile.inner
} endwhile.outer
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 width_in_bytes : base.u64
var n : base.u64
var src_ri : base.u64
var pixfmt : base.pixel_format
var bytes_per_pixel : base.u32[..= 32]
var bits_per_pixel : base.u32[..= 256]
var tab : table base.u8
var i : base.u64
var j : base.u64
var replicate_y0 : base.u32
var replicate_y1 : base.u32
var replicate_dst : slice base.u8
var replicate_src : slice base.u8
// TODO: the pixfmt variable shouldn't be necessary. We should be able to
// chain the two calls: "args.pb.pixel_format().bits_per_pixel()".
pixfmt = args.pb.pixel_format()
bits_per_pixel = pixfmt.bits_per_pixel()
if (bits_per_pixel & 7) <> 0 {
return base."#unsupported option"
}
bytes_per_pixel = bits_per_pixel >> 3
width_in_bytes = (this.width as base.u64) * (bytes_per_pixel as base.u64)
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 (clipped to the image bounds).
dst = tab.row(y: this.dst_y)
if this.dst_y >= this.height {
dst = dst[.. 0]
} else if width_in_bytes < dst.length() {
dst = dst[.. width_in_bytes]
}
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 & 0xFFFF_FFFF) as base.u32
this.dirty_max_excl_y = this.dirty_max_excl_y.max(a: this.dst_y ~sat+ 1)
}
if this.frame_rect_x1 <= this.dst_x {
this.dst_x = this.frame_rect_x0
if this.interlace == 0 {
this.dst_y ~sat+= 1
continue
}
// For the first frame of an interlaced, non-transparent GIF,
// replicate the early passes' rows. For example, when such an
// image is downloaded over a slow network, this produces a richer
// intermediate image while waiting for the complete image.
//
// Some other GIF implementations call this progressive display or
// a "Haeberli inspired" technique.
if (this.num_decoded_frames_value == 0) and
(not this.gc_has_transparent_index) and
(this.interlace > 1) {
replicate_src = tab.row(y: this.dst_y)
replicate_y0 = this.dst_y ~sat+ 1
replicate_y1 = this.dst_y ~sat+ (interlace_count[this.interlace] as base.u32)
replicate_y1 = replicate_y1.min(a: this.frame_rect_y1)
while replicate_y0 < replicate_y1 {
assert replicate_y0 < 0xFFFF_FFFF via "a < b: a < c; c <= b"(c: replicate_y1)
replicate_dst = tab.row(y: replicate_y0)
replicate_dst.copy_from_slice!(s: replicate_src)
replicate_y0 += 1
}
this.dirty_max_excl_y = this.dirty_max_excl_y.max(a: replicate_y1)
}
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(a: args.src.length() - src_ri)
src_ri ~sat+= n
this.dst_x ~sat+= (n & 0xFFFF_FFFF) 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
}
//#DONE PREPROC900