元画像は Jpeg なので、RGB の 3 色のみを扱う。
こちらの Ruby バージョンをとりあえず Java にポーティング。
今回縮小のみなので、拡大部分を削除。実行してみると 6 秒前後かかった・・・
もう少し早くならないかなと、さらにコメントのリンク先を読んでみる。
何をやっているかというと、中心を 1 として距離によって重みを付けるということのようだ。
| 0.3 | 0.5 | 0.3 |
|-----+-----+-----|
| 0.5 | 1.0 | 0.5 |
|-----+-----+-----|
| 0.3 | 0.5 | 0.3 |
といった具合に。
重みを計算するのが lanczos 関数。
lanczos2 や lanczos3 の数字は距離で、縮小画像のあるピクセルを計算するために、対応する元画像ピクセルからどれくらいの範囲を参照するかを決めるものっぽい。
となると、縮小率と距離の数字が決まれば、対象のピクセルがどこであろうと、重みは一緒?
というわけで、先に上記のような重みの表を作成しておき、各ピクセル計算時はそれを使用すれば計算量は減るだろう。
中心からのオフセットと重みを保持するクラス
public class Weight { public final int offsetX; public final int offsetY; public final double weight; public Weight(int x, int y, double w) { offsetX = x; offsetY = y; weight = w; } }
重みの一覧と合計を保持するクラス
public class WeightFilter { public final double weightTotal; public final Weight[] weightList; public WeightFilter(double sumw, Weight[] weights) { weightTotal = sumw; weightList = weights; } }
lanczos 関数
public static double lanczos(double x, double n) { if (x == 0.0) return 1.0; if (Math.abs(x) >= n) return 0.0; return Math.sin(Math.PI * x) / Math.PI / x * (Math.sin(Math.PI * x / n) / Math.PI / x * n); }
縮小率と距離で重み一覧作成
縦・横別でなく斜めも距離を計算してみるpublic static WeightFilter getReduceFilter(double scale, int distance) { double bdx0 = 0 + 0.5, bdy0 = 0 + 0.5; int ss = (int)((bdx0 - distance) / scale), se = (int)((bdx0 + distance) / scale); double sumw = 0.0; List<Weight> weightList = new ArrayList<>((se - ss) * (se - ss)); for (int sy = ss; sy <= se; ++sy) { for (int sx = ss; sx <= se; ++sx) { double xl = (sx + 0.5) * scale - bdx0; double yl = (sy + 0.5) * scale - bdy0; double w = lanczos(Math.sqrt(xl * xl + yl * yl), distance); if (w == 0.0) continue; weightList.add(new Weight(sx, sy, w)); sumw += w; } } return new WeightFilter(sumw, weightList.toArray(new Weight[0])); }
ユーティリティ関数も作成。
指定座標の RGB 値取得
範囲外は鏡像として取得。public static int getRGB(int[] image, int x, int y, int w, int h) { x = Math.abs(x); y = Math.abs(y); if (x >= w) x = w + w - x - 1; if (y >= h) y = h + h - y - 1; return image[y * w + x]; }
0 - 255 の範囲で色値取得
public static int getColor(double val, double sumw) { if (sumw != 0.0) val /= sumw; if (val < 0.0) return 0; if (val > 255.0) return 255; return (int)val; }
縮小処理
public static BufferedImage reduce3(BufferedImage image, int n, int dw, int dh) int sw = image.getWidth(), sh = image.getHeight(); double scale = (double)dw / sw; BufferedImage thumb = new BufferedImage(dw, dh, image.getType()); WeightFilter filter = getFilter(scale, n); double sumw = filter.weightTotal; int[] colors = image.getRGB(0, 0, sw, sh, null, 0, sw); for (int dy = 0; dy < dh; ++dy) { for (int dx = 0; dx < dw; ++dx) { double dr = 0.0, db = 0.0, dg = 0.0; int bsx = (int)(dx / scale), bsy = (int)(dy / scale); for (Weight weight : filter.weightList) { int rgb = getRGB(colors, bsx + weight.offsetX, bsy + weight.offsetY, sw, sh); dr += ((rgb >> 16) & 0xFF) * weight.weight; dg += ((rgb >> 8) & 0xFF) * weight.weight; db += (rgb & 0xFF) * weight.weight; } thumb.setRGB(dx, dy, (getColor(dr, sumw) << 16) | (getColor(dg, sumw) << 8) | getColor(db, sumw)); } } return thumb; }
呼び出し
lanczos2 と lanczos3 で縮小。long start3_1 = System.currentTimeMillis(); BufferedImage reduce3_1 = reduce3(base, 2, 120, 90); System.out.println("reduce3_1: " + (System.currentTimeMillis() - start3_1)); outputImage("reduce3_1.jpg", reduce3_1); long start3_2 = System.currentTimeMillis(); BufferedImage reduce3_2 = reduce3(base, 3, 120, 90); System.out.println("reduce3_2: " + (System.currentTimeMillis() - start3_2)); outputImage("reduce3_2.jpg", reduce3_2);
結果
lanczos2: 126.3mslanczos3: 233.0ms
元画像と左から、パターン1 3段階、パターン2 3段階、lanczos2、lanczos3
大分早くなった。JDK に比べると遅いが画質はいい。
一度作成してた後は、キャッシュするようにすればいいので、満足。