猫の手ツール

Rendering & Performance

Canvasによるチョーク質感の再現と
描画最適化のテクニック

「チョークで描いたような掠れた質感」を、外部テクスチャに頼らずCanvasの標準機能だけで再現。さらに、ランダムな配置を制御して「スライダーを動かしてもガタつかない」UIを実現するための設計思想を解説します。

このツールの入力から出力まで

現行コードの確認日:

入力
写真と半角カンマで区切った文字を入力し、文字の量を選びます。
処理
300個の配置をキャッシュし、白い輪郭をずらして文字を重ねます。入力文字を順番に繰り返します。
出力
出力は 1000×1000px の JPEG(品質指定 0.85)です。

まず確認する点:写真の位置を変えても文字は紙面側に留まります。文字の重なりや写真の重要な部分を覆う範囲を確認します。

仕上がりの例

写真に白いチョーク風の文字を重ねた例です。

加工前・加工後
左は緑のかぶりものを着けた茶トラの愛猫の写真。右は猫の周囲に白い手書き風の文字を加えた写真。

加工前・加工後

左は緑のかぶりものを着けた茶トラの愛猫の写真。右は猫の周囲に白い手書き風の文字を加えた写真。

掲載画像はサイトにある既存の公開作例です。保存済みの資料では運営者の愛猫の写真を使用しています。今回の確認日が、この作例を実行した日を表すわけではありません。

保存済み検証記録の条件を見る

資料の種類は「ツールの出力画像を保存した記録」です。公開作例と保存済み記録で素材や色などが異なる場合があるため、下の数値は保存済み検証記録の条件として読んでください。

記録の入力画像
800×1222px (JPEG)
記録の出力画像
1000×1000px (JPEG)
保存済み記録の主な操作・出力条件
画面の項目記録の値現行コードの初期値・範囲
記録で選択されていた項目普通—
落書きする文字(カンマ区切りで複数入力OK / 絵文字も使えます)おひるね, かわいい, ねこ初期値 エモい, 優勝, #BFF
文字の量50初期値 50 / 範囲 0〜100
チョークの濃さ80初期値 80 / 範囲 10〜100
写真のズーム100初期値 100 / 範囲 50〜300

記録では「エモい画像を生成!」から完成表示へ進んでいます。初期値と記録値を比べてから、効果を1項目ずつ変えると作用を確認しやすくなります。

資料ファイルの作成日は実行日として扱っていません。個別端末の処理速度や、ほかの写真に対する検出・加工の精度を測定した資料でもありません。

実装の確認箇所:src/pages/image/chalk-scribble/index.astro。以下のコード抜粋と出力条件は、この本番実装に基づいて説明します。

仕組みを読み進めるときは、Canvas APIと画像処理(画像寸法、drawImage、画素と透明度の扱い)、File APIとObject URL(選んだ画像の読み込みとBlob URLの解放)が参考になります。

1. 多段ストロークによる「疑似チョーク質感」の創出

チョークの質感は、不規則な掠れと厚みの変化にあります。本ツールでは、同じテキストを異なる「太さ」と「透明度」で重ねて描画する多段ストローク手法を採用しています。

メリット: 重いビットマップブラシや外部画像を使用せず、Canvasの strokeText のみで完結するため、外部のブラシ画像を用意せずに描画できます。透明度の高い太い輪郭と、不透明度の高い細い輪郭をわずかにずらして重ねることで、チョーク特有の粉っぽさを演出しています。

チョーク文字の輪郭を描く最小例です。ctx は Canvas の 2D コンテキスト、size は文字サイズです。

function chalkText(ctx, text, size, opacity = 0.8) {
  ctx.save();
  ctx.font = 'bold ' + size + 'px sans-serif';
  ctx.lineWidth = 3;
  ctx.strokeStyle = 'rgba(255,255,255,' + opacity * 0.6 + ')';
  ctx.strokeText(text, 0, 0);
  ctx.translate(1, 1);
  ctx.lineWidth = 1.5;
  ctx.strokeStyle = 'rgba(255,255,255,' + opacity * 0.9 + ')';
  ctx.strokeText(text, 0, 0);
  ctx.restore();
}

2. 座標配列のキャッシュ化による「決定論的」ランダム配置

文字を写真全体に散りばめる際、レンダリングのたびに Math.random() を呼んでしまうと、スライダー(文字量など)を動かすたびに配置が激しく入れ替わり、不快なガタつき(フリッカー)が発生します。

メリット: あらかじめ数百点分の「座標」「角度」「スケール偏差」を配列としてキャッシュ化。スライダーの値に応じて配列の「参照数」だけを増減させることで、配置の連続性を保ったまま滑らかなパラメータ調整を実現しています。

3. 高解像度写真に対応する「ガードレール」処理

最新スマホの写真は4000pxを超えることも珍しくありませんが、巨大なCanvasをそのままメモリ上で扱うとモバイルブラウザではメモリ不足になる場合があります。本ツールでは、読み込み時にアスペクト比を維持したまま、処理に最適な上限解像度へリサイズするガードレールを設けています。

メリット: 長辺 2500px 以下に縮小し、読み込み後の処理画像の画素数を減らします。また、オフスクリーンCanvasを用いた二段階処理により、プレビューを生成します。端末全体の使用量や応答速度の保証ではありません。

出力の仕様と確認する点

出力は 1000×1000px の JPEG(品質指定 0.85)です。写真の位置とズームを動かし、正方形に入る範囲を選びます。文字は写真に追従せず、正方形の上に配置されます。

文字は半角カンマで区切り、空の項目を除いて順番に繰り返します。量 100 でキャッシュ内の 300 個すべてを描画し、量 0 または空欄では文字を描きません。重なりを自動的に避ける配置ではありません。

太さ 3px と 1.5px の白い輪郭をずらす表現で、紙の粒子や実際のチョークの欠けを計算するものではありません。端末にあるフォントを使うため、同じ入力でも字形が異なる場合があります。