1
1
mirror of https://github.com/wader/fq.git synced 2024-11-26 10:33:53 +03:00
fq/format/gif/gif.go
2022-07-19 18:33:50 +02:00

156 lines
3.8 KiB
Go

package gif
// https://www.w3.org/Graphics/GIF/spec-gif87.txt
// https://en.wikipedia.org/wiki/GIF
// https://web.archive.org/web/20160304075538/http://qalle.net/gif89a.php#graphiccontrolextension
// TODO: local color map
// TODO: bit depth done correct?
import (
"bytes"
"github.com/wader/fq/format"
"github.com/wader/fq/pkg/decode"
"github.com/wader/fq/pkg/interp"
"github.com/wader/fq/pkg/scalar"
)
func init() {
interp.RegisterFormat(decode.Format{
Name: format.GIF,
Description: "Graphics Interchange Format",
Groups: []string{format.PROBE, format.IMAGE},
DecodeFn: gifDecode,
})
}
const (
extensionPlainText = 0x01
extensionGraphicalControl = 0xf9
extensionComment = 0xfe
extensionApplication = 0xff
)
var extensionNames = scalar.UToSymStr{
extensionPlainText: "PlainText",
extensionGraphicalControl: "GraphicalControl",
extensionComment: "Comment",
extensionApplication: "Application",
}
func fieldColorMap(d *decode.D, name string, bitDepth int) {
d.FieldArray(name, func(d *decode.D) {
for i := 0; i < 1<<bitDepth; i++ {
d.FieldArray("color", func(d *decode.D) {
d.FieldU8("r")
d.FieldU8("g")
d.FieldU8("b")
})
}
})
}
func gifDecode(d *decode.D, _ any) any {
d.Endian = decode.LittleEndian
d.FieldUTF8("header", 6, d.AssertStr("GIF87a", "GIF89a"))
d.FieldU16("width")
d.FieldU16("height")
gcpFollows := d.FieldBool("gcp_follows")
d.FieldUFn("color_resolution", func(d *decode.D) uint64 { return d.U3() + 1 })
d.FieldU1("zero")
bitDepth := d.FieldUFn("bit_depth", func(d *decode.D) uint64 { return d.U3() + 1 })
d.FieldU8("black_color")
d.FieldU8("pixel_aspect_ratio")
if gcpFollows {
fieldColorMap(d, "global_color_map", int(bitDepth))
}
d.FieldArray("blocks", func(d *decode.D) {
blocks:
for {
switch d.PeekBits(8) {
case ';':
break blocks
case '!': /* "!" */
d.FieldStruct("extension_block", func(d *decode.D) {
d.FieldU8("introducer")
functionCode := d.FieldU8("function_code", extensionNames, scalar.ActualHex)
dataBytes := &bytes.Buffer{}
d.FieldArray("func_data_bytes", func(d *decode.D) {
seenTerminator := false
for !seenTerminator {
d.FieldStruct("func_data_byte", func(d *decode.D) {
byteCount := d.FieldU8("byte_count")
b := d.FieldRawLen("data", int64(byteCount*8))
if d.PeekBits(8) == 0 {
d.FieldU8("terminator")
seenTerminator = true
}
d.CopyBits(dataBytes, d.CloneReadSeeker(b))
})
}
})
_ = functionCode
// TODO: need a FieldStructBitBuf or something
// switch functionCode {
// case extensionGraphicalControl:
// d.FieldFormatBitBuf(
// "graphics_control",
// bitio.NewReader(dataBytes.Bytes(), -1),
// )
// }
})
case ',':
d.FieldStruct("image", func(d *decode.D) {
d.FieldU8("separator_character")
d.FieldU16("left")
d.FieldU16("top")
d.FieldU16("width")
d.FieldU16("height")
localFollows := d.FieldBool("local_color_map_follows")
d.FieldBool("image_interlaced")
d.FieldU3("zero")
d.FieldUFn("bit_depth", func(d *decode.D) uint64 { return d.U3() + 1 })
d.FieldU8("code_size")
if localFollows {
fieldColorMap(d, "local_color_map", int(bitDepth))
}
d.FieldArray("image_bytes", func(d *decode.D) {
seenTerminator := false
for !seenTerminator {
d.FieldStruct("func_data_byte", func(d *decode.D) {
byteCount := d.FieldU8("byte_count")
d.FieldRawLen("data", int64(byteCount*8))
if d.PeekBits(8) == 0 {
d.FieldU8("terminator")
seenTerminator = true
}
})
}
})
})
default:
d.Fatalf("unknown block")
}
}
})
d.FieldU8("terminator")
return nil
}