The initial implementation doesn't fully expose `WebPAnimDecoderOptions`.master
|
After Width: | Height: | Size: 684 B |
|
After Width: | Height: | Size: 688 B |
|
After Width: | Height: | Size: 698 B |
|
After Width: | Height: | Size: 698 B |
|
After Width: | Height: | Size: 699 B |
|
After Width: | Height: | Size: 696 B |
|
After Width: | Height: | Size: 690 B |
|
After Width: | Height: | Size: 670 B |
|
After Width: | Height: | Size: 676 B |
|
After Width: | Height: | Size: 680 B |
|
After Width: | Height: | Size: 694 B |
|
After Width: | Height: | Size: 694 B |
|
After Width: | Height: | Size: 693 B |
|
After Width: | Height: | Size: 694 B |
|
After Width: | Height: | Size: 688 B |
|
After Width: | Height: | Size: 690 B |
|
After Width: | Height: | Size: 690 B |
|
After Width: | Height: | Size: 689 B |
|
After Width: | Height: | Size: 5.6 KiB |
@ -0,0 +1,123 @@
|
||||
package webp
|
||||
|
||||
/*
|
||||
#cgo LDFLAGS: -lwebpdemux
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <webp/demux.h>
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"image"
|
||||
"image/color"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// AnimationDecoder decodes an animated WebP.
|
||||
type AnimationDecoder struct {
|
||||
opts C.WebPAnimDecoderOptions
|
||||
c *C.WebPAnimDecoder
|
||||
cData *C.WebPData
|
||||
}
|
||||
|
||||
// AnimationInfo represents properties of an animation.
|
||||
type AnimationInfo struct {
|
||||
CanvasWidth int
|
||||
CanvasHeight int
|
||||
LoopCount int
|
||||
FrameCount int
|
||||
BackgroundColor color.RGBA
|
||||
}
|
||||
|
||||
// Animation represents a decoded WebP animation.
|
||||
type Animation struct {
|
||||
AnimationInfo
|
||||
|
||||
// Image is the list of decoded frames.
|
||||
Image []*image.RGBA
|
||||
|
||||
// Timestamp of each frame in milliseconds.
|
||||
Timestamp []int
|
||||
}
|
||||
|
||||
// NewAnimationDecoder initializes a new decoder.
|
||||
func NewAnimationDecoder(data []byte) (*AnimationDecoder, error) {
|
||||
ad := &AnimationDecoder{}
|
||||
|
||||
if C.WebPAnimDecoderOptionsInit(&ad.opts) == 0 {
|
||||
return nil, errors.New("failed to initialize animation decoder config")
|
||||
}
|
||||
ad.opts.color_mode = C.MODE_RGBA
|
||||
|
||||
ad.cData = &C.WebPData{}
|
||||
C.WebPDataInit(ad.cData)
|
||||
|
||||
ad.cData.bytes = (*C.uint8_t)(C.CBytes(data))
|
||||
ad.cData.size = (C.size_t)(len(data))
|
||||
|
||||
ad.c = C.WebPAnimDecoderNew(ad.cData, &ad.opts)
|
||||
if ad.c == nil {
|
||||
C.free(unsafe.Pointer(ad.cData.bytes))
|
||||
return nil, errors.New("failed to initialize animation decoder")
|
||||
}
|
||||
|
||||
return ad, nil
|
||||
}
|
||||
|
||||
// GetInfo retrieves properties of the animation.
|
||||
func (ad *AnimationDecoder) GetInfo() (*AnimationInfo, error) {
|
||||
info := &C.WebPAnimInfo{}
|
||||
|
||||
if C.WebPAnimDecoderGetInfo(ad.c, info) == 0 {
|
||||
return nil, errors.New("error in WebPAnimDecoderGetInfo")
|
||||
}
|
||||
|
||||
b := make([]uint8, 4)
|
||||
binary.BigEndian.PutUint32(b, uint32(info.bgcolor))
|
||||
|
||||
return &AnimationInfo{
|
||||
CanvasWidth: int(info.canvas_width),
|
||||
CanvasHeight: int(info.canvas_height),
|
||||
LoopCount: int(info.loop_count),
|
||||
FrameCount: int(info.frame_count),
|
||||
BackgroundColor: color.RGBA{b[0], b[1], b[2], b[3]},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Decode decodes a WebP animation.
|
||||
func (ad *AnimationDecoder) Decode() (*Animation, error) {
|
||||
info, err := ad.GetInfo()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
anim := &Animation{
|
||||
AnimationInfo: *info,
|
||||
}
|
||||
|
||||
for C.WebPAnimDecoderHasMoreFrames(ad.c) != 0 {
|
||||
var ts C.int
|
||||
var pix *C.uint8_t
|
||||
|
||||
if C.WebPAnimDecoderGetNext(ad.c, &pix, &ts) == 0 {
|
||||
return nil, errors.New("error in WebPAnimDecoderGetNext")
|
||||
}
|
||||
|
||||
img := image.NewRGBA(image.Rect(0, 0, info.CanvasWidth, info.CanvasHeight))
|
||||
C.memcpy(unsafe.Pointer(&img.Pix[0]), unsafe.Pointer(pix), C.size_t(len(img.Pix)))
|
||||
|
||||
anim.Image = append(anim.Image, img)
|
||||
anim.Timestamp = append(anim.Timestamp, int(ts))
|
||||
}
|
||||
|
||||
return anim, nil
|
||||
}
|
||||
|
||||
// Close deletes the decoder and frees resources.
|
||||
func (ad *AnimationDecoder) Close() {
|
||||
C.free(unsafe.Pointer(ad.cData.bytes))
|
||||
C.WebPAnimDecoderDelete(ad.c)
|
||||
}
|
||||
@ -0,0 +1,77 @@
|
||||
package webp_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/harukasan/go-libwebp/test/util"
|
||||
"github.com/harukasan/go-libwebp/webp"
|
||||
)
|
||||
|
||||
func TestDecodeAnimationInfo(t *testing.T) {
|
||||
data := util.ReadFile("weather-anim.webp")
|
||||
|
||||
dec, err := webp.NewAnimationDecoder(data)
|
||||
if err != nil {
|
||||
t.Fatalf("initializing decoder: %v", err)
|
||||
}
|
||||
defer dec.Close()
|
||||
|
||||
info, err := dec.GetInfo()
|
||||
if err != nil {
|
||||
t.Fatalf("getting animatiion info: %v", err)
|
||||
}
|
||||
|
||||
if got := info.CanvasWidth; got != 64 {
|
||||
t.Errorf("Expected CanvasWidth: %v, but got %v", 64, got)
|
||||
}
|
||||
if got := info.CanvasHeight; got != 32 {
|
||||
t.Errorf("Expected CanvasHeight: %v, but got %v", 32, got)
|
||||
}
|
||||
if got := info.LoopCount; got != 0 {
|
||||
t.Errorf("Expected LoopCount: %v, but got %v", 0, got)
|
||||
}
|
||||
if got := info.FrameCount; got != 18 {
|
||||
t.Errorf("Expected FrameCount: %v, but got %v", 18, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecodeAnimation(t *testing.T) {
|
||||
data := util.ReadFile("weather-anim.webp")
|
||||
|
||||
dec, err := webp.NewAnimationDecoder(data)
|
||||
if err != nil {
|
||||
t.Fatalf("initializing decoder: %v", err)
|
||||
}
|
||||
defer dec.Close()
|
||||
|
||||
anim, err := dec.Decode()
|
||||
if err != nil {
|
||||
t.Fatalf("error decoding: %v", err)
|
||||
}
|
||||
|
||||
if got := len(anim.Image); got != 18 {
|
||||
t.Errorf("Expected len(Image): %v, but got %v", 18, got)
|
||||
}
|
||||
if got := len(anim.Timestamp); got != 18 {
|
||||
t.Errorf("Expected len(Timestamp): %v, but got %v", 18, got)
|
||||
}
|
||||
|
||||
for i := 0; i < 18; i++ {
|
||||
frame := util.ReadPNG(fmt.Sprintf("weather-anim-frames/%02d.png", i))
|
||||
got := anim.Image[i]
|
||||
|
||||
for y := 0; y < got.Bounds().Dy(); y++ {
|
||||
for x := 0; x < got.Bounds().Dx(); x++ {
|
||||
if got.At(x, y) != frame.At(x, y) {
|
||||
t.Fatalf(
|
||||
"Expected frame %d (%d,%d): %v, but got %v",
|
||||
i, x, y,
|
||||
frame.At(x, y),
|
||||
got.At(x, y),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||