猫の手ツール

Algorithm & UI/UX

明るい画素に光の筋を重ねる:
キラキラ・クロス加工の裏側

夜景やジュエリーを輝かせる「クロスフィルター」の効果を、数式とCanvas描画だけで再現。サーバーを介さない高速な輝度検知と、動的なベクター描画の仕組みを紐解きます。

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

現行コードの確認日:

入力
写真を選び、輝きの強さや大きさを調整します。
処理
画像から間引いて明るい位置を読み取り、条件を満たす位置へ十字の光を描きます。
出力
入力は長辺 1500px 以下、出力は JPEG(品質指定 0.95)です。

まず確認する点:すべての点を読んでいるわけではありません。細い光や小さな明部が間引き位置によって選ばれないことを、元画像と見比べます。

仕上がりの例

写真に白いキラキラの光を重ねた例です。

加工前・加工後
左は緑のかぶりものを着けた茶トラの愛猫の写真。右は猫の周囲に白い星状の光を加えた写真。

加工前・加工後

左は緑のかぶりものを着けた茶トラの愛猫の写真。右は猫の周囲に白い星状の光を加えた写真。

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

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

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

記録の入力画像
800×805px (JPEG)
記録の出力画像
800×805px (JPEG)
保存済み記録の主な操作・出力条件
画面の項目記録の値現行コードの初期値・範囲
記録で選択されていた項目十字 (4本)、クリア(白)—
キラキラの大きさ50初期値 50 / 範囲 10〜100
キラキラの数(自動検知)235初期値 235 / 範囲 200〜254

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

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

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

仕組みを読み進めるときは、Canvas APIと画像処理(画像寸法、drawImage、画素と透明度の扱い)、メモリ管理と画像処理(画素バッファ、縮小と容量予算)が参考になります。

1. 適応的サンプリングによる高速輝度検知

写真の中から「輝いている場所」を自動で見つけ出すため、ピクセルごとの輝度を解析しています。しかし、数百万画素におよぶ高解像度画像をすべて走査すると、特にモバイル端末では大きな遅延が発生します。

本ツールでは、画像の解像度に応じて解析間隔を動的に調整する「サンプリング・ステップ」を導入しました。これにより、調べる地点を間引きます。小さな点光源はサンプリング位置から外れて検出されない場合があります。

自動検出は RGB の単純平均で判定します。加重輝度や光源の意味を認識する処理ではありません。

function isHighlight(r, g, b, threshold = 235) {
  return (r + g + b) / 3 > threshold;
}

2. 座標変換とベクターグラデーションによる「光の筋」の生成

キラキラの描写には静止画画像を使用せず、Canvasの描画命令のみで構成されています。これにより、サイズに合わせてパスを描けるため、さらに「虹色(プリズム)」のような複雑な色変化も、数式ベースのグラデーションでリアルタイムに生成可能です。

指定された本数(4本、6本、8本)に合わせて ctx.rotate を用いながら座標系を回転させ、中心から外側へ減衰する光の軌跡を描いています。

出力の仕様と確認する点

解析間隔は max(10, floor(画像幅 / 50))px です。しきい値は 200〜254 で、下げるほど明るい点が多く対象になります。白い壁や空にも多数の光が付くため、必要に応じてしきい値を上げます。

4・6・8 本の光の筋を選べます。タップ追加した光は自動検出の 1.5 倍の大きさで描きます。自動検出が細いハイライトを見逃した場合の補助に使えます。

入力は長辺 1500px 以下、出力は JPEG(品質指定 0.95)です。screen 合成の三角形とグラデーションによる装飾で、実際のクロスフィルターの回折やレンズ性能を計算するものではありません。