Я пытаюсь добавить гамма-коррекцию в свой движок рендеринга. У меня две проблемы:
1) Math.pow ОЧЕНЬ медленный (относительно того, что его вызывают тысячи раз в секунду). Поэтому мне нужно будет создать предварительно рассчитанную таблицу гаммы, к которой можно будет получить доступ, вместо расчета на лету. (Это дополнительная информация, а не фактическая проблема).
2) В настоящее время я могу сделать это, только распаковав целые пиксели, применив гамму, заменив каналы RGBA их соответствующими измененными значениями гаммы, а затем переупаковав пиксели и отправив их обратно в буфер изображения. Падение производительности не является ужасным..., но оно снижает фиксированный временной шаг с 60 кадров в секунду примерно до 40 кадров в секунду или около того (при рендеринге нескольких изображений).
Я попытался реализовать целочисленную распаковку/упаковку в собственном коде, но не увидел улучшения производительности и сбоя виртуальной машины (вероятно, ошибки проверки памяти, но сейчас мне все равно, что это исправить).
Есть ли способ применить гамму без распаковки/упаковки пикселей? Если нет, то какой метод вы бы порекомендовали использовать для этого?
Н.Б. Не говорите, что используйте 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
, потому что это, казалось, вызывало огромный удар по производительности. создавать новый массив с каждым вызовом, похоже, не влияет на производительность в текущем контексте.