5. 動的画像生成

Internet ComputerのBackend Canisterで動的に画像生成してみます。

ここで解説するサンプルは、Frontendから渡した画像サイズをもとにBackendで動的に画像データを生成して返すだけのシンプルなものです。

生成画像はBackend側のデータを利用しないため、厳密にはBackendを呼び出さずともFrontend内で生成可能なのですが、あえてBackendで動的生成させています。

Backendで動的に画像生成する仕組みを応用することで、Blockchainに刻まれたデータに基づいて動的に画像データを生成するといったこともできるでしょう。

1. テンプレート生成

生成した画像を表示するためのFrontendも用意しますので、手っ取り早くテンプレート生成を使うことにします。

$ dfx new --type=rust fractal
$ cd fractal

コマンドが成功すると、以下のようなディレクトリ構造でファイルが生成されていると思います。

fractal
├── Cargo.lock
├── Cargo.toml
├── README.md
├── dfx.json
├── node_modules
├── package-lock.json
├── package.json
├── src
│   ├── fractal_backend
│   │   ├── Cargo.toml
│   │   ├── fractal_backend.did
│   │   └── src
│   │       └── lib.rs
│   └── fractal_frontend
│       ├── assets
│       │   ├── favicon.ico
│       │   ├── logo2.svg
│       │   ├── main.css
│       │   └── sample-asset.txt
│       └── src
│           ├── index.html
│           └── index.js
└── webpack.config.js

2. プロジェクト資材修正

(1) dfx.jsonの編集

テンプレート生成されたものがそのまま使えるのでとくに修正は必要ありません。

(2) didファイルの編集

Backendを呼び出すためのI/Fを定義します。

引数として画像サイズ (x, y) を渡して、pngデータ (バイナリ)を返すことを考えます。サンプルですのでつくりを単純化するため、Backend側からエラーは返さないものとします。

src/fractal_backend/fractal_backend.did

service : {
    "fractal": (nat32, nat32) -> (blob) query;
}

(3) Backendプログラムの編集

I/Fに合わせてプログラムを用意します。

今回はBackendで動的に画像を生成する方法の検証が目的ですので、生成する画像にはこだわりません。

使用するimage createのページを見ると『Julia fractals』のコード例がありましたので、今回はこれを使ってみることにしましょう。

-https://crates.io/crates/image

描画ロジックの詳細については聞かないでください。

src/fractal_backend/src/lib.rs

// An example of generating julia fractals.
// https://crates.io/crates/image
use std::io::Cursor;

#[ic_cdk::query]
fn fractal(imgx:u32, imgy:u32) -> Vec<u8> {

  let mut result = vec![];
  imgbuf.write_to(&mut Cursor::new(&mut result), image::ImageOutputFormat::Png).unwrap();
  result
}

(4) Rust依存モジュールの追加

Julia fractalsでは計算に複素平面を使用するため、num-complex crateを使います。また、Rust上で画像を扱いpngフォーマットへの変換するために、image crateを追加します。

$ cargo add num-complex
$ cargo add --features png --no-default-features image

プロジェクトルートにあるCargo.tomlはWorkspaceの設定にすぎませんので、

Cargo.toml

[workspace]
members = [
    "src/fractal_backend",
]

実体があるsrc/fractal_backend/Cargo.tomlに依存ライブラリが追加されます。

src/fractal_backend/Cargo.toml


image = { version = "0.24.7", default-features = false, features = ["png"] }
num-complex = "0.4.4"

(5) Backend呼び出し用のJavaScriptソース生成

FrontendからBackendを呼び出せるようにJavaScriptソースを生成します。

$ npm run generate

※コマンドの実体はdfx generate fractal_backendです。

(6) index.htmlの編集

あくまでもサンプルですので、画像サイズ (x, y) を入力する<input>タグ、画像生成する<button>タグ、生成した画像を表示させる<img>タグ のみのシンプルなものとし、素のHTMLとJavaScriptで記述することにします。

<!DOCTYPE html>
<html>
  <head>
    <title>fractal</title>
  </head>
  <body>
    Size:
    <input id="x" type="number" min="100" max="1000" step="1" value="512">
    <input id="y" type="number" min="100" max="1000" step="1" value="512">
    <button id="button">Generate</button>
    <br/><br/>
    <img id="fractal">
  </body>
</html>

(7) index.jsの編集

buttonがクリックされた場合に、Backendのfractal()関数を呼び出し、取得できたpngデータを<img>タグに表示する例です。

<img>タグにpngデータを直接流し込むことはできないため、convertToDataUrl()関数を用意してDataUrl形式に変換し、src属性に設定する方法で実現しています。

import { fractal_backend } from "../../declarations/fractal_backend";

document.getElementById("button").addEventListener("click", async (e) => {
  e.preventDefault();
  const x = parseInt(document.getElementById("x").value);
  const y = parseInt(document.getElementById("y").value);

  const png = await fractal_backend.fractal(x, y);
  const blob = new Blob([png], { type: "image/png" });
  const url = await convertToDataUrl(blob);

  button.removeAttribute("disabled");
  document.getElementById("fractal").src = url;

  return false;
});

convertToDataUrl()関数はDfinityの公式サンプルを流用しています。

// Converts the given blob into a data url such that it can be assigned as a
// target of a link of as an image source.
function convertToDataUrl(blob) {
  return new Promise((resolve, _) => {
    const fileReader = new FileReader();
    fileReader.readAsDataURL(blob);
    fileReader.onloadend = function () {
      resolve(fileReader.result);
    }
  });
}

(8) dfxサービス起動

$ dfx start --background --clean

(9) Backend、Frontendのローカル配備

$ dfx deploy

実行例

参考

最終更新