apng

package module
v0.0.0-...-ff69277 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 23, 2022 License: BSD-3-Clause Imports: 10 Imported by: 31

README

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 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 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 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
package main

import (
  "log"
  "os"
  "github.com/kettek/apng"
)

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
package main

import (
  "image/png"
  "os"
  "github.com/kettek/apng"
)

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)
}
Custom Compression

A custom compression writer can be used instead of the default zlib writer. This can be done by creating a specific apng Encoder and assigning a construction function to the CompressionWriter field:

enc := apng.Encoder{
	CompressionWriter: func(w io.Writer) (apng.CompressionWriter, error) {
		return NewCustomZlibWriter(w)
	},
}
enc.Encode(out, a)

Documentation

Overview

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

Index

Examples

Constants

View Source
const (
	DISPOSE_OP_NONE       = 0
	DISPOSE_OP_BACKGROUND = 1
	DISPOSE_OP_PREVIOUS   = 2
)

dispose_op values, as per the APNG spec.

View Source
const (
	BLEND_OP_SOURCE = 0
	BLEND_OP_OVER   = 1
)

blend_op values, as per the APNG spec.

Variables

This section is empty.

Functions

func Decode

func Decode(r io.Reader) (image.Image, error)

Decode reads an APNG file from r and returns the default image.

func DecodeConfig

func DecodeConfig(r io.Reader) (image.Config, error)

DecodeConfig returns the color model and dimensions of a PNG image without decoding the entire image.

func Encode

func Encode(w io.Writer, a APNG) error

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.

Example
package main

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

	"github.com/kettek/apng"
)

func main() {
	w, h := 200, 200
	am := apng.APNG{Frames: make([]apng.Frame, 20)}
	var circles [3]image.Point

	for i := range am.Frames {
		im := image.NewRGBA(image.Rect(0, 0, w, h))
		am.Frames[i].Image = im

		theta := float64(i) * 2 * math.Pi / float64(len(am.Frames))
		for c := range circles {
			theta0 := float64(c) * 2 * math.Pi / 3
			circles[c].X = int(float64(w) * (0.5 - 0.15*math.Sin(theta0) - 0.1*math.Sin(theta0+theta)))
			circles[c].Y = int(float64(h) * (0.5 - 0.15*math.Cos(theta0) - 0.1*math.Cos(theta0+theta)))
		}

		for y := 0; y < h; y++ {
			for x := 0; x < w; x++ {
				var rgb [3]uint8
				for c := range circles {
					dx, dy := x-circles[c].X, y-circles[c].Y
					if dx*dx+dy*dy < int(float64(w*h)/20) {
						rgb[c] = 0xff
					}
				}
				im.Set(x, y, color.RGBA{R: rgb[0], G: rgb[1], B: rgb[2], A: 0xff})
			}
		}
	}

	f, err := os.Create("rgb.png")
	if err != nil {
		panic(err)
	}
	defer f.Close()
	if err := apng.Encode(f, am); err != nil {
		panic(err)
	}
}
Output:

Types

type 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
}

func DecodeAll

func DecodeAll(r io.Reader) (APNG, error)

DecodeAll 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 result. The type of Image returned depends on the PNG contents.

Example
package main

import (
	"encoding/base64"
	"image/color"
	"io"
	"strings"
	"time"

	"github.com/kettek/apng"
)

const gopher = `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`

// gopherPNG creates an io.Reader by decoding the base64 encoded image data string in the gopher constant.
func gopherPNG() io.Reader { return base64.NewDecoder(base64.StdEncoding, strings.NewReader(gopher)) }

func main() {
	am, err := apng.DecodeAll(gopherPNG())
	if err != nil {
		panic(err)
	}

	levels := []rune(" ░▒▓█")
	loops := int64(am.LoopCount)
	if loops == 0 {
		loops = -1
	}
	rect := am.Frames[0].Image.Bounds()
	buf := make([][]rune, rect.Dy())
	for y := range buf {
		buf[y] = make([]rune, rect.Dx())
		for x := range buf[y] {
			buf[y][x] = ' '
		}
	}
	n := time.Now()

	print("\x1b[2J") // clear screen
	for loops != 0 {
		for i, fr := range am.Frames {
			for y := 0; y < fr.Image.Bounds().Dy(); y++ {
				for x := 0; x < fr.Image.Bounds().Dx(); x++ {
					c := fr.Image.At(x, y)
					if _, _, _, a := c.RGBA(); a > 0 {
						buf[y+fr.YOffset][x+fr.XOffset] = levels[(color.GrayModel.Convert(c).(color.Gray).Y / 52)]
					} else if fr.BlendOp == apng.BLEND_OP_SOURCE {
						buf[y+fr.YOffset][x+fr.XOffset] = ' '
					}
				}
			}
			print("\x1b[H") // move top left
			for y := range buf {
				println(string(buf[y]))
			}
			time.Sleep(time.Duration(float64(time.Second)*float64(fr.DelayNumerator)/float64(fr.DelayDenominator)) - time.Now().Sub(n))
			n = time.Now()
			if fr.DisposeOp == apng.DISPOSE_OP_BACKGROUND || i == len(am.Frames)-1 {
				for y := 0; y < fr.Image.Bounds().Dy(); y++ {
					for x := 0; x < fr.Image.Bounds().Dx(); x++ {
						buf[y+fr.YOffset][x+fr.XOffset] = ' '
					}
				}
			}
		}
		if loops > 0 {
			loops--
		}
	}
}
Output:

type CompressionLevel

type CompressionLevel int

CompressionLevel indicates the compression level.

const (
	DefaultCompression CompressionLevel = 0
	NoCompression      CompressionLevel = -1
	BestSpeed          CompressionLevel = -2
	BestCompression    CompressionLevel = -3
)

type CompressionWriter

type CompressionWriter interface {
	Write(p []byte) (n int, err error)
	Reset(w io.Writer)
	Close() error
}

CompressionWriter zlib compression writer interface.

type Encoder

type Encoder struct {
	CompressionLevel CompressionLevel

	// BufferPool optionally specifies a buffer pool to get temporary
	// EncoderBuffers when encoding an image.
	BufferPool EncoderBufferPool

	// CompressionWriter optionally provides a external zlib compression
	// writer for writing PNG image data.
	CompressionWriter func(w io.Writer) (CompressionWriter, error)
}

Encoder configures encoding PNG images.

func (*Encoder) Encode

func (enc *Encoder) Encode(w io.Writer, a APNG) error

Encode writes the Animation a to w in PNG format.

type EncoderBuffer

type EncoderBuffer encoder

EncoderBuffer holds the buffers used for encoding PNG images.

type EncoderBufferPool

type EncoderBufferPool interface {
	Get() *EncoderBuffer
	Put(*EncoderBuffer)
}

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 FormatError

type FormatError string

A FormatError reports that the input is not a valid PNG.

func (FormatError) Error

func (e FormatError) Error() string

type Frame

type Frame struct {
	Image image.Image

	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
	// contains filtered or unexported fields
}

func (*Frame) GetDelay

func (f *Frame) GetDelay() float64

GetDelay returns the number of seconds in the frame.

type UnsupportedError

type UnsupportedError string

An UnsupportedError reports that the input uses a valid but unimplemented PNG feature.

func (UnsupportedError) Error

func (e UnsupportedError) Error() string

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL