gtsocial-umbx

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

png.go (8809B)


      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 // ChunkSlice encapsulates a slice of chunks.
     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 // Chunks exposes the actual slice.
     62 func (cs *ChunkSlice) Chunks() []*Chunk {
     63 	return cs.chunks
     64 }
     65 
     66 // Write encodes and writes all chunks.
     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 	// TODO(dustin): !! This should respect the safe-to-copy characteristic.
     78 	for _, c := range cs.chunks {
     79 		_, err := c.WriteTo(w)
     80 		log.PanicIf(err)
     81 	}
     82 
     83 	return nil
     84 }
     85 
     86 // Index returns a map of chunk types to chunk slices, grouping all like chunks.
     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 // FindExif returns the the segment that hosts the EXIF data.
    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 	// Never called.
    117 	return nil, nil
    118 }
    119 
    120 // Exif returns an `exif.Ifd` instance with the existing tags.
    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 	// TODO(dustin): Refactor and support `exif.GetExifData()`.
    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 // ConstructExifBuilder returns an `exif.IfdBuilder` instance (needed for
    145 // modifying) preloaded with all existing tags.
    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 // SetExif encodes and sets EXIF data into this segment.
    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 	// Encode.
    170 
    171 	ibe := exif.NewIfdByteEncoder()
    172 
    173 	exifData, err := ibe.EncodeToExif(ib)
    174 	log.PanicIf(err)
    175 
    176 	// Set.
    177 
    178 	exifChunk, err := cs.FindExif()
    179 	if err == nil {
    180 		// EXIF chunk already exists.
    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 		// Add a EXIF chunk for the first time.
    190 
    191 		exifChunk = &Chunk{
    192 			Type:   EXifChunkType,
    193 			Data:   exifData,
    194 			Length: uint32(len(exifData)),
    195 		}
    196 
    197 		// Insert it after the IHDR chunk (it's a reliably appropriate place to
    198 		// put it).
    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 // PngSplitter hosts the princpal `Split()` method uses by `bufio.Scanner`.
    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 // Chunk describes a single chunk.
    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 // Bytes encodes and returns the bytes for this chunk.
    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 // Write encodes and writes the bytes for this chunk.
    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 // readHeader verifies that the PNG header bytes appear next.
    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 // Split fulfills the `bufio.SplitFunc` function definition for
    351 // `bufio.Scanner`.
    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 	// We might have more than one chunk's worth, and, if `atEOF` is true, we
    360 	// won't be called again. We'll repeatedly try to read additional chunks,
    361 	// but, when we run out of the data we were given then we'll return the
    362 	// number of bytes fo rthe chunks we've already completely read. Then,
    363 	// we'll be called again from theend ofthose bytes, at which point we'll
    364 	// indicate that we don't yet have enough for another chunk, and we should
    365 	// be then called with more.
    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 	// Enforce interface conformance.
    415 	_ riimage.MediaContext = new(ChunkSlice)
    416 )