Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ Prototype] Add animated webp decode support #75

Draft
wants to merge 5 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions packages/webp/codec/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ all: $(OUT_JS)
# Define dependencies for all variations of build artifacts.
$(filter enc/%,$(OUT_JS)): enc/webp_enc.o
$(filter dec/%,$(OUT_JS)): dec/webp_dec.o
enc/webp_enc.js dec/webp_dec.js: $(CODEC_BASELINE_BUILD_DIR)/libwebp.a
enc/webp_enc_simd.js: $(CODEC_SIMD_BUILD_DIR)/libwebp.a
enc/webp_enc.js dec/webp_dec.js: $(CODEC_BASELINE_BUILD_DIR)/libwebp.a $(CODEC_BASELINE_BUILD_DIR)/libwebpdemux.a
enc/webp_enc_simd.js: $(CODEC_SIMD_BUILD_DIR)/libwebp.a $(CODEC_SIMD_BUILD_DIR)/libwebpdemux.a

$(OUT_JS):
$(LD) \
Expand Down Expand Up @@ -57,6 +57,7 @@ $(CODEC_SIMD_BUILD_DIR)/Makefile: CMAKE_FLAGS+=-DWEBP_ENABLE_SIMD=1
-DWEBP_BUILD_WEBPINFO=0 \
-DWEBP_BUILD_WEBPMUX=0 \
-DWEBP_BUILD_EXTRAS=0 \
-DWEBP_BUILD_DEMUX=1 \
-B $(@D) \
$(<D)

Expand Down
93 changes: 93 additions & 0 deletions packages/webp/codec/dec/webp_dec.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,100 @@ val decode(std::string buffer) {
: val::null();
}

struct Frame {
val imageData;
int duration;
};

val decodeAnimated(std::string buffer) {
WebPData webp_data;
webp_data.bytes = reinterpret_cast<const uint8_t*>(buffer.c_str());
webp_data.size = buffer.size();

WebPDemuxer* demux = WebPDemux(&webp_data);
if (!demux) return val::null();

// Get canvas dimensions from container
int canvas_width = WebPDemuxGetI(demux, WEBP_FF_CANVAS_WIDTH);
int canvas_height = WebPDemuxGetI(demux, WEBP_FF_CANVAS_HEIGHT);

// Buffer to store the current complete canvas state
std::vector<uint8_t> canvas_buffer(canvas_width * canvas_height * 4, 0);

val frames = val::array();
WebPIterator iter;
if (!WebPDemuxGetFrame(demux, 1, &iter)) {
WebPDemuxDelete(demux);
return val::null();
}

do {
int frame_width, frame_height;
uint8_t* frame_rgba = WebPDecodeRGBA(iter.fragment.bytes, iter.fragment.size,
&frame_width, &frame_height);

if (frame_rgba) {
int x_offset = iter.x_offset;
int y_offset = iter.y_offset;

for (int y = 0; y < frame_height; y++) {
for (int x = 0; x < frame_width; x++) {
int canvas_x = x + x_offset;
int canvas_y = y + y_offset;

if (canvas_x >= canvas_width || canvas_y >= canvas_height) continue;

size_t frame_idx = (y * frame_width + x) * 4;
size_t canvas_idx = (canvas_y * canvas_width + canvas_x) * 4;

uint8_t frame_alpha = frame_rgba[frame_idx + 3];
if (iter.blend_method == WEBP_MUX_BLEND) {
float src_alpha = frame_alpha / 255.0f;
float dst_alpha = canvas_buffer[canvas_idx + 3] / 255.0f;
float out_alpha = src_alpha + dst_alpha * (1 - src_alpha);

if (out_alpha > 0) {
float src_factor = src_alpha / out_alpha;
float dst_factor = (dst_alpha * (1 - src_alpha)) / out_alpha;

canvas_buffer[canvas_idx + 0] = frame_rgba[frame_idx + 0] * src_factor +
canvas_buffer[canvas_idx + 0] * dst_factor;
canvas_buffer[canvas_idx + 1] = frame_rgba[frame_idx + 1] * src_factor +
canvas_buffer[canvas_idx + 1] * dst_factor;
canvas_buffer[canvas_idx + 2] = frame_rgba[frame_idx + 2] * src_factor +
canvas_buffer[canvas_idx + 2] * dst_factor;
canvas_buffer[canvas_idx + 3] = out_alpha * 255;
}
} else {
canvas_buffer[canvas_idx + 0] = frame_rgba[frame_idx + 0];
canvas_buffer[canvas_idx + 1] = frame_rgba[frame_idx + 1];
canvas_buffer[canvas_idx + 2] = frame_rgba[frame_idx + 2];
canvas_buffer[canvas_idx + 3] = frame_alpha;
}
}
}

val imageData = ImageData.new_(
Uint8ClampedArray.new_(typed_memory_view(canvas_width * canvas_height * 4, canvas_buffer.data())),
canvas_width, canvas_height);

val frame = val::object();
frame.set("imageData", imageData);
frame.set("duration", iter.duration);
frames.call<void>("push", frame);

free(frame_rgba);
}
} while (WebPDemuxNextFrame(&iter));

WebPDemuxReleaseIterator(&iter);
WebPDemuxDelete(demux);

return frames;
}

EMSCRIPTEN_BINDINGS(my_module) {
function("decode", &decode);
function("decodeAnimated", &decodeAnimated);
function("version", &version);
}
3 changes: 3 additions & 0 deletions packages/webp/codec/dec/webp_dec.d.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
export interface WebPModule extends EmscriptenWasm.Module {
decode(data: BufferSource): ImageData | null;
decodeAnimated(data: BufferSource): WebPFrame[] | null;
}

export type WebPFrame = { imageData: ImageData, duration: number };

declare var moduleFactory: EmscriptenWasm.ModuleFactory<WebPModule>;

export default moduleFactory;
7 changes: 3 additions & 4 deletions packages/webp/codec/dec/webp_dec.js

Large diffs are not rendered by default.

Binary file modified packages/webp/codec/dec/webp_dec.wasm
Binary file not shown.
7 changes: 3 additions & 4 deletions packages/webp/codec/enc/webp_enc.js

Large diffs are not rendered by default.

Binary file modified packages/webp/codec/enc/webp_enc.wasm
Binary file not shown.
7 changes: 3 additions & 4 deletions packages/webp/codec/enc/webp_enc_simd.js

Large diffs are not rendered by default.

Binary file modified packages/webp/codec/enc/webp_enc_simd.wasm
Binary file not shown.
14 changes: 12 additions & 2 deletions packages/webp/decode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
* Notice: I (Jamie Sinclair) have modified this file to accept an ArrayBuffer instead of typed array
* and manually allow instantiation of the Wasm Module.
*/
import type { WebPModule } from './codec/dec/webp_dec.js';
import type { WebPFrame, WebPModule } from './codec/dec/webp_dec.js';

import webp_dec from './codec/dec/webp_dec.js';
import { initEmscriptenModule } from './utils.js';
Expand All @@ -25,12 +25,13 @@ let emscriptenModule: Promise<WebPModule>;
export async function init(
module?: WebAssembly.Module,
moduleOptionOverrides?: Partial<EmscriptenWasm.ModuleOpts>,
): Promise<void> {
): Promise<WebPModule> {
emscriptenModule = initEmscriptenModule(
webp_dec,
module,
moduleOptionOverrides,
);
return emscriptenModule;
}

export default async function decode(buffer: ArrayBuffer): Promise<ImageData> {
Expand All @@ -41,3 +42,12 @@ export default async function decode(buffer: ArrayBuffer): Promise<ImageData> {
if (!result) throw new Error('Decoding error');
return result;
}

export async function decodeAnimated(buffer: ArrayBuffer): Promise<WebPFrame[]> {
if (!emscriptenModule) init();

const module = await emscriptenModule;
const result = module.decodeAnimated(buffer);
if (!result) throw new Error('Decoding error');
return result;
}
1 change: 1 addition & 0 deletions packages/webp/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export { default as encode } from './encode.js';
export { default as decode } from './decode.js';
export { decodeAnimated } from './decode.js';
Loading