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

fix: 🤖 update emscripten bindings #40

Merged
merged 6 commits into from
Jan 16, 2024
Merged
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
7 changes: 6 additions & 1 deletion .github/workflows/check-pr.yaml
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
name: Check PR

on:
pull_request:
types: [opened]
branches: [main]

jobs:
check-pr:
if: github.head_ref != 'release'
runs-on: macos-latest
steps:
- name: Cancel Previous Runs
uses: styfle/cancel-workflow-action@0.9.1
with:
access_token: ${{ github.token }}
- uses: actions/checkout@v4
- uses: Homebrew/actions/setup-homebrew@master
- uses: maxim-lobanov/setup-xcode@v1
Expand Down
44 changes: 43 additions & 1 deletion dotlottie-ffi/emscripten_bindings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,51 @@
using namespace emscripten;
using namespace dotlottie_player;

val getBuffer(DotLottiePlayer &player)
val buffer(DotLottiePlayer &player)
{
auto buffer_ptr = player.buffer_ptr();
auto buffer_len = player.buffer_len();
return val(typed_memory_view(buffer_len, reinterpret_cast<uint8_t *>(buffer_ptr)));
}

EMSCRIPTEN_BINDINGS(DotLottiePlayer)
{
enum_<Mode>("Mode")
.value("Forward", Mode::FORWARD)
.value("Reverse", Mode::REVERSE)
.value("Bounce", Mode::BOUNCE)
.value("ReverseBounce", Mode::REVERSE_BOUNCE);

value_object<Config>("Config")
.field("autoplay", &Config::autoplay)
.field("loop_animation", &Config::loop_animation)
.field("mode", &Config::mode)
.field("speed", &Config::speed)
.field("use_frame_interpolation", &Config::use_frame_interpolation);

class_<DotLottiePlayer>("DotLottiePlayer")
.constructor(&DotLottiePlayer::init, allow_raw_pointers())
.function("buffer_len", &DotLottiePlayer::buffer_len)
.function("buffer_ptr", &DotLottiePlayer::buffer_ptr)
.function("buffer", &buffer)
.function("clear", &DotLottiePlayer::clear)
.function("config", &DotLottiePlayer::config)
.function("current_frame", &DotLottiePlayer::current_frame)
.function("duration", &DotLottiePlayer::duration)
.function("is_loaded", &DotLottiePlayer::is_loaded)
.function("is_paused", &DotLottiePlayer::is_paused)
.function("is_playing", &DotLottiePlayer::is_playing)
.function("is_stopped", &DotLottiePlayer::is_stopped)
.function("load_animation_data", &DotLottiePlayer::load_animation_data, allow_raw_pointers())
.function("load_animation_path", &DotLottiePlayer::load_animation_path, allow_raw_pointers())
.function("loop_count", &DotLottiePlayer::loop_count)
.function("pause", &DotLottiePlayer::pause)
.function("play", &DotLottiePlayer::play)
.function("render", &DotLottiePlayer::render)
.function("request_frame", &DotLottiePlayer::request_frame)
.function("resize", &DotLottiePlayer::resize)
.function("set_config", &DotLottiePlayer::set_config)
.function("set_frame", &DotLottiePlayer::set_frame)
.function("stop", &DotLottiePlayer::stop)
.function("total_frames", &DotLottiePlayer::total_frames);
}
3 changes: 2 additions & 1 deletion dotlottie-rs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ name = "dotlottie_player_core"

[dependencies]
thiserror = "1.0.48"
wasm-bindgen = "0.2.84"
# "emscripten-no-leading-underscore" branch fix this issue -> https://github.com/sebcrozet/instant/issues/35
instant = {git = "https://github.com/hoodmane/instant", branch = "emscripten-no-leading-underscore", features = ["inaccurate"]}

[build-dependencies]
bindgen = "0.69.1"
Expand Down
24 changes: 10 additions & 14 deletions dotlottie-rs/src/dotlottie_player.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use std::{sync::RwLock, time::SystemTime};
use instant::Instant;
use std::sync::RwLock;

use crate::LottieRenderer;

Expand Down Expand Up @@ -29,7 +30,7 @@ struct DotLottieRuntime {
renderer: LottieRenderer,
playback_state: PlaybackState,
is_loaded: bool,
start_time: SystemTime,
start_time: Instant,
loop_count: u32,
config: Config,
}
Expand All @@ -40,7 +41,7 @@ impl DotLottieRuntime {
renderer: LottieRenderer::new(),
playback_state: PlaybackState::Stopped,
is_loaded: false,
start_time: SystemTime::now(),
start_time: Instant::now(),
loop_count: 0,
config,
}
Expand Down Expand Up @@ -74,7 +75,7 @@ impl DotLottieRuntime {
pub fn play(&mut self) -> bool {
if self.is_loaded && !self.is_playing() {
self.playback_state = PlaybackState::Playing;
self.start_time = SystemTime::now();
self.start_time = Instant::now();

true
} else {
Expand Down Expand Up @@ -115,17 +116,12 @@ impl DotLottieRuntime {
return self.current_frame();
}

let current_time = SystemTime::now();
let elapsed_time = self.start_time.elapsed().as_secs_f32();

let elapsed_time = match current_time.duration_since(self.start_time) {
Ok(n) => n.as_millis(),
Err(_) => 0,
} as f32;

let duration = (self.duration() * 1000.0) / self.config.speed as f32;
let duration = self.duration() / self.config.speed;
let total_frames = self.total_frames() - 1.0;

let raw_next_frame = elapsed_time / duration * total_frames;
let raw_next_frame = (elapsed_time / duration) * total_frames;

let next_frame = if self.config.use_frame_interpolation {
raw_next_frame
Expand All @@ -144,7 +140,7 @@ impl DotLottieRuntime {
if next_frame >= total_frames {
if self.config.loop_animation {
self.loop_count += 1;
self.start_time = SystemTime::now();
self.start_time = Instant::now();
0.0
} else {
total_frames
Expand All @@ -157,7 +153,7 @@ impl DotLottieRuntime {
if next_frame <= 0.0 {
if self.config.loop_animation {
self.loop_count += 1;
self.start_time = SystemTime::now();
self.start_time = Instant::now();
total_frames
} else {
0.0
Expand Down
79 changes: 79 additions & 0 deletions node-example.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import createDotLottiePlayerModule from "./release/wasm/DotLottiePlayer.mjs";
import fs from "fs";
import path from "path";

const wasmBinary = fs.readFileSync(
path.join("./release/wasm/DotLottiePlayer.wasm")
);

const Module = await createDotLottiePlayerModule({
wasmBinary,
});

const WIDTH = 200;
const HEIGHT = 200;

const dotLottie = new Module.DotLottiePlayer({
autoplay: false,
loop_animation: false,
mode: Module.Mode.values[1],
speed: 1,
use_frame_interpolation: false,
});

const data = await fetch(
"https://lottie.host/647eb023-6040-4b60-a275-e2546994dd7f/zDCfp5lhLe.json"
).then((res) => res.text());

const loaded = dotLottie.load_animation_data(data, WIDTH, HEIGHT);

if (!loaded) {
console.log("failed to load animation data");
}

dotLottie.set_frame(10.0);
const rendered = dotLottie.render();

if (!rendered) {
console.log("failed to render");
}

const frameBuffer = dotLottie.buffer();

const bmpBuffer = createBMP(WIDTH, WIDTH, frameBuffer);

fs.writeFileSync("./output.bmp", bmpBuffer);

// This is for demonstration purposes only. to avoid adding a dependency
function createBMP(width, height, frameBuffer) {
// Each pixel in BMP is 4 bytes (BGRA)
const bmpDataSize = width * height * 4;
const headerSize = 54;
const fileSize = bmpDataSize + headerSize;
const bmpBuffer = Buffer.alloc(fileSize);

// Bitmap file header
bmpBuffer.write("BM", 0); // Signature
bmpBuffer.writeInt32LE(fileSize, 2); // File size
bmpBuffer.writeInt32LE(headerSize, 10); // Pixel data offset

// DIB header
bmpBuffer.writeInt32LE(40, 14); // DIB header size
bmpBuffer.writeInt32LE(width, 18); // Width
bmpBuffer.writeInt32LE(-height, 22); // Height (negative for top-down bitmap)
bmpBuffer.writeInt16LE(1, 26); // Color planes
bmpBuffer.writeInt16LE(32, 28); // Bits per pixel
bmpBuffer.writeInt32LE(0, 30); // Compression (0 for none)

// Convert RGBA to BGRA and write pixel data
for (let i = 0; i < width * height; i++) {
const rgbaIndex = i * 4;
const bgraIndex = headerSize + i * 4;
bmpBuffer[bgraIndex + 0] = frameBuffer[rgbaIndex + 2]; // Blue
bmpBuffer[bgraIndex + 1] = frameBuffer[rgbaIndex + 1]; // Green
bmpBuffer[bgraIndex + 2] = frameBuffer[rgbaIndex + 0]; // Red
bmpBuffer[bgraIndex + 3] = frameBuffer[rgbaIndex + 3]; // Alpha
}

return bmpBuffer;
}
59 changes: 29 additions & 30 deletions web-example.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@
<title>WASM test</title>
</head>
<body>
<canvas width="500" height="500"></canvas>
<div style="max-width: 500px">
<canvas style="width: 100%"></canvas>
</div>
<script type="module">
import createModule from "./release/wasm/DotLottiePlayer.js";
import createDotLottiePlayerModule from "./release/wasm/DotLottiePlayer.js";

const module = await createModule({
const Module = await createDotLottiePlayerModule({
locateFile: (path, prefix) => {
if (path.endsWith(".wasm")) {
return `./release/wasm/${path}`;
Expand All @@ -19,49 +21,46 @@
},
});

const player = new module.DotLottiePlayer();
const dotLottie = new Module.DotLottiePlayer({
autoplay: true,
loop_animation: true,
mode: Module.Mode.values[1],
speed: 1,
use_frame_interpolation: true,
});

const data = await fetch(
"https://lottie.host/647eb023-6040-4b60-a275-e2546994dd7f/zDCfp5lhLe.json"
).then((res) => res.text());
const canvas = document.querySelector("canvas");

const width = canvas.width;
const height = canvas.height;
const { width: client_width, height: client_height } =
canvas.getBoundingClientRect();

const loaded = player.loadAnimation(data, width, height);
const width = client_width * (window.devicePixelRatio || 1);
const height = client_height * (window.devicePixelRatio || 1);

const ctx = canvas.getContext("2d");
const imageData = ctx.createImageData(width, height);
canvas.width = width;
canvas.height = height;

const duration = player.getDuration(); // duration in seconds
const totalFrames = player.getTotalFrame();
const fps = totalFrames / duration;
const timePerFrame = 1000 / fps; // time per frame in milliseconds
const loaded = dotLottie.load_animation_data(data, width, height);

let lastFrameTime = 0;

let currentFrame = 0;
const ctx = canvas.getContext("2d");
const imageData = ctx.createImageData(width, height);

const animationLoop = (timestamp) => {
if (!lastFrameTime || timestamp - lastFrameTime >= timePerFrame) {
player.frame(currentFrame);
const buffer = player.getBuffer();
function animationLoop() {
const next_frame_number = dotLottie.request_frame();
dotLottie.set_frame(next_frame_number);
const rendered = dotLottie.render();

imageData.data.set(buffer);
if (rendered) {
const frame_buffer = dotLottie.buffer();
imageData.data.set(frame_buffer);
ctx.putImageData(imageData, 0, 0);

currentFrame++;

if (currentFrame >= totalFrames) {
currentFrame = 0;
}

lastFrameTime = timestamp;
}

requestAnimationFrame(animationLoop);
};
}

requestAnimationFrame(animationLoop);
</script>
Expand Down
Loading