mirror of
https://github.com/42wim/matterbridge.git
synced 2024-12-28 22:22:48 +01:00
53cafa9f3d
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>
1142 lines
28 KiB
Go
1142 lines
28 KiB
Go
// 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)
|
|
}
|