mirror of
https://github.com/wader/fq.git
synced 2024-12-02 12:45:53 +03:00
156 lines
3.8 KiB
Go
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
|
|
}
|