blob: 25d2250f183f92d4f96ffa6479b14c0d738c7074 [file] [log] [blame]
// 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
}
}
}