見出し画像

シルエット画像変換アプリを作ろう

WEB開発の技術は日に日に進化してきています。その中でフロントエンドの技術の流行の波は激しく、WEBアプリケーションのパフォーマンスとセキュリティを向上させるのが必須となってきました。

その中でも注目されるのが、WebAssembly(通称:Wasm)です。この記事では、チュートリアルでWebAssemblyとNext.jsの連携方法を解説し、その活用事例として、シルエット画像変換アプリの開発手法について解説しています。

ここでは、WebAssemblyの利点やセットアップ方法、そして具体的なアプリ実装手順までを丁寧に解説していきます。新しいWEB技術を習得していきましょう!


WebAssemblyとは

WebAssembly(またはWasm)はWebアプリケーションを高速実行するための最新技術で、JavaScript の足りない所を補強して併用できます。

高速で動く

バイナリ形式なのでブラウザー内のJavaScriptエンジンや他の実行環境で高速に動きます。3D ゲーム、仮想現実、拡張現実、コンピュータービジョン、画像/動画編集、その他ネイティブなパフォーマンス(コンピュータがもともと持っている性能)を必要とする多くの領域で活用できます。

どのブラウザでも動く

ほぼすべての主要なウェブブラウザーでサポートされており、さまざまなプラットフォームやデバイス上で実行できます。また、WebAssemblyは言語に依存しないバイナリ形式であるため、リソースが限られたプラットフォームでもよいパフォーマンスが出ます。

セキュリティが優れている

サンドボックス化された実行環境上で動作するように設計されています。(サンドボックスはユーザーが通常利用する領域から隔離され、保護された空間に構築された仮想環境のこと)
他のウェブ言語と同様に、ブラウザーに対して同一オリジンポリシーや権限ポリシーの確認を強制します。

同一オリジンポリシーの確認を強制されると、悪意のあるウェブサイトのJavaScriptが実行されないため、 ユーザーがウェブメールサービスや企業のイントラネットを使ったときに攻撃者を中継してデータを読み取ることを防ぎます。

もっとWebAssemblyについて知りたい方は下のサムネイルを参考にしてみてください!

WebAssemblyの動画解説

また、さらっとWebAssemblyを学びたい方は下の動画がおすすめです!

WebAssemblyの実用性を解説してくれている動画も貼っておきます!

シルエット画像変換アプリ概要

アプリの概要

アップロードした画像をシルエット風のイラストに変換し、それをダウンロードできるアプリです。
下のイラストはアップロードした画像とシルエット風に変換した画像です。

シルエット処理前と処理後

アプリ実装の流れ

アプリは以下の流れで作成していきます。
Rustで画像処理を実装し、そのコードをWebAssemblyにコンパイルしてNext.jsで動くように調整します。動くことを確認すると、Next.jsを提供するVercelにデプロイして公開します。

アプリ実装の流れ

WebAssemblyをReact、Next.jsで動かす方法のチュートリアルをしてから説明してからシルエット画像変換アプリの開発を説明します。

動画も作成したのでよかったらみて見てください!

WebAssembly&Next.jsの連携チュートリアル

この章では簡単なWebassemblyをNextjsで動かしていきます。

WebAssemblyのセットアップ

デスクトップに「silhouette-app」フォルダを作成します。
名前は任意なので好きな名前でフォルダを作成してもいいです。

~/Desktop $ mkdir silhouette-app
~/Desktop $ cd silhouette-app 

WebAssembly用のフォルダを作成

次はWebAssemblyで作成するプロジェクトを
プログラムはRustで書いていきます。

まずはRustのCargoツールを使用して新しいプロジェクトを作成します。
--libフラグをつけることでライブラリ(Library)プロジェクトを作成できます。
webassembly部分は任意なので好きな名前を入れましょう。

~/Desktop/silhouette-app $ cargo new --lib webassembly
     Created library `webassembly` package

webassemblyフォルダに移動して必要なライブラリをインポートします。
RustのCargoパッケージマネージャーを使用して、wasm-bindgenという外部ライブラリを追加します。

~/Desktop/silhouette-app/webassembly $ cargo add wasm-bindgen

wasm-bindgenは、Rustで書かれたコードをWebAssemblyとJavaScriptの間で相互にやり取りするためのライブラリです。Rustの関数やデータ型をJavaScriptから呼び出すための機能や、JavaScriptの関数やオブジェクトをRustから使用できるようにする機能を提供しています。

設定ファイルであるCargo.tomlを修正していきます。Cargo.tomlはプロジェクトのメタデータや依存関係が設定されています。

webassembly/Cargo.toml

[package]
name = "webassembly"
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.92"

[package]セクションでは、プロジェクトの基本的な情報を定義しています。nameはプロジェクトの名前、versionはプロジェクトのバージョン、editionはRustのエディションを指定します。このセクションには、他にもライセンスや作者などの情報を含められます。

[lib]セクションではライブラリのビルド設定を定義していて、crate-typeは、生成されるライブラリのタイプを指定しています。
["cdylib"]は動的リンク可能なライブラリ(C dynamic library)のことで、WebAssemblyを使うときに使われます。これを設定するとRustのコードをJavaScriptとの相互があるWebAssemblyモジュールをコンパイルできます。

[dependencies]セクションでは、プロジェクトの依存関係を指定します。wasm-bindgen = "0.2.92"は、wasm-bindgenという外部ライブラリがプロジェクトの依存関係として追加されていることを示しています。このライブラリは、WebAssemblyとJavaScriptの間で相互作用するための機能を提供します。

簡単なプログラム作成

Rustで簡単なプログラムを書いてみます。
WebAssemblyとJavaScriptの間でのデータや関数のやり取りができるようにRustで関数を書いていきます。JavaScript側でalert_name関数を呼び出すことで、Webページにメッセージを表示できます。

webassembly/src/lib.rs

use wasm_bindgen::prelude::*;

#[wasm_bindgen]
extern "C" {
    pub fn alert(s: &str);
}

#[wasm_bindgen]
pub fn alert_name(name: &str) {
    alert(&format!("こんにちは {}さん!", name));
}

use wasm_bindgen::prelude::*;:
wasm_bindgenライブラリの前提条件を使用することを宣言しています。これにより、wasm_bindgenライブラリの機能を簡単に使用できます。

#[wasm_bindgen]
RustのコードをWebAssemblyモジュールとして公開するための属性です。この属性を関数の上に付与することで、その関数がJavaScriptから呼び出せるようになります。

#[wasm_bindgen] extern "C" { pub fn alert(s: &str); }:
このブロックはRustからJavaScriptの関数を呼び出す方法を指定しています。extern "C"は、この関数がC言語の関数として扱われることを示しています。alertはJavaScriptのグローバル関数で、Rustのコードから呼び出せるようにしています。

#[wasm_bindgen] pub fn alert_name(name: &str) { ... }:
このブロックは、Rustで定義されたalert_nameという関数です。この関数は、引数として文字列nameを受け取り、それをフォーマットしてJavaScriptのalert関数を呼び出します。

format!
Rustのマクロの1つであり、文字列を構築するために使用されます。このマクロは、複数の値や変数を含む文字列を簡単に生成するための便利な方法を提供します。

プログラムをビルド

以下のコマンドを実行して作成したプログラムをビルドし、WebページでRustで書かれたコードを実行する準備をします。

~/Desktop/silhouette-app/webassembly $ wasm-pack build --target web

wasm-pack
Rustで書かれたプロジェクトをWebAssemblyにビルドするためのツールです。このツールは、プロジェクトのビルド、パッケージング、および公開に関連するさまざまなタスクを処理します。

build
wasm-packのサブコマンドの1つであり、プロジェクトをビルドするために使用されます。このサブコマンドを実行すると、RustのソースコードがWebAssemblyにコンパイルされます。

--target web
ビルドされたWebAssemblyファイルがブラウザーで実行できる形式になります。これにより、WebページでWebAssemblyを使用するための準備が整います。

ビルドしたら以下のフォルダとファイルが生成されます。

webassembly/pkg
├── package.json
├── webassembly.d.ts
├── webassembly.js
├── webassembly_bg.wasm
└── webassembly_bg.wasm.d.ts

これらのファイルは、WebAssemblyプロジェクトをブラウザーで使用します。

次はフロントエンドでwasmを使用できるようにします。

フロントエンド用のフォルダを作成

Next.jsアプリケーションのプロジェクトを作成します。
npx create-next-appコマンドを実行すると、新しいNext.jsプロジェクトの作成に関するいくつかの質問が表示されます。これらの質問に回答すると、create-next-appが指定されたディレクトリ内に新しいNext.jsプロジェクトを作成し、必要なファイルやディレクトリを生成します。

~/Desktop/silhouette-app $ npx create-next-app 
Need to install the following packages:
  create-next-app@14.2.2
Ok to proceed? (y) y
✔ What is your project named? … frontend
✔ Would you like to use TypeScript? … Yes
✔ Would you like to use ESLint? …  Yes
✔ Would you like to use Tailwind CSS? … No 
✔ Would you like to use `src/` directory? …  Yes
✔ Would you like to use App Router? (recommended) … No 
✔ Would you like to customize the default import alias (@/*)? … No
Creating a new Next.js app in ~/Desktop/silhouette-app/frontend.

frontendフォルダに移動してNext.jsを起動し、Next.jsを起動します。

~/Desktop/silhouette-app $ cd frontend 
~/Desktop/silhouette-app/frontend $ npm run dev

> frontend@0.1.0 dev
> next dev

  ▲ Next.js 14.2.2
  - Local:        http://localhost:3000

 ✓ Starting...
 ✓ Ready in 4.7s

http://localhost:3000にアクセスし、Next.jsの画面が出れば成功です。

wasmのプログラムを動かす

webassemblyでビルドしたファイルをフロントエンドにコピーします。

~/Desktop/silhouette-app/frontend $ cp -R ../webassembly/pkg ./src 

index.tsxを以下のように書き換えます。

frontend/src/pages/index.tsx

import init,{alert_name} from '../pkg/webassembly'
import { useEffect, useState } from "react";

export const Home = ()=>{
  useEffect(()=>{
    init()
  },[])

  const [name,setName] = useState<string>('')
  return (
    <>
    <input type="text" onChange={(e)=>setName(e.target.value)}/>
    <button onClick={()=>alert_name(name)}>挨拶する</button>
    </>
  )

}

export default Home

import init, { alert_name } from '../pkg/webassembly':
webassemblyパッケージからinit関数とalert_name関数をインポートしています。init関数は、WebAssemblyモジュールを初期化するために呼び出され、alert_name関数は、Rustで定義された関数を呼び出しています。

import { useEffect, useState } from "react";
この行は、ReactからuseEffectフックとuseStateフックをインポートしています。これらのフックは、Reactコンポーネントで副作用を処理するために使用されます。

export const Home = () => { ... }
この行から始まるブロックは、Homeという名前のReact関数コンポーネントを定義しています。このコンポーネントは、ユーザーが名前を入力し、ボタンをクリックすると、入力された名前を含む挨拶を表示します。

useEffect(() => { init() }, []):
init()useEffect内に配置されているのは、Reactコンポーネントがマウントされた直後にWebAssemblyモジュールを初期化するためです。

ReactのuseEffectフックは、コンポーネントのレンダリング後に副作用を実行するために使用されます。この場合、init()関数は初回のレンダリング後に1度だけ呼び出され、それ以降は再度呼び出されません([]が依存リストとして渡されているため)。

WebAssemblyモジュールの初期化は、リソースを確保したり、他の初期化処理を行ったりするために必要です。これをコンポーネントのレンダリング後に行うことで、Reactのレンダリング処理に影響を与えることなく、初期化を行うことができます。

const [name, setName] = useState<string>(''):
この行は、useStateフックを使用して、名前の状態を管理します。初期値は空の文字列''です。

<input type="text" onChange={(e) => setName(e.target.value)} />
この行は、テキスト入力フィールドをレンダリングします。ユーザーがテキストを入力すると、setName関数が呼び出され、入力された値が状態にセットされます。

<button onClick={() => alert_name(name)}>挨拶する</button>
 この行は、ボタンをレンダリングします。ボタンがクリックされると、alert_name関数が呼び出され、入力された名前を引数として渡します。

ユーザーインタラクションを処理し、Reactの状態とプロパティを使用してコンポーネントの動的なレンダリングを行います。

これでReactコンポーネントでWebAssemblyを使用して名前入りの挨拶をするアプリができました。

補足:MUI適用で見た目を整える

MUIをインポートし、index.tsxを書き換えるといい感じのフォームができます。

~/Desktop/silhouette-app/frontend $ npm install @mui/material @emotion/react @emotion/styled

index.tsxを書き換えます。

frontend/src/pages/index.tsx

import { Button,Box ,TextField} from '@mui/material';
import { useEffect, useState } from "react";
import init, { alert_name } from '../pkg/webassembly';

export const Home = ()=>{
  useEffect(()=>{
    init()
  },[])

  const [name,setName] = useState<string>('')
  return (
    <Box
    sx={{
      marginTop: '20%',
      display: 'flex',
      flexDirection: 'column',
      alignItems: 'center',
    }}
  >
    <TextField type="text" onChange={(e)=>setName(e.target.value)}/>
    <Button sx={{mt:2}}variant="contained" onClick={()=>alert_name(name)}>挨拶する</Button>
    </Box>
  )

}

export default Home

シルエット画像変換アプリ

それではここからはWebAssemblyでシルエット変換アプリを作っていきます。この章では「WebAssembly&Next.jsチュートリアル」で作成したプロジェクトをそのまま使います。

WebAssemblyのライブラリをインポート

まずは必要なライブラリをインポートします。

ここから先は

10,823字 / 8画像

¥ 500

サポートよろしくお願いいたします! いただいたサポートの一部ははクリエイターとしての活動費に使わせていただきます! ※ サポートの一部は子供たちの教育などの団体に寄付する予定です。