diff options
author | Benau <Benau@users.noreply.github.com> | 2021-08-25 04:32:50 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-08-24 22:32:50 +0200 |
commit | 53cafa9f3d0c8be33821fc7338b1da97e91d9cc6 (patch) | |
tree | 964a225219099a1a1c282e27913767da588191b4 /vendor/github.com/kettek/apng/reader.go | |
parent | d4195deb3a6305c49c50ff30e8af978c7f1bdd92 (diff) | |
download | matterbridge-msglm-53cafa9f3d0c8be33821fc7338b1da97e91d9cc6.tar.gz matterbridge-msglm-53cafa9f3d0c8be33821fc7338b1da97e91d9cc6.tar.bz2 matterbridge-msglm-53cafa9f3d0c8be33821fc7338b1da97e91d9cc6.zip |
Convert .tgs with go libraries (and cgo) (telegram) (#1569)
This commit adds support for go/cgo tgs conversion when building with the -tags `cgo`
The default binaries are still "pure" go and uses the old way of converting.
* Move lottie_convert.py conversion code to its own file
* Add optional libtgsconverter
* Update vendor
* Apply suggestions from code review
* Update bridge/helper/libtgsconverter.go
Co-authored-by: Wim <wim@42.be>
Diffstat (limited to 'vendor/github.com/kettek/apng/reader.go')
-rw-r--r-- | vendor/github.com/kettek/apng/reader.go | 1141 |
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) +} |