ブログ › 「1MB以下に」が思ったより難しい理由
書類をオンラインで提出するとき「写真は1MB以下」という条件によく出会います。多くの画像編集ツールは品質を1から100までのスライダーで調整させますが、ここで困ります。1MBにするには品質をいくつにすればいいのでしょうか。
答えは、計算では分かりません。保存してみるまで誰にも分かりません。
JPEGの品質値は、圧縮の過程でどれだけ細部を捨てるかを決める係数です。容量を指定する値ではありません。ですから同じ品質80でも、結果のファイルサイズは写真ごとに大きく異なります。
単色の背景に文字だけの画像は、そもそも捨てる情報が少ないので品質80でも95でもファイルは小さいままです。逆に葉が生い茂った風景写真は、隣り合うピクセルがばらばらなので圧縮が効きにくく、同じ品質でも数倍大きくなります。ノイズの多い夜間の写真も同様です。
品質と容量の関係は非線形でもあります。90から80に下げると容量は大きく減りますが、50から40に下げてもほとんど減りません。すでに捨てられるだけ捨てた状態だからです。ですから「半分にするには品質を半分に」といった直感は通用しません。
公式で解けないなら、残る方法は試すことです。ただし1から100まで全部試すわけにはいかないので二分探索を使います。これが成り立つのは1つの性質のおかげです — 品質を上げれば容量は必ず増える。値が単調に増えるので、真ん中を試して範囲を半分に絞っていけます。
全部試せば60回保存するところを6〜7回で終えます。しかも単に「目標以下」を探すのではなく、目標を超えない範囲で最も高い品質を探します。900KBにできるのに、わざわざ400KBにして画質を捨てる理由はありませんから。
探索範囲の下側を1ではなく35で止めています。それより下げるとJPEG特有の四角いブロックが目立ち始めるからです。8×8ピクセル単位で圧縮する方式なので、情報を捨てすぎるとその境界が格子として現れます。
品質20の1MBの写真と、解像度を70%に縮めた品質75の1MBの写真なら、後者がほぼ常に優れています。ですから品質をさらに削る代わりに、解像度を下げるほうへ移ります。
解像度を下げると決めたら、次の問いが生まれます。何パーセントに縮めれば目標に届くのか。ここでは1つの近似を使います — ファイルサイズはピクセル数におおよそ比例する。
縦横をそれぞれs倍すると、ピクセル数はs²倍になります。ですから今3MBのファイルを1MBにしたければピクセル数を1/3にする必要があり、倍率はその平方根の約0.58になります。この計算1回で試行錯誤を大きく減らせます。
ですからこの推定で一発で当てようとはしません。推定した倍率で改めて品質の二分探索を回し、それでも目標に届かなければ0.8倍ずつさらに縮めて最大5回繰り返します。その5回でほとんどが収まります。
目標が極端に小さい場合 — たとえば4,000万画素の写真を50KBに — 5回回しても到達できません。このとき黙って失敗したり無限に縮め続けたりする代わりに、最も小さくできた結果を返し、目標に届かなかったことを伝えます。目標を調整するか、その結果を使うかを利用者が判断できる必要があるからです。
圧縮の探索ロジックは実際のエンコードと分けてあります。「倍率と品質を受け取って結果のサイズを返す関数」を外から注入する構造です。実サービスではブラウザのキャンバスがその役割を担いますが、テスト時には偽のエンコーダーを差し込めます。
おかげでブラウザを立ち上げずに探索ロジックだけを検証できます。二分探索が本当に最大の品質を見つけるか、目標に届かないとき最小の結果を返すか、といったことをです。画像処理コードで遅く不安定になりがちなのがまさにこの検証で、エンコーダーを注入式にするとその問題が消えます。
目標容量をKBで指定して圧縮する
ここで説明した探索がブラウザの中でそのまま動きます。