代表的な縮小向けアルゴリズムの『面積平均法』を紹介します。
面積平均法の概要
面積平均法(平均画素法やAreaAvarageとも) はほぼ縮小専用の手法で、高速ながら比較的画質が良いのが特徴です。
その原理は非常に単純で、例えば3/5のサイズに縮小するのであれば出力1ピクセルあたり元画像から5/3ピクセル分のRGB成分を取得して平均を出すというものです。
わかりやすい解説は下記サイトに載っていますので割愛します。
処理の途中に縦横のピクセル数について元画像と縮小後の最小公倍数をとったサイズの中間画像を作成する必要がありますが、実際に作成するとえらい処理量とメモリ消費が発生して本末転倒となります。
しかし中間画像というのは元画像の単純な引き伸ばしであり、色を混ぜ合わせて新たな色を作り出すわけではありません。単にRGBの配合割合を取得するためだけに使います。なので、実データが無くても必要な値は簡単な計算で求めることができます。
このように出力よりも高解像度の中間画像を作成する技法を スーパーサンプリング と呼んだりします。
なお、面積平均法の実装はスーパーサンプリングを行わなくても可能ですが、小数を扱うことになるので、整数計算だけで済む前者の方が速度面で有利となります。
プログラム実装
C#による実装例です。紹介用に用意したものなので全くチューニングしておらず、動作は非常に遅いです。
static Bitmap AreaAverage(Bitmap srcbmp, int dstWidth, int dstHeight)
{
var dstbmp = new Bitmap(dstWidth, dstHeight);
int srcWidth = srcbmp.Width;
int srcHeight = srcbmp.Height;
// ユークリッドの互除法を用いた最小公倍数(LCM)の算出関数
int gcd(int a, int b) => b == 0 ? a : gcd(b, a % b);
int lcm(int slen, int dlen) => (slen * dlen) / gcd(slen, dlen);
int wlcm = lcm(srcWidth, dstWidth);
int srcTapX = wlcm / srcWidth;
int dstTapX = wlcm / dstWidth;
int hlcm = lcm(srcHeight, dstHeight);
int srcTapY = hlcm / srcHeight;
int dstTapY = hlcm / dstHeight;
for (int dsty = 0; dsty < dstHeight; dsty++)
{
for (int dstx = 0; dstx < dstWidth; dstx++)
{
int a = 0;
int r = 0;
int g = 0;
int b = 0;
// 仮想ピクセルからサンプリング
for (int offsety = 0; offsety < dstTapY; offsety++)
{
for (int offsetx = 0; offsetx < dstTapX; offsetx++)
{
int srcx = (dstx * dstTapX + offsetx) / srcTapX;
int srcy = (dsty * dstTapY + offsety) / srcTapY;
var srcColor = srcbmp.GetPixel(srcx, srcy);
a += srcColor.A;
r += srcColor.R;
g += srcColor.G;
b += srcColor.B;
}
}
// 平均化
a /= dstTapY * dstTapX;
r /= dstTapY * dstTapX;
g /= dstTapY * dstTapX;
b /= dstTapY * dstTapX;
dstbmp.SetPixel(dstx, dsty, Color.FromArgb(a, r, g, b));
}
}
return dstbmp;
}
実行結果
評価のために東北ずん子のイラストをお借りしました。
こちらのページのzzm_a1zunko20.png(800x1131px)を480x679pxに縮小しました。
単純なアルゴリズムの割になかなかいい感じに仕上がっています。
あとがき
面積平均法は処理量の割に画質が良好なので、高速縮小アルゴリズムのスタンダードとして用いられることが多いです。
画質だけで言えばLanczos3などの方が一般的に優秀ですが、やはり速度で大きく劣ります。ですが面積平均法といえど上記のようなどストレートな安直コードだと結局遅いままなのでチューニングは必要です。
単純とはいえ多重ループが発生するので、今までと同様の高速化手法を適用するのが有効です。特に最小公倍数は場合によって非常に大きくなり、入れ子のループの回数が膨大になる可能性もあります。工夫次第でその部分は大幅にコストを抑えることもできるので、それが実現出来てこそ真価を発揮できるでしょう。
コメント