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