web-sys使って写真ペイントツール作ってみた




# はじめに

下記でAsciiアート生成ツールを作成した際は、Fabric.jsを利用してCanvas操作していたが、

今回はweb-sysというRustからJavaScriptのWeb APIにアクセスするクレートを利用して

Canvas操作しつつ写真ペイントツールを作成してみようと思う。🧑‍🎨

(Wasmから直接DOM操作はできないため、

  JavaScriptのWeb APIで仲介しているだけなのでRustでやる意味はないかもしれません。🤔)


https://qiita.com/shisojuice/items/2413580f72244a4823c7


web-sysについて

https://rustwasm.github.io/wasm-bindgen/web-sys/index.html


# プロジェクトの作成

下記に既にCanvasのペイント処理のExampleがあったのでこれを参考にしながら

スマホのTouch操作の処理を実装と、写真のCanvas表示を実装してみる。


https://rustwasm.github.io/wasm-bindgen/examples/paint.html



取り込んだ写真をCanvasに表示するように実装、

web-sysを使ってstyleの設定とかもできた。


```rust:lib.rs
// 省略

 let document = web_sys::window().unwrap().document().unwrap();
    let canvas = document.create_element("canvas").unwrap()
        .dyn_into::<web_sys::HtmlCanvasElement>().unwrap();
    canvas.set_id("myCanvas");
    document.body().unwrap().append_child(&canvas).unwrap();
    canvas.set_width(img.width());
    canvas.set_height(img.height());
    canvas.style().set_property("border", "solid").unwrap();
    let context =
        canvas.get_context("2d").unwrap().unwrap()
            .dyn_into::<web_sys::CanvasRenderingContext2d>().unwrap();
    let context = Rc::new(context);

    // image crate の画像データを RGBA のベクタに変換
    let raw_pixels: Vec<u8> = match img {
        DynamicImage::ImageRgba8(ref img_buffer) => img_buffer.clone().into_raw(),
        _ => {
            img.to_rgba8().into_raw()
        }
    };
    let image_data = ImageData::new_with_u8_clamped_array_and_sh(
        wasm_bindgen::Clamped(&mut raw_pixels.clone()),
        img.width(),
        img.height(),
    ).unwrap();
    context.put_image_data(&image_data, 0.0, 0.0).unwrap();

// 省略

```

次にスマホのTouch操作の処理を実装する。

雑だが、touchmove中の座標をtouchend時に繋げるような感じで実装

```rust:lib.rs
// 省略
        let closure = Closure::<dyn FnMut(_)>::new(move |event: web_sys::TouchEvent| {
            // Canvas要素の位置を取得
            let rect = canvas_clone.get_bounding_client_rect();
            // タッチ座標をCanvas座標系に変換
            let canvas_x = event.touches().get(0).unwrap().client_x() as f64 - rect.left();
            let canvas_y = event.touches().get(0).unwrap().client_y() as f64 - rect.top();
            // 現在の位置に矩形を描画
            context.fill_rect(canvas_x, canvas_y, 1f64, 1f64);
            my_xy_clone.borrow_mut().push(Point{x:canvas_x, y:canvas_y});
        });
        canvas.add_event_listener_with_callback("touchmove", closure.as_ref().unchecked_ref()).unwrap();
        closure.forget();

// 省略

        let closure = Closure::<dyn FnMut(_)>::new(move |_event: web_sys::TouchEvent| {
            for i in 0..my_xy_clone.borrow().len(){
                if i > 0 && i < my_xy_clone.borrow().len() - 1 {
                    context.begin_path();
                    context.move_to(my_xy_clone.borrow().get(i-1).unwrap().x,my_xy_clone.borrow().get(i-1).unwrap().y);
                    context.line_to(my_xy_clone.borrow().get(i).unwrap().x,my_xy_clone.borrow().get(i).unwrap().y);
                    context.stroke();
                }
            }
            my_xy_clone.borrow_mut().clear();
        });
        canvas.add_event_listener_with_callback("touchend", closure.as_ref().unchecked_ref()).unwrap();
        closure.forget();

// 省略

```


# 今回の成果物

デモURL

https://shisojuice.github.io/Rust_PhotoEditor/


デモ画像

元画像

![web-sys使って写真ペイントツール作ってみた_001.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3777149/425a4027-afce-c965-b826-eedd7ebf85a9.jpeg)

お絵描き後の画像

![web-sys使って写真ペイントツール作ってみた_002.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3777149/f114b080-df1e-9c9a-d726-8c64a927a4b0.jpeg)

ソース

https://github.com/shisojuice/Rust_PhotoEditor/tree/paint_00001


# まとめ

Javascriptで直接操作したほうが、楽なような気がするが、

Rust初学者なため、こういったクレートを使うことも学習促進になるような気がした💪

まだまだ、理解できていないところも多いがコツコツと勉強していたい。

上記、画像が大きい場合に、Windowサイズを大きくはみ出てる課題があったため、

下記で修正版の記事を書きました。💪

https://qiita.com/shisojuice/items/2259763b3352c9c6ff97

※間違い等ありましたら、ご指摘いただけると助かります。