1use crate::blending::params::{BlendAlgorithmParams, Value};
6use crate::blending::BlendAlgorithm;
7use crate::errors::PConvertError;
8use crate::utils::{decode_png, encode_png};
9use crate::utils::{image_compression_from, image_filter_from};
10use crate::wasm::conversions::JSONParams;
11use image::codecs::png::{CompressionType, FilterType};
12use image::{ImageBuffer, Rgba};
13use js_sys::{Array, Uint8Array};
14use serde_json::Value as JSONValue;
15use std::collections::HashMap;
16use std::str::FromStr;
17use wasm_bindgen::prelude::*;
18use wasm_bindgen::Clamped;
19use wasm_bindgen_futures::JsFuture;
20use web_sys::{File, ImageData};
21
22#[wasm_bindgen]
23extern "C" {
24 #[derive(Clone, Debug)]
25 pub type NodeFs;
26
27 #[wasm_bindgen(js_namespace = console)]
28 pub fn log(s: &str);
29
30 #[wasm_bindgen(js_name = require)]
31 pub fn node_require(s: &str) -> NodeFs;
32
33 #[wasm_bindgen(method, js_name = readFileSync, structural)]
34 fn readFileSync(fs: &NodeFs, path: &str) -> Vec<u8>;
35
36 #[wasm_bindgen(method, js_name = readFile, structural)]
37 fn readFile(fs: &NodeFs, path: &str, callback: js_sys::Function);
38
39 #[wasm_bindgen(method, js_name = writeFileSync, structural)]
40 fn writeFileSync(fs: &NodeFs, path: &str, data: &[u8]);
41}
42
43macro_rules! console_log {
44 ($($t:tt)*) => (log(&format_args!($($t)*).to_string()))
45}
46
47pub async fn load_png(
49 file: File,
50 demultiply: bool,
51) -> Result<ImageBuffer<Rgba<u8>, Vec<u8>>, JsValue> {
52 let array_buffer = JsFuture::from(file.array_buffer()).await?;
53 let uint8_array = Uint8Array::new(&array_buffer);
54 let png = decode_png(&uint8_array.to_vec()[..], demultiply)?;
55 Ok(png)
56}
57
58pub fn encode_file(
61 image_buffer: ImageBuffer<Rgba<u8>, Vec<u8>>,
62 compression: CompressionType,
63 filter: FilterType,
64 target_file_name: String,
65) -> Result<File, JsValue> {
66 let mut encoded_data = Vec::<u8>::with_capacity(image_buffer.to_vec().capacity());
67 encode_png(&mut encoded_data, &image_buffer, compression, filter)?;
68
69 unsafe {
70 let array_buffer = Uint8Array::view(&encoded_data);
71 File::new_with_u8_array_sequence(&Array::of1(&array_buffer), &target_file_name)
72 }
73}
74
75pub fn encode_image_data(
78 image_buffer: ImageBuffer<Rgba<u8>, Vec<u8>>,
79 compression: CompressionType,
80 filter: FilterType,
81) -> Result<ImageData, JsValue> {
82 let (width, height) = image_buffer.dimensions();
83
84 let mut encoded_data = Vec::<u8>::with_capacity(image_buffer.to_vec().capacity());
85 encode_png(&mut encoded_data, &image_buffer, compression, filter)?;
86
87 let bytes = &mut image_buffer.to_vec();
88 let clamped_bytes: Clamped<&[u8]> = Clamped(bytes);
89
90 ImageData::new_with_u8_clamped_array_and_sh(clamped_bytes, width, height)
91}
92
93pub fn build_algorithm(algorithm: &str) -> Result<BlendAlgorithm, PConvertError> {
96 match BlendAlgorithm::from_str(algorithm) {
97 Ok(algorithm) => Ok(algorithm),
98 Err(algorithm) => Err(PConvertError::ArgumentError(format!(
99 "Invalid algorithm '{}'",
100 algorithm
101 ))),
102 }
103}
104
105pub fn build_params(
108 algorithms: &[JsValue],
109) -> Result<Vec<(BlendAlgorithm, Option<BlendAlgorithmParams>)>, PConvertError> {
110 let mut result = Vec::new();
111
112 for algorithm in algorithms {
113 if algorithm.is_string() {
114 let algorithm = build_algorithm(
115 &algorithm
116 .as_string()
117 .unwrap_or_else(|| "multiplicative".to_string()),
118 )?;
119
120 result.push((algorithm, None));
121 } else if algorithm.is_object() {
122 let params: JSONParams = serde_wasm_bindgen::from_value(algorithm.clone()).unwrap();
123 let algorithm = build_algorithm(¶ms.algorithm)?;
124
125 let mut blending_params = BlendAlgorithmParams::new();
126 for (param_name, param_value) in params.params {
127 let param_value: Value = param_value.into();
128 blending_params.insert(param_name, param_value);
129 }
130
131 result.push((algorithm, Some(blending_params)));
132 }
133 }
134
135 Ok(result)
136}
137
138pub fn get_compression_type(options: &Option<HashMap<String, JSONValue>>) -> CompressionType {
142 options.as_ref().map_or(CompressionType::Fast, |options| {
143 options
144 .get("compression")
145 .map_or(CompressionType::Fast, |compression| match compression {
146 JSONValue::String(compression) => image_compression_from(compression.to_string()),
147 _ => CompressionType::Fast,
148 })
149 })
150}
151
152pub fn get_filter_type(options: &Option<HashMap<String, JSONValue>>) -> FilterType {
156 options.as_ref().map_or(FilterType::NoFilter, |options| {
157 options
158 .get("filter")
159 .map_or(FilterType::NoFilter, |filter| match filter {
160 JSONValue::String(filter) => image_filter_from(filter.to_string()),
161 _ => FilterType::NoFilter,
162 })
163 })
164}
165
166pub fn log_benchmark_header() {
169 console_log!(
170 "{:<20}{:<20}{:<20}{:<20}",
171 "Algorithm",
172 "Compression",
173 "Filter",
174 "Times"
175 );
176}
177
178pub fn log_benchmark(
181 algorithm: String,
182 compression: CompressionType,
183 filter: FilterType,
184 blend_time: f64,
185 read_time: f64,
186 write_time: f64,
187) {
188 console_log!(
189 "{:<20}{:<20}{:<20}{:<20}",
190 algorithm,
191 format!("{:#?}", compression),
192 format!("{:#?}", filter),
193 format!(
194 "{}ms (blend {}ms, read {}ms, write {}ms)",
195 read_time + blend_time + write_time,
196 blend_time,
197 read_time,
198 write_time
199 )
200 );
201}
202
203pub fn node_read_file_sync(fs: &NodeFs, path: &str) -> Vec<u8> {
205 fs.readFileSync(path)
206}
207
208pub fn node_read_file_async(fs: &NodeFs, path: &str) -> wasm_bindgen_futures::JsFuture {
210 let promise = js_sys::Promise::new(&mut |resolve, reject| {
211 let callback = js_sys::Function::new_with_args(
212 "resolve, reject, err, data",
213 "err ? reject(err) : resolve(data);",
214 )
215 .bind2(&JsValue::NULL, &resolve, &reject);
216 fs.readFile(path, callback)
217 });
218
219 wasm_bindgen_futures::JsFuture::from(promise)
220}
221
222pub fn node_write_file_sync(fs: &NodeFs, path: &str, data: &[u8]) {
224 fs.writeFileSync(path, data);
225}