| // Copyright 2017 The Puffs 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. |
| |
| packageid "gif " |
| |
| pub error "bad GIF block" |
| pub error "bad GIF extension label" |
| pub error "bad GIF header" |
| pub error "bad LZW literal width" |
| |
| pub error "internal error: inconsistent limited read" |
| |
| pub error "TODO: unsupported Local Color Table" |
| |
| pub struct decoder?( |
| width u32[..65535], |
| height u32[..65535], |
| background_color_index u8, |
| // gct is the Global Color Table: 256 (R, G, B) entries. |
| // |
| // TODO: 4 byte per pixel RGBA or BGRA instead of 3 bpp RGB? |
| gct[3 * 256] u8, |
| lzw lzw_decoder, |
| ) |
| |
| pub func decoder.decode?(dst writer1, src reader1)() { |
| this.decode_header?(src:in.src) |
| this.decode_lsd?(src:in.src) |
| while true { |
| var c u8 = in.src.read_u8?() |
| if c == 0x21 { // The spec calls 0x21 the "Extension Introducer". |
| this.decode_extension?(src:in.src) |
| } else if c == 0x2C { // The spec calls 0x2C the "Image Separator". |
| // TODO: animated GIFs can have multiple Image Descriptors. |
| // |
| // TODO: reset this.lzw if it's not the first ID. |
| this.decode_id?(dst:in.dst, src:in.src) |
| } else if c == 0x3B { // The spec calls 0x3B the "Trailer". |
| return |
| } else { |
| return error "bad GIF block" |
| } |
| } |
| } |
| |
| // decode_header reads either "GIF87a" or "GIF89a". |
| // |
| // See the spec section 17 "Header" on page 7. |
| pri func decoder.decode_header?(src reader1)() { |
| var c[6] u8 |
| var i u32 |
| while i < 6 { |
| c[i] = in.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 error "bad GIF 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 reader1)() { |
| var c[7] u8 |
| var i u32 |
| while i < 7 { |
| c[i] = in.src.read_u8?() |
| i += 1 |
| } |
| this.width = (c[0] as u32) | ((c[1] as u32) << 8) |
| this.height = (c[2] as u32) | ((c[3] as u32) << 8) |
| this.background_color_index = c[5] |
| |
| // Read the optional Global Color Table. |
| if (c[4] & 0x80) != 0 { |
| var gct_size u32[..256] = (1 as u32) << (1 + (c[4] & 0x07)) |
| i = 0 |
| while i < gct_size { |
| assert i < 256 via "a < b: a < c; c <= b"(c:gct_size) |
| this.gct[(3 * i) + 0] = in.src.read_u8?() |
| this.gct[(3 * i) + 1] = in.src.read_u8?() |
| this.gct[(3 * i) + 2] = in.src.read_u8?() |
| 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 reader1)() { |
| var label u8 = in.src.read_u8?() |
| if label == 0x01 { // The spec calls 0x01 the "Plain Text Label". |
| } else if label == 0xF9 { // The spec calls 0xF9 the "Graphic Control Label". |
| } else if label == 0xFE { // The spec calls 0xFE the "Comment Label". |
| } else if label == 0xFF { // The spec calls 0xFF the "Application Extension Label". |
| } else { |
| return error "bad GIF extension label" |
| } |
| |
| // Skip the data blocks. |
| while true { |
| var block_size u8 = in.src.read_u8?() |
| if block_size == 0 { |
| break |
| } |
| in.src.skip32?(n:block_size as u32) |
| } |
| } |
| |
| // decode_id reads an Image Descriptor. The Image Separator byte has already |
| // been read. |
| // |
| // See the spec section 20 "Image Descriptor" on page 11. |
| pri func decoder.decode_id?(dst writer1, src reader1)() { |
| var c[9] u8 |
| var i u32 |
| while i < 9 { |
| c[i] = in.src.read_u8?() |
| i += 1 |
| } |
| |
| // TODO: use image left/top/width/height. |
| |
| // TODO: use interlace. |
| var interlace bool = (c[8] & 0x40) != 0 |
| if interlace { // Avoid "unused variable" C compiler warning. |
| } |
| |
| // TODO: read the optional Local Color Table. |
| if (c[8] & 0x80) != 0 { |
| return error "TODO: unsupported Local Color Table" |
| } |
| |
| var lw u8 = in.src.read_u8?() |
| if (lw < 2) or (8 < lw) { |
| return error "bad LZW literal width" |
| } |
| this.lzw.set_literal_width(lw:lw as u32) |
| |
| while true { |
| var block_size u64 = in.src.read_u8?() as u64 |
| if block_size == 0 { |
| break |
| } |
| while true { |
| var r reader1 = in.src |
| // TODO: should "mark" be "set_mark"? Unlike "limit", "mark" does |
| // not return a different reader1. |
| r.mark() |
| // TODO: remove the dummy param. It's needed for now so that |
| // writeSaveExprDerivedVars updates e.g. the b_rptr_src derived |
| // variables. |
| // |
| // TODO: enforce that limit can only be called in a "foo?" call? |
| var z status = try this.lzw.decode?(dst:in.dst, src:r.limit(l:block_size), dummy:in.src) |
| if z.is_ok() { |
| break |
| } |
| if block_size < r.since_mark().length() { |
| return error "internal error: inconsistent limited read" |
| } |
| block_size -= r.since_mark().length() |
| if (block_size == 0) and (z == suspension "short read") { |
| break |
| } |
| return z |
| } |
| } |
| } |