0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

高性能なブラウザ完結型画像加工ツールの裏側:オフスクリーンCanvasとDual-Maskアルゴリズムによる最適化

0
Posted at

今回は、ブラウザ上で動作する高機能なモザイク・ぼかしツール「BrushMosaicPro」を開発する中で採用した、いくつかの技術的な工夫について解説します。

絵師やクリエイターが「安全に」「手軽に」投稿用画像を加工できることを目指したこのツールには、フロントエンドならではの最適化が詰まっています。


1. オフスクリーンCanvasの活用:非破壊的な動的パラメータ調整

このツールの最大の特徴は、**「一度塗った後からでも、スライダーでモザイクの粗さやぼかし強度を自在に変更できる」**点です。これを実現するために、表示用のCanvasとは別に、メモリ上に保持された複数の「計算用(オフスクリーン)Canvas」をフル活用しています。

アーキテクチャ

  • Source Layer: オリジナルの画像データ(不変)
  • Effect Layers: オリジナル画像に「全力の全体モザイク」および「全力の全体ぼかし」を施したオフスクリーンCanvas
  • Mask Layers: ユーザーがブラシでなぞった軌跡を保存するCanvas

なぜ軽いのか?

ユーザーがスライダーを動かした際、Canvas全体を再計算して描画し直すと非常に重くなります。本ツールでは、パラメータ変更時にオフスクリーンのEffect Layerだけを更新し、表示Canvasにはそれらを「マスク」として合成するだけで済むため、高解像度の画像でもヌルヌルと動作します。


2. Dual-Mask構造のアルゴリズム:1ストロークで2つの処理を制御

「モザイクをかけた境界線がくっきりしすぎて不自然」という問題を解決するため、本ツールではDual-Mask構造を採用しています。

合成のロジック

1つのストロークデータ(mousedownからmouseupまでの一連の座標)に対し、内部では以下の2つのマスクを同時に生成しています。

  1. Core Mask (Mosaic用): ブラシサイズ通りの領域
  2. Wide Mask (Blur用): ブラシサイズに「パディング(余白)」を加えた一回り大きな領域

これを以下の数学的な優先順位で合成しています。

// 簡略化した合成ロジック
ctx.drawImage(originalImage, 0, 0);

// 1. 周囲をぼかす (Wide Mask + Blur Layer)
tempCtx.globalCompositeOperation = 'source-over';
tempCtx.drawImage(wideMask, 0, 0);
tempCtx.globalCompositeOperation = 'source-in';
tempCtx.drawImage(blurLayer, 0, 0);
ctx.drawImage(tempCanvas, 0, 0);

// 2. 中心をモザイク化 (Core Mask + Mosaic Layer)
tempCtx.globalCompositeOperation = 'source-over';
tempCtx.drawImage(coreMask, 0, 0);
tempCtx.globalCompositeOperation = 'source-in';
tempCtx.drawImage(mosaicLayer, 0, 0);
ctx.drawImage(tempCanvas, 0, 0);

この処理により、**「中心はしっかり隠しつつ、外側に向かって自然に周辺画像と馴染ませる」**という高度な加工が、ひと塗りで完結します。


3. Viteでのビルド最適化:サーバー不要のスタンドアロン配布

「ツールをダウンロードしてローカルで直接開きたい」というニーズに応えるため、Viteの設定を極限までチューニングしました。

詰まりどころ:絶対パスとCORS

モダンなWeb開発(Viteなど)では通常、アセットは絶対パス(/assets/...)として管理されます。しかし、これをブラウザで直接(file://プロトコルで)開くと、ブラウザのセキュリティ制限によって外部JSの読み込みがブロックされてしまいます。

解決策

  1. 相対パス設定: base: './' を指定し、すべてのリソースを相対パスで解決させます。
  2. シングルファイル化: vite-plugin-singlefile を導入。JSやCSSをすべて index.html 内にインライン展開し、アセット読み込みという概念そのものを消去しました。
  3. Polyfillの無効化: modulePreload.polyfill: false を設定。file:// 環境では不要(かつエラーの原因となる)なプリロード処理を排除しました。

これにより、**「ダブルクリックするだけですぐに起動する、1つのHTMLファイル」**としての書き出しを実現しました。


技術スタックまとめ

  • Frontend: React (Hooksを活用した状態管理)
  • Canvas Control: 2D Context API (合成モードをフル活用)
  • Icons: Lucide-React
  • Build Tool: Vite + TypeScript
  • Optimization: vite-plugin-singlefile

サーバーに画像を一切送信せず、ブラウザのメモリのみで完結するプライバシー性能。そしてオフスクリーンCanvasによる軽快な操作性。これらが合わさることで、クリエイターにとって「痒い所に手が届く」ツールが完成しました。

今後はスマホ版をリリースできるように、さらなる改変に取り組みたいと思います!

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?