| // 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 { |
| args.dst.set!( |
| pixfmt: base.PIXEL_FORMAT__INDEXED__BGRA_BINARY, |
| 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.skip_u32_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.skip_u32_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.set!(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.skip_u32?(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 we don't have 0x21 or 0x2C then, according to the spec, the |
| // only valid byte is 0x3B, called the "Trailer". In practice, some |
| // other popular decoders allow anything (other than 0x21 or 0x2C) |
| // to be equivalent to 0x3B, which ends the animated GIF image, and |
| // we do likewise here. Some real world GIF files that exhibit this |
| // are at https://github.com/golang/go/issues/38853 |
| // |
| // Chromium's decoder |
| // https://skia.googlesource.com/libgifcodec/+/c002ec500aba1e1b0189547629787cb02db78193/SkGifImageReader.cpp#563 |
| // |
| // Firefox's decoder |
| // https://dxr.mozilla.org/mozilla-central/rev/93a33cb7f2369ac4f1d1f2ac97ec14ba60e1e7d7/image/decoders/nsGIFDecoder2.cpp#569 |
| 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 |
| } |
| } 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.skip_u32?(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.skip_u32?(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.skip_u32?(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.skip_u32?(n: block_size as base.u32) |
| break.goto_done |
| } |
| c = args.src.read_u8?() |
| if c <> 0x01 { |
| args.src.skip_u32?(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) |
| } |
| } |