Skip to content

xogas/apng

Repository files navigation

apng

A zero-dependency Go APNG (Animated PNG) encoder and decoder --- operates directly on zlib + PNG filter scanlines, avoiding the image/png round-trip.

Features

  • Zero dependencies --- standard library only, no third-party packages
  • Full codec --- supports BlendOp (Source / Over), DisposeOp (None / Background / Previous)
  • Temporal diffing --- only the changed bounding box between frames is encoded
  • High performance --- direct zlib + PNG filter pipeline, no png.Encode / png.Decode overhead
  • All color types --- decoder handles PNG color types 0–6, all bit depths, palette (PLTE), and 16-bit down-scaling
  • Auto canvas --- zero Width / Height triggers auto-calculation from frame bounds
  • Round-trip fidelity --- Encode then Decode preserves the original frame fragment semantics

Installation

go get github.com/xogas/apng

Usage

Decode

package main

import (
    "fmt"
    "os"

    "github.com/xogas/apng"
)

func main() {
    f, _ := os.Open("input.png")
    defer f.Close()

    a, err := apng.Decode(f)
    if err != nil {
        panic(err)
    }

    fmt.Printf("%d frames, canvas %d×%d, loops %d\n",
        len(a.Frames), a.Width, a.Height, a.LoopCount)

    for i, frame := range a.Frames {
        fmt.Printf("frame %d: %v, delay %v, dispose=%d blend=%d\n",
            i, frame.Bounds(), frame.Delay(),
            frame.DisposeOp, frame.BlendOp)
    }
}

Encode

package main

import (
    "image"
    "image/color"
    "os"

    "github.com/xogas/apng"
)

func main() {
    red := image.NewRGBA(image.Rect(0, 0, 32, 32))
    for y := 0; y < 32; y++ {
        for x := 0; x < 32; x++ {
            red.Set(x, y, color.RGBA{R: 255, A: 255})
        }
    }

    green := image.NewRGBA(image.Rect(0, 0, 16, 16))
    for y := 0; y < 16; y++ {
        for x := 0; x < 16; x++ {
            green.Set(x, y, color.RGBA{G: 255, A: 255})
        }
    }

    a := &apng.APNG{
        Width:     32,
        Height:    32,
        LoopCount: 0, // loop forever
        Frames: []apng.Frame{
            {Image: red, BlendOp: apng.BlendOpSource},
            {Image: green, XOffset: 8, YOffset: 8, BlendOp: apng.BlendOpOver},
        },
    }

    f, _ := os.Create("output.png")
    defer f.Close()

    if err := apng.Encode(a, f); err != nil {
        panic(err)
    }
}

Composite canvases

canvases := a.CompositeFrames()
for i, c := range canvases {
    fmt.Printf("frame %d full canvas: %v\n", i, c.Bounds())
    // c is an independent *image.RGBA --- modifying it does not affect a
}

Types

APNG

Field Type Description
Width uint32 Canvas width; auto-computed when zero (encode only)
Height uint32 Canvas height; auto-computed when zero (encode only)
Frames []Frame Animation frames in display order (at least one required)
LoopCount uint32 Number of loops; 0 = loop forever
Background image.Image Optional static fallback for non-APNG viewers

Frame

Field Type Description
Image image.Image Raw pixel fragment (not the composited full canvas)
XOffset int Left offset of the frame on the canvas
YOffset int Top offset of the frame on the canvas
DelayNum uint16 Frame delay numerator
DelayDen uint16 Frame delay denominator (seconds); 0 is treated as 100
DisposeOp DisposeOp Canvas disposal after this frame is displayed
BlendOp BlendOp How the frame is composited onto the canvas

DisposeOp

Constant Value Description
DisposeOpNone 0 Leave the canvas as-is (default)
DisposeOpBackground 1 Clear the frame region to transparent black
DisposeOpPrevious 2 Restore the frame region to its pre-render state

BlendOp

Constant Value Description
BlendOpSource 0 Replace canvas pixels directly (default)
BlendOpOver 1 Alpha-blend onto the canvas

Methods

func (f *Frame) Bounds() image.Rectangle          // frame region in canvas coordinates
func (f *Frame) Delay() time.Duration             // display duration of this frame
func (a *APNG) CompositeFrames() []*image.RGBA    // full canvas per frame after compositing

Sentinel errors

// Format / I/O errors
var (
    ErrInvalidSignature // invalid PNG signature
    ErrNotAPNG          // missing acTL chunk (not an APNG file)
    ErrCRCMismatch      // CRC-32 checksum mismatch
    ErrInvalidChunk     // malformed chunk
)

// Input validation errors
var (
    ErrNoFrames       // no frames to encode
    ErrNilImage       // frame has nil Image
    ErrNegativeOffset // frame has negative offset
    ErrZeroCanvas     // canvas size is zero
    ErrZeroImage      // image has zero dimensions
)

Use errors.Is(err, apng.ErrNotAPNG) to test error types.

How it works

The encoder converts images to RGBA, picks the best PNG filter per scanline (None / Sub / Up / Average / Paeth), and feeds the result through compress/zlib before wrapping in IDAT / fdAT chunks. The decoder reverses this: zlib decompress → unfilter → color-space conversion to *image.RGBA. No synthetic PNG scaffolding, no image/png round-trip.

License

The MIT License (MIT) --- see license

About

APNG decoder and encoder library for Go

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages