blob: b61ebae03a8cb0ab9f39c2d1af38912d484b484f [file] [log] [blame]
// This file was automatically generated by "preprocess-wuffs.go".
// --------
// 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.
// --------
// --------
pub struct config_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 : base.u32,
metadata_io_position : base.u64,
quirks : array[QUIRKS_COUNT] 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,
util : base.utility,
)(
palettes : array[1] array[4 * 256] base.u8,
)
pub func config_decoder.set_quirk_enabled!(quirk: base.u32, enabled: base.bool) {
if (this.call_sequence == 0) and (args.quirk >= QUIRKS_BASE) {
args.quirk -= QUIRKS_BASE
if args.quirk < QUIRKS_COUNT {
this.quirks[args.quirk] = args.enabled
}
}
}
pub func config_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.quirks[QUIRK_HONOR_BACKGROUND_COLOR - QUIRKS_BASE] {
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: 0x8304_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 config_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 config_decoder.tell_me_more?(dst: base.io_writer, minfo: nptr base.more_information, src: base.io_reader) {
var chunk_length : base.u64
if this.call_sequence <> 1 {
return base."#bad call sequence"
}
if this.metadata_fourcc == 0 {
return base."#no more information"
}
while true {
while true,
post args.src.available() > 0,
{
if args.src.position() <> this.metadata_io_position {
if args.minfo <> nullptr {
args.minfo.set!(
flavor: 2, // WUFFS_BASE__MORE_INFORMATION__FLAVOR__IO_SEEK
w: 0,
x: this.metadata_io_position,
y: 0,
z: 0)
}
yield? base."$mispositioned read"
continue
}
if args.src.available() <= 0 {
if args.minfo <> nullptr {
args.minfo.set!(
flavor: 0,
w: 0,
x: 0,
y: 0,
z: 0)
}
yield? base."$short read"
continue
}
break
} endwhile
chunk_length = args.src.peek_u8_as_u64()
if chunk_length <= 0 {
// Consume the '\x00' that means a zero-length block.
args.src.skip32_fast!(actual: 1, worst_case: 1)
break
}
if this.metadata_fourcc == '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.
chunk_length += 1
} else {
args.src.skip32_fast!(actual: 1, worst_case: 1)
}
this.metadata_io_position = args.src.position() ~sat+ chunk_length
if args.minfo <> nullptr {
args.minfo.set!(
flavor: 3, // WUFFS_BASE__MORE_INFORMATION__FLAVOR__METADATA
w: this.metadata_fourcc,
x: 0,
y: args.src.position(),
z: this.metadata_io_position)
}
yield? base."$even more information"
} endwhile
if args.minfo <> nullptr {
args.minfo.set!(
flavor: 3, // WUFFS_BASE__MORE_INFORMATION__FLAVOR__METADATA
w: this.metadata_fourcc,
x: 0,
y: this.metadata_io_position,
z: this.metadata_io_position)
}
this.call_sequence = 2
this.metadata_fourcc = 0
this.metadata_io_position = 0
return ok
}
pub func config_decoder.num_animation_loops() base.u32 {
if this.seen_num_loops {
return this.num_loops
}
return 1
}
pub func config_decoder.num_decoded_frame_configs() base.u64 {
return this.num_decoded_frame_configs_value
}
pub func config_decoder.num_decoded_frames() base.u64 {
return this.num_decoded_frames_value
}
pub func config_decoder.frame_dirty_rect() base.rect_ie_u32 {
return this.util.empty_rect_ie_u32()
}
pub func config_decoder.workbuf_len() base.range_ii_u64 {
return this.util.empty_range_ii_u64()
}
pub func config_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 config_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
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.quirks[QUIRK_FIRST_FRAME_LOCAL_PALETTE_MEANS_BLACK_BACKGROUND - QUIRKS_BASE] and
(this.num_decoded_frame_configs_value == 0) {
while args.src.available() <= 0,
post args.src.available() > 0,
{
yield? base."$short read"
} endwhile
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 config_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.quirks[QUIRK_DELAY_NUM_DECODED_FRAMES - QUIRKS_BASE] {
this.delayed_num_decoded_frames = true
} else {
this.num_decoded_frames_value ~sat+= 1
}
this.reset_gc!()
}
// TODO: honor args.opts.
pub func config_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) {
return base."#unsupported method"
}
pri func config_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 config_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"
}
} endwhile
}
// decode_header reads either "GIF87a" or "GIF89a".
//
// See the spec section 17 "Header" on page 7.
pri func config_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
} endwhile
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 config_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
} endwhile
if this.quirks[QUIRK_HONOR_BACKGROUND_COLOR - QUIRKS_BASE] {
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
} endwhile
}
// 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 config_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 config_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)
} endwhile
}
// decode_ae reads an Application Extension.
pri func config_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
while.goto_done true {{
if this.metadata_fourcc <> 0 {
return base."@metadata reported"
}
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.goto_done
}
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
} endwhile
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.goto_done
}
c = args.src.read_u8?()
if c <> 0x01 {
args.src.skip32?(n: 2)
break.goto_done
}
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 {
this.metadata_fourcc = 'ICCP'be
this.metadata_io_position = args.src.position()
this.call_sequence = 1
return base."@metadata reported"
} else if is_xmp and this.report_metadata_xmp {
this.metadata_fourcc = 'XMP 'be
this.metadata_io_position = args.src.position()
this.call_sequence = 1
return base."@metadata reported"
}
break.goto_done
}} endwhile.goto_done
this.skip_blocks?(src: args.src)
}
// decode_gc reads a Graphic Control.
pri func config_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 config_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
// 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.quirks[QUIRK_IMAGE_BOUNDS_ARE_STRICT - QUIRKS_BASE]) {
this.width = this.width.max(a: this.frame_rect_x1)
this.height = this.height.max(a: this.frame_rect_y1)
}
}