1package png 2 3// http://www.libpng.org/pub/png/spec/1.2/PNG-Contents.html 4// https://ftp-osl.osuosl.org/pub/libpng/documents/pngext-1.5.0.html 5// https://wiki.mozilla.org/APNG_Specification 6 7import ( 8 "compress/zlib" 9 "hash/crc32" 10 11 "github.com/wader/fq/format" 12 "github.com/wader/fq/format/registry" 13 "github.com/wader/fq/pkg/decode" 14 "github.com/wader/fq/pkg/scalar" 15) 16 17var iccProfileFormat decode.Group 18var exifFormat decode.Group 19 20func init() { 21 registry.MustRegister(decode.Format{ 22 Name: format.PNG, 23 Description: "Portable Network Graphics file", 24 Groups: []string{format.PROBE, format.IMAGE}, 25 DecodeFn: pngDecode, 26 Dependencies: []decode.Dependency{ 27 {Names: []string{format.ICC_PROFILE}, Group: &iccProfileFormat}, 28 {Names: []string{format.EXIF}, Group: &exifFormat}, 29 }, 30 }) 31} 32 33const ( 34 compressionDeflate = 0 35) 36 37var compressionNames = scalar.UToSymStr{ 38 compressionDeflate: "deflate", 39} 40 41const ( 42 disposeOpNone = 0 43 disposeOpBackground = 1 44 disposeOpPrevious = 2 45) 46 47var disposeOpNames = scalar.UToSymStr{ 48 disposeOpNone: "none", 49 disposeOpBackground: "background", 50 disposeOpPrevious: "previous", 51} 52 53const ( 54 blendOpNone = 0 55 blendOpBackground = 1 56) 57 58var blendOpNames = scalar.UToSymStr{ 59 blendOpNone: "source", 60 blendOpBackground: "over", 61} 62 63const ( 64 colorTypeGrayscale = 0 65 colorTypeRGB = 2 66 colorTypePalette = 3 67 colorTypeGrayscaleWithAlpha = 4 68 colorTypeRGBA = 6 69) 70 71var colorTypeMap = scalar.UToScalar{ 72 colorTypeGrayscale: {Sym: "g", Description: "Grayscale"}, 73 colorTypeRGB: {Sym: "rgb", Description: "RGB"}, 74 colorTypePalette: {Sym: "p", Description: "Palette"}, 75 colorTypeGrayscaleWithAlpha: {Sym: "ga", Description: "Grayscale with alpha"}, 76 colorTypeRGBA: {Sym: "rgba", Description: "RGBA"}, 77} 78 79func pngDecode(d *decode.D, in interface{}) interface{} { 80 iEndFound := false 81 var colorType uint64 82 83 d.FieldRawLen("signature", 8*8, d.AssertBitBuf([]byte("\x89PNG\r\n\x1a\n"))) 84 d.FieldStructArrayLoop("chunks", "chunk", func() bool { return d.NotEnd() && !iEndFound }, func(d *decode.D) { 85 chunkLength := d.FieldU32("length") 86 crcStartPos := d.Pos() 87 // TODO: this is a bit weird, use struct? 88 chunkType := d.FieldStrFn("type", func(d *decode.D) string { 89 chunkType := d.UTF8(4) 90 // upper/lower case in chunk type is used to set flags 91 d.SeekRel(-4 * 8) 92 d.SeekRel(3) 93 d.FieldBool("ancillary") 94 d.SeekRel(7) 95 d.FieldBool("private") 96 d.SeekRel(7) 97 d.FieldBool("reserved") 98 d.SeekRel(7) 99 d.FieldBool("safe_to_copy") 100 d.SeekRel(4) 101 return chunkType 102 }) 103 104 d.LenFn(int64(chunkLength)*8, func(d *decode.D) { 105 switch chunkType { 106 case "IHDR": 107 d.FieldU32("width") 108 d.FieldU32("height") 109 d.FieldU8("bit_depth") 110 colorType = d.FieldU8("color_type", colorTypeMap) 111 d.FieldU8("compression_method", compressionNames) 112 d.FieldU8("filter_method", scalar.UToSymStr{ 113 0: "Adaptive filtering", 114 }) 115 d.FieldU8("interlace_method", scalar.UToSymStr{ 116 0: "No interlace", 117 1: "Adam7 interlace", 118 }) 119 case "tEXt": 120 d.FieldUTF8Null("keyword") 121 d.FieldUTF8("text", int(d.BitsLeft())/8) 122 case "zTXt": 123 d.FieldUTF8Null("keyword") 124 compressionMethod := d.FieldU8("compression_method", compressionNames) 125 dataLen := d.BitsLeft() 126 127 // TODO: make nicer 128 d.FieldRawLen("compressed", dataLen) 129 d.SeekRel(-dataLen) 130 131 switch compressionMethod { 132 case compressionDeflate: 133 d.FieldFormatReaderLen("uncompressed", dataLen, zlib.NewReader, decode.FormatFn(func(d *decode.D, in interface{}) interface{} { 134 d.FieldUTF8("text", int(d.BitsLeft()/8)) 135 return nil 136 })) 137 default: 138 d.FieldRawLen("data", dataLen) 139 } 140 case "iCCP": 141 d.FieldUTF8Null("profile_name") 142 compressionMethod := d.FieldU8("compression_method", compressionNames) 143 dataLen := d.BitsLeft() 144 145 d.FieldRawLen("compressed", dataLen) 146 d.SeekRel(-dataLen) 147 148 switch compressionMethod { 149 case compressionDeflate: 150 d.FieldFormatReaderLen("uncompressed", dataLen, zlib.NewReader, iccProfileFormat) 151 default: 152 d.FieldRawLen("data", dataLen) 153 } 154 case "pHYs": 155 d.FieldU32("x_pixels_per_unit") 156 d.FieldU32("y_pixels_per_unit") 157 d.FieldU8("unit") 158 case "bKGD": 159 switch colorType { 160 case colorTypePalette: 161 d.FieldU8("index") 162 case colorTypeGrayscale, colorTypeGrayscaleWithAlpha: 163 d.FieldU16("gray") 164 case colorTypeRGB, colorTypeRGBA: 165 d.FieldU16("r") 166 d.FieldU16("g") 167 d.FieldU16("b") 168 } 169 case "gAMA": 170 d.FieldU32("value") 171 case "cHRM": 172 df := func(d *decode.D) float64 { return float64(d.U32()) / 1000.0 } 173 d.FieldFFn("white_point_x", df) 174 d.FieldFFn("white_point_y", df) 175 d.FieldFFn("red_x", df) 176 d.FieldFFn("red_y", df) 177 d.FieldFFn("green_x", df) 178 d.FieldFFn("green_y", df) 179 d.FieldFFn("blue_x", df) 180 d.FieldFFn("blue_y", df) 181 case "eXIf": 182 d.FieldFormatLen("exif", d.BitsLeft(), exifFormat, nil) 183 case "acTL": 184 d.FieldU32("num_frames") 185 d.FieldU32("num_plays") 186 case "fcTL": 187 d.FieldU32("sequence_number") 188 d.FieldU32("width") 189 d.FieldU32("height") 190 d.FieldU32("x_offset") 191 d.FieldU32("y_offset") 192 d.FieldU16("delay_num") 193 d.FieldU16("delay_sep") 194 d.FieldU8("dispose_op", disposeOpNames) 195 d.FieldU8("blend_op", blendOpNames) 196 case "fdAT": 197 d.FieldU32("sequence_number") 198 d.FieldRawLen("data", d.BitsLeft()-32) 199 default: 200 if chunkType == "IEND" { 201 iEndFound = true 202 } else { 203 d.FieldRawLen("data", d.BitsLeft()) 204 } 205 } 206 }) 207 208 chunkCRC := crc32.NewIEEE() 209 d.MustCopy(chunkCRC, d.BitBufRange(crcStartPos, d.Pos()-crcStartPos)) 210 d.FieldU32("crc", d.ValidateUBytes(chunkCRC.Sum(nil)), scalar.Hex) 211 }) 212 213 return nil 214} 215