blob: fcb800344f80a6ff855250c4caaea4969c77d2f2 [file]
// Copyright 2024 The Wuffs Authors.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//
// SPDX-License-Identifier: Apache-2.0 OR MIT
// ----------------
// Package uncompng encodes pixel data as not-compressed PNG-formatted bytes.
// The outputs are valid PNG files but avoids any compression techniques built
// into the PNG format. This obviously results in larger files than what the
// standard library's image/png package produces. Most programmers should use
// the standard package instead.
//
// The main purpose of this alternative PNG-encoding package is its simplicity
// of implementation, including all of its transitive dependencies. Its Go code
// can easily be studied (if you want to learn about the PNG file format),
// tested (the test code can depend on all of the standard library, including
// its image/png package), customized or ported to other languages. Unlike the
// standard package, this package has only two dependencies (the errors.New
// function and the io.Writer interface) and both are trivial.
//
// A secondary purpose is that, when starting with a slice of pixel data,
// producing an uncompressed PNG is faster than producing a compressed one - an
// extreme example of the general trade-off between compression speed and
// compression ratio. Similarly, decoding an uncompressed PNG can also be
// faster, even if the uncompressed PNG is bigger (in terms of byte size). If
// using PNG as a commonly-spoken format between two processes (connected by a
// Unix-like pipe) or between two libraries within the same process, and the
// transient PNG data never hits a disk drive or bandwidth-limited network, it
// may be better (faster) overall to skip any compression and decompression on
// either side of the communication channel.
//
// The Encoder and AnimationEncoder methods also make no further memory
// allocations (above whatever its given io.Writer makes, if any). They are
// lower-level APIs. See the encodeImage function in this package's test code
// for a higher-level API (with more dependencies and without the no-allocation
// guarantee) that takes an image.Image argument.
package uncompng
import (
"errors"
"io"
)
// ColorType states how to interpret the []byte pixel data as colors.
type ColorType byte
const (
// ColorTypeGray means 1 byte per pixel (or 2 for Depth16, big-endian).
//
// This matches Go's image.Gray.Pix (or Gray16, for Depth16) layout.
ColorTypeGray = ColorType(1)
// ColorTypeRGBX means 4 bytes per pixel (or 8 for Depth16, big-endian).
// Red, Green, Blue and the 4th channel is ignored.
//
// This matches Go's image.RGBA.Pix and image.NRGBA.Pix layouts (or RGBA64
// or NRGBA64, for Depth16), provided that all of the pixels' alpha values
// are 0xFF (or 0xFFFF, for Depth16).
ColorTypeRGBX = ColorType(2)
// ColorTypeRGBX means 4 bytes per pixel (or 8 for Depth16, big-endian).
// Red, Green, Blue and Alpha. RGB uses non-premultiplied alpha.
//
// This matches Go's image.NRGBA.Pix layout (or NRGBA64, for Depth16). If
// all of the pixels' alpha values are 0xFF (or 0xFFFF, for Depth16) then
// either ColorTypeRGBX or ColorTypeNRGBA will produce the same PNG output
// (in terms of pixels) but smaller (ColorTypeRGBX) or larger
// (ColorTypeNRGBA) output in terms of byte count.
ColorTypeNRGBA = ColorType(3)
)
func (c ColorType) pngFileFormatEncoding() byte {
// https://www.w3.org/TR/2003/REC-PNG-20031110/#6Colour-values
switch c {
case ColorTypeGray:
return 0
case ColorTypeRGBX:
return 2
case ColorTypeNRGBA:
return 6
}
return 0xFF
}
// Depth is the number of bits per channel.
type Depth byte
const (
// Depth8 means one byte per pixel.
Depth8 = Depth(8)
// Depth16 means two bytes per pixel. Values are big-endian like the Go
// standard library's Gray16, RGBA64 and NRGBA64 image types.
Depth16 = Depth(16)
)
// Encoder is an opaque type that can convert a slice of pixel data to
// PNG-formatted bytes. For APNG (Animated PNG), see AnimationEncoder.
//
// It contains all of the buffers needed for the Encode method. Once the
// Encoder itself is allocated, Encoder.Encode makes no further allocations
// (above whatever its given io.Writer makes, if any).
//
// It can be re-used (but it is not thread-safe). One Encoder can make multiple
// Encode calls, each call producing a complete, stand-alone PNG image.
type Encoder struct {
// buf is the fixed-size buffer to accumulate PNG-formatted output.
//
// A minimal PNG file consists of:
// - An 8-byte magic signature.
// - An IHDR chunk.
// - One or more IDAT chunks.
// - An IEND chunk.
//
// Each chunk consists of:
// - A 4-byte chunk payload length.
// - A 4-byte chunk type.
// - The chunk payload.
// - A 4-byte CRC-32/IEEE checksum of the chunk type and payload.
//
// Concatenating the IDAT payloads produces a ZLIB-compressed data stream.
// The ZLIB-decoded data consists of (((BPP * W) + 1) * H) bytes, where:
// - BPP is the number of bytes per pixel (e.g. 4 for RGBA).
// - W is the image width in pixels.
// - H is the image height in pixels.
//
// The +1 is because every row's pixel data is preceded by one byte for the
// per-row PNG filter. This package hard-codes a zero byte (no filtering).
//
// For a normal PNG encoder, the ZLIB-encoded data is shorter than the
// ZLIB-decoded data (as compression is the whole point of ZLIB). This PNG
// encoder prioritizes simplicity of implementation over compression ratio,
// so the ZLIB-encoded data is actually longer. The ZLIB-formatted stream
// consists of:
// - A 2-byte header.
// - A raw DEFLATE stream.
// - A 4-byte Adler-32 checksum of the ZLIB-decoded data.
//
// A raw DEFLATE stream is just one or more DEFLATE blocks.
//
// The DEFLATE format allows for uncompressed and compressed blocks. This
// encoder only emits uncompressed blocks. Each uncompressed block is:
// - A 1-byte header (0 means non-final, 1 means final).
// - A 2-byte u16le payload length.
// - That 2-byte u16le payload length again, xor'ed with 0xFFFF.
// - The uncompressed block payload (literal bytes).
//
// ----
//
// After that long preamble, here's the data layout of the buf field. Each
// time a slice of buf is passed to an io.Writer, it contains exactly one
// IDAT chunk (or fdAT chunk, for animated PNG). The final time will also
// contain an IEND chunk after the IDAT/fdAT chunk, unless it won't fit, in
// which case there will be one more Write call with just the 12-byte IEND
// chunk (and no IDAT/fdAT chunk).
//
// When Encode'ing still PNGs, for all but the first Write, the 4-byte
// IDAT/fdAT chunk length starts at buf[0x0000:]. For the first Write, it
// will start at buf[0x0021:], since it is preceded by the magic signature
// and the IHDR chunk.
//
// Either way, each IDAT/fdAT chunk contains exactly one DEFLATE block (an
// uncompressed block). The very first IDAT (in the very first Write) also
// contains the 2-byte ZLIB header. The very last IDAT also contains the
// 4-byte Adler-32 checksum.
//
// For example, here's how buf might be filled (four times) and shipped out
// to an io.Writer, when calling Encode to produce a still image.
//
// +--------------------------------------+
// | MAGIC | IHDR | IDAT | Encode
// +--------------------------------------+ :
// | IDAT | :
// +--------------------------------------+ :
// | IDAT | :
// +--------------------------------------+ :
// | IDAT | IEND | unused | :
// +--------------------------------------+
//
// The buf-packing is more complicated for an animated PNG. Reference (ยง).
//
// +--------------------------------------+
// | MAGIC | IHDR | acTL | unused | EncodeHeader
// +--------------------------------------+
// | fcTL | IDAT | EncodeFrame
// +--------------------------------------+ :
// | IDAT | :
// +--------------------------------------+ :
// | IDAT | unused | :
// +--------------------------------------+
// | fcTL | fdAT | EncodeFrame
// +--------------------------------------+ :
// | fdAT | :
// +--------------------------------------+ :
// | fdAT | unused | :
// +--------------------------------------+
// | fcTL | fdAT | EncodeFrame
// +--------------------------------------+ :
// | fdAT | :
// +--------------------------------------+ :
// | fdAT | IEND | unused | :
// +--------------------------------------+
//
// Each DEFLATE uncompressed block's payload spans e.buf[ei:ej], for two
// indexes ei and ej, either explicit local variables or implicit state.
// Writing pixel data to the Encoder increments ej, flushing to the
// io.Writer beforehand (which resets ej = ei) if the data won't fit. ei is
// 0x0030 before the first Write call and at 0x000D afterwards.
//
// "The data won't fit" if ej would end up greater than ejMax, where ejMax
// is the size of the buffer (0x10000) minus 8 bytes that leaves enough
// room for two trailing 4-byte checksums (Adler-32 and CRC-32/IEEE).
//
// The last 4 bytes are also repurposed to hold the in-progress Adler-32
// checksum state, as these 4 bytes won't be overwritten unless emitting
// the final IDAT chunk (containing the final DEFLATE block).
buf [65536]byte
// For still PNG images, there's 1 Encode call, which leads to:
//
// - 1 encodeImageHeader call. This sets buf to hold the magic signature,
// IHDR chunk and the start of the first IDAT chunk, but does not write
// to the io.Writer yet.
// - 1 encodeFramePayload call, with frameTypeStill. This fills in the
// rest of the first IDAT, and all of any remaining IDATs, and the final
// IEND, writing to the io.Writer one or more times (on every flush
// call, and possibly once more for the IEND chunk). Each flush is
// passed frameTypeStill.
//
// For animated PNG images, there's 1 EncodeHeader call, which leads to...
//
// - 1 encodeImageHeader call. This sets buf to hold the magic signature,
// IHDR chunk and acTL chunk, but not any part of any IDAT chunks. It
// does, however, write to the io.Writer.
//
// ...and then N EncodeFrame calls, N = numFrames, each of which leads to:
//
// - 1 encodeFrameHeader call. This sets buf to hold the fcTL chunk and
// the start of an IDAT/fdAT chunk, but does not write to the io.Writer.
// - 1 encodeFramePayload call. This fills in the rest of the first
// IDAT/fdAT, and all of any remaining IDATs/fdATs for that frame (and
// the IEND if the last frame), writing to the io.Writer via flush,
// similar to the still image case.
//
// For animated PNG images, the frameType argument, for encodeFramePayload
// and flush, will have the frameTypeBitAnimated bit on. Of the N frames,
// the frameTypeBitFirstFrame and frameTypeBitLastFrame bits are on for the
// first and last frames. If N == 1, both bits are set for the only frame.
}
type frameType byte
const (
frameTypeBitFirstFrame = frameType(1)
frameTypeBitLastFrame = frameType(2)
frameTypeBitAnimated = frameType(4)
frameTypeStill = frameTypeBitFirstFrame | frameTypeBitLastFrame
)
// Encode writes the pixel data to w. It makes no allocations above whatever
// w.Write makes, if any.
//
// pix holds the pixel data, either 1 or 4 bytes per pixel (doubled for
// Depth16) depending on the colorType. width and height are measured in
// pixels. stride is measured in bytes. depth must be either Depth8 or Depth16.
func (e *Encoder) Encode(
w io.Writer,
depth Depth,
colorType ColorType,
width int,
height int,
pix []byte,
stride int) error {
if err := checkArguments(depth, colorType, width, height, 1, 0); err != nil {
return err
}
ej := e.encodeImageHeader(depth, colorType, width, height, 0, 0)
return e.encodeFramePayload(
w, frameTypeStill, nil, depth, colorType, width, height, pix, stride, ej)
}
func (e *Encoder) encodeFramePayload(
w io.Writer,
fType frameType,
seqNumPtr *uint32,
depth Depth,
colorType ColorType,
width int,
height int,
pix []byte,
stride int,
ej int) (err error) {
// AdlerB=0, Adler32A=1.
e.buf[0xFFFC] = 0
e.buf[0xFFFD] = 0
e.buf[0xFFFE] = 0
e.buf[0xFFFF] = 1
const ejMax = 0xFFF8
for y := 0; y < height; y++ {
if (ej + 1) > ejMax {
ej, err = e.flush(w, fType, seqNumPtr, ej, false)
if err != nil {
return err
}
}
e.buf[ej+0] = 0 // PNG 'none' filter.
ej += 1
row := pix[y*stride:]
switch ColorType(depth) | colorType {
case 0x08 | ColorTypeGray:
row = row[:1*width]
for x := 0; x < width; x++ {
if (ej + 1) > ejMax {
ej, err = e.flush(w, fType, seqNumPtr, ej, false)
if err != nil {
return err
}
}
e.buf[ej+0] = row[0]
ej += 1
row = row[1:]
}
case 0x08 | ColorTypeRGBX:
row = row[:4*width]
for x := 0; x < width; x++ {
if (ej + 3) > ejMax {
ej, err = e.flush(w, fType, seqNumPtr, ej, false)
if err != nil {
return err
}
}
e.buf[ej+0] = row[0]
e.buf[ej+1] = row[1]
e.buf[ej+2] = row[2]
ej += 3
row = row[4:]
}
case 0x08 | ColorTypeNRGBA:
row = row[:4*width]
for x := 0; x < width; x++ {
if (ej + 4) > ejMax {
ej, err = e.flush(w, fType, seqNumPtr, ej, false)
if err != nil {
return err
}
}
e.buf[ej+0] = row[0]
e.buf[ej+1] = row[1]
e.buf[ej+2] = row[2]
e.buf[ej+3] = row[3]
ej += 4
row = row[4:]
}
case 0x10 | ColorTypeGray:
row = row[:2*width]
for x := 0; x < width; x++ {
if (ej + 2) > ejMax {
ej, err = e.flush(w, fType, seqNumPtr, ej, false)
if err != nil {
return err
}
}
e.buf[ej+0] = row[0]
e.buf[ej+1] = row[1]
ej += 2
row = row[2:]
}
case 0x10 | ColorTypeRGBX:
row = row[:8*width]
for x := 0; x < width; x++ {
if (ej + 6) > ejMax {
ej, err = e.flush(w, fType, seqNumPtr, ej, false)
if err != nil {
return err
}
}
e.buf[ej+0] = row[0]
e.buf[ej+1] = row[1]
e.buf[ej+2] = row[2]
e.buf[ej+3] = row[3]
e.buf[ej+4] = row[4]
e.buf[ej+5] = row[5]
ej += 6
row = row[8:]
}
case 0x10 | ColorTypeNRGBA:
row = row[:8*width]
for x := 0; x < width; x++ {
if (ej + 8) > ejMax {
ej, err = e.flush(w, fType, seqNumPtr, ej, false)
if err != nil {
return err
}
}
e.buf[ej+0] = row[0]
e.buf[ej+1] = row[1]
e.buf[ej+2] = row[2]
e.buf[ej+3] = row[3]
e.buf[ej+4] = row[4]
e.buf[ej+5] = row[5]
e.buf[ej+6] = row[6]
e.buf[ej+7] = row[7]
ej += 8
row = row[8:]
}
}
}
_, err = e.flush(w, fType, seqNumPtr, ej, true)
return err
}
func (e *Encoder) encodeImageHeader(
depth Depth,
colorType ColorType,
width int,
height int,
numFrames uint32,
numPlays uint32) int {
// PNG magic signature.
e.buf[0x0000] = 0x89
e.buf[0x0001] = 'P'
e.buf[0x0002] = 'N'
e.buf[0x0003] = 'G'
e.buf[0x0004] = 0x0D
e.buf[0x0005] = 0x0A
e.buf[0x0006] = 0x1A
e.buf[0x0007] = 0x0A
// IHDR chunk length.
e.buf[0x0008] = 0
e.buf[0x0009] = 0
e.buf[0x000A] = 0
e.buf[0x000B] = 0x0D
// IHDR chunk type.
e.buf[0x000C] = 'I'
e.buf[0x000D] = 'H'
e.buf[0x000E] = 'D'
e.buf[0x000F] = 'R'
// IHDR chunk payload.
e.buf[0x0010] = byte(width >> 24)
e.buf[0x0011] = byte(width >> 16)
e.buf[0x0012] = byte(width >> 8)
e.buf[0x0013] = byte(width >> 0)
e.buf[0x0014] = byte(height >> 24)
e.buf[0x0015] = byte(height >> 16)
e.buf[0x0016] = byte(height >> 8)
e.buf[0x0017] = byte(height >> 0)
e.buf[0x0018] = byte(depth)
e.buf[0x0019] = colorType.pngFileFormatEncoding()
e.buf[0x001A] = 0 // Compression method.
e.buf[0x001B] = 0 // Filter method.
e.buf[0x001C] = 0 // Interlace method.
// IHDR CRC-32/IEEE checksum.
ihdrCRC32 := crc32IEEE(e.buf[0x000C:0x001D])
e.buf[0x001D] = byte(ihdrCRC32 >> 24)
e.buf[0x001E] = byte(ihdrCRC32 >> 16)
e.buf[0x001F] = byte(ihdrCRC32 >> 8)
e.buf[0x0020] = byte(ihdrCRC32 >> 0)
if numFrames > 0 {
// acTL chunk length.
e.buf[0x0021] = 0
e.buf[0x0022] = 0
e.buf[0x0023] = 0
e.buf[0x0024] = 0x08
// acTL chunk type.
e.buf[0x0025] = 'a'
e.buf[0x0026] = 'c'
e.buf[0x0027] = 'T'
e.buf[0x0028] = 'L'
// acTL chunk payload.
e.buf[0x0029] = byte(numFrames >> 24)
e.buf[0x002A] = byte(numFrames >> 16)
e.buf[0x002B] = byte(numFrames >> 8)
e.buf[0x002C] = byte(numFrames >> 0)
e.buf[0x002D] = byte(numPlays >> 24)
e.buf[0x002E] = byte(numPlays >> 16)
e.buf[0x002F] = byte(numPlays >> 8)
e.buf[0x0030] = byte(numPlays >> 0)
// acTL CRC-32/IEEE checksum.
actlCRC32 := crc32IEEE(e.buf[0x0025:0x0031])
e.buf[0x0031] = byte(actlCRC32 >> 24)
e.buf[0x0032] = byte(actlCRC32 >> 16)
e.buf[0x0033] = byte(actlCRC32 >> 8)
e.buf[0x0034] = byte(actlCRC32 >> 0)
return 0x0035
}
// IDAT chunk length placeholder. idatChunkStart is 0x0021.
e.buf[0x0021] = 0
e.buf[0x0022] = 0
e.buf[0x0023] = 0
e.buf[0x0024] = 0
// IDAT chunk type.
e.buf[0x0025] = 'I'
e.buf[0x0026] = 'D'
e.buf[0x0027] = 'A'
e.buf[0x0028] = 'T'
// ZLIB header: CM=8, CINFO=7, FDICT=0, FLEVEL=0. See RFC 1950.
e.buf[0x0029] = 0x78
e.buf[0x002A] = 0x01
// DEFLATE uncompressed block header, with 4-byte placeholder for length.
e.buf[0x002B] = 0
e.buf[0x002C] = 0
e.buf[0x002D] = 0
e.buf[0x002E] = 0
e.buf[0x002F] = 0
return 0x0030
}
func (e *Encoder) updateAdler32(
ei int,
ej int) {
b := (uint32(e.buf[0xFFFC]) << 8) | uint32(e.buf[0xFFFD])
a := (uint32(e.buf[0xFFFE]) << 8) | uint32(e.buf[0xFFFF])
for ei < ej {
end := ei + 5552
if end > ej {
end = ej
}
for ; ei < end; ei++ {
a += uint32(e.buf[ei])
b += a
}
a %= 65521
b %= 65521
}
e.buf[0xFFFC] = byte(b >> 8)
e.buf[0xFFFD] = byte(b >> 0)
e.buf[0xFFFE] = byte(a >> 8)
e.buf[0xFFFF] = byte(a >> 0)
}
func (e *Encoder) flush(
w io.Writer,
fType frameType,
seqNumPtr *uint32,
ej int,
finalIDATOfFrame bool) (retEj int, retErr error) {
// Calculate where the IDAT/fdAT chunk starts, in e.buf. See (ยง).
idatChunkStart := 0
if e.buf[0x0007] == 0x0A {
// 0x0A is magicSignature[7]. First IDAT chunk of a still image.
idatChunkStart = 0x0021
} else if e.buf[0x0007] == 0x4C {
// 0x4C is "????fcTL"[7]. First IDAT/fdAT chunk of an animated frame.
idatChunkStart = 0x0026
} else if e.buf[0x0007] == 0x54 {
// 0x54 is "??????AT"[7]. Later IDAT/fdAT chunk.
idatChunkStart = 0x0000
} else {
return 0, errors.New("uncompng: unreachable")
}
// 13 is the 0x000D in "Write... 0x000D bytes total" below.
ei := idatChunkStart + 13
if e.buf[0x0007] != 0x54 {
// 2 more bytes for the ZLIB header (CM, CINFO, FDICT, FLEVEL).
ei += 2
}
if (fType & frameTypeBitFirstFrame) == 0 {
// fdAT chunks are longer than IDAT chunks by 4 bytes.
ei += 4
}
// Update the IDAT/fdAT chunk length placeholder.
idatChunkLen := ej - (idatChunkStart + 8)
if finalIDATOfFrame {
idatChunkLen += 4 // The Adler-32 checksum occupies 4 bytes.
}
e.buf[idatChunkStart+0] = byte(idatChunkLen >> 24)
e.buf[idatChunkStart+1] = byte(idatChunkLen >> 16)
e.buf[idatChunkStart+2] = byte(idatChunkLen >> 8)
e.buf[idatChunkStart+3] = byte(idatChunkLen >> 0)
// Update the DEFLATE uncompressed block header placeholder.
deflateBlockLen := ej - ei
e.buf[ei-5] = btou8(finalIDATOfFrame)
e.buf[ei-4] = 0x00 ^ byte(deflateBlockLen>>0)
e.buf[ei-3] = 0x00 ^ byte(deflateBlockLen>>8)
e.buf[ei-2] = 0xFF ^ byte(deflateBlockLen>>0)
e.buf[ei-1] = 0xFF ^ byte(deflateBlockLen>>8)
// Update (and maybe write) the Adler-32 checksum.
e.updateAdler32(ei, ej)
if finalIDATOfFrame {
e.buf[ej+0] = e.buf[0xFFFC]
e.buf[ej+1] = e.buf[0xFFFD]
e.buf[ej+2] = e.buf[0xFFFE]
e.buf[ej+3] = e.buf[0xFFFF]
ej += 4
}
// Write the CRC-32/IEEE checksum.
idatCRC32 := crc32IEEE(e.buf[idatChunkStart+4 : ej])
e.buf[ej+0] = byte(idatCRC32 >> 24)
e.buf[ej+1] = byte(idatCRC32 >> 16)
e.buf[ej+2] = byte(idatCRC32 >> 8)
e.buf[ej+3] = byte(idatCRC32 >> 0)
ej += 4
if !finalIDATOfFrame {
if _, err := w.Write(e.buf[:ej]); err != nil {
return 0, err
}
// Write (or reserve space for) 0x000D bytes total:
// - 4 bytes for the IDAT/fdAT chunk length placeholder.
// - 4 bytes for the IDAT/fdAT chunk type.
// - 5 bytes for the DEFLATE uncompressed block header placeholder.
t := fType & frameTypeBitFirstFrame
e.buf[0x0004] = "fI"[t]
e.buf[0x0005] = "dD"[t]
e.buf[0x0006] = 'A'
e.buf[0x0007] = 'T'
if (t != 0) || (seqNumPtr == nil) {
return 0x000D + 0, nil
}
seqNum := *seqNumPtr
*seqNumPtr++
e.buf[0x0008] = byte(seqNum >> 24)
e.buf[0x0009] = byte(seqNum >> 16)
e.buf[0x000A] = byte(seqNum >> 8)
e.buf[0x000B] = byte(seqNum >> 0)
return 0x000D + 4, nil
}
if (fType & frameTypeBitLastFrame) == 0 {
if _, err := w.Write(e.buf[:ej]); err != nil {
return 0, err
}
return 0, nil
}
const iendChunk = "\x00\x00\x00\x00IEND\xAE\x42\x60\x82"
writeSeparateIENDChunk := (ej + len(iendChunk)) > len(e.buf)
if !writeSeparateIENDChunk {
copy(e.buf[ej:ej+len(iendChunk)], iendChunk)
ej += len(iendChunk)
}
if _, err := w.Write(e.buf[:ej]); err != nil {
return 0, err
}
if writeSeparateIENDChunk {
copy(e.buf[:], iendChunk)
if _, err := w.Write(e.buf[:len(iendChunk)]); err != nil {
return 0, err
}
}
return 0, nil
}
// AnimationEncoder is like Encoder but for APNG (Animated PNG), not still PNG.
//
// It contains all of the buffers needed for the EncodeXxx methods. Once the
// AnimationEncoder itself is allocated, AnimationEncoder.EncodeXxx makes no
// further allocations (above whatever its given io.Writer makes, if any).
//
// To create one APNG, call EncodeHeader once (with a given numFrames > 0
// argument) and then EncodeFrame numFrames times.
//
// It can be re-used (but it is not thread-safe). One AnimationEncoder can make
// multiple (1 EncodeHeader + N EncodeFrame) call sequences, potentially with
// different N values, each producing a complete, stand-alone APNG image.
type AnimationEncoder struct {
enc Encoder
depth Depth
colorType ColorType
width uint32
height uint32
seqNum uint32
numFrames uint32
curFrame uint32
}
// EncodeHeader writes the APNG header to w. It makes no allocations above
// whatever w.Write makes, if any.
//
// The arguments are similar to Encoder.Encode, as well as numFrames > 0 being
// the number of animation frames and numPlays being the number of times each
// animation frame is played (zero means to loop forever).
func (e *AnimationEncoder) EncodeHeader(
w io.Writer,
depth Depth,
colorType ColorType,
width int,
height int,
numFrames uint32,
numPlays uint32) error {
if err := checkArguments(depth, colorType, width, height, numFrames, numPlays); err != nil {
return err
}
e.depth = depth
e.colorType = colorType
e.width = uint32(width)
e.height = uint32(height)
e.seqNum = 0
e.numFrames = numFrames
e.curFrame = 0
ej := e.enc.encodeImageHeader(depth, colorType, width, height, numFrames, numPlays)
_, err := w.Write(e.enc.buf[:ej])
return err
}
// EncodeFrame writes an animation frame to w. It makes no allocations above
// whatever w.Write makes, if any.
//
// The arguments are similar to Encoder.Encode, as well as (delayNumerator /
// delayDenominator) being how many seconds to display the frame. For example,
// if delayDenominator == 1000, delayNumerator is measured in milliseconds.
func (e *AnimationEncoder) EncodeFrame(
w io.Writer,
pix []byte,
stride int,
delayNumerator uint16,
delayDenominator uint16) error {
if e.numFrames == 0 {
return errors.New("uncompng: EncodeHeader was not called")
} else if e.curFrame >= e.numFrames {
return errors.New("uncompng: too many EncodeFrame calls")
} else if e.seqNum >= 0x80000000 {
return errors.New("uncompng: too many frames")
}
fType := frameTypeBitAnimated
if e.curFrame == 0 {
fType |= frameTypeBitFirstFrame
}
e.curFrame++
if e.curFrame == e.numFrames {
fType |= frameTypeBitLastFrame
}
ej := e.encodeFrameHeader(w, delayNumerator, delayDenominator)
return e.enc.encodeFramePayload(
w, fType, &e.seqNum, e.depth, e.colorType, int(e.width), int(e.height), pix, stride, ej)
}
func (e *AnimationEncoder) encodeFrameHeader(
w io.Writer,
delayNumerator uint16,
delayDenominator uint16) int {
// fcTL chunk length.
e.enc.buf[0x0000] = 0
e.enc.buf[0x0001] = 0
e.enc.buf[0x0002] = 0
e.enc.buf[0x0003] = 0x1A
// fcTL chunk type.
e.enc.buf[0x0004] = 'f'
e.enc.buf[0x0005] = 'c'
e.enc.buf[0x0006] = 'T'
e.enc.buf[0x0007] = 'L'
// fcTL payload.
e.enc.buf[0x0008] = byte(e.seqNum >> 24)
e.enc.buf[0x0009] = byte(e.seqNum >> 16)
e.enc.buf[0x000A] = byte(e.seqNum >> 8)
e.enc.buf[0x000B] = byte(e.seqNum >> 0)
e.enc.buf[0x000C] = byte(e.width >> 24)
e.enc.buf[0x000D] = byte(e.width >> 16)
e.enc.buf[0x000E] = byte(e.width >> 8)
e.enc.buf[0x000F] = byte(e.width >> 0)
e.enc.buf[0x0010] = byte(e.height >> 24)
e.enc.buf[0x0011] = byte(e.height >> 16)
e.enc.buf[0x0012] = byte(e.height >> 8)
e.enc.buf[0x0013] = byte(e.height >> 0)
e.enc.buf[0x0014] = 0 // X offset.
e.enc.buf[0x0015] = 0
e.enc.buf[0x0016] = 0
e.enc.buf[0x0017] = 0
e.enc.buf[0x0018] = 0 // Y offset.
e.enc.buf[0x0019] = 0
e.enc.buf[0x001A] = 0
e.enc.buf[0x001B] = 0
e.enc.buf[0x001C] = byte(delayNumerator >> 8)
e.enc.buf[0x001D] = byte(delayNumerator >> 0)
e.enc.buf[0x001E] = byte(delayDenominator >> 8)
e.enc.buf[0x001F] = byte(delayDenominator >> 0)
e.enc.buf[0x0020] = 0 // APNG_DISPOSE_OP_NONE.
e.enc.buf[0x0021] = 0 // APNG_BLEND_OP_SOURCE.
// fcTL CRC-32/IEEE checksum.
fctlCRC32 := crc32IEEE(e.enc.buf[0x0004:0x0022])
e.enc.buf[0x0022] = byte(fctlCRC32 >> 24)
e.enc.buf[0x0023] = byte(fctlCRC32 >> 16)
e.enc.buf[0x0024] = byte(fctlCRC32 >> 8)
e.enc.buf[0x0025] = byte(fctlCRC32 >> 0)
useIDAT := 0
if e.seqNum == 0 {
useIDAT = 1
}
e.seqNum++
// IDAT/fdAT chunk length placeholder. idatChunkStart is 0x0026.
e.enc.buf[0x0026] = 0
e.enc.buf[0x0027] = 0
e.enc.buf[0x0028] = 0
e.enc.buf[0x0029] = 0
// IDAT/fdAT chunk type.
e.enc.buf[0x002A] = "fI"[useIDAT]
e.enc.buf[0x002B] = "dD"[useIDAT]
e.enc.buf[0x002C] = 'A'
e.enc.buf[0x002D] = 'T'
ej := 0x002E
if useIDAT == 0 {
e.enc.buf[0x002E] = byte(e.seqNum >> 24)
e.enc.buf[0x002F] = byte(e.seqNum >> 16)
e.enc.buf[0x0030] = byte(e.seqNum >> 8)
e.enc.buf[0x0031] = byte(e.seqNum >> 0)
e.seqNum++
ej = 0x0032
}
// ZLIB header: CM=8, CINFO=7, FDICT=0, FLEVEL=0. See RFC 1950.
e.enc.buf[ej+0x00] = 0x78
e.enc.buf[ej+0x01] = 0x01
// DEFLATE uncompressed block header, with 4-byte placeholder for length.
e.enc.buf[ej+0x02] = 0
e.enc.buf[ej+0x03] = 0
e.enc.buf[ej+0x04] = 0
e.enc.buf[ej+0x05] = 0
e.enc.buf[ej+0x06] = 0
return ej + 0x07
}
func checkArguments(
depth Depth,
colorType ColorType,
width int,
height int,
numFrames uint32,
numPlays uint32) error {
if ((depth != Depth8) && (depth != Depth16)) ||
(colorType.pngFileFormatEncoding() == 0xFF) ||
(width <= 0) || (height <= 0) || (numFrames <= 0) {
return errors.New("uncompng: invalid argument")
} else if (width > 0xFFFFFF) || (height > 0xFFFFFF) {
return errors.New("uncompng: unsupported image size")
}
return nil
}
func btou8(
a bool) uint8 {
if a {
return 1
}
return 0
}
func crc32IEEE(
b []byte) uint32 {
hash := uint32(0xFFFF_FFFF)
for _, v := range b {
hash = crc32IEEETable[uint8(hash)^v] ^ (hash >> 8)
}
return hash ^ uint32(0xFFFF_FFFF)
}
var crc32IEEETable = [256]uint32{
0x0000_0000, 0x7707_3096, 0xEE0E_612C, 0x9909_51BA, 0x076D_C419, 0x706A_F48F, 0xE963_A535, 0x9E64_95A3,
0x0EDB_8832, 0x79DC_B8A4, 0xE0D5_E91E, 0x97D2_D988, 0x09B6_4C2B, 0x7EB1_7CBD, 0xE7B8_2D07, 0x90BF_1D91,
0x1DB7_1064, 0x6AB0_20F2, 0xF3B9_7148, 0x84BE_41DE, 0x1ADA_D47D, 0x6DDD_E4EB, 0xF4D4_B551, 0x83D3_85C7,
0x136C_9856, 0x646B_A8C0, 0xFD62_F97A, 0x8A65_C9EC, 0x1401_5C4F, 0x6306_6CD9, 0xFA0F_3D63, 0x8D08_0DF5,
0x3B6E_20C8, 0x4C69_105E, 0xD560_41E4, 0xA267_7172, 0x3C03_E4D1, 0x4B04_D447, 0xD20D_85FD, 0xA50A_B56B,
0x35B5_A8FA, 0x42B2_986C, 0xDBBB_C9D6, 0xACBC_F940, 0x32D8_6CE3, 0x45DF_5C75, 0xDCD6_0DCF, 0xABD1_3D59,
0x26D9_30AC, 0x51DE_003A, 0xC8D7_5180, 0xBFD0_6116, 0x21B4_F4B5, 0x56B3_C423, 0xCFBA_9599, 0xB8BD_A50F,
0x2802_B89E, 0x5F05_8808, 0xC60C_D9B2, 0xB10B_E924, 0x2F6F_7C87, 0x5868_4C11, 0xC161_1DAB, 0xB666_2D3D,
0x76DC_4190, 0x01DB_7106, 0x98D2_20BC, 0xEFD5_102A, 0x71B1_8589, 0x06B6_B51F, 0x9FBF_E4A5, 0xE8B8_D433,
0x7807_C9A2, 0x0F00_F934, 0x9609_A88E, 0xE10E_9818, 0x7F6A_0DBB, 0x086D_3D2D, 0x9164_6C97, 0xE663_5C01,
0x6B6B_51F4, 0x1C6C_6162, 0x8565_30D8, 0xF262_004E, 0x6C06_95ED, 0x1B01_A57B, 0x8208_F4C1, 0xF50F_C457,
0x65B0_D9C6, 0x12B7_E950, 0x8BBE_B8EA, 0xFCB9_887C, 0x62DD_1DDF, 0x15DA_2D49, 0x8CD3_7CF3, 0xFBD4_4C65,
0x4DB2_6158, 0x3AB5_51CE, 0xA3BC_0074, 0xD4BB_30E2, 0x4ADF_A541, 0x3DD8_95D7, 0xA4D1_C46D, 0xD3D6_F4FB,
0x4369_E96A, 0x346E_D9FC, 0xAD67_8846, 0xDA60_B8D0, 0x4404_2D73, 0x3303_1DE5, 0xAA0A_4C5F, 0xDD0D_7CC9,
0x5005_713C, 0x2702_41AA, 0xBE0B_1010, 0xC90C_2086, 0x5768_B525, 0x206F_85B3, 0xB966_D409, 0xCE61_E49F,
0x5EDE_F90E, 0x29D9_C998, 0xB0D0_9822, 0xC7D7_A8B4, 0x59B3_3D17, 0x2EB4_0D81, 0xB7BD_5C3B, 0xC0BA_6CAD,
0xEDB8_8320, 0x9ABF_B3B6, 0x03B6_E20C, 0x74B1_D29A, 0xEAD5_4739, 0x9DD2_77AF, 0x04DB_2615, 0x73DC_1683,
0xE363_0B12, 0x9464_3B84, 0x0D6D_6A3E, 0x7A6A_5AA8, 0xE40E_CF0B, 0x9309_FF9D, 0x0A00_AE27, 0x7D07_9EB1,
0xF00F_9344, 0x8708_A3D2, 0x1E01_F268, 0x6906_C2FE, 0xF762_575D, 0x8065_67CB, 0x196C_3671, 0x6E6B_06E7,
0xFED4_1B76, 0x89D3_2BE0, 0x10DA_7A5A, 0x67DD_4ACC, 0xF9B9_DF6F, 0x8EBE_EFF9, 0x17B7_BE43, 0x60B0_8ED5,
0xD6D6_A3E8, 0xA1D1_937E, 0x38D8_C2C4, 0x4FDF_F252, 0xD1BB_67F1, 0xA6BC_5767, 0x3FB5_06DD, 0x48B2_364B,
0xD80D_2BDA, 0xAF0A_1B4C, 0x3603_4AF6, 0x4104_7A60, 0xDF60_EFC3, 0xA867_DF55, 0x316E_8EEF, 0x4669_BE79,
0xCB61_B38C, 0xBC66_831A, 0x256F_D2A0, 0x5268_E236, 0xCC0C_7795, 0xBB0B_4703, 0x2202_16B9, 0x5505_262F,
0xC5BA_3BBE, 0xB2BD_0B28, 0x2BB4_5A92, 0x5CB3_6A04, 0xC2D7_FFA7, 0xB5D0_CF31, 0x2CD9_9E8B, 0x5BDE_AE1D,
0x9B64_C2B0, 0xEC63_F226, 0x756A_A39C, 0x026D_930A, 0x9C09_06A9, 0xEB0E_363F, 0x7207_6785, 0x0500_5713,
0x95BF_4A82, 0xE2B8_7A14, 0x7BB1_2BAE, 0x0CB6_1B38, 0x92D2_8E9B, 0xE5D5_BE0D, 0x7CDC_EFB7, 0x0BDB_DF21,
0x86D3_D2D4, 0xF1D4_E242, 0x68DD_B3F8, 0x1FDA_836E, 0x81BE_16CD, 0xF6B9_265B, 0x6FB0_77E1, 0x18B7_4777,
0x8808_5AE6, 0xFF0F_6A70, 0x6606_3BCA, 0x1101_0B5C, 0x8F65_9EFF, 0xF862_AE69, 0x616B_FFD3, 0x166C_CF45,
0xA00A_E278, 0xD70D_D2EE, 0x4E04_8354, 0x3903_B3C2, 0xA767_2661, 0xD060_16F7, 0x4969_474D, 0x3E6E_77DB,
0xAED1_6A4A, 0xD9D6_5ADC, 0x40DF_0B66, 0x37D8_3BF0, 0xA9BC_AE53, 0xDEBB_9EC5, 0x47B2_CF7F, 0x30B5_FFE9,
0xBDBD_F21C, 0xCABA_C28A, 0x53B3_9330, 0x24B4_A3A6, 0xBAD0_3605, 0xCDD7_0693, 0x54DE_5729, 0x23D9_67BF,
0xB366_7A2E, 0xC461_4AB8, 0x5D68_1B02, 0x2A6F_2B94, 0xB40B_BE37, 0xC30C_8EA1, 0x5A05_DF1B, 0x2D02_EF8D,
}