Reputation: 33
First post, so forgive me if something is not correct. I am trying to update the image data from a HTML canvas with a PNG file as part of an exercise. The function fetch_url_binary(url: String) returns the pixel information from the PNG.
#[wasm_bindgen]
pub async fn fetch_url_binary(url: String) -> Result<Uint8Array, JsValue>
{
let window = web_sys::window().unwrap(); // Browser window
let promise = JsFuture::from(window.fetch_with_str(&url)); // File fetch promise
let result = promise.await?; // Await fulfillment of fetch
let response: web_sys::Response = result.dyn_into().unwrap(); // Type casting
let image_data = JsFuture::from(response.array_buffer()?).await?; // Get text
Ok(Uint8Array::new(&image_data))
}
The function unred(url: String, canvas: String) sets the red channel to zero then updates the canvas with the image.
#[wasm_bindgen]
pub async fn unred(url: String, canvas: String) -> Result<(), JsValue>
{
let binary = fetch_url_binary(url).await.unwrap();
let mut altbuf: Vec<u8> = Vec::new();
for n in 0..binary.length() {
if n % 4 == 0 {
binary.set_index(n,0);
}
altbuf.push(binary.get_index(n));
}
let window = web_sys::window().unwrap();
let document = window.document().expect("Could not get document");
let canvas = document.get_element_by_id(&canvas).unwrap().dyn_into::<web_sys::HtmlCanvasElement>()?;
let context = canvas.get_context("2d")?.unwrap().dyn_into::<web_sys::CanvasRenderingContext2d>()?;
let image_data_temp = ImageData::new_with_u8_clamped_array(Clamped(&altbuf), altbuf.len().try_into().unwrap());
context.put_image_data(&image_data_temp.unwrap(), 0.0, 0.0);
Ok(())
}
The associated HTML code is here:
<!doctype html><html><body>
<canvas id="myCanvas" width="300" height="200" style="border:1px solid #d3d3d3;">
<script type="module">
import init, {unred, fetch_url_binary} from './pkg/hi_lib.js';
var result;
async function run()
{
await init(); // Initialize module
unred("myPng.png", "myCanvas");
}
run(); // Execute async wrapper
</script>
</body></html>
I then test the code in a python3 http server where I get the following error:
Uncaught (in promise) hi_lib_bg.wasm:0x5a79
RuntimeError: unreachable
at hi_lib_bg.wasm:0x5a79
at hi_lib_bg.wasm:0x663a
at hi_lib_bg.wasm:0x71ba
at hi_lib_bg.wasm:0x72c9
at hi_lib_bg.wasm:0x673c
at hi_lib_bg.wasm:0x20e7
at hi_lib_bg.wasm:0x3fea
at hi_lib_bg.wasm:0x7580
at __wbg_adapter_16 (hi_lib.js:202)
at real (hi_lib.js:187)
$func69 @ hi_lib_bg.wasm:0x5a79
$func86 @ hi_lib_bg.wasm:0x663a
$func120 @ hi_lib_bg.wasm:0x71ba
$func126 @ hi_lib_bg.wasm:0x72c9
$func88 @ hi_lib_bg.wasm:0x673c
$func37 @ hi_lib_bg.wasm:0x20e7
$func48 @ hi_lib_bg.wasm:0x3fea
$_dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h51eff7df35816d6c @ hi_lib_bg.wasm:0x7580
__wbg_adapter_16 @ hi_lib.js:202
real @ hi_lib.js:187
Promise.then (async)
imports.wbg.__wbg_then_2fcac196782070cc @ hi_lib.js:440
$func67 @ hi_lib_bg.wasm:0x584d
$func66 @ hi_lib_bg.wasm:0x5709
$func61 @ hi_lib_bg.wasm:0x52af
$func103 @ hi_lib_bg.wasm:0x6cbc
$_dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h51eff7df35816d6c @ hi_lib_bg.wasm:0x7580
__wbg_adapter_16 @ hi_lib.js:202
real @ hi_lib.js:187
Promise.then (async)
imports.wbg.__wbg_then_8c2d62e8ae5978f7 @ hi_lib.js:444
$func44 @ hi_lib_bg.wasm:0x3723
$func45 @ hi_lib_bg.wasm:0x399b
$func37 @ hi_lib_bg.wasm:0x1ad6
$func48 @ hi_lib_bg.wasm:0x3fea
$_dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h51eff7df35816d6c @ hi_lib_bg.wasm:0x7580
__wbg_adapter_16 @ hi_lib.js:202
real @ hi_lib.js:187
Promise.then (async)
imports.wbg.__wbg_then_2fcac196782070cc @ hi_lib.js:440
$func67 @ hi_lib_bg.wasm:0x584d
$func66 @ hi_lib_bg.wasm:0x5709
$func61 @ hi_lib_bg.wasm:0x52af
$func103 @ hi_lib_bg.wasm:0x6cbc
$_dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h51eff7df35816d6c @ hi_lib_bg.wasm:0x7580
__wbg_adapter_16 @ hi_lib.js:202
real @ hi_lib.js:187
Promise.then (async)
imports.wbg.__wbg_then_8c2d62e8ae5978f7 @ hi_lib.js:444
$func44 @ hi_lib_bg.wasm:0x3723
$func45 @ hi_lib_bg.wasm:0x3913
$func37 @ hi_lib_bg.wasm:0x1ad6
$func48 @ hi_lib_bg.wasm:0x3fea
$_dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h51eff7df35816d6c @ hi_lib_bg.wasm:0x7580
__wbg_adapter_16 @ hi_lib.js:202
real @ hi_lib.js:187
Promise.then (async)
imports.wbg.__wbg_then_2fcac196782070cc @ hi_lib.js:440
$func67 @ hi_lib_bg.wasm:0x584d
$func64 @ hi_lib_bg.wasm:0x5503
$func79 @ hi_lib_bg.wasm:0x6202
$wasm_bindgen__convert__closures__invoke2_mut__hc0a39dba83c8fc65 @ hi_lib_bg.wasm:0x7540
__wbg_adapter_55 @ hi_lib.js:296
cb0 @ hi_lib.js:424
imports.wbg.__wbg_new_b1d61b5687f5e73a @ hi_lib.js:429
$func183 @ hi_lib_bg.wasm:0x77e4
$unred @ hi_lib_bg.wasm:0x66a2
unred @ hi_lib.js:226
run @ rust_html.html:16
await in run (async)
(anonymous) @ rust_html.html:20
I assume it has something to do with the asynchronous code, but I do not have any idea on how to solve it. I am also trying to avoid altering the javascript directly as the exercise should be completable purely in Rust.
EDIT (1) After doing some review, I think the issue is that I convert the Uint8Array into a vector and then Clamp it. It seems like the function expects an clamped array instead.
EDIT (2) I noticed another error, this time with fetch_url_binary(). The Uint8Array does not match the PNG file. There are 44750 with 4 bytes of information (RGBA) associated with each one. The Uint8Array is sized at 6384.
EDIT (3) Request for the Cargo.toml file.
[package]
name = "hi_lib"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
crate-type = ["cdylib"]
[dependencies]
wasm-bindgen = "0.2.78"
wasm-bindgen-futures = "0.4.28"
image = "0.23.14"
[dependencies.js-sys]
version = "0.3.4"
[dependencies.web-sys]
version = "0.3.55"
features = ['Response','Window', 'HtmlElement', 'HtmlCanvasElement', 'HtmlImageElement','CanvasRenderingContext2d','Document', 'Element', 'Attr', 'ImageData']
I would recommmend going with the Cargo.toml in the answer below. There is definitely a lot of bloat with my version.
Upvotes: 3
Views: 1855
Reputation: 8241
Your current code produces an IndexSizeError
because ImageData
expects an array of pixel colors (4 byte per pixel, using one of the allowed pixel representations), but you are supplying a whole PNG, which for example also includes the png header, but then compresses some pixel values for memory efficency. You will need to decode your png first, for example using image-rs and then supply the pixel values only.
Here is a basic example how to do this using the image
crate:
lib.rs:
use image::GenericImageView;
use js_sys::Uint8Array;
use wasm_bindgen::{prelude::*, JsCast, Clamped};
use wasm_bindgen_futures::JsFuture;
use web_sys::ImageData;
#[wasm_bindgen]
pub async fn fetch_url_binary(url: String) -> Result<Uint8Array, JsValue> {
let window = web_sys::window().unwrap(); // Browser window
let promise = JsFuture::from(window.fetch_with_str(&url)); // File fetch promise
let result = promise.await?; // Await fulfillment of fetch
let response: web_sys::Response = result.dyn_into().unwrap(); // Type casting
let image_data = JsFuture::from(response.array_buffer()?).await?; // Get text
Ok(Uint8Array::new(&image_data))
}
#[wasm_bindgen]
pub async fn unred(url: String, canvas: String) -> Result<(), JsValue> {
let binary = fetch_url_binary(url).await?;
let altbuf = binary.to_vec();
// Convert the png encoded bytes to an rgba pixel buffer (given the PNG is actually in 8byte RGBA format).
let image = image::load_from_memory_with_format(&altbuf, image::ImageFormat::Png).unwrap();
let mut rgba_image = image.to_rgba8();
// I suppose this is what you tried to do in your original loop
// judging by the function name:
for (_, _, pixel) in rgba_image.enumerate_pixels_mut() {
if pixel[0] > 0 {
*pixel = image::Rgba([0, pixel[1], pixel[2], pixel[3]]);
}
}
let window = web_sys::window().unwrap();
let document = window.document().expect("Could not get document");
let canvas = document
.get_element_by_id(&canvas)
.unwrap()
.dyn_into::<web_sys::HtmlCanvasElement>()?;
let context = canvas
.get_context("2d")?
.unwrap()
.dyn_into::<web_sys::CanvasRenderingContext2d>()?;
let clamped_buf: Clamped<&[u8]> = Clamped(rgba_image.as_raw());
let image_data_temp =
ImageData::new_with_u8_clamped_array_and_sh(clamped_buf, image.width(), image.height())?;
context.put_image_data(&image_data_temp, 0.0, 0.0)?;
Ok(())
}
Cargo.toml:
[package]
name = "hi_lib"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib"]
[dependencies]
image = "0.23.14"
js-sys = "0.3.55"
wasm-bindgen = "0.2.78"
wasm-bindgen-futures = "0.4.28"
web-sys = {version = "0.3.55", features=[
"CanvasRenderingContext2d",
"Document",
"HtmlCanvasElement",
"ImageData",
"Response",
"Window"
]}
It would have been good if you would have provided the list of dependencies, that way you save people who try to test your code quite some time (especially here with all these web-sys
features).
For completeness sake (I changed nothing substantial) index.html:
<!doctype html>
<html lang="en">
<head>
<title>Unred image</title>
</head>
<body>
<canvas id="myCanvas" width="1024" height="1024" style="border:1px solid #d3d3d3;">
<script type="module">
import init, { unred, fetch_url_binary } from './pkg/hi_lib.js';
var result;
async function run() {
await init(); // Initialize module
unred("myPng.png", "myCanvas");
}
run(); // Execute async wrapper
</script>
</body>
</html>
and the image I used for testing (the PNG format matters here, so be sure to use 8 bit RGBA format when encoding, or convert the image accordingly):
Now when building with wasm-pack build --target web
and then serving the crates' root directory with a http server, this is the output:
Upvotes: 5