diff --git a/webp/anim_encode.go b/webp/anim_encode.go new file mode 100644 index 0000000..a4b28f0 --- /dev/null +++ b/webp/anim_encode.go @@ -0,0 +1,121 @@ +package webp + +/* +#include +#include +#include +#include + +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) +} \ No newline at end of file