// Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // Package png implements a PNG image decoder and encoder. // // The PNG specification is at https://www.w3.org/TR/PNG/. package imgsz import ( "encoding/binary" "fmt" "hash" "hash/crc32" "io" ) // A cb is a combination of color type and bit depth. const ( cbInvalid = iota cbG1 cbG2 cbG4 cbG8 cbGA8 cbTC8 cbP1 cbP2 cbP4 cbP8 cbTCA8 cbG16 cbGA16 cbTC16 cbTCA16 ) func cbPaletted(cb int) bool { return cbP1 <= cb && cb <= cbP8 } // Interlace type. const ( itNone = 0 itAdam7 = 1 ) // Decoding stage. // The PNG specification says that the IHDR, PLTE (if present), tRNS (if // present), IDAT and IEND chunks must appear in that order. There may be // multiple IDAT chunks, and IDAT chunks must be sequential (i.e. they may not // have any other chunks between them). // https://www.w3.org/TR/PNG/#5ChunkOrdering const ( dsStart = iota dsSeenIHDR dsSeenPLTE dsSeentRNS dsSeenIDAT dsSeenIEND ) const pngHeader = "\x89PNG\r\n\x1a\n" type decoder struct { r io.Reader crc hash.Hash32 width, height int cb int stage int idatLength uint32 tmp [3 * 256]byte interlace int } var chunkOrderError = FormatError("chunk out of order") func min(a, b int) int { if a < b { return a } return b } func (d *decoder) parseIHDR(length uint32) error { if length != 13 { return FormatError("bad IHDR length") } if _, err := io.ReadFull(d.r, d.tmp[:13]); err != nil { return err } d.crc.Write(d.tmp[:13]) if d.tmp[10] != 0 { return UnsupportedError("compression method") } if d.tmp[11] != 0 { return UnsupportedError("filter method") } if d.tmp[12] != itNone && d.tmp[12] != itAdam7 { return FormatError("invalid interlace method") } d.interlace = int(d.tmp[12]) w := int32(binary.BigEndian.Uint32(d.tmp[0:4])) h := int32(binary.BigEndian.Uint32(d.tmp[4:8])) if w <= 0 || h <= 0 { return FormatError("non-positive dimension") } d.width, d.height = int(w), int(h) return d.verifyChecksum() } // Read presents one or more IDAT chunks as one continuous stream (minus the // intermediate chunk headers and footers). If the PNG data looked like: // ... len0 IDAT xxx crc0 len1 IDAT yy crc1 len2 IEND crc2 // then this reader presents xxxyy. For well-formed PNG data, the decoder state // immediately before the first Read call is that d.r is positioned between the // first IDAT and xxx, and the decoder state immediately after the last Read // call is that d.r is positioned between yy and crc1. func (d *decoder) Read(p []byte) (int, error) { if len(p) == 0 { return 0, nil } for d.idatLength == 0 { // We have exhausted an IDAT chunk. Verify the checksum of that chunk. if err := d.verifyChecksum(); err != nil { return 0, err } // Read the length and chunk type of the next chunk, and check that // it is an IDAT chunk. if _, err := io.ReadFull(d.r, d.tmp[:8]); err != nil { return 0, err } d.idatLength = binary.BigEndian.Uint32(d.tmp[:4]) if string(d.tmp[4:8]) != "IDAT" { return 0, FormatError("not enough pixel data") } d.crc.Reset() d.crc.Write(d.tmp[4:8]) } if int(d.idatLength) < 0 { return 0, UnsupportedError("IDAT chunk length overflow") } n, err := d.r.Read(p[:min(len(p), int(d.idatLength))]) d.crc.Write(p[:n]) d.idatLength -= uint32(n) return n, err } func (d *decoder) parseSize() (ok bool, err error) { // Read the length and chunk type. if _, err = io.ReadFull(d.r, d.tmp[:8]); err != nil { return } length := binary.BigEndian.Uint32(d.tmp[:4]) d.crc.Reset() d.crc.Write(d.tmp[4:8]) // Read the chunk data. switch string(d.tmp[4:8]) { case "IHDR": if d.stage != dsStart { return false, chunkOrderError } d.stage = dsSeenIHDR return true, d.parseIHDR(length) } if length > 0x7fffffff { return false, FormatError(fmt.Sprintf("Bad chunk length: %d", length)) } // Ignore this chunk (of a known length). var ignored [4096]byte for length > 0 { n, err := io.ReadFull(d.r, ignored[:min(len(ignored), int(length))]) if err != nil { return false, err } d.crc.Write(ignored[:n]) length -= uint32(n) } return false, d.verifyChecksum() } func (d *decoder) verifyChecksum() error { if _, err := io.ReadFull(d.r, d.tmp[:4]); err != nil { return err } if binary.BigEndian.Uint32(d.tmp[:4]) != d.crc.Sum32() { return FormatError("invalid checksum") } return nil } func (d *decoder) checkHeader() error { _, err := io.ReadFull(d.r, d.tmp[:len(pngHeader)]) if err != nil { return err } if string(d.tmp[:len(pngHeader)]) != pngHeader { return FormatError("not a PNG file") } return nil } // decodepng returns the color model and dimensions of a PNG image without // decoding the entire image. func decodepng(r io.Reader) (Size, error) { d := &decoder{ r: r, crc: crc32.NewIEEE(), } if err := d.checkHeader(); err != nil { if err == io.EOF { err = io.ErrUnexpectedEOF } return Size{}, err } for { ok, err := d.parseSize() if err != nil { if err == io.EOF { err = io.ErrUnexpectedEOF } return Size{}, err } if ok { return Size{d.width, d.height}, nil } paletted := cbPaletted(d.cb) if d.stage == dsSeenIHDR && !paletted { break } if d.stage == dsSeenPLTE && paletted { break } } return Size{d.width, d.height}, nil }