use crate::blending::params::{BlendAlgorithmParams, Value};
use crate::blending::BlendAlgorithm;
use crate::errors::PConvertError;
use crate::utils::{decode_png, encode_png};
use crate::utils::{image_compression_from, image_filter_from};
use crate::wasm::conversions::JSONParams;
use image::codecs::png::{CompressionType, FilterType};
use image::{ImageBuffer, Rgba};
use js_sys::{Array, Uint8Array};
use serde_json::Value as JSONValue;
use std::collections::HashMap;
use std::str::FromStr;
use wasm_bindgen::prelude::*;
use wasm_bindgen::Clamped;
use wasm_bindgen_futures::JsFuture;
use web_sys::{File, ImageData};
#[wasm_bindgen]
extern "C" {
#[derive(Clone, Debug)]
pub type NodeFs;
#[wasm_bindgen(js_namespace = console)]
pub fn log(s: &str);
#[wasm_bindgen(js_name = require)]
pub fn node_require(s: &str) -> NodeFs;
#[wasm_bindgen(method, js_name = readFileSync, structural)]
fn readFileSync(fs: &NodeFs, path: &str) -> Vec<u8>;
#[wasm_bindgen(method, js_name = readFile, structural)]
fn readFile(fs: &NodeFs, path: &str, callback: js_sys::Function);
#[wasm_bindgen(method, js_name = writeFileSync, structural)]
fn writeFileSync(fs: &NodeFs, path: &str, data: &[u8]);
}
macro_rules! console_log {
($($t:tt)*) => (log(&format_args!($($t)*).to_string()))
}
pub async fn load_png(
file: File,
demultiply: bool,
) -> Result<ImageBuffer<Rgba<u8>, Vec<u8>>, JsValue> {
let array_buffer = JsFuture::from(file.array_buffer()).await?;
let uint8_array = Uint8Array::new(&array_buffer);
let png = decode_png(&uint8_array.to_vec()[..], demultiply)?;
Ok(png)
}
pub fn encode_file(
image_buffer: ImageBuffer<Rgba<u8>, Vec<u8>>,
compression: CompressionType,
filter: FilterType,
target_file_name: String,
) -> Result<File, JsValue> {
let mut encoded_data = Vec::<u8>::with_capacity(image_buffer.to_vec().capacity());
encode_png(&mut encoded_data, &image_buffer, compression, filter)?;
unsafe {
let array_buffer = Uint8Array::view(&encoded_data);
File::new_with_u8_array_sequence(&Array::of1(&array_buffer), &target_file_name)
}
}
pub fn encode_image_data(
image_buffer: ImageBuffer<Rgba<u8>, Vec<u8>>,
compression: CompressionType,
filter: FilterType,
) -> Result<ImageData, JsValue> {
let (width, height) = image_buffer.dimensions();
let mut encoded_data = Vec::<u8>::with_capacity(image_buffer.to_vec().capacity());
encode_png(&mut encoded_data, &image_buffer, compression, filter)?;
let bytes = &mut image_buffer.to_vec();
let clamped_bytes: Clamped<&mut [u8]> = Clamped(bytes);
ImageData::new_with_u8_clamped_array_and_sh(clamped_bytes, width, height)
}
pub fn build_algorithm(algorithm: &String) -> Result<BlendAlgorithm, PConvertError> {
match BlendAlgorithm::from_str(&algorithm) {
Ok(algorithm) => Ok(algorithm),
Err(algorithm) => Err(PConvertError::ArgumentError(format!(
"Invalid algorithm '{}'",
algorithm
))),
}
}
pub fn build_params(
algorithms: Box<[JsValue]>,
) -> Result<Vec<(BlendAlgorithm, Option<BlendAlgorithmParams>)>, PConvertError> {
let mut result = Vec::new();
for i in 0..algorithms.len() {
let element = &algorithms[i];
if element.is_string() {
let algorithm =
build_algorithm(&element.as_string().unwrap_or("multiplicative".to_string()))?;
result.push((algorithm, None));
} else if element.is_object() {
let params: JSONParams = element.into_serde::<JSONParams>().unwrap();
let algorithm = build_algorithm(¶ms.algorithm)?;
let mut blending_params = BlendAlgorithmParams::new();
for (param_name, param_value) in params.params {
let param_value: Value = param_value.into();
blending_params.insert(param_name, param_value);
}
result.push((algorithm, Some(blending_params)));
}
}
Ok(result)
}
pub fn get_compression_type(options: &Option<HashMap<String, JSONValue>>) -> CompressionType {
options.as_ref().map_or(CompressionType::Fast, |options| {
options
.get("compression")
.map_or(CompressionType::Fast, |compression| match compression {
JSONValue::String(compression) => image_compression_from(compression.to_string()),
_ => CompressionType::Fast,
})
})
}
pub fn get_filter_type(options: &Option<HashMap<String, JSONValue>>) -> FilterType {
options.as_ref().map_or(FilterType::NoFilter, |options| {
options
.get("filter")
.map_or(FilterType::NoFilter, |filter| match filter {
JSONValue::String(filter) => image_filter_from(filter.to_string()),
_ => FilterType::NoFilter,
})
})
}
pub fn log_benchmark_header() {
console_log!(
"{:<20}{:<20}{:<20}{:<20}",
"Algorithm",
"Compression",
"Filter",
"Times"
);
}
pub fn log_benchmark(
algorithm: String,
compression: CompressionType,
filter: FilterType,
blend_time: f64,
read_time: f64,
write_time: f64,
) {
console_log!(
"{:<20}{:<20}{:<20}{:<20}",
algorithm,
format!("{:#?}", compression),
format!("{:#?}", filter),
format!(
"{}ms (blend {}ms, read {}ms, write {}ms)",
read_time + blend_time + write_time,
blend_time,
read_time,
write_time
)
);
}
pub fn node_read_file_sync(fs: &NodeFs, path: &str) -> Vec<u8> {
fs.readFileSync(path)
}
pub fn node_read_file_async(fs: &NodeFs, path: &str) -> wasm_bindgen_futures::JsFuture {
let promise = js_sys::Promise::new(&mut |resolve, reject| {
let callback = js_sys::Function::new_with_args(
"resolve, reject, err, data",
"err ? reject(err) : resolve(data);",
)
.bind2(&JsValue::NULL, &resolve, &reject);
fs.readFile(path, callback)
});
wasm_bindgen_futures::JsFuture::from(promise)
}
pub fn node_write_file_sync(fs: &NodeFs, path: &str, data: &[u8]) {
fs.writeFileSync(path, data);
}