Algorithm & UI/UX
クッキリした境界を作る「二段階描画」とレトロ配色のロジック
ただ画像を縮小するだけでは「ボケたモザイク」になってしまいます。本ツールがいかにして「ピクセルが際立つドット絵」をフロントエンドのみで生成しているか、その手法を紐解きます。
1. 「ニアレストネイバー法」によるシャープな再拡大
一般的な画像リサイズではバイリニア法などが用いられ、周囲の画素と平均化されるため境界がボケます。本ツールでは、一度 Canvas を極小サイズに縮小して情報を間引いた後、imageSmoothingEnabled = false を設定したメイン Canvas へ描き戻しています。
これにより、拡大時に補間処理が行われず、隣接するピクセルの色がそのまま維持される「ニアレストネイバー(Nearest Neighbor)」効果をブラウザ標準機能だけで実現しています。
// 1. 小さな仮想Canvasを作成して縮小描画(情報の間引き)
const smallCanvas = document.createElement('canvas');
smallCanvas.width = // ユーザー設定に基づいた極小幅;
smallCanvas.height = // ユーザー設定に基づいた極小高;
const sCtx = smallCanvas.getContext('2d');
sCtx.drawImage(originalImage, 0, 0, smallW, smallH);
// 2. メインCanvasの補間を無効化して再拡大
tCtx.imageSmoothingEnabled = false;
tCtx.drawImage(smallCanvas, 0, 0, smallW, smallH, 0, 0, renderW, renderH);
2. 輝度ベースのパレットマッピング(GB風モード)
レトロ感を出すための「GB風」パレットは、単なる色被せではありません。まずピクセルのRGB値からグレースケール輝度を算出し、その輝度をあらかじめ定義した4つの階調(パレット)のいずれかに割り当てる「ディザリングに近い減色処理」を行っています。
この実装により、元の写真の明暗差がドット絵の「色の濃淡」として正しく変換され、昔の携帯ゲーム機のような深みのある表現が可能になります。
// 輝度(Gray)の算出とパレット割り当ての概念
const gbColors = [/* 独自のチューニングカラー4色 */];
for (let i = 0; i < data.length; i += 4) {
// 心理学的輝度公式を用いたグレースケール化
const gray = // 独自の係数による計算処理;
// 輝度しきい値に基づいてパレットインデックスを決定
const idx = gray < 64 ? 0 : gray < 128 ? 1 : gray < 192 ? 2 : 3;
data[i] = gbColors[idx][0]; // R
data[i+1] = gbColors[idx][1]; // G
data[i+2] = gbColors[idx][2]; // B
}
3. iPhoneユーザーのための HEIC クライアントサイド変換
iPhoneの標準画像形式であるHEICは、ブラウザのCanvas APIで直接読み込めないケースが多いという課題があります。本ツールでは heic2any ライブラリを活用し、ブラウザ内で JPEG への動的変換を行っています。
これを「サーバーを介さずクライアントサイドで完結」させることで、ユーザーのプライバシーを守りつつ、スマホからの操作性を劇的に向上させています。
// HEIC形式の検知と動的デコード
if (file.name.toLowerCase().match(/\.(heic|heif)$/)) {
const convertedBlob = await heic2any({
blob: file,
toType: "image/jpeg",
quality: // パフォーマンス重視の圧縮率
});
// 以降、通常の画像と同様にCanvasで処理
}
Developer's Note
このツールで一番こだわったのは、「ピクセルの一粒一粒が自立していること」です。単なるフィルター加工ではなく、一度解像度を物理的に落としてから「補間なし」で引き伸ばすという工程を経ることで、当時のゲームソフトのようなソリッドな質感を再現しました。
また、外部サーバーに一切データを送らない設計は、開発者としてのポリシーです。画像処理という重いタスクをブラウザのメモリ(Canvas)だけでやりくりするため、最大解像度の制限やプレビュー用・保存用のCanvasの使い分けなど、見えない部分でのメモリ管理を徹底しています。