diff --git a/.github/workflows/check-pr.yaml b/.github/workflows/check-pr.yaml index 574a2100..afbdd468 100644 --- a/.github/workflows/check-pr.yaml +++ b/.github/workflows/check-pr.yaml @@ -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 diff --git a/dotlottie-ffi/emscripten_bindings.cpp b/dotlottie-ffi/emscripten_bindings.cpp index 1ea4a0c7..2b25b799 100644 --- a/dotlottie-ffi/emscripten_bindings.cpp +++ b/dotlottie-ffi/emscripten_bindings.cpp @@ -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(buffer_ptr))); } + +EMSCRIPTEN_BINDINGS(DotLottiePlayer) +{ + enum_("Mode") + .value("Forward", Mode::FORWARD) + .value("Reverse", Mode::REVERSE) + .value("Bounce", Mode::BOUNCE) + .value("ReverseBounce", Mode::REVERSE_BOUNCE); + + value_object("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") + .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); +} \ No newline at end of file diff --git a/dotlottie-rs/Cargo.toml b/dotlottie-rs/Cargo.toml index 23589106..72228b03 100644 --- a/dotlottie-rs/Cargo.toml +++ b/dotlottie-rs/Cargo.toml @@ -13,7 +13,8 @@ name = "dotlottie_player_core" [dependencies] dotlottie_fms = { path = "../dotlottie-fms" } 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" diff --git a/dotlottie-rs/src/dotlottie_player.rs b/dotlottie-rs/src/dotlottie_player.rs index 480f977b..ea1cdeb7 100644 --- a/dotlottie-rs/src/dotlottie_player.rs +++ b/dotlottie-rs/src/dotlottie_player.rs @@ -1,4 +1,5 @@ -use std::{sync::RwLock, time::SystemTime}; +use instant::Instant; +use std::sync::RwLock; use dotlottie_fms::DotLottieManager; @@ -31,7 +32,7 @@ struct DotLottieRuntime { renderer: LottieRenderer, playback_state: PlaybackState, is_loaded: bool, - start_time: SystemTime, + start_time: Instant, loop_count: u32, config: Config, dotlottie_manager: DotLottieManager, @@ -43,7 +44,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, dotlottie_manager: DotLottieManager::new(None), @@ -78,7 +79,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 { @@ -166,17 +167,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 @@ -195,7 +191,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 @@ -208,7 +204,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 diff --git a/node-example.mjs b/node-example.mjs new file mode 100644 index 00000000..13373b9f --- /dev/null +++ b/node-example.mjs @@ -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; +} diff --git a/web-example.html b/web-example.html index 766cd5be..915e6de9 100644 --- a/web-example.html +++ b/web-example.html @@ -6,11 +6,13 @@ WASM test - +
+ +