forked from tidbyt/go-libwebp
/
animation-encoder.go
133 lines (107 loc) · 3.05 KB
/
animation-encoder.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
package webp
/*
#include <stdlib.h>
#include <string.h>
#include <webp/encode.h>
#include <webp/mux.h>
int writeWebP(uint8_t*, size_t, struct WebPPicture*);
static WebPPicture *calloc_WebPPicture(void) {
return calloc(sizeof(WebPPicture), 1);
}
static void free_WebPPicture(WebPPicture* webpPicture) {
free(webpPicture);
}
*/
import "C"
import (
"errors"
"fmt"
"image"
"time"
"unsafe"
)
// AnimationEncoder encodes multiple images into an animated WebP.
type AnimationEncoder struct {
opts C.WebPAnimEncoderOptions
c *C.WebPAnimEncoder
duration time.Duration
}
// NewAnimationEncoder initializes a new encoder.
func NewAnimationEncoder(width, height, kmin, kmax int) (*AnimationEncoder, error) {
ae := &AnimationEncoder{}
if C.WebPAnimEncoderOptionsInit(&ae.opts) == 0 {
return nil, errors.New("failed to initialize animation encoder config")
}
ae.opts.kmin = C.int(kmin)
ae.opts.kmax = C.int(kmax)
ae.c = C.WebPAnimEncoderNew(C.int(width), C.int(height), &ae.opts)
if ae.c == nil {
return nil, errors.New("failed to initialize animation encoder")
}
return ae, nil
}
// AddFrame adds a frame to the encoder.
func (ae *AnimationEncoder) AddFrame(img image.Image, duration time.Duration) error {
pic := C.calloc_WebPPicture()
if pic == nil {
return errors.New("could not allocate webp picture")
}
defer C.free_WebPPicture(pic)
if C.WebPPictureInit(pic) == 0 {
return errors.New("could not initialize webp picture")
}
defer C.WebPPictureFree(pic)
pic.use_argb = 1
pic.width = C.int(img.Bounds().Dx())
pic.height = C.int(img.Bounds().Dy())
pic.writer = C.WebPWriterFunction(C.writeWebP)
switch p := img.(type) {
case *RGBImage:
C.WebPPictureImportRGB(pic, (*C.uint8_t)(&p.Pix[0]), C.int(p.Stride))
case *image.RGBA:
C.WebPPictureImportRGBA(pic, (*C.uint8_t)(&p.Pix[0]), C.int(p.Stride))
case *image.NRGBA:
C.WebPPictureImportRGBA(pic, (*C.uint8_t)(&p.Pix[0]), C.int(p.Stride))
default:
return errors.New("unsupported image type")
}
timestamp := C.int(ae.duration / time.Millisecond)
ae.duration += duration
if C.WebPAnimEncoderAdd(ae.c, pic, timestamp, nil) == 0 {
return fmt.Errorf(
"encoding error: %d - %s",
int(pic.error_code),
C.GoString(C.WebPAnimEncoderGetError(ae.c)),
)
}
return nil
}
// Assemble assembles all frames into animated WebP.
func (ae *AnimationEncoder) Assemble() ([]byte, error) {
// add final empty frame
if C.WebPAnimEncoderAdd(ae.c, nil, C.int(ae.duration/time.Millisecond), nil) == 0 {
return nil, errors.New("couldn't add final empty frame")
}
data := &C.WebPData{}
C.WebPDataInit(data)
defer C.WebPDataClear(data)
if C.WebPAnimEncoderAssemble(ae.c, data) == 0 {
return nil, errors.New("error assembling animation")
}
size := int(data.size)
out := make([]byte, size)
n := copy(
out, C.GoBytes(
unsafe.Pointer(data.bytes),
C.int(size),
),
)
if n != size {
return nil, errors.New("error copying animation from C to Go")
}
return out, nil
}
// Close deletes the encoder and frees resources.
func (ae *AnimationEncoder) Close() {
C.WebPAnimEncoderDelete(ae.c)
}