mirror of
https://github.com/cwinfo/matterbridge.git
synced 2025-07-03 22:27:44 +00:00
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>
This commit is contained in:
30
vendor/github.com/kettek/apng/LICENSE
generated
vendored
Normal file
30
vendor/github.com/kettek/apng/LICENSE
generated
vendored
Normal file
@ -0,0 +1,30 @@
|
||||
Original PNG code Copyright (c) 2009 The Go Authors.
|
||||
APNG enhancements Copyright (c) 2018 Ketchetwahmeegwun T. Southall /
|
||||
kts of kettek.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
114
vendor/github.com/kettek/apng/README.md
generated
vendored
Normal file
114
vendor/github.com/kettek/apng/README.md
generated
vendored
Normal file
@ -0,0 +1,114 @@
|
||||
# APNG golang library
|
||||
This `apng` package provides methods for decoding and encoding APNG files. It is based upon the work in the official "image/png" package.
|
||||
|
||||
See [apngr](https://github.com/kettek/apngr) for an APNG extraction and combination tool using this library.
|
||||
|
||||
**NOTE**: The decoder should work for most anything you throw at it. Malformed PNGs should result in an error message. The encoder currently doesn't handle differences of Image formats and similar and has not been tested as thoroughly.
|
||||
|
||||
If a regular PNG file is read, the first Frame of the APNG returned by `DecodeAll(*File)` will be the PNG data.
|
||||
|
||||
## Types
|
||||
### APNG
|
||||
The APNG type contains the frames of a decoded `.apng` file, along with any important properties. It may also be created and used for Encoding.
|
||||
|
||||
| Signature | Description |
|
||||
|---------------------------|-------------------------------|
|
||||
| Frames []Frame | The stored frames of the APNG.|
|
||||
| LoopCount uint | The number of times an animation should be restarted during display. A value of 0 means to loop forever. |
|
||||
|
||||
### Frame
|
||||
The Frame type contains an individual frame of an APNG. The following table provides the important properties and methods.
|
||||
|
||||
| Signature | Description |
|
||||
|---------------------------|------------------|
|
||||
| Image image.Image | Frame image data. |
|
||||
| IsDefault bool | Indicates if this frame is a default image that should not be included as part of the animation frames. May only be true for the first Frame. |
|
||||
| XOffset int | Returns the x offset of the frame. |
|
||||
| YOffset int | Returns the y offset of the frame. |
|
||||
| DelayNumerator int | Returns the delay numerator. |
|
||||
| DelayDenominator int | Returns the delay denominator. |
|
||||
| DisposeOp byte | Returns the frame disposal operation. May be `apng.DISPOSE_OP_NONE`, `apng.DISPOSE_OP_BACKGROUND`, or `apng.DISPOSE_OP_PREVIOUS`. See the [APNG Specification](https://wiki.mozilla.org/APNG_Specification#.60fcTL.60:_The_Frame_Control_Chunk) for more information. |
|
||||
| BlendOp byte | Returns the frame blending operation. May be `apng.BLEND_OP_SOURCE` or `apng.BLEND_OP_OVER`. See the [APNG Specification](https://wiki.mozilla.org/APNG_Specification#.60fcTL.60:_The_Frame_Control_Chunk) for more information. |
|
||||
|
||||
## Methods
|
||||
### DecodeAll(io.Reader) (APNG, error)
|
||||
This method returns an APNG type containing the frames and associated data within the passed file.
|
||||
|
||||
### Example
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/kettek/apng"
|
||||
"os"
|
||||
"log"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Open our animated PNG file
|
||||
f, err := os.Open("animation.png")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer f.Close()
|
||||
// Decode all frames into an APNG
|
||||
a, err := apng.DecodeAll(f)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// Print some information on the APNG
|
||||
log.Printf("Found %d frames\n", len(a.Frames))
|
||||
for i, frame := range a.Frames {
|
||||
b := frame.Image.Bounds()
|
||||
log.Printf("Frame %d: %dx%d\n", i, b.Max.X, b.Max.Y)
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### Decode(io.Reader) (image.Image, error)
|
||||
This method returns the Image of the default frame of an APNG file.
|
||||
|
||||
### Encode(io.Writer, APNG) error
|
||||
This method writes the passed APNG object to the given io.Writer as an APNG binary file.
|
||||
|
||||
### Example
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/kettek/apng"
|
||||
"image/png"
|
||||
"os"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Define our variables
|
||||
output := "animation.png"
|
||||
images := [4]string{"0.png", "1.png", "2.png", "3.png"}
|
||||
a := apng.APNG{
|
||||
Frames: make([]apng.Frame, len(images)),
|
||||
}
|
||||
// Open our file for writing
|
||||
out, err := os.Create(output)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer out.Close()
|
||||
// Assign each decoded PNG's Image to the appropriate Frame Image
|
||||
for i, s := range images {
|
||||
in, err := os.Open(s)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer in.Close()
|
||||
m, err := png.Decode(in)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
a.Frames[i].Image = m
|
||||
}
|
||||
// Write APNG to our output file
|
||||
apng.Encode(out, a)
|
||||
}
|
||||
```
|
13
vendor/github.com/kettek/apng/apng.go
generated
vendored
Normal file
13
vendor/github.com/kettek/apng/apng.go
generated
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
// Copyright 2018 kts of kettek / Ketchetwahmeegwun Tecumseh Southall. 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
|
||||
|
||||
type APNG struct {
|
||||
Frames []Frame
|
||||
// LoopCount defines the number of times an animation will be
|
||||
// restarted during display.
|
||||
// A LoopCount of 0 means to loop forever
|
||||
LoopCount uint
|
||||
}
|
47
vendor/github.com/kettek/apng/frame.go
generated
vendored
Normal file
47
vendor/github.com/kettek/apng/frame.go
generated
vendored
Normal file
@ -0,0 +1,47 @@
|
||||
// Copyright 2018 kts of kettek / Ketchetwahmeegwun Tecumseh Southall. 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
|
||||
|
||||
import (
|
||||
"image"
|
||||
)
|
||||
|
||||
// dispose_op values, as per the APNG spec.
|
||||
const (
|
||||
DISPOSE_OP_NONE = 0
|
||||
DISPOSE_OP_BACKGROUND = 1
|
||||
DISPOSE_OP_PREVIOUS = 2
|
||||
)
|
||||
|
||||
// blend_op values, as per the APNG spec.
|
||||
const (
|
||||
BLEND_OP_SOURCE = 0
|
||||
BLEND_OP_OVER = 1
|
||||
)
|
||||
|
||||
type Frame struct {
|
||||
Image image.Image
|
||||
width, height int
|
||||
XOffset, YOffset int
|
||||
DelayNumerator uint16
|
||||
DelayDenominator uint16
|
||||
DisposeOp byte
|
||||
BlendOp byte
|
||||
// IsDefault indicates if the Frame is a default image that
|
||||
// should not be used in the animation. IsDefault can only
|
||||
// be true on the first frame.
|
||||
IsDefault bool
|
||||
}
|
||||
|
||||
// GetDelay returns the number of seconds in the frame.
|
||||
func (f *Frame) GetDelay() float64 {
|
||||
d := uint16(0)
|
||||
if f.DelayDenominator == 0 {
|
||||
d = 100
|
||||
} else {
|
||||
d = f.DelayDenominator
|
||||
}
|
||||
return float64(f.DelayNumerator) / float64(d)
|
||||
}
|
71
vendor/github.com/kettek/apng/paeth.go
generated
vendored
Normal file
71
vendor/github.com/kettek/apng/paeth.go
generated
vendored
Normal file
@ -0,0 +1,71 @@
|
||||
// Copyright 2012 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 apng
|
||||
|
||||
// intSize is either 32 or 64.
|
||||
const intSize = 32 << (^uint(0) >> 63)
|
||||
|
||||
func abs(x int) int {
|
||||
// m := -1 if x < 0. m := 0 otherwise.
|
||||
m := x >> (intSize - 1)
|
||||
|
||||
// In two's complement representation, the negative number
|
||||
// of any number (except the smallest one) can be computed
|
||||
// by flipping all the bits and add 1. This is faster than
|
||||
// code with a branch.
|
||||
// See Hacker's Delight, section 2-4.
|
||||
return (x ^ m) - m
|
||||
}
|
||||
|
||||
// paeth implements the Paeth filter function, as per the PNG specification.
|
||||
func paeth(a, b, c uint8) uint8 {
|
||||
// This is an optimized version of the sample code in the PNG spec.
|
||||
// For example, the sample code starts with:
|
||||
// p := int(a) + int(b) - int(c)
|
||||
// pa := abs(p - int(a))
|
||||
// but the optimized form uses fewer arithmetic operations:
|
||||
// pa := int(b) - int(c)
|
||||
// pa = abs(pa)
|
||||
pc := int(c)
|
||||
pa := int(b) - pc
|
||||
pb := int(a) - pc
|
||||
pc = abs(pa + pb)
|
||||
pa = abs(pa)
|
||||
pb = abs(pb)
|
||||
if pa <= pb && pa <= pc {
|
||||
return a
|
||||
} else if pb <= pc {
|
||||
return b
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// filterPaeth applies the Paeth filter to the cdat slice.
|
||||
// cdat is the current row's data, pdat is the previous row's data.
|
||||
func filterPaeth(cdat, pdat []byte, bytesPerPixel int) {
|
||||
var a, b, c, pa, pb, pc int
|
||||
for i := 0; i < bytesPerPixel; i++ {
|
||||
a, c = 0, 0
|
||||
for j := i; j < len(cdat); j += bytesPerPixel {
|
||||
b = int(pdat[j])
|
||||
pa = b - c
|
||||
pb = a - c
|
||||
pc = abs(pa + pb)
|
||||
pa = abs(pa)
|
||||
pb = abs(pb)
|
||||
if pa <= pb && pa <= pc {
|
||||
// No-op.
|
||||
} else if pb <= pc {
|
||||
a = b
|
||||
} else {
|
||||
a = c
|
||||
}
|
||||
a += int(cdat[j])
|
||||
a &= 0xff
|
||||
cdat[j] = uint8(a)
|
||||
c = b
|
||||
}
|
||||
}
|
||||
}
|
1141
vendor/github.com/kettek/apng/reader.go
generated
vendored
Normal file
1141
vendor/github.com/kettek/apng/reader.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
713
vendor/github.com/kettek/apng/writer.go
generated
vendored
Normal file
713
vendor/github.com/kettek/apng/writer.go
generated
vendored
Normal file
@ -0,0 +1,713 @@
|
||||
// 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
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"compress/zlib"
|
||||
"encoding/binary"
|
||||
"hash/crc32"
|
||||
"image"
|
||||
"image/color"
|
||||
"io"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// Encoder configures encoding PNG images.
|
||||
type Encoder struct {
|
||||
CompressionLevel CompressionLevel
|
||||
|
||||
// BufferPool optionally specifies a buffer pool to get temporary
|
||||
// EncoderBuffers when encoding an image.
|
||||
BufferPool EncoderBufferPool
|
||||
}
|
||||
|
||||
// EncoderBufferPool is an interface for getting and returning temporary
|
||||
// instances of the EncoderBuffer struct. This can be used to reuse buffers
|
||||
// when encoding multiple images.
|
||||
type EncoderBufferPool interface {
|
||||
Get() *EncoderBuffer
|
||||
Put(*EncoderBuffer)
|
||||
}
|
||||
|
||||
// EncoderBuffer holds the buffers used for encoding PNG images.
|
||||
type EncoderBuffer encoder
|
||||
|
||||
type encoder struct {
|
||||
enc *Encoder
|
||||
w io.Writer
|
||||
a APNG
|
||||
write_type int // 0 = IDAT, 1 = fdAT
|
||||
seq int
|
||||
cb int
|
||||
err error
|
||||
header [8]byte
|
||||
footer [4]byte
|
||||
tmp [4 * 256]byte
|
||||
cr [nFilter][]uint8
|
||||
pr []uint8
|
||||
zw *zlib.Writer
|
||||
zwLevel int
|
||||
bw *bufio.Writer
|
||||
}
|
||||
|
||||
type CompressionLevel int
|
||||
|
||||
const (
|
||||
DefaultCompression CompressionLevel = 0
|
||||
NoCompression CompressionLevel = -1
|
||||
BestSpeed CompressionLevel = -2
|
||||
BestCompression CompressionLevel = -3
|
||||
|
||||
// Positive CompressionLevel values are reserved to mean a numeric zlib
|
||||
// compression level, although that is not implemented yet.
|
||||
)
|
||||
|
||||
type opaquer interface {
|
||||
Opaque() bool
|
||||
}
|
||||
|
||||
// Returns whether or not the image is fully opaque.
|
||||
func opaque(m image.Image) bool {
|
||||
if o, ok := m.(opaquer); ok {
|
||||
return o.Opaque()
|
||||
}
|
||||
b := m.Bounds()
|
||||
for y := b.Min.Y; y < b.Max.Y; y++ {
|
||||
for x := b.Min.X; x < b.Max.X; x++ {
|
||||
_, _, _, a := m.At(x, y).RGBA()
|
||||
if a != 0xffff {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// The absolute value of a byte interpreted as a signed int8.
|
||||
func abs8(d uint8) int {
|
||||
if d < 128 {
|
||||
return int(d)
|
||||
}
|
||||
return 256 - int(d)
|
||||
}
|
||||
|
||||
func (e *encoder) writeChunk(b []byte, name string) {
|
||||
if e.err != nil {
|
||||
return
|
||||
}
|
||||
n := uint32(len(b))
|
||||
if int(n) != len(b) {
|
||||
e.err = UnsupportedError(name + " chunk is too large: " + strconv.Itoa(len(b)))
|
||||
return
|
||||
}
|
||||
binary.BigEndian.PutUint32(e.header[:4], n)
|
||||
e.header[4] = name[0]
|
||||
e.header[5] = name[1]
|
||||
e.header[6] = name[2]
|
||||
e.header[7] = name[3]
|
||||
crc := crc32.NewIEEE()
|
||||
crc.Write(e.header[4:8])
|
||||
crc.Write(b)
|
||||
binary.BigEndian.PutUint32(e.footer[:4], crc.Sum32())
|
||||
|
||||
_, e.err = e.w.Write(e.header[:8])
|
||||
if e.err != nil {
|
||||
return
|
||||
}
|
||||
_, e.err = e.w.Write(b)
|
||||
if e.err != nil {
|
||||
return
|
||||
}
|
||||
_, e.err = e.w.Write(e.footer[:4])
|
||||
}
|
||||
|
||||
func (e *encoder) writeIHDR() {
|
||||
b := e.a.Frames[0].Image.Bounds()
|
||||
binary.BigEndian.PutUint32(e.tmp[0:4], uint32(b.Dx()))
|
||||
binary.BigEndian.PutUint32(e.tmp[4:8], uint32(b.Dy()))
|
||||
// Set bit depth and color type.
|
||||
switch e.cb {
|
||||
case cbG8:
|
||||
e.tmp[8] = 8
|
||||
e.tmp[9] = ctGrayscale
|
||||
case cbTC8:
|
||||
e.tmp[8] = 8
|
||||
e.tmp[9] = ctTrueColor
|
||||
case cbP8:
|
||||
e.tmp[8] = 8
|
||||
e.tmp[9] = ctPaletted
|
||||
case cbP4:
|
||||
e.tmp[8] = 4
|
||||
e.tmp[9] = ctPaletted
|
||||
case cbP2:
|
||||
e.tmp[8] = 2
|
||||
e.tmp[9] = ctPaletted
|
||||
case cbP1:
|
||||
e.tmp[8] = 1
|
||||
e.tmp[9] = ctPaletted
|
||||
case cbTCA8:
|
||||
e.tmp[8] = 8
|
||||
e.tmp[9] = ctTrueColorAlpha
|
||||
case cbG16:
|
||||
e.tmp[8] = 16
|
||||
e.tmp[9] = ctGrayscale
|
||||
case cbTC16:
|
||||
e.tmp[8] = 16
|
||||
e.tmp[9] = ctTrueColor
|
||||
case cbTCA16:
|
||||
e.tmp[8] = 16
|
||||
e.tmp[9] = ctTrueColorAlpha
|
||||
}
|
||||
e.tmp[10] = 0 // default compression method
|
||||
e.tmp[11] = 0 // default filter method
|
||||
e.tmp[12] = 0 // non-interlaced
|
||||
e.writeChunk(e.tmp[:13], "IHDR")
|
||||
}
|
||||
|
||||
func (e *encoder) writeacTL() {
|
||||
binary.BigEndian.PutUint32(e.tmp[0:4], uint32(len(e.a.Frames)))
|
||||
binary.BigEndian.PutUint32(e.tmp[4:8], uint32(e.a.LoopCount))
|
||||
e.writeChunk(e.tmp[:8], "acTL")
|
||||
}
|
||||
|
||||
func (e *encoder) writefcTL(f Frame) {
|
||||
binary.BigEndian.PutUint32(e.tmp[0:4], uint32(e.seq))
|
||||
e.seq = e.seq + 1
|
||||
b := f.Image.Bounds()
|
||||
binary.BigEndian.PutUint32(e.tmp[4:8], uint32(b.Dx()))
|
||||
binary.BigEndian.PutUint32(e.tmp[8:12], uint32(b.Dy()))
|
||||
binary.BigEndian.PutUint32(e.tmp[12:16], uint32(f.XOffset))
|
||||
binary.BigEndian.PutUint32(e.tmp[16:20], uint32(f.YOffset))
|
||||
binary.BigEndian.PutUint16(e.tmp[20:22], uint16(f.DelayNumerator))
|
||||
binary.BigEndian.PutUint16(e.tmp[22:24], uint16(f.DelayDenominator))
|
||||
e.tmp[24] = f.DisposeOp
|
||||
e.tmp[25] = f.BlendOp
|
||||
e.writeChunk(e.tmp[:26], "fcTL")
|
||||
}
|
||||
|
||||
func (e *encoder) writefdATs(f Frame) {
|
||||
e.write_type = 1
|
||||
if e.err != nil {
|
||||
return
|
||||
}
|
||||
if e.bw == nil {
|
||||
e.bw = bufio.NewWriterSize(e, 1<<15)
|
||||
} else {
|
||||
e.bw.Reset(e)
|
||||
}
|
||||
e.err = e.writeImage(e.bw, f.Image, e.cb, levelToZlib(e.enc.CompressionLevel))
|
||||
if e.err != nil {
|
||||
return
|
||||
}
|
||||
e.err = e.bw.Flush()
|
||||
}
|
||||
|
||||
func (e *encoder) writePLTEAndTRNS(p color.Palette) {
|
||||
if len(p) < 1 || len(p) > 256 {
|
||||
e.err = FormatError("bad palette length: " + strconv.Itoa(len(p)))
|
||||
return
|
||||
}
|
||||
last := -1
|
||||
for i, c := range p {
|
||||
c1 := color.NRGBAModel.Convert(c).(color.NRGBA)
|
||||
e.tmp[3*i+0] = c1.R
|
||||
e.tmp[3*i+1] = c1.G
|
||||
e.tmp[3*i+2] = c1.B
|
||||
if c1.A != 0xff {
|
||||
last = i
|
||||
}
|
||||
e.tmp[3*256+i] = c1.A
|
||||
}
|
||||
e.writeChunk(e.tmp[:3*len(p)], "PLTE")
|
||||
if last != -1 {
|
||||
e.writeChunk(e.tmp[3*256:3*256+1+last], "tRNS")
|
||||
}
|
||||
}
|
||||
|
||||
// An encoder is an io.Writer that satisfies writes by writing PNG IDAT chunks,
|
||||
// including an 8-byte header and 4-byte CRC checksum per Write call. Such calls
|
||||
// should be relatively infrequent, since writeIDATs uses a bufio.Writer.
|
||||
//
|
||||
// This method should only be called from writeIDATs (via writeImage).
|
||||
// No other code should treat an encoder as an io.Writer.
|
||||
func (e *encoder) Write(b []byte) (int, error) {
|
||||
if e.write_type == 0 {
|
||||
e.writeChunk(b, "IDAT")
|
||||
} else {
|
||||
c := make([]byte, 4)
|
||||
binary.BigEndian.PutUint32(c[0:4], uint32(e.seq))
|
||||
e.seq = e.seq + 1
|
||||
b = append(c, b...)
|
||||
e.writeChunk(b, "fdAT")
|
||||
}
|
||||
if e.err != nil {
|
||||
return 0, e.err
|
||||
}
|
||||
return len(b), nil
|
||||
}
|
||||
|
||||
// Chooses the filter to use for encoding the current row, and applies it.
|
||||
// The return value is the index of the filter and also of the row in cr that has had it applied.
|
||||
func filter(cr *[nFilter][]byte, pr []byte, bpp int) int {
|
||||
// We try all five filter types, and pick the one that minimizes the sum of absolute differences.
|
||||
// This is the same heuristic that libpng uses, although the filters are attempted in order of
|
||||
// estimated most likely to be minimal (ftUp, ftPaeth, ftNone, ftSub, ftAverage), rather than
|
||||
// in their enumeration order (ftNone, ftSub, ftUp, ftAverage, ftPaeth).
|
||||
cdat0 := cr[0][1:]
|
||||
cdat1 := cr[1][1:]
|
||||
cdat2 := cr[2][1:]
|
||||
cdat3 := cr[3][1:]
|
||||
cdat4 := cr[4][1:]
|
||||
pdat := pr[1:]
|
||||
n := len(cdat0)
|
||||
|
||||
// The up filter.
|
||||
sum := 0
|
||||
for i := 0; i < n; i++ {
|
||||
cdat2[i] = cdat0[i] - pdat[i]
|
||||
sum += abs8(cdat2[i])
|
||||
}
|
||||
best := sum
|
||||
filter := ftUp
|
||||
|
||||
// The Paeth filter.
|
||||
sum = 0
|
||||
for i := 0; i < bpp; i++ {
|
||||
cdat4[i] = cdat0[i] - pdat[i]
|
||||
sum += abs8(cdat4[i])
|
||||
}
|
||||
for i := bpp; i < n; i++ {
|
||||
cdat4[i] = cdat0[i] - paeth(cdat0[i-bpp], pdat[i], pdat[i-bpp])
|
||||
sum += abs8(cdat4[i])
|
||||
if sum >= best {
|
||||
break
|
||||
}
|
||||
}
|
||||
if sum < best {
|
||||
best = sum
|
||||
filter = ftPaeth
|
||||
}
|
||||
|
||||
// The none filter.
|
||||
sum = 0
|
||||
for i := 0; i < n; i++ {
|
||||
sum += abs8(cdat0[i])
|
||||
if sum >= best {
|
||||
break
|
||||
}
|
||||
}
|
||||
if sum < best {
|
||||
best = sum
|
||||
filter = ftNone
|
||||
}
|
||||
|
||||
// The sub filter.
|
||||
sum = 0
|
||||
for i := 0; i < bpp; i++ {
|
||||
cdat1[i] = cdat0[i]
|
||||
sum += abs8(cdat1[i])
|
||||
}
|
||||
for i := bpp; i < n; i++ {
|
||||
cdat1[i] = cdat0[i] - cdat0[i-bpp]
|
||||
sum += abs8(cdat1[i])
|
||||
if sum >= best {
|
||||
break
|
||||
}
|
||||
}
|
||||
if sum < best {
|
||||
best = sum
|
||||
filter = ftSub
|
||||
}
|
||||
|
||||
// The average filter.
|
||||
sum = 0
|
||||
for i := 0; i < bpp; i++ {
|
||||
cdat3[i] = cdat0[i] - pdat[i]/2
|
||||
sum += abs8(cdat3[i])
|
||||
}
|
||||
for i := bpp; i < n; i++ {
|
||||
cdat3[i] = cdat0[i] - uint8((int(cdat0[i-bpp])+int(pdat[i]))/2)
|
||||
sum += abs8(cdat3[i])
|
||||
if sum >= best {
|
||||
break
|
||||
}
|
||||
}
|
||||
if sum < best {
|
||||
best = sum
|
||||
filter = ftAverage
|
||||
}
|
||||
|
||||
return filter
|
||||
}
|
||||
|
||||
func zeroMemory(v []uint8) {
|
||||
for i := range v {
|
||||
v[i] = 0
|
||||
}
|
||||
}
|
||||
|
||||
func (e *encoder) writeImage(w io.Writer, m image.Image, cb int, level int) error {
|
||||
if e.zw == nil || e.zwLevel != level {
|
||||
zw, err := zlib.NewWriterLevel(w, level)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
e.zw = zw
|
||||
e.zwLevel = level
|
||||
} else {
|
||||
e.zw.Reset(w)
|
||||
}
|
||||
defer e.zw.Close()
|
||||
|
||||
bitsPerPixel := 0
|
||||
|
||||
switch cb {
|
||||
case cbG8:
|
||||
bitsPerPixel = 8
|
||||
case cbTC8:
|
||||
bitsPerPixel = 24
|
||||
case cbP8:
|
||||
bitsPerPixel = 8
|
||||
case cbP4:
|
||||
bitsPerPixel = 4
|
||||
case cbP2:
|
||||
bitsPerPixel = 2
|
||||
case cbP1:
|
||||
bitsPerPixel = 1
|
||||
case cbTCA8:
|
||||
bitsPerPixel = 32
|
||||
case cbTC16:
|
||||
bitsPerPixel = 48
|
||||
case cbTCA16:
|
||||
bitsPerPixel = 64
|
||||
case cbG16:
|
||||
bitsPerPixel = 16
|
||||
}
|
||||
|
||||
// cr[*] and pr are the bytes for the current and previous row.
|
||||
// cr[0] is unfiltered (or equivalently, filtered with the ftNone filter).
|
||||
// cr[ft], for non-zero filter types ft, are buffers for transforming cr[0] under the
|
||||
// other PNG filter types. These buffers are allocated once and re-used for each row.
|
||||
// The +1 is for the per-row filter type, which is at cr[*][0].
|
||||
b := m.Bounds()
|
||||
sz := 1 + (bitsPerPixel*b.Dx()+7)/8
|
||||
for i := range e.cr {
|
||||
if cap(e.cr[i]) < sz {
|
||||
e.cr[i] = make([]uint8, sz)
|
||||
} else {
|
||||
e.cr[i] = e.cr[i][:sz]
|
||||
}
|
||||
e.cr[i][0] = uint8(i)
|
||||
}
|
||||
cr := e.cr
|
||||
if cap(e.pr) < sz {
|
||||
e.pr = make([]uint8, sz)
|
||||
} else {
|
||||
e.pr = e.pr[:sz]
|
||||
zeroMemory(e.pr)
|
||||
}
|
||||
pr := e.pr
|
||||
|
||||
gray, _ := m.(*image.Gray)
|
||||
rgba, _ := m.(*image.RGBA)
|
||||
paletted, _ := m.(*image.Paletted)
|
||||
nrgba, _ := m.(*image.NRGBA)
|
||||
|
||||
for y := b.Min.Y; y < b.Max.Y; y++ {
|
||||
// Convert from colors to bytes.
|
||||
i := 1
|
||||
switch cb {
|
||||
case cbG8:
|
||||
if gray != nil {
|
||||
offset := (y - b.Min.Y) * gray.Stride
|
||||
copy(cr[0][1:], gray.Pix[offset:offset+b.Dx()])
|
||||
} else {
|
||||
for x := b.Min.X; x < b.Max.X; x++ {
|
||||
c := color.GrayModel.Convert(m.At(x, y)).(color.Gray)
|
||||
cr[0][i] = c.Y
|
||||
i++
|
||||
}
|
||||
}
|
||||
case cbTC8:
|
||||
// We have previously verified that the alpha value is fully opaque.
|
||||
cr0 := cr[0]
|
||||
stride, pix := 0, []byte(nil)
|
||||
if rgba != nil {
|
||||
stride, pix = rgba.Stride, rgba.Pix
|
||||
} else if nrgba != nil {
|
||||
stride, pix = nrgba.Stride, nrgba.Pix
|
||||
}
|
||||
if stride != 0 {
|
||||
j0 := (y - b.Min.Y) * stride
|
||||
j1 := j0 + b.Dx()*4
|
||||
for j := j0; j < j1; j += 4 {
|
||||
cr0[i+0] = pix[j+0]
|
||||
cr0[i+1] = pix[j+1]
|
||||
cr0[i+2] = pix[j+2]
|
||||
i += 3
|
||||
}
|
||||
} else {
|
||||
for x := b.Min.X; x < b.Max.X; x++ {
|
||||
r, g, b, _ := m.At(x, y).RGBA()
|
||||
cr0[i+0] = uint8(r >> 8)
|
||||
cr0[i+1] = uint8(g >> 8)
|
||||
cr0[i+2] = uint8(b >> 8)
|
||||
i += 3
|
||||
}
|
||||
}
|
||||
case cbP8:
|
||||
if paletted != nil {
|
||||
offset := (y - b.Min.Y) * paletted.Stride
|
||||
copy(cr[0][1:], paletted.Pix[offset:offset+b.Dx()])
|
||||
} else {
|
||||
pi := m.(image.PalettedImage)
|
||||
for x := b.Min.X; x < b.Max.X; x++ {
|
||||
cr[0][i] = pi.ColorIndexAt(x, y)
|
||||
i += 1
|
||||
}
|
||||
}
|
||||
|
||||
case cbP4, cbP2, cbP1:
|
||||
pi := m.(image.PalettedImage)
|
||||
|
||||
var a uint8
|
||||
var c int
|
||||
for x := b.Min.X; x < b.Max.X; x++ {
|
||||
a = a<<uint(bitsPerPixel) | pi.ColorIndexAt(x, y)
|
||||
c++
|
||||
if c == 8/bitsPerPixel {
|
||||
cr[0][i] = a
|
||||
i += 1
|
||||
a = 0
|
||||
c = 0
|
||||
}
|
||||
}
|
||||
if c != 0 {
|
||||
for c != 8/bitsPerPixel {
|
||||
a = a << uint(bitsPerPixel)
|
||||
c++
|
||||
}
|
||||
cr[0][i] = a
|
||||
}
|
||||
|
||||
case cbTCA8:
|
||||
if nrgba != nil {
|
||||
offset := (y - b.Min.Y) * nrgba.Stride
|
||||
copy(cr[0][1:], nrgba.Pix[offset:offset+b.Dx()*4])
|
||||
} else {
|
||||
// Convert from image.Image (which is alpha-premultiplied) to PNG's non-alpha-premultiplied.
|
||||
for x := b.Min.X; x < b.Max.X; x++ {
|
||||
c := color.NRGBAModel.Convert(m.At(x, y)).(color.NRGBA)
|
||||
cr[0][i+0] = c.R
|
||||
cr[0][i+1] = c.G
|
||||
cr[0][i+2] = c.B
|
||||
cr[0][i+3] = c.A
|
||||
i += 4
|
||||
}
|
||||
}
|
||||
case cbG16:
|
||||
for x := b.Min.X; x < b.Max.X; x++ {
|
||||
c := color.Gray16Model.Convert(m.At(x, y)).(color.Gray16)
|
||||
cr[0][i+0] = uint8(c.Y >> 8)
|
||||
cr[0][i+1] = uint8(c.Y)
|
||||
i += 2
|
||||
}
|
||||
case cbTC16:
|
||||
// We have previously verified that the alpha value is fully opaque.
|
||||
for x := b.Min.X; x < b.Max.X; x++ {
|
||||
r, g, b, _ := m.At(x, y).RGBA()
|
||||
cr[0][i+0] = uint8(r >> 8)
|
||||
cr[0][i+1] = uint8(r)
|
||||
cr[0][i+2] = uint8(g >> 8)
|
||||
cr[0][i+3] = uint8(g)
|
||||
cr[0][i+4] = uint8(b >> 8)
|
||||
cr[0][i+5] = uint8(b)
|
||||
i += 6
|
||||
}
|
||||
case cbTCA16:
|
||||
// Convert from image.Image (which is alpha-premultiplied) to PNG's non-alpha-premultiplied.
|
||||
for x := b.Min.X; x < b.Max.X; x++ {
|
||||
c := color.NRGBA64Model.Convert(m.At(x, y)).(color.NRGBA64)
|
||||
cr[0][i+0] = uint8(c.R >> 8)
|
||||
cr[0][i+1] = uint8(c.R)
|
||||
cr[0][i+2] = uint8(c.G >> 8)
|
||||
cr[0][i+3] = uint8(c.G)
|
||||
cr[0][i+4] = uint8(c.B >> 8)
|
||||
cr[0][i+5] = uint8(c.B)
|
||||
cr[0][i+6] = uint8(c.A >> 8)
|
||||
cr[0][i+7] = uint8(c.A)
|
||||
i += 8
|
||||
}
|
||||
}
|
||||
|
||||
// Apply the filter.
|
||||
// Skip filter for NoCompression and paletted images (cbP8) as
|
||||
// "filters are rarely useful on palette images" and will result
|
||||
// in larger files (see http://www.libpng.org/pub/png/book/chapter09.html).
|
||||
f := ftNone
|
||||
if level != zlib.NoCompression && cb != cbP8 && cb != cbP4 && cb != cbP2 && cb != cbP1 {
|
||||
// Since we skip paletted images we don't have to worry about
|
||||
// bitsPerPixel not being a multiple of 8
|
||||
bpp := bitsPerPixel / 8
|
||||
f = filter(&cr, pr, bpp)
|
||||
}
|
||||
|
||||
// Write the compressed bytes.
|
||||
if _, err := e.zw.Write(cr[f]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// The current row for y is the previous row for y+1.
|
||||
pr, cr[0] = cr[0], pr
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Write the actual image data to one or more IDAT chunks.
|
||||
func (e *encoder) writeIDATs() {
|
||||
e.write_type = 0
|
||||
if e.err != nil {
|
||||
return
|
||||
}
|
||||
if e.bw == nil {
|
||||
e.bw = bufio.NewWriterSize(e, 1<<15)
|
||||
} else {
|
||||
e.bw.Reset(e)
|
||||
}
|
||||
e.err = e.writeImage(e.bw, e.a.Frames[0].Image, e.cb, levelToZlib(e.enc.CompressionLevel))
|
||||
if e.err != nil {
|
||||
return
|
||||
}
|
||||
e.err = e.bw.Flush()
|
||||
}
|
||||
|
||||
// This function is required because we want the zero value of
|
||||
// Encoder.CompressionLevel to map to zlib.DefaultCompression.
|
||||
func levelToZlib(l CompressionLevel) int {
|
||||
switch l {
|
||||
case DefaultCompression:
|
||||
return zlib.DefaultCompression
|
||||
case NoCompression:
|
||||
return zlib.NoCompression
|
||||
case BestSpeed:
|
||||
return zlib.BestSpeed
|
||||
case BestCompression:
|
||||
return zlib.BestCompression
|
||||
default:
|
||||
return zlib.DefaultCompression
|
||||
}
|
||||
}
|
||||
|
||||
func (e *encoder) writeIEND() { e.writeChunk(nil, "IEND") }
|
||||
|
||||
// Encode writes the APNG a to w in PNG format. Any Image may be
|
||||
// encoded, but images that are not image.NRGBA might be encoded lossily.
|
||||
func Encode(w io.Writer, a APNG) error {
|
||||
var e Encoder
|
||||
return e.Encode(w, a)
|
||||
}
|
||||
|
||||
// Encode writes the Animation a to w in PNG format.
|
||||
func (enc *Encoder) Encode(w io.Writer, a APNG) error {
|
||||
// Obviously, negative widths and heights are invalid. Furthermore, the PNG
|
||||
// spec section 11.2.2 says that zero is invalid. Excessively large images are
|
||||
// also rejected.
|
||||
mw, mh := int64(a.Frames[0].Image.Bounds().Dx()), int64(a.Frames[0].Image.Bounds().Dy())
|
||||
if mw <= 0 || mh <= 0 || mw >= 1<<32 || mh >= 1<<32 {
|
||||
return FormatError("invalid image size: " + strconv.FormatInt(mw, 10) + "x" + strconv.FormatInt(mh, 10))
|
||||
}
|
||||
|
||||
var e *encoder
|
||||
if enc.BufferPool != nil {
|
||||
buffer := enc.BufferPool.Get()
|
||||
e = (*encoder)(buffer)
|
||||
|
||||
}
|
||||
if e == nil {
|
||||
e = &encoder{}
|
||||
}
|
||||
if enc.BufferPool != nil {
|
||||
defer enc.BufferPool.Put((*EncoderBuffer)(e))
|
||||
}
|
||||
|
||||
e.enc = enc
|
||||
e.w = w
|
||||
e.a = a
|
||||
|
||||
var pal color.Palette
|
||||
// cbP8 encoding needs PalettedImage's ColorIndexAt method.
|
||||
if _, ok := a.Frames[0].Image.(image.PalettedImage); ok {
|
||||
pal, _ = a.Frames[0].Image.ColorModel().(color.Palette)
|
||||
}
|
||||
if pal != nil {
|
||||
if len(pal) <= 2 {
|
||||
e.cb = cbP1
|
||||
} else if len(pal) <= 4 {
|
||||
e.cb = cbP2
|
||||
} else if len(pal) <= 16 {
|
||||
e.cb = cbP4
|
||||
} else {
|
||||
e.cb = cbP8
|
||||
}
|
||||
} else {
|
||||
switch a.Frames[0].Image.ColorModel() {
|
||||
case color.GrayModel:
|
||||
e.cb = cbG8
|
||||
case color.Gray16Model:
|
||||
e.cb = cbG16
|
||||
case color.RGBAModel, color.NRGBAModel, color.AlphaModel:
|
||||
isOpaque := true
|
||||
for _, v := range a.Frames {
|
||||
if !opaque(v.Image) {
|
||||
isOpaque = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if isOpaque {
|
||||
e.cb = cbTC8
|
||||
} else {
|
||||
e.cb = cbTCA8
|
||||
}
|
||||
default:
|
||||
isOpaque := true
|
||||
for _, v := range a.Frames {
|
||||
if !opaque(v.Image) {
|
||||
isOpaque = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if isOpaque {
|
||||
e.cb = cbTC16
|
||||
} else {
|
||||
e.cb = cbTCA16
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_, e.err = io.WriteString(w, pngHeader)
|
||||
e.writeIHDR()
|
||||
if pal != nil {
|
||||
e.writePLTEAndTRNS(pal)
|
||||
}
|
||||
if len(e.a.Frames) > 1 {
|
||||
e.writeacTL()
|
||||
}
|
||||
if !e.a.Frames[0].IsDefault {
|
||||
e.writefcTL(e.a.Frames[0])
|
||||
}
|
||||
e.writeIDATs()
|
||||
for i := 0; i < len(e.a.Frames); i = i + 1 {
|
||||
if i != 0 && !e.a.Frames[i].IsDefault {
|
||||
e.writefcTL(e.a.Frames[i])
|
||||
e.writefdATs(e.a.Frames[i])
|
||||
}
|
||||
}
|
||||
e.writeIEND()
|
||||
return e.err
|
||||
}
|
Reference in New Issue
Block a user