Skip to content

Commit 5c52729

Browse files
Added WASM Support (#368)
* add: wasm support * add: wasm-bindgen-rayon for multithreading --------- Co-authored-by: Kornel <kornel@geekhood.net>
1 parent f1be670 commit 5c52729

6 files changed

Lines changed: 252 additions & 1 deletion

File tree

‎Cargo.lock‎

Lines changed: 118 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎Cargo.toml‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,10 @@ ordered-channel = { version = "1.2.0", features = ["crossbeam-channel"] }
4242
wild = { version = "2.2.1", optional = true, features = ["glob-quoted-on-windows"] }
4343
y4m = { version = "0.8.0", optional = true }
4444
yuv = { version = "0.1.9", optional = true }
45+
wasm-bindgen = { version = "0.2.108", optional = true }
46+
web-sys = {version="0.3.85", features = ["console"], optional = true }
47+
console_error_panic_hook = {version = "0.1.7", optional = true}
48+
wasm-bindgen-rayon = "1.3.0"
4549

4650
[dependencies.ffmpeg]
4751
package = "ffmpeg-next"
@@ -69,6 +73,7 @@ video-static = ["video", "ffmpeg/build"]
6973
video-prebuilt-static = ["video", "ffmpeg/static"]
7074
# Support lossy LZW encoding when lower quality is set
7175
gifsicle = []
76+
wasm = ["dep:wasm-bindgen", "dep:console_error_panic_hook", "dep:web-sys"]
7277

7378
[lib]
7479
path = "src/lib.rs"
@@ -98,3 +103,9 @@ generation = false
98103

99104
[package.metadata.capi.install.include]
100105
asset = [{from = "gifski.h"}]
106+
107+
[package.metadata.wasm-pack.profile.release]
108+
wasm-opt = ["-O3", "--enable-bulk-memory", "--enable-nontrapping-float-to-int", "--enable-sign-ext", "--enable-mutable-globals"]
109+
110+
[package.metadata.wasm-pack.profile.dev]
111+
wasm-opt = false

‎README.md‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,15 @@ cargo cinstall --prefix=/usr --release --destdir=pkgroot
102102

103103
The `cbuild` command can be omitted, since `cinstall` will trigger a build if it hasn't been done already.
104104

105+
### For WASM
106+
To build the library for WebAssembly, you need to have [wasm-pack](https://rustwasm.github.io/wasm-pack/installer/) installed. Then run:
107+
108+
```sh
109+
wasm-pack build --target web --features wasm --no-default-features
110+
```
111+
112+
113+
105114
## License
106115

107116
AGPL 3 or later. I can offer alternative licensing options, including [commercial licenses](https://supso.org/projects/pngquant). Let [me](https://kornel.ski/contact) know if you'd like to use it in a product incompatible with this license.

‎src/lib.rs‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,12 @@ use std::sync::atomic::Ordering::Relaxed;
6969
use std::sync::Mutex;
7070
use std::thread;
7171

72+
#[cfg(feature = "wasm")]
73+
pub mod wasm;
74+
75+
#[cfg(feature = "wasm")]
76+
pub use wasm::GifskiWasm;
77+
7278
/// Number of repetitions
7379
pub type Repeat = gif::Repeat;
7480

‎src/wasm.rs‎

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
use wasm_bindgen::prelude::*;
2+
use crate::{Settings, Repeat};
3+
use imgref::ImgVec;
4+
use rgb::RGBA8;
5+
use std::sync::Arc;
6+
use std::sync::Mutex;
7+
8+
#[wasm_bindgen]
9+
pub struct GifskiWasm {
10+
settings: Settings,
11+
frames: Vec<(ImgVec<RGBA8>, f64)>,
12+
width: u32,
13+
height: u32,
14+
}
15+
16+
#[wasm_bindgen]
17+
impl GifskiWasm {
18+
#[wasm_bindgen(constructor)]
19+
pub fn new(width: u32, height: u32, quality: u8) -> Result<GifskiWasm, JsValue> {
20+
// Better panic messages in browser console
21+
console_error_panic_hook::set_once();
22+
23+
let settings = Settings {
24+
width: if width > 0 { Some(width) } else { None },
25+
height: if height > 0 { Some(height) } else { None },
26+
quality,
27+
fast: false,
28+
repeat: Repeat::Infinite,
29+
};
30+
31+
Ok(GifskiWasm {
32+
settings,
33+
frames: Vec::new(),
34+
width,
35+
height,
36+
})
37+
}
38+
39+
pub fn add_frame_rgba(&mut self, rgba_data: &[u8], width: u32, height: u32, timestamp: f64) -> Result<(), JsValue> {
40+
if rgba_data.len() != (width * height * 4) as usize {
41+
return Err(JsValue::from_str("Invalid RGBA data size"));
42+
}
43+
44+
// flat RGBA bytes to RGBA8 pixels
45+
if width != self.width || height != self.height {
46+
return Err(JsValue::from_str("Frame dimensions don't match"));
47+
}
48+
49+
let pixels: Vec<RGBA8> = rgba_data
50+
.chunks_exact(4)
51+
.map(|chunk| RGBA8::new(chunk[0], chunk[1], chunk[2], chunk[3]))
52+
.collect();
53+
54+
let img = ImgVec::new(pixels, width as usize, height as usize);
55+
56+
self.frames.push((img, timestamp));
57+
58+
Ok(())
59+
}
60+
61+
pub fn finish(self) -> Result<Vec<u8>, JsValue> {
62+
if self.frames.is_empty() {
63+
return Err(JsValue::from_str("No frames added"));
64+
}
65+
66+
let (collector, writer) = crate::new(self.settings)
67+
.map_err(|e| JsValue::from_str(&format!("Failed to create gifski: {:?}", e)))?;
68+
69+
let frames = self.frames;
70+
let output = Arc::new(Mutex::new(Vec::new()));
71+
let output_clone = output.clone();
72+
73+
let writer_handle = std::thread::spawn(move || {
74+
let mut output_guard = output_clone.lock().unwrap();
75+
writer.write(&mut *output_guard, &mut crate::progress::NoProgress {})
76+
});
77+
78+
for (frame_index, (img, timestamp)) in frames.into_iter().enumerate() {
79+
collector.add_frame_rgba(frame_index, img, timestamp)
80+
.map_err(|e| JsValue::from_str(&format!("Failed to add frame {}: {:?}", frame_index, e)))?;
81+
}
82+
83+
drop(collector);
84+
85+
writer_handle.join()
86+
.map_err(|_| JsValue::from_str("Writer thread panicked"))?
87+
.map_err(|e| JsValue::from_str(&format!("Failed to write GIF: {:?}", e)))?;
88+
89+
let output_vec = Arc::try_unwrap(output)
90+
.map_err(|_| JsValue::from_str("Failed to unwrap output"))?
91+
.into_inner()
92+
.map_err(|_| JsValue::from_str("Failed to lock output"))?;
93+
94+
Ok(output_vec)
95+
}
96+
}
97+
98+
// thread pool for wasm-bindgen-rayon
99+
#[wasm_bindgen]
100+
pub fn init_thread_pool(num_threads: usize) -> Result<(), JsValue> {
101+
wasm_bindgen_rayon::init_thread_pool(num_threads);
102+
Ok(())
103+
}

‎wasm-pack.toml‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
[package]
2+
name = "gifski-wasm"
3+
4+
[profile.release]
5+
wasm-opt = ["-O3", "--enable-simd"]

0 commit comments

Comments
 (0)