1 package pngstructure
2
3 import (
4 "bytes"
5 "errors"
6 "fmt"
7 "io"
8
9 "encoding/binary"
10 "hash/crc32"
11
12 "github.com/dsoprea/go-exif/v3"
13 "github.com/dsoprea/go-exif/v3/common"
14 "github.com/dsoprea/go-logging"
15 "github.com/dsoprea/go-utility/v2/image"
16 )
17
18 var (
19 PngSignature = [8]byte{137, 'P', 'N', 'G', '\r', '\n', 26, '\n'}
20 EXifChunkType = "eXIf"
21 IHDRChunkType = "IHDR"
22 )
23
24 var (
25 ErrNotPng = errors.New("not png data")
26 ErrCrcFailure = errors.New("crc failure")
27 )
28
29
30 type ChunkSlice struct {
31 chunks []*Chunk
32 }
33
34 func NewChunkSlice(chunks []*Chunk) *ChunkSlice {
35 if len(chunks) == 0 {
36 log.Panicf("ChunkSlice must be initialized with at least one chunk (IHDR)")
37 } else if chunks[0].Type != IHDRChunkType {
38 log.Panicf("first chunk in any ChunkSlice must be an IHDR")
39 }
40
41 return &ChunkSlice{
42 chunks: chunks,
43 }
44 }
45
46 func NewPngChunkSlice() *ChunkSlice {
47
48 ihdrChunk := &Chunk{
49 Type: IHDRChunkType,
50 }
51
52 ihdrChunk.UpdateCrc32()
53
54 return NewChunkSlice([]*Chunk{ihdrChunk})
55 }
56
57 func (cs *ChunkSlice) String() string {
58 return fmt.Sprintf("ChunkSlize<LEN=(%d)>", len(cs.chunks))
59 }
60
61
62 func (cs *ChunkSlice) Chunks() []*Chunk {
63 return cs.chunks
64 }
65
66
67 func (cs *ChunkSlice) WriteTo(w io.Writer) (err error) {
68 defer func() {
69 if state := recover(); state != nil {
70 err = log.Wrap(state.(error))
71 }
72 }()
73
74 _, err = w.Write(PngSignature[:])
75 log.PanicIf(err)
76
77
78 for _, c := range cs.chunks {
79 _, err := c.WriteTo(w)
80 log.PanicIf(err)
81 }
82
83 return nil
84 }
85
86
87 func (cs *ChunkSlice) Index() (index map[string][]*Chunk) {
88 index = make(map[string][]*Chunk)
89 for _, c := range cs.chunks {
90 if grouped, found := index[c.Type]; found == true {
91 index[c.Type] = append(grouped, c)
92 } else {
93 index[c.Type] = []*Chunk{c}
94 }
95 }
96
97 return index
98 }
99
100
101 func (cs *ChunkSlice) FindExif() (chunk *Chunk, err error) {
102 defer func() {
103 if state := recover(); state != nil {
104 err = log.Wrap(state.(error))
105 }
106 }()
107
108 index := cs.Index()
109
110 if chunks, found := index[EXifChunkType]; found == true {
111 return chunks[0], nil
112 }
113
114 log.Panic(exif.ErrNoExif)
115
116
117 return nil, nil
118 }
119
120
121 func (cs *ChunkSlice) Exif() (rootIfd *exif.Ifd, data []byte, err error) {
122 defer func() {
123 if state := recover(); state != nil {
124 err = log.Wrap(state.(error))
125 }
126 }()
127
128 chunk, err := cs.FindExif()
129 log.PanicIf(err)
130
131 im, err := exifcommon.NewIfdMappingWithStandard()
132 log.PanicIf(err)
133
134 ti := exif.NewTagIndex()
135
136
137
138 _, index, err := exif.Collect(im, ti, chunk.Data)
139 log.PanicIf(err)
140
141 return index.RootIfd, chunk.Data, nil
142 }
143
144
145
146 func (cs *ChunkSlice) ConstructExifBuilder() (rootIb *exif.IfdBuilder, err error) {
147 defer func() {
148 if state := recover(); state != nil {
149 err = log.Wrap(state.(error))
150 }
151 }()
152
153 rootIfd, _, err := cs.Exif()
154 log.PanicIf(err)
155
156 ib := exif.NewIfdBuilderFromExistingChain(rootIfd)
157
158 return ib, nil
159 }
160
161
162 func (cs *ChunkSlice) SetExif(ib *exif.IfdBuilder) (err error) {
163 defer func() {
164 if state := recover(); state != nil {
165 err = log.Wrap(state.(error))
166 }
167 }()
168
169
170
171 ibe := exif.NewIfdByteEncoder()
172
173 exifData, err := ibe.EncodeToExif(ib)
174 log.PanicIf(err)
175
176
177
178 exifChunk, err := cs.FindExif()
179 if err == nil {
180
181
182 exifChunk.Data = exifData
183 exifChunk.Length = uint32(len(exifData))
184 } else {
185 if log.Is(err, exif.ErrNoExif) != true {
186 log.Panic(err)
187 }
188
189
190
191 exifChunk = &Chunk{
192 Type: EXifChunkType,
193 Data: exifData,
194 Length: uint32(len(exifData)),
195 }
196
197
198
199 cs.chunks = append(cs.chunks[:1], append([]*Chunk{exifChunk}, cs.chunks[1:]...)...)
200 }
201
202 exifChunk.UpdateCrc32()
203
204 return nil
205 }
206
207
208 type PngSplitter struct {
209 chunks []*Chunk
210 currentOffset int
211
212 doCheckCrc bool
213 crcErrors []string
214 }
215
216 func (ps *PngSplitter) Chunks() *ChunkSlice {
217 return NewChunkSlice(ps.chunks)
218 }
219
220 func (ps *PngSplitter) DoCheckCrc(doCheck bool) {
221 ps.doCheckCrc = doCheck
222 }
223
224 func (ps *PngSplitter) CrcErrors() []string {
225 return ps.crcErrors
226 }
227
228 func NewPngSplitter() *PngSplitter {
229 return &PngSplitter{
230 chunks: make([]*Chunk, 0),
231 doCheckCrc: true,
232 crcErrors: make([]string, 0),
233 }
234 }
235
236
237 type Chunk struct {
238 Offset int
239 Length uint32
240 Type string
241 Data []byte
242 Crc uint32
243 }
244
245 func (c *Chunk) String() string {
246 return fmt.Sprintf("Chunk<OFFSET=(%d) LENGTH=(%d) TYPE=[%s] CRC=(%d)>", c.Offset, c.Length, c.Type, c.Crc)
247 }
248
249 func calculateCrc32(chunk *Chunk) uint32 {
250 c := crc32.NewIEEE()
251
252 c.Write([]byte(chunk.Type))
253 c.Write(chunk.Data)
254
255 return c.Sum32()
256 }
257
258 func (c *Chunk) UpdateCrc32() {
259 c.Crc = calculateCrc32(c)
260 }
261
262 func (c *Chunk) CheckCrc32() bool {
263 expected := calculateCrc32(c)
264 return c.Crc == expected
265 }
266
267
268 func (c *Chunk) Bytes() []byte {
269 defer func() {
270 if state := recover(); state != nil {
271 err := log.Wrap(state.(error))
272 log.Panic(err)
273 }
274 }()
275
276 if len(c.Data) != int(c.Length) {
277 log.Panicf("length of data not correct")
278 }
279
280 preallocated := make([]byte, 0, 4+4+c.Length+4)
281 b := bytes.NewBuffer(preallocated)
282
283 err := binary.Write(b, binary.BigEndian, c.Length)
284 log.PanicIf(err)
285
286 _, err = b.Write([]byte(c.Type))
287 log.PanicIf(err)
288
289 if c.Data != nil {
290 _, err = b.Write(c.Data)
291 log.PanicIf(err)
292 }
293
294 err = binary.Write(b, binary.BigEndian, c.Crc)
295 log.PanicIf(err)
296
297 return b.Bytes()
298 }
299
300
301 func (c *Chunk) WriteTo(w io.Writer) (count int, err error) {
302 defer func() {
303 if state := recover(); state != nil {
304 err = log.Wrap(state.(error))
305 }
306 }()
307
308 if len(c.Data) != int(c.Length) {
309 log.Panicf("length of data not correct")
310 }
311
312 err = binary.Write(w, binary.BigEndian, c.Length)
313 log.PanicIf(err)
314
315 _, err = w.Write([]byte(c.Type))
316 log.PanicIf(err)
317
318 _, err = w.Write(c.Data)
319 log.PanicIf(err)
320
321 err = binary.Write(w, binary.BigEndian, c.Crc)
322 log.PanicIf(err)
323
324 return 4 + len(c.Type) + len(c.Data) + 4, nil
325 }
326
327
328 func (ps *PngSplitter) readHeader(r io.Reader) (err error) {
329 defer func() {
330 if state := recover(); state != nil {
331 err = log.Wrap(state.(error))
332 }
333 }()
334
335 len_ := len(PngSignature)
336 header := make([]byte, len_)
337
338 _, err = r.Read(header)
339 log.PanicIf(err)
340
341 ps.currentOffset += len_
342
343 if bytes.Compare(header, PngSignature[:]) != 0 {
344 log.Panic(ErrNotPng)
345 }
346
347 return nil
348 }
349
350
351
352 func (ps *PngSplitter) Split(data []byte, atEOF bool) (advance int, token []byte, err error) {
353 defer func() {
354 if state := recover(); state != nil {
355 err = log.Wrap(state.(error))
356 }
357 }()
358
359
360
361
362
363
364
365
366 for {
367 len_ := len(data)
368 if len_ < 8 {
369 return advance, nil, nil
370 }
371
372 length := binary.BigEndian.Uint32(data[:4])
373 type_ := string(data[4:8])
374 chunkSize := (8 + int(length) + 4)
375
376 if len_ < chunkSize {
377 return advance, nil, nil
378 }
379
380 crcIndex := 8 + length
381 crc := binary.BigEndian.Uint32(data[crcIndex : crcIndex+4])
382
383 content := make([]byte, length)
384 copy(content, data[8:8+length])
385
386 c := &Chunk{
387 Length: length,
388 Type: type_,
389 Data: content,
390 Crc: crc,
391 Offset: ps.currentOffset,
392 }
393
394 ps.chunks = append(ps.chunks, c)
395
396 if c.CheckCrc32() == false {
397 ps.crcErrors = append(ps.crcErrors, type_)
398
399 if ps.doCheckCrc == true {
400 log.Panic(ErrCrcFailure)
401 }
402 }
403
404 advance += chunkSize
405 ps.currentOffset += chunkSize
406
407 data = data[chunkSize:]
408 }
409
410 return advance, nil, nil
411 }
412
413 var (
414
415 _ riimage.MediaContext = new(ChunkSlice)
416 )
417
View as plain text