summaryrefslogtreecommitdiffstats
path: root/vendor/github.com/kettek/apng/reader.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/kettek/apng/reader.go')
-rw-r--r--vendor/github.com/kettek/apng/reader.go1141
1 files changed, 1141 insertions, 0 deletions
diff --git a/vendor/github.com/kettek/apng/reader.go b/vendor/github.com/kettek/apng/reader.go
new file mode 100644
index 00000000..fd6ccee3
--- /dev/null
+++ b/vendor/github.com/kettek/apng/reader.go
@@ -0,0 +1,1141 @@
+// Original PNG code Copyright 2009 The Go Authors.
+// Additional APNG enhancements Copyright 2018 Ketchetwahmeegwun
+// Tecumseh Southall / kts of kettek.
+// All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package apng implements an APNG image decoder.
+//
+// The PNG specification is at https://www.w3.org/TR/PNG/.
+// The APNG specification is at https://wiki.mozilla.org/APNG_Specification
+package apng
+
+import (
+ "compress/zlib"
+ "encoding/binary"
+ "fmt"
+ "hash"
+ "hash/crc32"
+ "image"
+ "image/color"
+ "io"
+)
+
+// Color type, as per the PNG spec.
+const (
+ ctGrayscale = 0
+ ctTrueColor = 2
+ ctPaletted = 3
+ ctGrayscaleAlpha = 4
+ ctTrueColorAlpha = 6
+)
+
+// 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
+}
+
+// Filter type, as per the PNG spec.
+const (
+ ftNone = 0
+ ftSub = 1
+ ftUp = 2
+ ftAverage = 3
+ ftPaeth = 4
+ nFilter = 5
+)
+
+// Interlace type.
+const (
+ itNone = 0
+ itAdam7 = 1
+)
+
+// interlaceScan defines the placement and size of a pass for Adam7 interlacing.
+type interlaceScan struct {
+ xFactor, yFactor, xOffset, yOffset int
+}
+
+// interlacing defines Adam7 interlacing, with 7 passes of reduced images.
+// See https://www.w3.org/TR/PNG/#8Interlace
+var interlacing = []interlaceScan{
+ {8, 8, 0, 0},
+ {8, 8, 4, 0},
+ {4, 8, 0, 4},
+ {4, 4, 2, 0},
+ {2, 4, 0, 2},
+ {2, 2, 1, 0},
+ {1, 2, 0, 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
+ dsSeenacTL
+ dsSeenIDAT
+ dsSeenfdAT
+ dsSeenIEND
+)
+
+const pngHeader = "\x89PNG\r\n\x1a\n"
+
+type decoder struct {
+ r io.Reader
+ num_frames uint32
+ a APNG
+ frame_index int
+ crc hash.Hash32
+ width, height int
+ depth int
+ palette color.Palette
+ cb int
+ stage int
+ idatLength uint32
+ tmp [3 * 256]byte
+ interlace int
+
+ // useTransparent and transparent are used for grayscale and truecolor
+ // transparency, as opposed to palette transparency.
+ useTransparent bool
+ transparent [6]byte
+}
+
+// A FormatError reports that the input is not a valid PNG.
+type FormatError string
+
+func (e FormatError) Error() string { return "apng: invalid format: " + string(e) }
+
+var chunkOrderError = FormatError("chunk out of order")
+
+// An UnsupportedError reports that the input uses a valid but unimplemented PNG feature.
+type UnsupportedError string
+
+func (e UnsupportedError) Error() string { return "apng: unsupported feature: " + string(e) }
+
+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")
+ }
+ nPixels := int64(w) * int64(h)
+ if nPixels != int64(int(nPixels)) {
+ return UnsupportedError("dimension overflow")
+ }
+ // There can be up to 8 bytes per pixel, for 16 bits per channel RGBA.
+ if nPixels != (nPixels*8)/8 {
+ return UnsupportedError("dimension overflow")
+ }
+
+ d.cb = cbInvalid
+ d.depth = int(d.tmp[8])
+ switch d.depth {
+ case 1:
+ switch d.tmp[9] {
+ case ctGrayscale:
+ d.cb = cbG1
+ case ctPaletted:
+ d.cb = cbP1
+ }
+ case 2:
+ switch d.tmp[9] {
+ case ctGrayscale:
+ d.cb = cbG2
+ case ctPaletted:
+ d.cb = cbP2
+ }
+ case 4:
+ switch d.tmp[9] {
+ case ctGrayscale:
+ d.cb = cbG4
+ case ctPaletted:
+ d.cb = cbP4
+ }
+ case 8:
+ switch d.tmp[9] {
+ case ctGrayscale:
+ d.cb = cbG8
+ case ctTrueColor:
+ d.cb = cbTC8
+ case ctPaletted:
+ d.cb = cbP8
+ case ctGrayscaleAlpha:
+ d.cb = cbGA8
+ case ctTrueColorAlpha:
+ d.cb = cbTCA8
+ }
+ case 16:
+ switch d.tmp[9] {
+ case ctGrayscale:
+ d.cb = cbG16
+ case ctTrueColor:
+ d.cb = cbTC16
+ case ctGrayscaleAlpha:
+ d.cb = cbGA16
+ case ctTrueColorAlpha:
+ d.cb = cbTCA16
+ }
+ }
+ if d.cb == cbInvalid {
+ return UnsupportedError(fmt.Sprintf("bit depth %d, color type %d", d.tmp[8], d.tmp[9]))
+ }
+ d.a.Frames[0].width, d.a.Frames[0].height = int(w), int(h)
+ return d.verifyChecksum()
+}
+
+func (d *decoder) parsePLTE(length uint32) error {
+ np := int(length / 3) // The number of palette entries.
+ if length%3 != 0 || np <= 0 || np > 256 || np > 1<<uint(d.depth) {
+ return FormatError("bad PLTE length")
+ }
+ n, err := io.ReadFull(d.r, d.tmp[:3*np])
+ if err != nil {
+ return err
+ }
+ d.crc.Write(d.tmp[:n])
+ switch d.cb {
+ case cbP1, cbP2, cbP4, cbP8:
+ d.palette = make(color.Palette, 256)
+ for i := 0; i < np; i++ {
+ d.palette[i] = color.RGBA{d.tmp[3*i+0], d.tmp[3*i+1], d.tmp[3*i+2], 0xff}
+ }
+ for i := np; i < 256; i++ {
+ // Initialize the rest of the palette to opaque black. The spec (section
+ // 11.2.3) says that "any out-of-range pixel value found in the image data
+ // is an error", but some real-world PNG files have out-of-range pixel
+ // values. We fall back to opaque black, the same as libpng 1.5.13;
+ // ImageMagick 6.5.7 returns an error.
+ d.palette[i] = color.RGBA{0x00, 0x00, 0x00, 0xff}
+ }
+ d.palette = d.palette[:np]
+ case cbTC8, cbTCA8, cbTC16, cbTCA16:
+ // As per the PNG spec, a PLTE chunk is optional (and for practical purposes,
+ // ignorable) for the ctTrueColor and ctTrueColorAlpha color types (section 4.1.2).
+ default:
+ return FormatError("PLTE, color type mismatch")
+ }
+ return d.verifyChecksum()
+}
+
+func (d *decoder) parsetRNS(length uint32) error {
+ switch d.cb {
+ case cbG1, cbG2, cbG4, cbG8, cbG16:
+ if length != 2 {
+ return FormatError("bad tRNS length")
+ }
+ n, err := io.ReadFull(d.r, d.tmp[:length])
+ if err != nil {
+ return err
+ }
+ d.crc.Write(d.tmp[:n])
+
+ copy(d.transparent[:], d.tmp[:length])
+ switch d.cb {
+ case cbG1:
+ d.transparent[1] *= 0xff
+ case cbG2:
+ d.transparent[1] *= 0x55
+ case cbG4:
+ d.transparent[1] *= 0x11
+ }
+ d.useTransparent = true
+
+ case cbTC8, cbTC16:
+ if length != 6 {
+ return FormatError("bad tRNS length")
+ }
+ n, err := io.ReadFull(d.r, d.tmp[:length])
+ if err != nil {
+ return err
+ }
+ d.crc.Write(d.tmp[:n])
+
+ copy(d.transparent[:], d.tmp[:length])
+ d.useTransparent = true
+
+ case cbP1, cbP2, cbP4, cbP8:
+ if length > 256 {
+ return FormatError("bad tRNS length")
+ }
+ n, err := io.ReadFull(d.r, d.tmp[:length])
+ if err != nil {
+ return err
+ }
+ d.crc.Write(d.tmp[:n])
+
+ if len(d.palette) < n {
+ d.palette = d.palette[:n]
+ }
+ for i := 0; i < n; i++ {
+ rgba := d.palette[i].(color.RGBA)
+ d.palette[i] = color.NRGBA{rgba.R, rgba.G, rgba.B, d.tmp[i]}
+ }
+
+ default:
+ return FormatError("tRNS, color type mismatch")
+ }
+ 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
+ }
+
+ if d.stage < dsSeenfdAT {
+ d.idatLength = binary.BigEndian.Uint32(d.tmp[:4])
+ if string(d.tmp[4:8]) != "IDAT" {
+ return 0, FormatError(fmt.Sprintf("expected IDAT, found %s", string(d.tmp[4:8])))
+ }
+ } else {
+ d.idatLength = binary.BigEndian.Uint32(d.tmp[:4]) - 4
+ if string(d.tmp[4:8]) != "fdAT" {
+ return 0, FormatError(fmt.Sprintf("expected fdAT, found %s", string(d.tmp[4:8])))
+ }
+ }
+ d.crc.Reset()
+ d.crc.Write(d.tmp[4:8])
+ if d.stage >= dsSeenfdAT {
+ if _, err := io.ReadFull(d.r, d.tmp[:4]); err != nil {
+ return 0, err
+ }
+ d.crc.Write(d.tmp[:4])
+ }
+ }
+ if int(d.idatLength) < 0 {
+ return 0, UnsupportedError("IDAT/fdAT 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
+}
+
+// decode decodes the IDAT data into an image.
+func (d *decoder) decode() (image.Image, error) {
+ r, err := zlib.NewReader(d)
+ if err != nil {
+ return nil, err
+ }
+ defer r.Close()
+ var img image.Image
+ if d.interlace == itNone {
+ img, err = d.readImagePass(r, 0, false)
+ if err != nil {
+ return nil, err
+ }
+ } else if d.interlace == itAdam7 {
+ // Allocate a blank image of the full size.
+ img, err = d.readImagePass(nil, 0, true)
+ if err != nil {
+ return nil, err
+ }
+ for pass := 0; pass < 7; pass++ {
+ imagePass, err := d.readImagePass(r, pass, false)
+ if err != nil {
+ return nil, err
+ }
+ if imagePass != nil {
+ d.mergePassInto(img, imagePass, pass)
+ }
+ }
+ }
+
+ // Check for EOF, to verify the zlib checksum.
+ n := 0
+ for i := 0; n == 0 && err == nil; i++ {
+ if i == 100 {
+ return nil, io.ErrNoProgress
+ }
+ n, err = r.Read(d.tmp[:1])
+ }
+ if err != nil && err != io.EOF {
+ return nil, FormatError(err.Error())
+ }
+ if n != 0 || d.idatLength != 0 {
+ return nil, FormatError("too much pixel data")
+ }
+
+ return img, nil
+}
+
+// readImagePass reads a single image pass, sized according to the pass number.
+func (d *decoder) readImagePass(r io.Reader, pass int, allocateOnly bool) (image.Image, error) {
+ bitsPerPixel := 0
+ pixOffset := 0
+ var (
+ gray *image.Gray
+ rgba *image.RGBA
+ paletted *image.Paletted
+ nrgba *image.NRGBA
+ gray16 *image.Gray16
+ rgba64 *image.RGBA64
+ nrgba64 *image.NRGBA64
+ img image.Image
+ width int
+ height int
+ )
+ width = d.a.Frames[d.frame_index].width
+ height = d.a.Frames[d.frame_index].height
+ if d.interlace == itAdam7 && !allocateOnly {
+ p := interlacing[pass]
+ // Add the multiplication factor and subtract one, effectively rounding up.
+ width = (width - p.xOffset + p.xFactor - 1) / p.xFactor
+ height = (height - p.yOffset + p.yFactor - 1) / p.yFactor
+ // A PNG image can't have zero width or height, but for an interlaced
+ // image, an individual pass might have zero width or height. If so, we
+ // shouldn't even read a per-row filter type byte, so return early.
+ if width == 0 || height == 0 {
+ return nil, nil
+ }
+ }
+ switch d.cb {
+ case cbG1, cbG2, cbG4, cbG8:
+ bitsPerPixel = d.depth
+ if d.useTransparent {
+ nrgba = image.NewNRGBA(image.Rect(0, 0, width, height))
+ img = nrgba
+ } else {
+ gray = image.NewGray(image.Rect(0, 0, width, height))
+ img = gray
+ }
+ case cbGA8:
+ bitsPerPixel = 16
+ nrgba = image.NewNRGBA(image.Rect(0, 0, width, height))
+ img = nrgba
+ case cbTC8:
+ bitsPerPixel = 24
+ if d.useTransparent {
+ nrgba = image.NewNRGBA(image.Rect(0, 0, width, height))
+ img = nrgba
+ } else {
+ rgba = image.NewRGBA(image.Rect(0, 0, width, height))
+ img = rgba
+ }
+ case cbP1, cbP2, cbP4, cbP8:
+ bitsPerPixel = d.depth
+ paletted = image.NewPaletted(image.Rect(0, 0, width, height), d.palette)
+ img = paletted
+ case cbTCA8:
+ bitsPerPixel = 32
+ nrgba = image.NewNRGBA(image.Rect(0, 0, width, height))
+ img = nrgba
+ case cbG16:
+ bitsPerPixel = 16
+ if d.useTransparent {
+ nrgba64 = image.NewNRGBA64(image.Rect(0, 0, width, height))
+ img = nrgba64
+ } else {
+ gray16 = image.NewGray16(image.Rect(0, 0, width, height))
+ img = gray16
+ }
+ case cbGA16:
+ bitsPerPixel = 32
+ nrgba64 = image.NewNRGBA64(image.Rect(0, 0, width, height))
+ img = nrgba64
+ case cbTC16:
+ bitsPerPixel = 48
+ if d.useTransparent {
+ nrgba64 = image.NewNRGBA64(image.Rect(0, 0, width, height))
+ img = nrgba64
+ } else {
+ rgba64 = image.NewRGBA64(image.Rect(0, 0, width, height))
+ img = rgba64
+ }
+ case cbTCA16:
+ bitsPerPixel = 64
+ nrgba64 = image.NewNRGBA64(image.Rect(0, 0, width, height))
+ img = nrgba64
+ }
+ if allocateOnly {
+ return img, nil
+ }
+ bytesPerPixel := (bitsPerPixel + 7) / 8
+
+ // The +1 is for the per-row filter type, which is at cr[0].
+ rowSize := 1 + (bitsPerPixel*width+7)/8
+ // cr and pr are the bytes for the current and previous row.
+ cr := make([]uint8, rowSize)
+ pr := make([]uint8, rowSize)
+
+ for y := 0; y < height; y++ {
+ // Read the decompressed bytes.
+ _, err := io.ReadFull(r, cr)
+ if err != nil {
+ if err == io.EOF || err == io.ErrUnexpectedEOF {
+ return nil, FormatError("not enough pixel data")
+ }
+ return nil, err
+ }
+
+ // Apply the filter.
+ cdat := cr[1:]
+ pdat := pr[1:]
+ switch cr[0] {
+ case ftNone:
+ // No-op.
+ case ftSub:
+ for i := bytesPerPixel; i < len(cdat); i++ {
+ cdat[i] += cdat[i-bytesPerPixel]
+ }
+ case ftUp:
+ for i, p := range pdat {
+ cdat[i] += p
+ }
+ case ftAverage:
+ // The first column has no column to the left of it, so it is a
+ // special case. We know that the first column exists because we
+ // check above that width != 0, and so len(cdat) != 0.
+ for i := 0; i < bytesPerPixel; i++ {
+ cdat[i] += pdat[i] / 2
+ }
+ for i := bytesPerPixel; i < len(cdat); i++ {
+ cdat[i] += uint8((int(cdat[i-bytesPerPixel]) + int(pdat[i])) / 2)
+ }
+ case ftPaeth:
+ filterPaeth(cdat, pdat, bytesPerPixel)
+ default:
+ return nil, FormatError("bad filter type")
+ }
+
+ // Convert from bytes to colors.
+ switch d.cb {
+ case cbG1:
+ if d.useTransparent {
+ ty := d.transparent[1]
+ for x := 0; x < width; x += 8 {
+ b := cdat[x/8]
+ for x2 := 0; x2 < 8 && x+x2 < width; x2++ {
+ ycol := (b >> 7) * 0xff
+ acol := uint8(0xff)
+ if ycol == ty {
+ acol = 0x00
+ }
+ nrgba.SetNRGBA(x+x2, y, color.NRGBA{ycol, ycol, ycol, acol})
+ b <<= 1
+ }
+ }
+ } else {
+ for x := 0; x < width; x += 8 {
+ b := cdat[x/8]
+ for x2 := 0; x2 < 8 && x+x2 < width; x2++ {
+ gray.SetGray(x+x2, y, color.Gray{(b >> 7) * 0xff})
+ b <<= 1
+ }
+ }
+ }
+ case cbG2:
+ if d.useTransparent {
+ ty := d.transparent[1]
+ for x := 0; x < width; x += 4 {
+ b := cdat[x/4]
+ for x2 := 0; x2 < 4 && x+x2 < width; x2++ {
+ ycol := (b >> 6) * 0x55
+ acol := uint8(0xff)
+ if ycol == ty {
+ acol = 0x00
+ }
+ nrgba.SetNRGBA(x+x2, y, color.NRGBA{ycol, ycol, ycol, acol})
+ b <<= 2
+ }
+ }
+ } else {
+ for x := 0; x < width; x += 4 {
+ b := cdat[x/4]
+ for x2 := 0; x2 < 4 && x+x2 < width; x2++ {
+ gray.SetGray(x+x2, y, color.Gray{(b >> 6) * 0x55})
+ b <<= 2
+ }
+ }
+ }
+ case cbG4:
+ if d.useTransparent {
+ ty := d.transparent[1]
+ for x := 0; x < width; x += 2 {
+ b := cdat[x/2]
+ for x2 := 0; x2 < 2 && x+x2 < width; x2++ {
+ ycol := (b >> 4) * 0x11
+ acol := uint8(0xff)
+ if ycol == ty {
+ acol = 0x00
+ }
+ nrgba.SetNRGBA(x+x2, y, color.NRGBA{ycol, ycol, ycol, acol})
+ b <<= 4
+ }
+ }
+ } else {
+ for x := 0; x < width; x += 2 {
+ b := cdat[x/2]
+ for x2 := 0; x2 < 2 && x+x2 < width; x2++ {
+ gray.SetGray(x+x2, y, color.Gray{(b >> 4) * 0x11})
+ b <<= 4
+ }
+ }
+ }
+ case cbG8:
+ if d.useTransparent {
+ ty := d.transparent[1]
+ for x := 0; x < width; x++ {
+ ycol := cdat[x]
+ acol := uint8(0xff)
+ if ycol == ty {
+ acol = 0x00
+ }
+ nrgba.SetNRGBA(x, y, color.NRGBA{ycol, ycol, ycol, acol})
+ }
+ } else {
+ copy(gray.Pix[pixOffset:], cdat)
+ pixOffset += gray.Stride
+ }
+ case cbGA8:
+ for x := 0; x < width; x++ {
+ ycol := cdat[2*x+0]
+ nrgba.SetNRGBA(x, y, color.NRGBA{ycol, ycol, ycol, cdat[2*x+1]})
+ }
+ case cbTC8:
+ if d.useTransparent {
+ pix, i, j := nrgba.Pix, pixOffset, 0
+ tr, tg, tb := d.transparent[1], d.transparent[3], d.transparent[5]
+ for x := 0; x < width; x++ {
+ r := cdat[j+0]
+ g := cdat[j+1]
+ b := cdat[j+2]
+ a := uint8(0xff)
+ if r == tr && g == tg && b == tb {
+ a = 0x00
+ }
+ pix[i+0] = r
+ pix[i+1] = g
+ pix[i+2] = b
+ pix[i+3] = a
+ i += 4
+ j += 3
+ }
+ pixOffset += nrgba.Stride
+ } else {
+ pix, i, j := rgba.Pix, pixOffset, 0
+ for x := 0; x < width; x++ {
+ pix[i+0] = cdat[j+0]
+ pix[i+1] = cdat[j+1]
+ pix[i+2] = cdat[j+2]
+ pix[i+3] = 0xff
+ i += 4
+ j += 3
+ }
+ pixOffset += rgba.Stride
+ }
+ case cbP1:
+ for x := 0; x < width; x += 8 {
+ b := cdat[x/8]
+ for x2 := 0; x2 < 8 && x+x2 < width; x2++ {
+ idx := b >> 7
+ if len(paletted.Palette) <= int(idx) {
+ paletted.Palette = paletted.Palette[:int(idx)+1]
+ }
+ paletted.SetColorIndex(x+x2, y, idx)
+ b <<= 1
+ }
+ }
+ case cbP2:
+ for x := 0; x < width; x += 4 {
+ b := cdat[x/4]
+ for x2 := 0; x2 < 4 && x+x2 < width; x2++ {
+ idx := b >> 6
+ if len(paletted.Palette) <= int(idx) {
+ paletted.Palette = paletted.Palette[:int(idx)+1]
+ }
+ paletted.SetColorIndex(x+x2, y, idx)
+ b <<= 2
+ }
+ }
+ case cbP4:
+ for x := 0; x < width; x += 2 {
+ b := cdat[x/2]
+ for x2 := 0; x2 < 2 && x+x2 < width; x2++ {
+ idx := b >> 4
+ if len(paletted.Palette) <= int(idx) {
+ paletted.Palette = paletted.Palette[:int(idx)+1]
+ }
+ paletted.SetColorIndex(x+x2, y, idx)
+ b <<= 4
+ }
+ }
+ case cbP8:
+ if len(paletted.Palette) != 255 {
+ for x := 0; x < width; x++ {
+ if len(paletted.Palette) <= int(cdat[x]) {
+ paletted.Palette = paletted.Palette[:int(cdat[x])+1]
+ }
+ }
+ }
+ copy(paletted.Pix[pixOffset:], cdat)
+ pixOffset += paletted.Stride
+ case cbTCA8:
+ copy(nrgba.Pix[pixOffset:], cdat)
+ pixOffset += nrgba.Stride
+ case cbG16:
+ if d.useTransparent {
+ ty := uint16(d.transparent[0])<<8 | uint16(d.transparent[1])
+ for x := 0; x < width; x++ {
+ ycol := uint16(cdat[2*x+0])<<8 | uint16(cdat[2*x+1])
+ acol := uint16(0xffff)
+ if ycol == ty {
+ acol = 0x0000
+ }
+ nrgba64.SetNRGBA64(x, y, color.NRGBA64{ycol, ycol, ycol, acol})
+ }
+ } else {
+ for x := 0; x < width; x++ {
+ ycol := uint16(cdat[2*x+0])<<8 | uint16(cdat[2*x+1])
+ gray16.SetGray16(x, y, color.Gray16{ycol})
+ }
+ }
+ case cbGA16:
+ for x := 0; x < width; x++ {
+ ycol := uint16(cdat[4*x+0])<<8 | uint16(cdat[4*x+1])
+ acol := uint16(cdat[4*x+2])<<8 | uint16(cdat[4*x+3])
+ nrgba64.SetNRGBA64(x, y, color.NRGBA64{ycol, ycol, ycol, acol})
+ }
+ case cbTC16:
+ if d.useTransparent {
+ tr := uint16(d.transparent[0])<<8 | uint16(d.transparent[1])
+ tg := uint16(d.transparent[2])<<8 | uint16(d.transparent[3])
+ tb := uint16(d.transparent[4])<<8 | uint16(d.transparent[5])
+ for x := 0; x < width; x++ {
+ rcol := uint16(cdat[6*x+0])<<8 | uint16(cdat[6*x+1])
+ gcol := uint16(cdat[6*x+2])<<8 | uint16(cdat[6*x+3])
+ bcol := uint16(cdat[6*x+4])<<8 | uint16(cdat[6*x+5])
+ acol := uint16(0xffff)
+ if rcol == tr && gcol == tg && bcol == tb {
+ acol = 0x0000
+ }
+ nrgba64.SetNRGBA64(x, y, color.NRGBA64{rcol, gcol, bcol, acol})
+ }
+ } else {
+ for x := 0; x < width; x++ {
+ rcol := uint16(cdat[6*x+0])<<8 | uint16(cdat[6*x+1])
+ gcol := uint16(cdat[6*x+2])<<8 | uint16(cdat[6*x+3])
+ bcol := uint16(cdat[6*x+4])<<8 | uint16(cdat[6*x+5])
+ rgba64.SetRGBA64(x, y, color.RGBA64{rcol, gcol, bcol, 0xffff})
+ }
+ }
+ case cbTCA16:
+ for x := 0; x < width; x++ {
+ rcol := uint16(cdat[8*x+0])<<8 | uint16(cdat[8*x+1])
+ gcol := uint16(cdat[8*x+2])<<8 | uint16(cdat[8*x+3])
+ bcol := uint16(cdat[8*x+4])<<8 | uint16(cdat[8*x+5])
+ acol := uint16(cdat[8*x+6])<<8 | uint16(cdat[8*x+7])
+ nrgba64.SetNRGBA64(x, y, color.NRGBA64{rcol, gcol, bcol, acol})
+ }
+ }
+
+ // The current row for y is the previous row for y+1.
+ pr, cr = cr, pr
+ }
+
+ return img, nil
+}
+
+// mergePassInto merges a single pass into a full sized image.
+func (d *decoder) mergePassInto(dst image.Image, src image.Image, pass int) {
+ p := interlacing[pass]
+ var (
+ srcPix []uint8
+ dstPix []uint8
+ stride int
+ rect image.Rectangle
+ bytesPerPixel int
+ )
+ switch target := dst.(type) {
+ case *image.Alpha:
+ srcPix = src.(*image.Alpha).Pix
+ dstPix, stride, rect = target.Pix, target.Stride, target.Rect
+ bytesPerPixel = 1
+ case *image.Alpha16:
+ srcPix = src.(*image.Alpha16).Pix
+ dstPix, stride, rect = target.Pix, target.Stride, target.Rect
+ bytesPerPixel = 2
+ case *image.Gray:
+ srcPix = src.(*image.Gray).Pix
+ dstPix, stride, rect = target.Pix, target.Stride, target.Rect
+ bytesPerPixel = 1
+ case *image.Gray16:
+ srcPix = src.(*image.Gray16).Pix
+ dstPix, stride, rect = target.Pix, target.Stride, target.Rect
+ bytesPerPixel = 2
+ case *image.NRGBA:
+ srcPix = src.(*image.NRGBA).Pix
+ dstPix, stride, rect = target.Pix, target.Stride, target.Rect
+ bytesPerPixel = 4
+ case *image.NRGBA64:
+ srcPix = src.(*image.NRGBA64).Pix
+ dstPix, stride, rect = target.Pix, target.Stride, target.Rect
+ bytesPerPixel = 8
+ case *image.Paletted:
+ srcPix = src.(*image.Paletted).Pix
+ dstPix, stride, rect = target.Pix, target.Stride, target.Rect
+ bytesPerPixel = 1
+ case *image.RGBA:
+ srcPix = src.(*image.RGBA).Pix
+ dstPix, stride, rect = target.Pix, target.Stride, target.Rect
+ bytesPerPixel = 4
+ case *image.RGBA64:
+ srcPix = src.(*image.RGBA64).Pix
+ dstPix, stride, rect = target.Pix, target.Stride, target.Rect
+ bytesPerPixel = 8
+ }
+ s, bounds := 0, src.Bounds()
+ for y := bounds.Min.Y; y < bounds.Max.Y; y++ {
+ dBase := (y*p.yFactor+p.yOffset-rect.Min.Y)*stride + (p.xOffset-rect.Min.X)*bytesPerPixel
+ for x := bounds.Min.X; x < bounds.Max.X; x++ {
+ d := dBase + x*p.xFactor*bytesPerPixel
+ copy(dstPix[d:], srcPix[s:s+bytesPerPixel])
+ s += bytesPerPixel
+ }
+ }
+}
+
+func (d *decoder) parseacTL(length uint32) (err error) {
+ if length != 8 {
+ return FormatError("bad acTL length")
+ }
+ if _, err := io.ReadFull(d.r, d.tmp[:8]); err != nil {
+ return err
+ }
+
+ d.num_frames = binary.BigEndian.Uint32(d.tmp[:4])
+ d.a.LoopCount = uint(binary.BigEndian.Uint32(d.tmp[4:8]))
+
+ d.crc.Write(d.tmp[:8])
+ return d.verifyChecksum()
+}
+
+func (d *decoder) parsefcTL(length uint32) (err error) {
+ if length != 26 {
+ return FormatError("bad fcTL length")
+ }
+ if _, err := io.ReadFull(d.r, d.tmp[:26]); err != nil {
+ return err
+ }
+
+ if d.frame_index >= len(d.a.Frames) {
+ d.a.Frames = append(d.a.Frames, Frame{})
+ }
+
+ d.a.Frames[d.frame_index].IsDefault = false
+ d.a.Frames[d.frame_index].width = int(int32(binary.BigEndian.Uint32(d.tmp[4:8])))
+ d.a.Frames[d.frame_index].height = int(int32(binary.BigEndian.Uint32(d.tmp[8:12])))
+ d.a.Frames[d.frame_index].XOffset = int(binary.BigEndian.Uint32(d.tmp[12:16]))
+ d.a.Frames[d.frame_index].YOffset = int(binary.BigEndian.Uint32(d.tmp[16:20]))
+ d.a.Frames[d.frame_index].DelayNumerator = binary.BigEndian.Uint16(d.tmp[20:22])
+ d.a.Frames[d.frame_index].DelayDenominator = binary.BigEndian.Uint16(d.tmp[22:24])
+ d.a.Frames[d.frame_index].DisposeOp = byte(d.tmp[24])
+ d.a.Frames[d.frame_index].BlendOp = byte(d.tmp[25])
+
+ d.crc.Write(d.tmp[:26])
+ return d.verifyChecksum()
+}
+
+func (d *decoder) parsefdAT(length uint32) (err error) {
+ if _, err := io.ReadFull(d.r, d.tmp[:4]); err != nil {
+ return err
+ }
+ d.crc.Write(d.tmp[:4])
+ d.idatLength = length - 4
+ d.a.Frames[d.frame_index].Image, err = d.decode()
+ if err != nil {
+ return err
+ }
+ return d.verifyChecksum()
+}
+
+func (d *decoder) parseIDAT(length uint32) (err error) {
+ d.idatLength = length
+ d.a.Frames[d.frame_index].Image, err = d.decode()
+ if err != nil {
+ return err
+ }
+ return d.verifyChecksum()
+}
+
+func (d *decoder) parseIEND(length uint32) error {
+ if length != 0 {
+ return FormatError("bad IEND length")
+ }
+ return d.verifyChecksum()
+}
+
+func (d *decoder) parseChunk() error {
+ // Read the length and chunk type.
+ n, err := io.ReadFull(d.r, d.tmp[:8])
+ if err != nil {
+ return err
+ }
+ 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 chunkOrderError
+ }
+ d.stage = dsSeenIHDR
+ return d.parseIHDR(length)
+ case "PLTE":
+ if d.stage != dsSeenIHDR {
+ return chunkOrderError
+ }
+ d.stage = dsSeenPLTE
+ return d.parsePLTE(length)
+ case "tRNS":
+ if cbPaletted(d.cb) {
+ if d.stage != dsSeenPLTE {
+ return chunkOrderError
+ }
+ } else if d.stage != dsSeenIHDR {
+ return chunkOrderError
+ }
+ d.stage = dsSeentRNS
+ return d.parsetRNS(length)
+ case "acTL":
+ if d.stage >= dsSeenIDAT {
+ return chunkOrderError
+ }
+ return d.parseacTL(length)
+ case "fcTL":
+ if d.stage >= dsSeenIDAT {
+ d.frame_index = d.frame_index + 1
+ }
+ return d.parsefcTL(length)
+ case "fdAT":
+ if d.stage < dsSeenIDAT {
+ return chunkOrderError
+ }
+ d.stage = dsSeenfdAT
+ return d.parsefdAT(length)
+ case "IDAT":
+ if d.stage < dsSeenIHDR || d.stage > dsSeenIDAT || (d.stage == dsSeenIHDR && cbPaletted(d.cb)) {
+ return chunkOrderError
+ } else if d.stage == dsSeenIDAT {
+ // Ignore trailing zero-length or garbage IDAT chunks.
+ //
+ // This does not affect valid PNG images that contain multiple IDAT
+ // chunks, since the first call to parseIDAT below will consume all
+ // consecutive IDAT chunks required for decoding the image.
+ break
+ }
+ d.stage = dsSeenIDAT
+ return d.parseIDAT(length)
+ case "IEND":
+ if d.stage < dsSeenIDAT {
+ return chunkOrderError
+ }
+ d.stage = dsSeenIEND
+ return d.parseIEND(length)
+ }
+ if length > 0x7fffffff {
+ return 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 err
+ }
+ d.crc.Write(ignored[:n])
+ length -= uint32(n)
+ }
+ return 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
+}
+
+// Decode reads an APNG file from r and returns it as an APNG
+// Type. If the first frame returns true for IsDefault(), that
+// frame should not be part of the a.
+// The type of Image returned depends on the PNG contents.
+func DecodeAll(r io.Reader) (APNG, error) {
+ d := &decoder{
+ r: r,
+ crc: crc32.NewIEEE(),
+ frame_index: 0,
+ a: APNG{Frames: make([]Frame, 1)},
+ }
+ d.a.Frames[0].IsDefault = true
+ if err := d.checkHeader(); err != nil {
+ if err == io.EOF {
+ err = io.ErrUnexpectedEOF
+ }
+ return d.a, err
+ }
+ for d.stage != dsSeenIEND {
+ if err := d.parseChunk(); err != nil {
+ if err == io.EOF {
+ err = io.ErrUnexpectedEOF
+ }
+ return d.a, err
+ }
+ }
+ return d.a, nil
+}
+
+// Decode reads an APNG file from r and returns the default image.
+func Decode(r io.Reader) (image.Image, error) {
+ a, err := DecodeAll(r)
+ if err != nil {
+ return nil, err
+ }
+ return a.Frames[0].Image, nil
+}
+
+// DecodeConfig returns the color model and dimensions of a PNG image without
+// decoding the entire image.
+func DecodeConfig(r io.Reader) (image.Config, error) {
+ d := &decoder{
+ r: r,
+ crc: crc32.NewIEEE(),
+ }
+ if err := d.checkHeader(); err != nil {
+ if err == io.EOF {
+ err = io.ErrUnexpectedEOF
+ }
+ return image.Config{}, err
+ }
+ for {
+ if err := d.parseChunk(); err != nil {
+ if err == io.EOF {
+ err = io.ErrUnexpectedEOF
+ }
+ return image.Config{}, err
+ }
+ paletted := cbPaletted(d.cb)
+ if d.stage == dsSeenIHDR && !paletted {
+ break
+ }
+ if d.stage == dsSeenPLTE && paletted {
+ break
+ }
+ }
+ var cm color.Model
+ switch d.cb {
+ case cbG1, cbG2, cbG4, cbG8:
+ cm = color.GrayModel
+ case cbGA8:
+ cm = color.NRGBAModel
+ case cbTC8:
+ cm = color.RGBAModel
+ case cbP1, cbP2, cbP4, cbP8:
+ cm = d.palette
+ case cbTCA8:
+ cm = color.NRGBAModel
+ case cbG16:
+ cm = color.Gray16Model
+ case cbGA16:
+ cm = color.NRGBA64Model
+ case cbTC16:
+ cm = color.RGBA64Model
+ case cbTCA16:
+ cm = color.NRGBA64Model
+ }
+ return image.Config{
+ ColorModel: cm,
+ Width: int(d.a.Frames[0].width),
+ Height: int(d.a.Frames[0].height),
+ }, nil
+}
+
+func init() {
+ image.RegisterFormat("apng", pngHeader, Decode, DecodeConfig)
+}