猫の手ツール

Algorithm & UI/UX

Canvasで描く「動く吹き出し」:
座標計算とイベント処理の裏側

写真に漫画のような吹き出しを合成するプロセス。Canvas APIを用いたテキスト計測、マルチデバイス対応のドラッグ操作、そして「爆発吹き出し」などを描画する幾何学的なアルゴリズムの実装について解説します。

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

現行コードの確認日:

入力
写真とセリフ、吹き出しの形・位置を選びます。
処理
図形で吹き出しと尾を描き、文字をその上へ合成します。
出力
入力は長辺 1500px 以下、保存は JPEG(品質指定 0.95)。

まず確認する点:吹き出しは1つで、しっぽの向きは固定です。長文は手動で改行し、文字やしっぽが画像端から欠けない位置へ動かします。

仕上がりの例

写真に吹き出しとセリフを加えた例です。

加工前・加工後
左は箱から顔を出した茶トラの愛猫の写真。右は猫の頭の上に白い吹き出しとセリフを加えた写真。

加工前・加工後

左は箱から顔を出した茶トラの愛猫の写真。右は猫の頭の上に白い吹き出しとセリフを加えた写真。

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

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

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

記録の入力画像
800×667px (JPEG)
記録の出力画像
800×667px (JPEG)
保存済み記録の主な操作・出力条件
画面の項目記録の値現行コードの初期値・範囲
記録で選択されていた項目楕円、白背景—
セリフを入力ここ、ぼくの席です初期値 空欄
文字サイズ32初期値 読込前のHTML定義は40。読込後は画像の長辺×0.04を切り捨て(最低20) / 範囲 最小10。読込前の最大150。読込後の最大は画像の長辺×0.2を切り捨て(最低150)

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

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

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

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

1. テキスト量に追随する「動的領域」計算

セリフの長さに合わせて吹き出しのサイズを自動調整するため、Canvasの measureText メソッドを活用しています。単に文字数で測るのではなく、改行を含めた最大幅と行高から矩形領域を算出し、楕円形の吹き出しでは文字がはみ出さないよう、幾何学的な安全係数を掛けてサイズを決定しています。

16 個の突起を持つ爆発形の頂点を求める例です。外周と内周の半径を交互に使います。

function spikyPoints(cx, cy, rx, ry) {
  return Array.from({ length: 32 }, (_, i) => {
    const scale = i % 2 === 0 ? 1 : 0.8;
    const angle = i * Math.PI / 16;
    return [cx + Math.cos(angle) * rx * scale,
      cy + Math.sin(angle) * ry * scale];
  });
}

この計算により、ユーザーが文字サイズやセリフを変更した瞬間に、文字幅と行数に応じた大きさの吹き出しが再描画されます。

2. PC/スマホ共用:座標系の正規化とドラッグ制御

マウスとタッチ操作の両方で「直感的な配置」を実現するため、入力イベントの座標をCanvas内部の座標系に正規化する処理を行っています。CSS上の表示サイズとCanvasの論理サイズが異なる場合(レスポンシブ対応時)でも、ドラッグした指の真下に吹き出しが吸い付くように計算しています。

3. 「爆発吹き出し」の幾何学的描画アルゴリズム

漫画特有の「ギザギザした爆発吹き出し」は、極座標系を利用したアルゴリズムで描画しています。円周を一定数に分割し、中心からの距離を「外側(頂点)」と「内側(谷間)」で交互に変化させながら、パス(lineTo)を繋いでいくことで、勢いのある形状を生成しています。

出力の仕様と確認する点

吹き出しは1つをドラッグして配置します。複数の独立した吹き出しを追加する機能ではありません。楕円・角丸・爆発・文字だけを選び、色と文字サイズを変更できます。

文章は手動改行で行を分け、自動折り返しはしません。楕円や爆発形では文字幅・高さに √2 を掛けた余白を設けますが、画像の内側に収まるよう自動縮小する処理はありません。

入力は長辺 1500px 以下、保存は JPEG(品質指定 0.95)。吹き出しのしっぽの向きは固定で、独立して移動できません。画像端でしっぽや文字が欠けない位置に置いてから保存します。