Algorithm & Optimization
容量収束アルゴリズムと
ブラウザ内HEIC変換の舞台裏
就活やオンライン申請でよくある「〇〇KB以下」という厳しい指定。これを、画質を犠牲にしすぎず、かつ確実にクリアするために、本ツールが採用した「二段階探索アルゴリズム」と、プライバシーを守るための完全ローカル処理について解説します。
1. 縮尺と画質を二段階で追い込む「容量収束ロジック」
画像のファイルサイズを特定のKB数以下に抑えるには、2つの手段があります。一つは「画質(Quality)を下げること」、もう一つは「解像度(Scale)を下げること」です。本ツールでは、まず解像度を維持したまま画質をバイナリサーチ的に調整し、それでも目標に届かない場合に初めて解像度を下げるという、二段階の試行アルゴリズムを採用しています。
メリット: 小さなKB数を指定された場合でも、単にモザイクのような低画質にするのではなく、適切なサイズまで「縮小」することで、文字などが読み取りやすい状態(高精細な縮小画像)を維持したまま容量制限をクリアできます。
// 目標サイズに追い込むための繰り返し演算ロジック(概要)
async function findBestSettings(img, targetBytes) {
let scale = // 独自の初期スケール値;
// 最大数回のスケーリング試行
for (let s = 0; s < // 独自の最大試行回数; s++) {
// 現在の解像度で画質(Q)を0.1〜0.95の間で探索
let lowQ = 0.1, highQ = 0.95;
for (let q = 0; q < // 独自のQ探索回数; q++) {
const currentQ = (lowQ + highQ) / 2;
const dataUrl = canvas.toDataURL('image/jpeg', currentQ);
const size = // Base64からバイトサイズを算出;
if (size <= targetBytes) {
// 目標以下なら、もう少し画質を上げられるか試す
lowQ = currentQ;
} else {
// 目標オーバーなら画質を下げる
highQ = currentQ;
}
}
// この解像度で目標以下が見つかれば確定
// 見つからなければスケールをさらに下げて再試行
scale *= // 独自の減衰係数;
}
} 2. 動的インポートによる「ブラウザ完結型」HEIC変換
iPhoneの標準形式であるHEICは、ブラウザの標準機能(Canvas等)では直接扱えません。本ツールでは、heic2any ライブラリを必要な時だけ import() する仕組みを導入し、クライアントサイドのみでJPEGへの変換を実現しています。
メリット: サーバーサイドに重い変換処理を任せる必要がないため、アップロード待機時間がなく、かつ「顔写真などのプライベートな画像を一度もネットワークに流さない」という究極のセキュリティを担保できます。
// 必要な時だけ変換エンジンをロードする設計
if (file.name.match(/\.(heic|heif)$/i)) {
// 巨大な変換ライブラリを動的に読み込み、初期ロードを軽量化
const heic2any = (await import('heic2any')).default;
// ブラウザ内でJPEGへ非破壊変換
const convertedBlob = await heic2any({
blob: file,
toType: "image/jpeg",
quality: // 独自の初期品質設定
});
// 以降、通常のJPEGとして圧縮パイプラインへ
} 3. メモリ解放を意識した Blob オブジェクト管理
高解像度の画像を扱う際、ブラウザのメモリ(RAM)消費が問題になります。特に URL.createObjectURL で生成したURLは、明示的に解放しない限りメモリに残り続け、スマホでのブラウザ落ちの原因になります。本ツールでは、新しい画像が読み込まれるたびに古いリソースを確実に破棄するライフサイクル管理を行っています。
メリット: メモリ容量の少ない古いスマートフォンでも、複数枚の画像を連続して処理する際の安定性が飛躍的に向上します。
// メモリリークを防ぐためのリソース管理
let activeUrl = null;
function updateImage(newBlob) {
// 以前生成したオブジェクトURLがあれば即座に解放
if (activeUrl) {
URL.revokeObjectURL(activeUrl);
}
// 新しいURLを生成して保持
activeUrl = URL.createObjectURL(newBlob);
previewElement.src = activeUrl;
} Developer's Note
この圧縮ツールの開発で最も苦労したのは、「速さ」と「正確さ」のバランスです。
厳密に「目標KB」に近づけようとすると、何度もCanvasでの再描画と toDataURL によるバイト数チェックが必要になりますが、これはCPU負荷が高く、スマホでは画面が固まる原因になります。そこで、本ツールでは探索回数を数学的に最適化し、数回の試行で目標の95%付近に収束するようにチューニングを重ねました。
証明写真やエントリーシートの画像は、人に見られたくないプライベートな情報です。それを「どこにも送信せずに、あなたのスマホの中で完結させる」。この安全性の提供こそが、フロントエンドエンジニアとしてのこだわりの結晶です。