1#[macro_use]
4pub mod utils;
5
6pub mod benchmark;
7pub mod conversions;
8
9use crate::blending::params::BlendAlgorithmParams;
10use crate::blending::{
11 blend_images, demultiply_image, get_blending_algorithm, is_algorithm_multiplied, BlendAlgorithm,
12};
13use crate::constants;
14use crate::errors::PConvertError;
15use crate::utils::{decode_png, encode_png};
16use image::{ImageBuffer, Rgba, RgbaImage};
17use js_sys::try_iter;
18use serde::Serialize;
19use serde_json::json;
20use serde_wasm_bindgen::Serializer;
21use utils::{
22 build_algorithm, build_params, encode_file, encode_image_data, get_compression_type,
23 get_filter_type, load_png, node_read_file_async, node_read_file_sync, node_require,
24 node_write_file_sync,
25};
26use wasm_bindgen::prelude::*;
27use web_sys::{File, ImageData};
28
29#[wasm_bindgen(js_name = blendImages)]
32pub async fn blend_images_js(
33 bot: File,
34 top: File,
35 target_file_name: String,
36 algorithm: Option<String>,
37 is_inline: Option<bool>,
38 options: JsValue,
39) -> Result<File, JsValue> {
40 let options = match options.is_object() {
41 true => serde_wasm_bindgen::from_value(options).ok(),
42 false => None,
43 };
44
45 let mut bot = load_png(bot, false).await?;
46 let mut top = load_png(top, false).await?;
47
48 blend_image_buffers(&mut bot, &mut top, algorithm, is_inline)?;
49
50 encode_file(
51 bot,
52 get_compression_type(&options),
53 get_filter_type(&options),
54 target_file_name,
55 )
56}
57
58#[wasm_bindgen(js_name = blendImagesData)]
61pub fn blend_images_data_js(
62 bot: ImageData,
63 top: ImageData,
64 algorithm: Option<String>,
65 is_inline: Option<bool>,
66 options: JsValue,
67) -> Result<ImageData, JsValue> {
68 let options = match options.is_object() {
69 true => serde_wasm_bindgen::from_value(options).ok(),
70 false => None,
71 };
72
73 let (width, height) = (bot.width(), bot.height());
74 let mut bot = ImageBuffer::from_vec(width, height, bot.data().to_vec())
75 .ok_or_else(|| PConvertError::ArgumentError("Could not parse \"bot\"".to_string()))?;
76 let mut top = ImageBuffer::from_vec(width, height, top.data().to_vec())
77 .ok_or_else(|| PConvertError::ArgumentError("Could not parse \"top\"".to_string()))?;
78
79 blend_image_buffers(&mut bot, &mut top, algorithm, is_inline)?;
80
81 encode_image_data(
82 bot,
83 get_compression_type(&options),
84 get_filter_type(&options),
85 )
86}
87
88pub fn blend_image_buffers(
91 bot: &mut ImageBuffer<Rgba<u8>, Vec<u8>>,
92 top: &mut ImageBuffer<Rgba<u8>, Vec<u8>>,
93 algorithm: Option<String>,
94 is_inline: Option<bool>,
95) -> Result<(), PConvertError> {
96 let algorithm = algorithm.unwrap_or_else(|| String::from("multiplicative"));
97 let algorithm = build_algorithm(&algorithm)?;
98 let algorithm_fn = get_blending_algorithm(&algorithm);
99 let demultiply = is_algorithm_multiplied(&algorithm);
100 let _is_inline = is_inline.unwrap_or(false);
101
102 if demultiply {
103 demultiply_image(bot);
104 demultiply_image(top);
105 }
106
107 blend_images(bot, top, &algorithm_fn, &None);
108 Ok(())
109}
110
111#[wasm_bindgen(js_name = blendMultiple)]
114pub async fn blend_multiple_js(
115 image_files: JsValue,
116 target_file_name: String,
117 algorithm: Option<String>,
118 algorithms: Option<Vec<JsValue>>,
119 is_inline: Option<bool>,
120 options: JsValue,
121) -> Result<File, JsValue> {
122 let options = match options.is_object() {
123 true => serde_wasm_bindgen::from_value(options).ok(),
124 false => None,
125 };
126
127 let mut image_buffers = Vec::new();
128 let image_files = try_iter(&image_files).unwrap().unwrap();
129 for file in image_files {
130 let file = file?;
131 let img = load_png(file.into(), false).await?;
132
133 image_buffers.push(img);
134 }
135
136 let composition = blend_multiple_buffers(image_buffers, algorithm, algorithms, is_inline)?;
137 encode_file(
138 composition,
139 get_compression_type(&options),
140 get_filter_type(&options),
141 target_file_name,
142 )
143}
144
145#[wasm_bindgen(js_name = blendMultipleData)]
148pub fn blend_multiple_data_js(
149 images: &JsValue,
150 algorithm: Option<String>,
151 algorithms: Option<Vec<JsValue>>,
152 is_inline: Option<bool>,
153 options: JsValue,
154) -> Result<ImageData, JsValue> {
155 let options = match options.is_object() {
156 true => serde_wasm_bindgen::from_value(options).ok(),
157 false => None,
158 };
159
160 let mut image_buffers: Vec<RgbaImage> = Vec::new();
161 let mut images = try_iter(images).unwrap().unwrap();
162 while let Some(Ok(img_data)) = images.next() {
163 let img_data: ImageData = img_data.into();
164 let img_buffer: RgbaImage = ImageBuffer::from_vec(
165 img_data.width(),
166 img_data.height(),
167 img_data.data().to_vec(),
168 )
169 .ok_or_else(|| PConvertError::ArgumentError("Could not parse \"bot\"".to_string()))?;
170
171 image_buffers.push(img_buffer);
172 }
173
174 let composition = blend_multiple_buffers(image_buffers, algorithm, algorithms, is_inline)?;
175 encode_image_data(
176 composition,
177 get_compression_type(&options),
178 get_filter_type(&options),
179 )
180}
181
182#[wasm_bindgen(js_name = getModuleConstants)]
184pub fn get_module_constants_js() -> JsValue {
185 let filters: Vec<String> = constants::FILTER_TYPES
186 .to_vec()
187 .iter()
188 .map(|x| format!("{:?}", x))
189 .collect();
190
191 let compressions: Vec<String> = constants::COMPRESSION_TYPES
192 .to_vec()
193 .iter()
194 .map(|x| format!("{:?}", x))
195 .collect();
196
197 json!({
198 "COMPILATION_DATE": constants::COMPILATION_DATE,
199 "COMPILATION_TIME": constants::COMPILATION_TIME,
200 "VERSION": constants::VERSION,
201 "ALGORITHMS": constants::ALGORITHMS,
202 "COMPILER": constants::COMPILER,
203 "COMPILER_VERSION": constants::COMPILER_VERSION,
204 "LIBPNG_VERSION": constants::LIBPNG_VERSION,
205 "FEATURES": constants::FEATURES,
206 "PLATFORM_CPU_BITS": constants::PLATFORM_CPU_BITS,
207 "FILTER_TYPES": filters,
208 "COMPRESSION_TYPES": compressions
209 })
210 .serialize(&Serializer::json_compatible())
211 .unwrap()
212}
213
214#[wasm_bindgen(js_name = blendMultipleFs)]
218pub fn blend_multiple_fs(
219 image_paths: Vec<JsValue>,
220 out_path: String,
221 algorithm: Option<String>,
222 algorithms: Option<Vec<JsValue>>,
223 is_inline: Option<bool>,
224 options: JsValue,
225) -> Result<(), JsValue> {
226 let num_images = image_paths.len();
227
228 if num_images < 1 {
229 return Err(PConvertError::ArgumentError(
230 "ArgumentError: 'img_paths' must contain at least one path".to_string(),
231 )
232 .into());
233 }
234
235 let options = match options.is_object() {
236 true => serde_wasm_bindgen::from_value(options).ok(),
237 false => None,
238 };
239
240 let algorithms_to_apply: Vec<(BlendAlgorithm, Option<BlendAlgorithmParams>)> =
241 if let Some(algorithms) = algorithms {
242 build_params(&algorithms)?
243 } else if let Some(algorithm) = algorithm {
244 let algorithm = build_algorithm(&algorithm)?;
245 vec![(algorithm, None); num_images - 1]
246 } else {
247 vec![(BlendAlgorithm::Multiplicative, None); num_images - 1]
248 };
249
250 if algorithms_to_apply.len() != num_images - 1 {
251 return Err(PConvertError::ArgumentError(format!(
252 "ArgumentError: 'algorithms' must be of size {} (one per blending operation)",
253 num_images - 1
254 ))
255 .into());
256 };
257
258 let _is_inline = is_inline.unwrap_or(false);
259
260 let mut img_paths_iter = image_paths.iter();
263 let first_path = img_paths_iter
264 .next()
265 .unwrap()
266 .as_string()
267 .expect("path must be a string");
268
269 let node_fs = node_require("fs");
270
271 let first_demultiply = if !algorithms_to_apply.is_empty() {
272 is_algorithm_multiplied(&algorithms_to_apply[0].0)
273 } else {
274 false
275 };
276 let composition = node_read_file_sync(&node_fs, &first_path);
277 let mut composition = decode_png(&composition[..], first_demultiply)?;
278
279 let zip_iter = img_paths_iter.zip(algorithms_to_apply.iter());
280 for pair in zip_iter {
281 let path = pair.0.as_string().expect("path must be a string");
282 let (algorithm, algorithm_params) = pair.1;
283 let demultiply = is_algorithm_multiplied(algorithm);
284 let algorithm_fn = get_blending_algorithm(algorithm);
285 let current_layer = node_read_file_sync(&node_fs, &path);
286 let current_layer = decode_png(¤t_layer[..], demultiply)?;
287 blend_images(
288 &mut composition,
289 ¤t_layer,
290 &algorithm_fn,
291 algorithm_params,
292 );
293 }
294
295 let compression_type = get_compression_type(&options);
296 let filter_type = get_filter_type(&options);
297
298 let mut encoded_data = Vec::<u8>::with_capacity(composition.to_vec().capacity());
299 encode_png(
300 &mut encoded_data,
301 &composition,
302 compression_type,
303 filter_type,
304 )?;
305
306 node_write_file_sync(&node_fs, &out_path, &encoded_data);
307
308 Ok(())
309}
310
311#[wasm_bindgen(js_name = blendMultipleFsAsync)]
315pub async fn blend_multiple_fs_async(
316 image_paths: Vec<JsValue>,
317 out_path: String,
318 algorithm: Option<String>,
319 algorithms: Option<Vec<JsValue>>,
320 is_inline: Option<bool>,
321 options: JsValue,
322) -> Result<(), JsValue> {
323 let num_images = image_paths.len();
324
325 if num_images < 1 {
326 return Err(PConvertError::ArgumentError(
327 "ArgumentError: 'img_paths' must contain at least one path".to_string(),
328 )
329 .into());
330 }
331
332 let options = match options.is_object() {
333 true => serde_wasm_bindgen::from_value(options).ok(),
334 false => None,
335 };
336
337 let algorithms_to_apply: Vec<(BlendAlgorithm, Option<BlendAlgorithmParams>)> =
338 if let Some(algorithms) = algorithms {
339 build_params(&algorithms)?
340 } else if let Some(algorithm) = algorithm {
341 let algorithm = build_algorithm(&algorithm)?;
342 vec![(algorithm, None); num_images - 1]
343 } else {
344 vec![(BlendAlgorithm::Multiplicative, None); num_images - 1]
345 };
346
347 if algorithms_to_apply.len() != num_images - 1 {
348 return Err(PConvertError::ArgumentError(format!(
349 "ArgumentError: 'algorithms' must be of size {} (one per blending operation)",
350 num_images - 1
351 ))
352 .into());
353 };
354
355 let _is_inline = is_inline.unwrap_or(false);
356
357 let node_fs = node_require("fs");
358
359 let mut png_futures: Vec<Option<wasm_bindgen_futures::JsFuture>> =
360 Vec::with_capacity(num_images);
361 for path in image_paths.iter() {
362 let path = path.as_string().expect("path must be a string");
363 let png_future = node_read_file_async(&node_fs, &path);
364 png_futures.push(Some(png_future));
365 }
366
367 let first_demultiply = if !algorithms_to_apply.is_empty() {
368 is_algorithm_multiplied(&algorithms_to_apply[0].0)
369 } else {
370 false
371 };
372 let composition = png_futures[0].take().unwrap().await?;
373 let composition = js_sys::Uint8Array::from(composition).to_vec();
374 let mut composition = decode_png(&composition[..], first_demultiply)?;
375
376 for i in 1..png_futures.len() {
380 let (algorithm, algorithm_params) = &algorithms_to_apply[i - 1];
381 let demultiply = is_algorithm_multiplied(algorithm);
382 let algorithm_fn = get_blending_algorithm(algorithm);
383 let current_layer = png_futures[i].take().unwrap().await?;
384 let current_layer = js_sys::Uint8Array::from(current_layer).to_vec();
385 let current_layer = decode_png(¤t_layer[..], demultiply)?;
386
387 blend_images(
388 &mut composition,
389 ¤t_layer,
390 &algorithm_fn,
391 algorithm_params,
392 );
393 }
394
395 let compression_type = get_compression_type(&options);
396 let filter_type = get_filter_type(&options);
397
398 let mut encoded_data = Vec::<u8>::with_capacity(composition.to_vec().capacity());
399 encode_png(
400 &mut encoded_data,
401 &composition,
402 compression_type,
403 filter_type,
404 )?;
405
406 node_write_file_sync(&node_fs, &out_path, &encoded_data);
407
408 Ok(())
409}
410
411fn blend_multiple_buffers(
412 image_buffers: Vec<ImageBuffer<Rgba<u8>, Vec<u8>>>,
413 algorithm: Option<String>,
414 algorithms: Option<Vec<JsValue>>,
415 is_inline: Option<bool>,
416) -> Result<ImageBuffer<Rgba<u8>, Vec<u8>>, PConvertError> {
417 let num_images = image_buffers.len();
418 if num_images < 1 {
419 return Err(PConvertError::ArgumentError(
420 "'images' must contain at least one path".to_string(),
421 ));
422 }
423
424 if algorithms.is_some() && algorithms.as_ref().unwrap().len() != num_images - 1 {
425 return Err(PConvertError::ArgumentError(format!(
426 "'algorithms' must be of size {} (one per blending operation)",
427 num_images - 1
428 )));
429 };
430
431 let _is_inline = is_inline.unwrap_or(false);
432
433 let algorithms_to_apply: Vec<(BlendAlgorithm, Option<BlendAlgorithmParams>)> =
434 if let Some(algorithms) = algorithms {
435 build_params(&algorithms)?
436 } else if let Some(algorithm) = algorithm {
437 let algorithm = build_algorithm(&algorithm)?;
438 vec![(algorithm, None); num_images - 1]
439 } else {
440 vec![(BlendAlgorithm::Multiplicative, None); num_images - 1]
441 };
442
443 let mut image_buffers_iter = image_buffers.iter();
444 let first_demultiply = if !algorithms_to_apply.is_empty() {
445 is_algorithm_multiplied(&algorithms_to_apply[0].0)
446 } else {
447 false
448 };
449 let mut composition = image_buffers_iter.next().unwrap().to_owned();
450 if first_demultiply {
451 demultiply_image(&mut composition);
452 }
453 let zip_iter = image_buffers_iter.zip(algorithms_to_apply.iter());
454 for pair in zip_iter {
455 let mut current_layer = pair.0.to_owned();
456 let (algorithm, algorithm_params) = pair.1;
457 let demultiply = is_algorithm_multiplied(algorithm);
458 let algorithm_fn = get_blending_algorithm(algorithm);
459
460 if demultiply {
461 demultiply_image(&mut current_layer);
462 }
463
464 blend_images(
465 &mut composition,
466 ¤t_layer,
467 &algorithm_fn,
468 algorithm_params,
469 );
470 }
471
472 Ok(composition)
473}