レンダリング エンジンにガンマ補正を追加しようとしています。2 つの問題があります。
1) Math.pow は本当に遅いです (毎秒数千回呼び出されることに比べて)。そのため、その場で計算する代わりに、アクセスできる事前計算済みのガンマ テーブルを作成する必要があります。(これは追加情報であり、実際の問題ではありません)。
2) 現在、これを行うには、整数ピクセルをアンパックし、RGBA チャンネルを対応するガンマ修正値に置き換えてガンマを適用し、ピクセルを再パックして画像バッファーに送り返す必要があります。パフォーマンスへの影響はひどいもの ではありませんが、60fps の固定タイムステップが約 40fps にまで低下しています (いくつかの画像がレンダリングされています)。
整数のアンパック/パックをネイティブ コードに実装しようとしましたが、パフォーマンスの向上は見られず、VM がクラッシュしました (おそらくメモリ チェック エラーですが、今は修正する必要はありません)。
ピクセルをアンパック/パックせずにガンマを適用する方法はありますか? そうでない場合、これを行うためにどの方法を使用することをお勧めしますか?
注意 BufferedImageOp を使用するとは言わないでください。遅く、画像全体でしか操作できません (ピクセル固有が必要です)。
追加情報:
ピクセルのパッケージ:
public static int[] unpackInt(int argb, int type) {
int[] vals = null;
int p1 = 0;
int p2 = 1;
int p3 = 2;
int p4 = 3;
switch (type) {
case TYPE_RGB:
vals = new int[3];
vals[p1] = argb >> 16 & 0xFF;
vals[p2] = argb >> 8 & 0xFF;
vals[p3] = argb & 0xFF;
break;
case TYPE_RGBA:
case TYPE_ARGB:
vals = new int[4];
vals[p4] = argb & 0xFF;
vals[p3] = argb >> 8 & 0xFF;
vals[p2] = argb >> 16 & 0xFF;
vals[p1] = argb >> 24 & 0xFF;
break;
default:
throw (new IllegalArgumentException(
"type must be a valid field defined by ColorUtils class"));
}
return vals;
}
public static int packInt(int... rgbs) {
if (rgbs.length != 3 && rgbs.length != 4) {
throw (new IllegalArgumentException(
"args must be valid RGB, ARGB or RGBA value."));
}
int color = rgbs[0];
for (int i = 1; i < rgbs.length; i++) {
color = (color << 8) + rgbs[i];
}
return color;
}
以前にコードを破棄しましたが、ガンマ補正に次のアルゴリズムを使用していました。
protected int correctGamma(int pixel, float gamma) {
float ginv = 1 / gamma;
int[] rgbVals = ColorUtils.unpackInt(pixel, ColorUtils.TYPE_ARGB);
for(int i = 0; i < rgbVals.length; i++) {
rgbVals[i] = (int) Math.round(255 - Math.pow(rgbVals[i] / 255.0, ginv));
}
return ColorUtils.packInt(rgbVals);
}
解決
最終的に、GargantuChet が提案した多くのアイデアを組み合わせて、かなりうまく機能しているように見える (パフォーマンスの低下なし) システムに仕上げました。
GammaTable というクラスは、ガンマ値修飾子 (0.0 ~ 1.0 は暗く、>1.0 は明るくなります) でインスタンス化されます。コンストラクターは、この値のガンマ テーブルを構築する内部メソッドを呼び出します。このメソッドは、後でガンマをリセットするためにも使用されます。
/**
* Called when a new gamma value is set to rebuild the gamma table.
*/
private synchronized void buildGammaTable() {
table = new int[TABLE_SIZE];
float ginv = 1 / gamma;
double colors = COLORS;
for(int i=0;i<table.length;i++) {
table[i] = (int) Math.round(colors * Math.pow(i / colors, ginv));
}
}
ガンマを適用するために、GammaTable は整数ピクセルを取り、それをアンパックし、変更されたガンマ値を検索し、再パックされた整数を返します*
/**
* Applies the current gamma table to the given integer pixel.
* @param color the integer pixel to which gamma will be applied
* @param type a pixel type defined by ColorUtils
* @param rgbArr optional pre-instantiated array to use when unpacking. May be null.
* @return the modified pixel value
*/
public int applyGamma(int color, int type, int[] rgbArr) {
int[] argb = (rgbArr != null) ? ColorUtils.unpackInt(rgbArr, color):ColorUtils.unpackInt(color, type);
for(int i = 0; i < argb.length; i++) {
int col = argb[i];
argb[i] = table[col];
}
int newColor = ColorUtils.packInt(argb);
return newColor;
}
このapplyGamma
メソッドは、画面上の各ピクセルに対して呼び出されます。
*結局のところ、ピクセルをアンパックして再パッケージ化しても速度はまったく低下しませんでした。なんらかの理由で、呼び出しをネストすると (つまりColorUtils.packInt(ColorUtils.unpackInt))
、メソッドの実行時間が大幅に長くなります。興味深いことに、事前にインスタンス化された配列の使用も停止するColorUtils.unpackInt
必要がありました。これは、パフォーマンスに大きな打撃を与えるように思われたためです。アンパック メソッドが各呼び出しでの新しい配列は、現在のコンテキストでのパフォーマンスに影響を与えないようです。