mirror of
https://github.com/fumiama/imgsz.git
synced 2026-06-29 08:30:33 +08:00
🎉 完成
This commit is contained in:
240
png.go
Normal file
240
png.go
Normal file
@@ -0,0 +1,240 @@
|
||||
// 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
|
||||
}
|
||||
Reference in New Issue
Block a user