Опитвам се да добавя гама корекция към моя механизъм за изобразяване. Имам два проблема:
1) Math.pow е НАИСТИНА бавен (в сравнение с това, че се извиква хиляди пъти в секунда). Така че ще трябва да създам предварително изчислена гама таблица, която да бъде достъпна, вместо да изчислявам в движение. (Това е допълнителна информация, а не действителният проблем).
2) Понастоящем мога да направя това само чрез разопаковане на целочислените пиксели, прилагане на гама чрез замяна на RGBA каналите със съответните им модифицирани гама стойности и след това преопаковане на пикселите и изпращане обратно към буфера на изображението. Постижението в производителността не е ужасно..., но сваля солидни фиксирани времеви стъпки от 60 кадъра в секунда до около 40 кадъра в секунда или така (с изобразени няколко изображения).
Опитах се да внедря целочисленото разопаковане/опаковане в собствения код, само за да не видя подобрение на производителността и да получа VM да се срива (вероятно грешки при проверка на паметта, но не ми се иска да го поправя сега).
Има ли начин да се приложи гамата без разопаковане/опаковане на пиксели? Ако не, какъв метод бихте препоръчали да използвате за това?
N.B. Не казвайте да използвате 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
, защото изглежда, че причинява огромен удар в производителността. Позволява на метода за разопаковане да създаване на нов масив с всяко повикване не изглежда да влияе на производителността в текущия контекст.