WP TIPS に戻る

JPEG 画像を綺麗に速くリサイズ(拡大縮小)する

サンプルプロジェクト ImageResize_Quality.zip

アプリを作っていると元の画像からサムネイルを作成して表示する事もあると思いますが、実は画像の読み込み方法やサムネイルの作成方法次第で「画質や読み込み速度」に大きな差が出ます。

良くない例

以下のコードでは、PictureDecoder.DecodeJpeg メソッドを使用してストリームの JPEG 画像を読み込み、WritableBitmap の Save メソッドで dststram に出力しています。
最終的に dststream にはサイズが変更された Jpeg が入るので、Bitmap.SetSource(dststream) として Image.Source に渡せば画像の表示が可能です。

実は、この方法は画像の読み込み&リサイズの一番良くない例です。

2000x1500 → 200x150ピクセルにリサイズするために 120ms 程度の時間がかかり*1、リサイズも NearestNeighbor で行われるので非常に汚い画像になってしまいます。

NearestNeighbor
NearestNeighbor.jpg

まず PictureDecoder.DecodeJpeg は第2、第3引数に幅と高さを指定することで自動的に補間をした Jpeg 画像を出力してくれますが、このコードではサイズを指定していないので WritableBitmap には 2000x1500 の画像そのものが入ります。ということはその分メモリと読み込む時間を多く消費します。
次に WritableBitmap.SaveJpeg で出力する画像のサイズを指定していますが、WritableBitmap では最近傍補間もしくは直線補間でリサイズされるので、出力結果は NearestNeighbor のように、かなりがたがたで汚い画像になります。

public static void ImageResize_LowQuality(Stream srcstream, Stream dststream, Size origSize, int size)
{ 
   // こちらのコードは遅いし汚い。10回で 1264ms。

   // 画像を JPEG にデコードする
   WriteableBitmap wb = PictureDecoder.DecodeJpeg(srcstream);

   // サムネイルのサイズを取得する
   Size s = GetResize(wb.PixelWidth, wb.PixelHeight, size, size);

   // サムネイルを JPEG で保存する
   wb.SaveJpeg(dststream, (int)s.Width, (int)s.Height, 0, 100);
}
 
public static Size GetResize(int pixelWidth, int pixelHeight, int targetWidth, int targetHeight)
{
   // 横長
   if (pixelWidth > pixelHeight)
   {
       double per = (double)pixelWidth / (double)targetWidth;
       return new Size(pixelWidth / per, pixelHeight / per);
   }

   // 縦長
   else
   {
       double per = (double)pixelHeight / (double)targetHeight;
       return new Size(pixelWidth / per, pixelHeight / per);
   }
}

良い例

では一番高速で高画質に読み込む方法は、元画像のサイズからリサイズ後の画像サイズを計算しておいて、Extensions.LoadJpeg/SaveJpeg メソッドにそのサイズを与える方法です。

JPEG の画像サイズを知る の GetJpegSize メソッドで取得した元画像のサイズから「縦横比を維持した縮小サイズ」を計算します(GetResizeメソッド)。それからそのサイズの WritableBitmap を生成し Extensions.LoadJpeg メソッドで「縮小された画像を取得します。
これで WritableBitmap に入った画像データはリサイズ後のデータになるため、最小限のメモリ消費で済みます。また LoadJpeg メソッド実行時に既に Bicubic もしくは Bilinear で補間がされており、綺麗な画像を得ることがます。

またこのコードにすることで、読み込み&リサイズ処理が 120ms→43ms へと3倍高速になりました。

NearestNeighborBilinear
NearestNeighbor.jpgBilinear.jpg
public static void ImageResize(Stream srcstream, Stream dststream, Size origSize, int size)
{
   // こちらのコードの方が速い。10回で 434ms。

   // リサイズするサイズを取得する
   Size s = GetResize((int)origSize.Width, (int)origSize.Height, size, size);

   // 画像を JPEG にデコードする
   WriteableBitmap wb = new WriteableBitmap((int)s.Width, (int)s.Height);
   Extensions.LoadJpeg(wb, srcstream);

   // サムネイルを JPEG で保存する
   Extensions.SaveJpeg(wb, dststream, (int)s.Width, (int)s.Height, 0, 100);

   // こちらのコードの方が遅い。10回で 654ms。

   // リサイズするサイズを取得する
   //Size s = GetResize((int)origSize.Width, (int)origSize.Height, size, size);

   // 画像を JPEG にデコードする
   //WriteableBitmap wb = PictureDecoder.DecodeJpeg(srcstream, (int)s.Width, (int)s.Height);

   // サムネイルを JPEG で保存する
   //wb.SaveJpeg(dststream, (int)s.Width, (int)s.Height, 0, 100);
}

※「PictureDecoder で読み込み・WritableBitmap で出力」の組み合わせよりも、Extensions.LoadJpeg/SaveJpeg の組み合わせの方が高速に処理できました。内部で何かが違うんですかね?

画像

各種補間の参考例を挙げておきます。
この画像は WinForm の Graphics.InterpolationMode で設定した物で、WP7 では補間モードの設定はできないようです。

NearestNeighbor
NearestNeighbor.jpg
BicubicBilinear
Bicubic.jpgBilinear.jpg
HighQualityBicubicHighQualityBilinear
HighQualityBicubic.jpgHighQualityBilinear.jpg

*1 Core i7 860で