mirror of
https://github.com/talgo-cloud/talgo-libwebp.git
synced 2026-03-07 21:48:16 -08:00
We were never freeing the buffer that holds the assembled WebP animation. Instead, copy the buffer into Go and free it right after assembly.
135 lines
3.1 KiB
Go
135 lines
3.1 KiB
Go
package webp
|
|
|
|
/*
|
|
#cgo LDFLAGS: -lwebpmux
|
|
|
|
#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)
|
|
}
|