This initial implementation doesn't fully expose the `WebPAnimEncoderOptions`. It also still needs some unit tests and examples.master
parent
68562c9c99
commit
1c4bc9fe39
@ -0,0 +1,121 @@
|
||||
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"
|
||||
"time"
|
||||
"image"
|
||||
"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((duration + ae.duration) / time.Millisecond)
|
||||
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)),
|
||||
)
|
||||
}
|
||||
|
||||
ae.duration += duration
|
||||
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)
|
||||
|
||||
if C.WebPAnimEncoderAssemble(ae.c, data) == 0 {
|
||||
return nil, errors.New("Error assembling animation")
|
||||
}
|
||||
|
||||
return C.GoBytes(
|
||||
unsafe.Pointer(data.bytes),
|
||||
C.int(int(data.size)),
|
||||
), nil
|
||||
}
|
||||
|
||||
// Close deletes the encoder and frees resources.
|
||||
func (ae *AnimationEncoder) Close() {
|
||||
C.WebPAnimEncoderDelete(ae.c)
|
||||
}
|
||||
Loading…
Reference in new issue