猫の手ツール

Algorithm & UI/UX

Canvas上での柔軟なスタンプ描画とマルチデバイス座標変換

本ツールでは、サーバーへの画像アップロードを一切行わず、ブラウザ内のみで高度なスタンプ合成を実現しています。その核となるCanvas操作の最適化と、直感的な操作を支えるアルゴリズムについて紐解きます。

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

現行コードの確認日:

入力
写真と日付・短い文章などを入力します。
処理
写真に余白や文字、スタンプ表現をレイヤーの順に重ね、1枚の記録画像へまとめます。
出力
保存は長辺最大 1500px の JPEG(品質指定 0.95)です。

まず確認する点:端末にあるフォントで字形が変わることがあります。文字数が増えたときの行や余白の収まりを確認します。

仕上がりの例

写真に手書き風のメモを添えた例です。

加工前・加工後
左は愛猫2匹が並ぶ写真。右は写真の左上に小さなメモを加えた画像。

加工前・加工後

左は愛猫2匹が並ぶ写真。右は写真の左上に小さなメモを加えた画像。

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

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

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

記録の入力画像
800×667px (JPEG)
記録の出力画像
800×667px (JPEG)
保存済み記録の主な操作・出力条件
画面の項目記録の値現行コードの初期値・範囲
記録で選択されていた項目、ふせん風—
ログの内容(すべて編集可能)2026.10.8 本日もソファを占拠。 おやつの時間だけ起床。初期値 現在日時とサンプル文章(初期化処理で設定)
文字の大きさ100初期値 100 / 範囲 50〜250

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

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

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

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

1. マルチライン・テキストの動的計測と自動レイアウト

ユーザーが入力した任意の行数のテキストに対し、文字幅に合わせたサイズのスタンプ背景(ふせん)を描画するため、Canvas APIの measureText を利用した動的なサイズ計算を行っています。 単に文字を描画するだけでなく、全行の中で最も長い行の幅を基準にボックスのサイズを決定し、フォントスケールに合わせた余白(padding)を動的に割り当てています。

複数行の最大幅を使ってふせんの大きさを求める例です。ctx のフォントを実際の描画とそろえます。

function noteBox(ctx, text, fontSize) {
  ctx.font = fontSize + 'px sans-serif';
  const lines = text.split('\n');
  const width = Math.max(...lines.map(line => ctx.measureText(line).width));
  return { width: width + fontSize * 2,
    height: lines.length * fontSize * 1.5 + fontSize };
}

2. 解像度依存を排除する座標変換アルゴリズム

Canvasの内部解像度(描画サイズ)と、画面上の表示サイズ(CSSサイズ)が異なる場合、マウスやタッチの座標をそのまま使うとスタンプの位置がズレてしまいます。 これを防ぐため、getBoundingClientRect で取得した表示領域と実際の解像度の比率をリアルタイムに計算し、座標を正規化するロジックを実装しています。

3. クライアントサイドでのHEIC変換とリサイズ処理

iPhone等の最新デバイスで撮影されたHEIC形式の画像をそのままブラウザで扱うため、heic2any を用いた動的な変換処理を行っています。 また、高解像度画像によるブラウザのメモリ枯渇を防ぐため、アスペクト比を維持しつつ、長辺 1500px 以下に縮小します。読み込み時のメモリ不足まで完全に防ぐものではありません。

出力の仕様と確認する点

改行で行を分け、最も長い行の幅を基準に背景を作ります。自動折り返しや画像内に収める縮小はないため、長文は手動で改行するか文字サイズを小さくします。

基準文字サイズは画像高の 2.5%(最低 16px)に、50〜250% のサイズ設定を掛けます。ふせん風では背景と上辺のアクセントを描き、シンプルでは文字を直接写真に重ねます。

保存は長辺最大 1500px の JPEG(品質指定 0.95)です。スタンプはドラッグで移動できますが、画像外へ出た文字は保存時も欠けます。編集できる文字レイヤーを含む形式ではありません。