/webp_encoder

A cross platform C++ (win, linux, unix, macos, wasm) library that encodes sequence image frames into WebP format.

Primary LanguageC++MIT LicenseMIT

webp_encoder

A cross platform C++ (win, linux, unix, macos, wasm) library that encodes sequence image frames into WebP format.

ci

Online Page

See online page https://xiaozhuai.github.io/webp_encoder/

Usage

C++

#include <string>
#include <vector>
#include <webp_encoder.hpp>

int main() {
    WebpEncoder encoder;
    encoder.Init(file_options);
    WebpFileOptions file_options{
            .minimize = true,
            .loop = 0,
            .kmax = 0,
            .kmin = 0,
            .mixed = true,
    };
    WebpFrameOptions frame_options{
            .duration = 100,
            .lossless = false,
            .quality = 100.0f,
            .method = 0,
            .exact = false,
    };

    // Push frames in RGBA format
    encoder.Push(frame_00001, width, height, frame_options);
    encoder.Push(frame_00002, width, height, frame_options);
    encoder.Push(frame_00003, width, height, frame_options);
    frame_options.duration = 200;
    encoder.Push(frame_00004, width, height, frame_options);
    // Add more frames here...

    encoder.Write("test.webp");
    encoder.Release();
}

Javascript

const WebpEncoder = {
    ENCODE_RET_UINT8ARRAY: 0,
    ENCODE_RET_BLOB: 1,
    ENCODE_RET_URL: 2,
    _module: null,
    _getImageDataCanvas: null,
    _getImageDataCanvasCtx: null,
    async loadImage(url) {
        const image = new Image();
        await new Promise((resolve, reject) => {
            image.onload = resolve;
            image.onerror = () => {
                reject(new Error(`Error load image ${url}`));
            };
            image.src = url;
        });
        return image;
    },
    getImageData(image) {
        if (!this._getImageDataCanvas) {
            this._getImageDataCanvas = document.createElement('canvas');
            this._getImageDataCanvasCtx = this._getImageDataCanvas.getContext('2d', {
                willReadFrequently: true,
            });
        }
        let canvas = this._getImageDataCanvas;
        let context = this._getImageDataCanvasCtx;
        canvas.width = image.naturalWidth;
        canvas.height = image.naturalHeight;
        context.drawImage(image, 0, 0);
        return context.getImageData(0, 0, image.naturalWidth, image.naturalHeight);
    },
    /**
     *
     * @param frames            Array<{src: string, options: Object}>
     * @param fileOptions       Object
     * @param callback          function(progress: number)
     * @param returnType        0: Uint8Array
     *                          1: {blob, size}
     *                          2: {url, size}
     * @returns {Promise<Uint8Array|{blob: Blob, size: number}|{url: string, size: number}>}
     */
    async encode(frames, callback, fileOptions = {}, returnType = 0) {
        await callback(0);
        if (!this._module) {
            this._module = await WebpEncoderWasm();
        }
        let encoder = new this._module.WebpEncoder();
        encoder.init(fileOptions);
        await callback(5);

        let c = 0;
        for (let frame of frames) {
            let image = await this.loadImage(frame.src);
            let imageData = this.getImageData(image);
            let rgbaPixels = new Uint8Array(imageData.data.buffer);
            await encoder.push(rgbaPixels, image.naturalWidth, image.naturalHeight, frame.options);
            c++;
            await callback(c / frames.length * 90 + 5);
        }

        await callback(95);
        let bytes = encoder.encode();
        switch (returnType) {
            case 0: {
                bytes = new Uint8Array(bytes);
                encoder.release();
                await callback(100);
                return bytes;
            }
            case 1: {
                let blob = new Blob([bytes], {type: 'image/webp'});
                let size = bytes.length;
                encoder.release();
                await callback(100);
                return {blob, size};
            }
            case 2: {
                let blob = new Blob([bytes], {type: 'image/webp'});
                let size = bytes.length;
                encoder.release();
                let url = URL.createObjectURL(blob);
                await callback(100);
                return {url, size};
            }
            default: {
                throw new Error(`Invalid return type ${returnType}`)
            }
        }
    },
}

async function main() {
    let fileOptions = {
        loop: 0,
        kmax: 0,
        kmin: 0,
        minimize: true,
        mixed: true,
    };
    let frameOptions = {
        duration: 100,
        quality: 100,
        method: 0,
        lossless: false,
        exact: false,
    };
    let images = [
        {
            src: 'https://xiaozhuai.github.io/webp_encoder/frames/frame_0.jpg',
            options: frameOptions,
        },
        {
            src: 'https://xiaozhuai.github.io/webp_encoder/frames/frame_1.jpg',
            options: frameOptions,
        },
        {
            src: 'https://xiaozhuai.github.io/webp_encoder/frames/frame_2.jpg',
            options: frameOptions,
        },
        {
            src: 'https://xiaozhuai.github.io/webp_encoder/frames/frame_3.jpg',
            options: {...frameOptions, duration: 200},
        },
    ];

    /**
     * Got webp blob url and length in byte
     */
    let {url, size} = WebpEncoder.encode(frames, progress => {
        console.log(`progress: ${progress}%`);
    }, fileOptions, webpEncoder.ENCODE_RET_URL);
}

main();