1
1
mirror of https://github.com/wader/fq.git synced 2024-11-27 14:14:58 +03:00
fq/format/png/png.go
Mattias Wadman 2fc0a71a47 decode: Refactor scalar usage
Move scalar into own package.
Split scalar code into decode related scalar code (that reads etc) and
scalar code that just transform the scalar value.
Use a scalar.Mapper interface instead of just a function.
Make mappers, assert and validat impement the interface.
2021-12-02 17:39:26 +01:00

215 lines
5.7 KiB
Go

package png
// http://www.libpng.org/pub/png/spec/1.2/PNG-Contents.html
// https://ftp-osl.osuosl.org/pub/libpng/documents/pngext-1.5.0.html
// https://wiki.mozilla.org/APNG_Specification
import (
"compress/zlib"
"hash/crc32"
"github.com/wader/fq/format"
"github.com/wader/fq/format/registry"
"github.com/wader/fq/pkg/decode"
"github.com/wader/fq/pkg/scalar"
)
var iccProfileFormat decode.Group
var exifFormat decode.Group
func init() {
registry.MustRegister(decode.Format{
Name: format.PNG,
Description: "Portable Network Graphics file",
Groups: []string{format.PROBE, format.IMAGE},
DecodeFn: pngDecode,
Dependencies: []decode.Dependency{
{Names: []string{format.ICC_PROFILE}, Group: &iccProfileFormat},
{Names: []string{format.EXIF}, Group: &exifFormat},
},
})
}
const (
compressionDeflate = 0
)
var compressionNames = scalar.UToSymStr{
compressionDeflate: "deflate",
}
const (
disposeOpNone = 0
disposeOpBackground = 1
disposeOpPrevious = 2
)
var disposeOpNames = scalar.UToSymStr{
disposeOpNone: "none",
disposeOpBackground: "background",
disposeOpPrevious: "previous",
}
const (
blendOpNone = 0
blendOpBackground = 1
)
var blendOpNames = scalar.UToSymStr{
blendOpNone: "source",
blendOpBackground: "over",
}
const (
colorTypeGrayscale = 0
colorTypeRGB = 2
colorTypePalette = 3
colorTypeGrayscaleWithAlpha = 4
colorTypeRGBA = 6
)
var colorTypeMap = scalar.UToScalar{
colorTypeGrayscale: {Sym: "g", Description: "Grayscale"},
colorTypeRGB: {Sym: "rgb", Description: "RGB"},
colorTypePalette: {Sym: "p", Description: "Palette"},
colorTypeGrayscaleWithAlpha: {Sym: "ga", Description: "Grayscale with alpha"},
colorTypeRGBA: {Sym: "rgba", Description: "RGBA"},
}
func pngDecode(d *decode.D, in interface{}) interface{} {
iEndFound := false
var colorType uint64
d.FieldRawLen("signature", 8*8, d.AssertBitBuf([]byte("\x89PNG\r\n\x1a\n")))
d.FieldStructArrayLoop("chunks", "chunk", func() bool { return d.NotEnd() && !iEndFound }, func(d *decode.D) {
chunkLength := d.FieldU32("length")
crcStartPos := d.Pos()
// TODO: this is a bit weird, use struct?
chunkType := d.FieldStrFn("type", func(d *decode.D) string {
chunkType := d.UTF8(4)
// upper/lower case in chunk type is used to set flags
d.SeekRel(-4 * 8)
d.SeekRel(3)
d.FieldBool("ancillary")
d.SeekRel(7)
d.FieldBool("private")
d.SeekRel(7)
d.FieldBool("reserved")
d.SeekRel(7)
d.FieldBool("safe_to_copy")
d.SeekRel(4)
return chunkType
})
d.LenFn(int64(chunkLength)*8, func(d *decode.D) {
switch chunkType {
case "IHDR":
d.FieldU32("width")
d.FieldU32("height")
d.FieldU8("bit_depth")
colorType = d.FieldU8("color_type", colorTypeMap)
d.FieldU8("compression_method", compressionNames)
d.FieldU8("filter_method", scalar.UToSymStr{
0: "Adaptive filtering",
})
d.FieldU8("interlace_method", scalar.UToSymStr{
0: "No interlace",
1: "Adam7 interlace",
})
case "tEXt":
d.FieldUTF8Null("keyword")
d.FieldUTF8("text", int(d.BitsLeft())/8)
case "zTXt":
d.FieldUTF8Null("keyword")
compressionMethod := d.FieldU8("compression_method", compressionNames)
dataLen := d.BitsLeft()
// TODO: make nicer
d.FieldRawLen("compressed", dataLen)
d.SeekRel(-dataLen)
switch compressionMethod {
case compressionDeflate:
d.FieldFormatReaderLen("uncompressed", dataLen, zlib.NewReader, decode.FormatFn(func(d *decode.D, in interface{}) interface{} {
d.FieldUTF8("text", int(d.BitsLeft()/8))
return nil
}))
default:
d.FieldRawLen("data", dataLen)
}
case "iCCP":
d.FieldUTF8Null("profile_name")
compressionMethod := d.FieldU8("compression_method", compressionNames)
dataLen := d.BitsLeft()
d.FieldRawLen("compressed", dataLen)
d.SeekRel(-dataLen)
switch compressionMethod {
case compressionDeflate:
d.FieldFormatReaderLen("uncompressed", dataLen, zlib.NewReader, iccProfileFormat)
default:
d.FieldRawLen("data", dataLen)
}
case "pHYs":
d.FieldU32("x_pixels_per_unit")
d.FieldU32("y_pixels_per_unit")
d.FieldU8("unit")
case "bKGD":
switch colorType {
case colorTypePalette:
d.FieldU8("index")
case colorTypeGrayscale, colorTypeGrayscaleWithAlpha:
d.FieldU16("gray")
case colorTypeRGB, colorTypeRGBA:
d.FieldU16("r")
d.FieldU16("g")
d.FieldU16("b")
}
case "gAMA":
d.FieldU32("value")
case "cHRM":
df := func(d *decode.D) float64 { return float64(d.U32()) / 1000.0 }
d.FieldFFn("white_point_x", df)
d.FieldFFn("white_point_y", df)
d.FieldFFn("red_x", df)
d.FieldFFn("red_y", df)
d.FieldFFn("green_x", df)
d.FieldFFn("green_y", df)
d.FieldFFn("blue_x", df)
d.FieldFFn("blue_y", df)
case "eXIf":
d.FieldFormatLen("exif", d.BitsLeft(), exifFormat, nil)
case "acTL":
d.FieldU32("num_frames")
d.FieldU32("num_plays")
case "fcTL":
d.FieldU32("sequence_number")
d.FieldU32("width")
d.FieldU32("height")
d.FieldU32("x_offset")
d.FieldU32("y_offset")
d.FieldU16("delay_num")
d.FieldU16("delay_sep")
d.FieldU8("dispose_op", disposeOpNames)
d.FieldU8("blend_op", blendOpNames)
case "fdAT":
d.FieldU32("sequence_number")
d.FieldRawLen("data", d.BitsLeft()-32)
default:
if chunkType == "IEND" {
iEndFound = true
} else {
d.FieldRawLen("data", d.BitsLeft())
}
}
})
chunkCRC := crc32.NewIEEE()
d.MustCopy(chunkCRC, d.BitBufRange(crcStartPos, d.Pos()-crcStartPos))
d.FieldU32("crc", d.ValidateUBytes(chunkCRC.Sum(nil)), scalar.Hex)
})
return nil
}