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



# はじめに

下記でweb-sys使って写真ペイントツールを作ったが、

画像の解像度が高いとWindowサイズを大きくはみ出てしまったり、

スマホの場合は画像がデカすぎてお絵描きできない現象が発生していたので、

今回は、この課題を解決していく。


https://qiita.com/shisojuice/items/d4bf349a658944aa4456


web-sysについて

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


# プロジェクトの作成

前回までのような写真を取り込み、そのままのサイズでCanvasに表示すると

どうしても大きくなってしまうので、発想を変えてみる。

取り込み写真をCanvas上に直接表示するのではなく、

・写真のレイヤーとCanvasのレイヤーを分ける。

・写真のレイヤーがCanvasのレイヤーの背後にくるように設定する。

・レイヤーのサイズは端末のWindowサイズで調整する。

上記のような感じで実装してみる。



まず、前回ではCanvasのお絵描きイベント処理をweb-sysで行ったが、

素直にJavascriptに直接実装した。(web-sysだと、コード量が多くて面倒なため。)


```javascript:index.js
// 省略

myCanvas.addEventListener("mousedown", (event) => {
    myContext.beginPath();
    myContext.moveTo(event.offsetX, event.offsetY);
    pressed = true;
});
document.addEventListener("mousemove", (event) => {
    // Canvas要素の位置を取得
    const myRect = myCanvas.getBoundingClientRect();
    const mouse_x = event.clientX - myRect.left;
    const mouse_y = event.clientY - myRect.top;
    //Canvas外
    if (mouse_x < 0 || mouse_x > myCanvas.clientWidth || mouse_y < 0 || mouse_y > myCanvas.clientHeight) {
        pressed = false;
    }
    if (pressed === true) {
        myContext.lineTo(event.offsetX, event.offsetY);
        myContext.stroke();
        myContext.beginPath();
        myContext.moveTo(event.offsetX, event.offsetY);
    }
});
myCanvas.addEventListener("mouseup", (event) => {
    pressed = false;
    myContext.lineTo(event.offsetX, event.offsetY);
    myContext.stroke();
});
myCanvas.addEventListener("touchmove", (event) => {
    event.preventDefault();
    // タッチ座標をCanvas座標系に変換
    const myRect = myCanvas.getBoundingClientRect();
    let canvas_x = event.touches[0].clientX - myRect.left;
    let canvas_y = event.touches[0].clientY - myRect.top;
    // 現在の位置に矩形を描画
    myContext.fillRect(canvas_x, canvas_y, 1, 1);
    myXY.push({ x: canvas_x, y: canvas_y });
}, { passive: false });
myCanvas.addEventListener("touchend", (event) => {
    for (let i = 0; i < myXY.length; i++) {
        if (i > 0 && i < myXY.length - 1) {
            myContext.beginPath();
            myContext.moveTo(myXY[i - 1].x, myXY[i - 1].y);
            myContext.lineTo(myXY[i].x, myXY[i].y);
            myContext.stroke();
        }
    }
    myXY.length = 0;
});

// 省略

```

Windowサイズに合うように取り込み画像をRust側で処理する。

取り込み画像を指定されたサイズにリサイズしてImgタグに配置。

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

#[wasm_bindgen]
pub fn img_canvas(image_data: &[u8],my_size : f32)  -> JsValue{
    let img = image::load_from_memory(image_data).unwrap();
    let document = web_sys::window().unwrap().document().unwrap();
    let bg_img = document
        .get_element_by_id("bgImg").unwrap()
        .dyn_into::<web_sys::HtmlImageElement>().unwrap();

    let mut buffer = Cursor::new(Vec::new());
    let my_resize = resize_image(img.clone(), my_size);
    my_resize.canvas.write_to(&mut buffer, ImageFormat::Png).unwrap();
    let img_data = buffer.into_inner();
    let base64_data = general_purpose::STANDARD.encode(img_data);
    let data_url = format!("data:image/png;base64,{}", base64_data);
    bg_img.set_src(&*data_url);

    let my_struct = MyStruct {
        w: img.clone().width(),
        h: img.clone().height(),
        resize_w: my_resize.resize_w,
        resize_h: my_resize.resize_h,
    };
    serde_wasm_bindgen::to_value(&my_struct).unwrap()
}

fn resize_image(img: DynamicImage, my_size: f32) -> MyResize{
    let aspect_ratio = img.width() as f32 / img.height() as f32;
    let (new_width, new_height) = if aspect_ratio > 1.0 {
        // 横長の画像の場合
        (my_size, (my_size / aspect_ratio) as u32)
    } else {
        // 縦長の画像の場合
        (my_size * aspect_ratio , my_size as u32)
    };
    // リサイズ
    let resized_img = img.resize_exact(new_width as u32, new_height  as u32, FilterType::Lanczos3);
    let mut canvas = DynamicImage::new_rgba8(my_size as u32, my_size as u32);
    // 白を背景色とする
    let bkg_image= ImageBuffer::from_pixel(my_size as u32, my_size as u32, Rgba([255, 255, 255, 255]));
    image::imageops::overlay(&mut canvas, &bkg_image, 0, 0);
    // リサイズ画像をキャンバスの中央に貼付
    let x = (my_size as u32- resized_img.width()) / 2;
    let y = (my_size as u32 - resized_img.height()) / 2;
    image::imageops::overlay(&mut canvas, &resized_img, x.into(), y.into());

    let my_resize = MyResize {
        canvas,
        resize_w: resized_img.width(),
        resize_h: resized_img.height(),
    };
    my_resize
}

// 省略

```

Javascript側でwasmの関数から受け取った幅と高さをCanvasに設定する。

```javascript:index.js
// 省略

  const ret = img_canvas(uint8Array, myCanvas.clientWidth);
  myCanvas.width = ret.resize_w;
  myCanvas.height = ret.resize_h;
  myCanvas.style.width = ret.resize_w + "px";
  myCanvas.style.height = ret.resize_h + "px";

// 省略

```

ダウンロード処理では、

取り込み前と同じ画質でダウンロードを実行したいため、

取り込み画像に合うようにCanvasのレイヤーをリサイズし、

それをオーバーレイして出力するように実装する。

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

  let mut file_img = image::load_from_memory(file_image_data).unwrap();
  let canvas_img = image::load_from_memory(canvas_image_data).unwrap();
  let resized_img = canvas_img.resize_exact(file_img.width() ,file_img.height() , FilterType::Lanczos3);
  image::imageops::overlay(&mut file_img, &resized_img, 0, 0);

// 省略

```

これでいい感じに画像が調整されるようになった。

その他の課題であったTouch操作時のスクロールもevent.preventDefault()で防いで

タッチでもお絵描きしやすいように実装して完成！


# 今回の成果物

デモURL

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


デモ画像

元画像

![web-sys使って写真ペイントツール作ってみた_修正版_001.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3777149/daacf6b0-6a46-4dfd-33de-ec28d4faa5c2.jpeg)

お絵描き後の画像

![web-sys使って写真ペイントツール作ってみた_修正版_002.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3777149/348eb3a8-a69c-e89e-67d8-753d66433229.jpeg)

ソース

https://github.com/shisojuice/Rust_PhotoEditor


# まとめ

前回作成したペイントツール🧑‍🎨を修正してみたが、

学習用の成果物でも細かな課題やバグを修正していき、精度を上げていきたいと思う。💪


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