mirror of
https://github.com/fumiama/imgsz.git
synced 2026-06-05 00:32:53 +08:00
241 lines
5.3 KiB
Go
241 lines
5.3 KiB
Go
// 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
|
|
}
|