/*
JWildfire - an image and animation processor written in Java
Copyright (C) 1995-2016 Andreas Maschke
This is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser
General Public License as published by the Free Software Foundation; either version 2.1 of the
License, or (at your option) any later version.
This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along with this software;
if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jwildfire.create.tina.render;
import static org.jwildfire.base.mathlib.MathLib.pow;
import org.jwildfire.base.Tools;
import org.jwildfire.base.mathlib.GfxMathLib;
import org.jwildfire.base.mathlib.MathLib;
import org.jwildfire.create.tina.base.Flame;
public class GammaCorrectionFilter {
private final Flame flame;
private int vibInt;
private int inverseVibInt;
private double gamma;
private double sclGamma;
private boolean withAlpha;
private double modSaturation;
private final double alphaScale;
public static class ColorF {
public double r, g, b;
}
public static class ColorI {
public int r, g, b;
}
public GammaCorrectionFilter(Flame pFlame, boolean pWithAlpha, int pRasterWidth, int pRasterHeight) {
flame = pFlame;
withAlpha = pWithAlpha;
alphaScale = 1.0 - MathLib.atan(3.0 * (pFlame.getForegroundOpacity() - 1.0)) / 1.25;
initFilter();
}
private void initFilter() {
gamma = (flame.getGamma() == 0.0) ? flame.getGamma() : 1.0 / flame.getGamma();
vibInt = (int) (flame.getVibrancy() * 256.0 + 0.5);
if (vibInt < 0) {
vibInt = 0;
}
else if (vibInt > 256) {
vibInt = 256;
}
inverseVibInt = 256 - vibInt;
sclGamma = 0.0;
if (flame.getGammaThreshold() != 0.0) {
sclGamma = pow(flame.getGammaThreshold(), gamma - 1);
}
modSaturation = flame.getSaturation() - 1.0;
if (modSaturation < -1.0)
modSaturation = -1.0;
}
public void transformPoint(LogDensityPoint logDensityPnt, GammaCorrectedRGBPoint pRGBPoint, int pX, int pY) {
pRGBPoint.bgRed = logDensityPnt.bgRed;
pRGBPoint.bgGreen = logDensityPnt.bgGreen;
pRGBPoint.bgBlue = logDensityPnt.bgBlue;
double logScl;
int inverseAlphaInt;
if (logDensityPnt.intensity > 0.0 || logDensityPnt.hasSolidColors || logDensityPnt.receiveOnlyShadows) {
ColorF transfColor;
if (logDensityPnt.hasSolidColors) {
transfColor = new ColorF();
transfColor.r = logDensityPnt.solidRed;
transfColor.g = logDensityPnt.solidGreen;
transfColor.b = logDensityPnt.solidBlue;
double fixedGamma = 1.0 + gamma;
double alpha = pow(logDensityPnt.intensity, fixedGamma);
int alphaInt = (int) (alpha * 255 * alphaScale + 0.5);
if (alphaInt < 0)
alphaInt = 0;
else if (alphaInt > 255)
alphaInt = 255;
inverseAlphaInt = 255 - alphaInt;
pRGBPoint.alpha = withAlpha ? alphaInt : 255;
ColorI finalColor = addBackground(pRGBPoint, transfColor, inverseAlphaInt);
pRGBPoint.red = finalColor.r;
pRGBPoint.green = finalColor.g;
pRGBPoint.blue = finalColor.b;
}
else if (logDensityPnt.receiveOnlyShadows) {
transfColor = new ColorF();
double shadowInt = GfxMathLib.clamp(logDensityPnt.red);
if (shadowInt > MathLib.EPSILON && shadowInt < 1.0 - MathLib.EPSILON) {
int alphaInt = Tools.roundColor((1.0 - shadowInt) * 255.0);
inverseAlphaInt = 255 - alphaInt;
transfColor.r = 0.0;
transfColor.g = 0.0;
transfColor.b = 0.0;
pRGBPoint.alpha = withAlpha ? alphaInt : 255;
ColorI finalColor = addBackground(pRGBPoint, transfColor, inverseAlphaInt);
pRGBPoint.red = finalColor.r;
pRGBPoint.green = finalColor.g;
pRGBPoint.blue = finalColor.b;
}
else {
pRGBPoint.red = pRGBPoint.bgRed;
pRGBPoint.green = pRGBPoint.bgGreen;
pRGBPoint.blue = pRGBPoint.bgBlue;
pRGBPoint.alpha = withAlpha ? 0 : 255;
}
}
else {
double alpha;
if (logDensityPnt.intensity <= flame.getGammaThreshold()) {
double frac = logDensityPnt.intensity / flame.getGammaThreshold();
alpha = (1.0 - frac) * logDensityPnt.intensity * sclGamma + frac * pow(logDensityPnt.intensity, gamma);
}
else {
alpha = pow(logDensityPnt.intensity, gamma);
}
logScl = vibInt * alpha / logDensityPnt.intensity;
int alphaInt = (int) (alpha * 255 * alphaScale + 0.5);
if (alphaInt < 0)
alphaInt = 0;
else if (alphaInt > 255)
alphaInt = 255;
inverseAlphaInt = 255 - alphaInt;
pRGBPoint.alpha = withAlpha ? alphaInt : 255;
transfColor = applyLogScale(logDensityPnt, logScl);
ColorI finalColor = addBackground(pRGBPoint, transfColor, inverseAlphaInt);
pRGBPoint.red = finalColor.r;
pRGBPoint.green = finalColor.g;
pRGBPoint.blue = finalColor.b;
}
}
else {
pRGBPoint.red = pRGBPoint.bgRed;
pRGBPoint.green = pRGBPoint.bgGreen;
pRGBPoint.blue = pRGBPoint.bgBlue;
pRGBPoint.alpha = withAlpha ? 0 : 255;
}
if (modSaturation != 0) {
applyModSaturation(pRGBPoint, modSaturation);
}
}
private ColorI addBackground(GammaCorrectedRGBPoint pRGBPoint, ColorF pTransfColor, int pInverseAlphaInt) {
ColorI res = new ColorI();
res.r = (int) (pTransfColor.r + 0.5) + ((pInverseAlphaInt * pRGBPoint.bgRed) >> 8);
if (res.r < 0)
res.r = 0;
else if (res.r > 255)
res.r = 255;
res.g = (int) (pTransfColor.g + 0.5) + ((pInverseAlphaInt * pRGBPoint.bgGreen) >> 8);
if (res.g < 0)
res.g = 0;
else if (res.g > 255)
res.g = 255;
res.b = (int) (pTransfColor.b + 0.5) + ((pInverseAlphaInt * pRGBPoint.bgBlue) >> 8);
if (res.b < 0)
res.b = 0;
else if (res.b > 255)
res.b = 255;
return res;
}
final static double ALPHA_RANGE = 256.0;
private ColorF addBackgroundF(PointWithBackgroundColor pBGColor, ColorF pTransfColor, double pInverseAlphaInt) {
ColorF res = new ColorF();
res.r = pTransfColor.r + (pInverseAlphaInt * pBGColor.bgRed) / ALPHA_RANGE;
if (res.r < 0.0)
res.r = 0.0;
res.g = pTransfColor.g + (pInverseAlphaInt * pBGColor.bgGreen) / ALPHA_RANGE;
if (res.g < 0.0)
res.g = 0.0;
res.b = pTransfColor.b + (pInverseAlphaInt * pBGColor.bgBlue) / ALPHA_RANGE;
if (res.b < 0.0)
res.b = 0.0;
return res;
}
private ColorF applyLogScale(LogDensityPoint pLogDensityPnt, double pLogScl) {
ColorF res = new ColorF();
double rawRed = 0.0, rawGreen = 0.0, rawBlue = 0.0;
if (inverseVibInt > 0) {
rawRed = pLogScl * pLogDensityPnt.red + inverseVibInt * pow(pLogDensityPnt.red, gamma);
rawGreen = pLogScl * pLogDensityPnt.green + inverseVibInt * pow(pLogDensityPnt.green, gamma);
rawBlue = pLogScl * pLogDensityPnt.blue + inverseVibInt * pow(pLogDensityPnt.blue, gamma);
}
else {
rawRed = pLogScl * pLogDensityPnt.red;
rawGreen = pLogScl * pLogDensityPnt.green;
rawBlue = pLogScl * pLogDensityPnt.blue;
}
res.r = rawRed;
res.g = rawGreen;
res.b = rawBlue;
return res;
}
private void applyModSaturation(GammaCorrectedRGBPoint pRGBPoint, double currModSaturation) {
HSLRGBConverter hslrgbConverter = pRGBPoint.hslrgbConverter;
hslrgbConverter.fromRgb(pRGBPoint.red / COLORSCL, pRGBPoint.green / COLORSCL, pRGBPoint.blue / COLORSCL);
hslrgbConverter.fromHsl(hslrgbConverter.getHue(), hslrgbConverter.getSaturation() + currModSaturation, hslrgbConverter.getLuminosity());
pRGBPoint.red = Tools.roundColor(hslrgbConverter.getRed() * COLORSCL);
pRGBPoint.green = Tools.roundColor(hslrgbConverter.getGreen() * COLORSCL);
pRGBPoint.blue = Tools.roundColor(hslrgbConverter.getBlue() * COLORSCL);
}
public static final double COLORSCL = 255.0;
public void transformPointHDR(LogDensityPoint logDensityPnt, GammaCorrectedHDRPoint pHDRPoint, int pX, int pY) {
pHDRPoint.bgRed = logDensityPnt.bgRed;
pHDRPoint.bgGreen = logDensityPnt.bgGreen;
pHDRPoint.bgBlue = logDensityPnt.bgBlue;
double logScl;
double inverseAlphaInt;
if (logDensityPnt.intensity > 0.0 || logDensityPnt.hasSolidColors) {
ColorF transfColor;
if (logDensityPnt.hasSolidColors) {
transfColor = new ColorF();
transfColor.r = logDensityPnt.solidRed;
transfColor.g = logDensityPnt.solidGreen;
transfColor.b = logDensityPnt.solidBlue;
double fixedGamma = 1.0 + gamma;
double alpha = pow(logDensityPnt.intensity, fixedGamma);
double alphaInt = (alpha * ALPHA_RANGE * alphaScale);
if (alphaInt < 0.0)
alphaInt = 0.0;
else if (alphaInt > ALPHA_RANGE)
alphaInt = ALPHA_RANGE;
inverseAlphaInt = ALPHA_RANGE - alphaInt;
}
else {
double alpha;
if (logDensityPnt.intensity <= flame.getGammaThreshold()) {
double frac = logDensityPnt.intensity / flame.getGammaThreshold();
alpha = (1.0 - frac) * logDensityPnt.intensity * sclGamma + frac * pow(logDensityPnt.intensity, gamma);
}
else {
alpha = pow(logDensityPnt.intensity, gamma);
}
logScl = vibInt * alpha / logDensityPnt.intensity;
double alphaInt = alpha * ALPHA_RANGE;
if (alphaInt < 0.0)
alphaInt = 0.0;
else if (alphaInt > ALPHA_RANGE)
alphaInt = ALPHA_RANGE;
inverseAlphaInt = ALPHA_RANGE - alphaInt;
transfColor = applyLogScale(logDensityPnt, logScl);
}
ColorF finalColor = addBackgroundF(pHDRPoint, transfColor, inverseAlphaInt);
pHDRPoint.red = (float) (finalColor.r / ALPHA_RANGE);
pHDRPoint.green = (float) (finalColor.g / ALPHA_RANGE);
pHDRPoint.blue = (float) (finalColor.b / ALPHA_RANGE);
}
else {
pHDRPoint.red = (float) (pHDRPoint.bgRed / ALPHA_RANGE);
pHDRPoint.green = (float) (pHDRPoint.bgGreen / ALPHA_RANGE);
pHDRPoint.blue = (float) (pHDRPoint.bgBlue / ALPHA_RANGE);
}
}
public static class HSLRGBConverter {
private double red, green, blue;
private double hue, saturation, luminosity;
public void fromHsl(double pHue, double pSaturation, double pLuminosity) {
hue = limitVal(pHue, 0.0, 1.0);
saturation = limitVal(pSaturation, 0.0, 1.0);
luminosity = limitVal(pLuminosity, 0.0, 1.0);
double v = (luminosity <= 0.5) ? (luminosity * (1.0 + saturation))
: (luminosity + saturation - luminosity * saturation);
if (v <= 0) {
red = 0.0;
green = 0.0;
blue = 0.0;
return;
}
hue *= 6.0;
if (hue < 0.0)
hue = 0.0;
else if (hue > 6.0)
hue = 6.0;
double y = luminosity + luminosity - v;
double x = y + (v - y) * (hue - (int) hue);
double z = v - (v - y) * (hue - (int) hue);
switch ((int) hue) {
case 0:
red = v;
green = x;
blue = y;
break;
case 1:
red = z;
green = v;
blue = y;
break;
case 2:
red = y;
green = v;
blue = x;
break;
case 3:
red = y;
green = z;
blue = v;
break;
case 4:
red = x;
green = y;
blue = v;
break;
case 5:
red = v;
green = y;
blue = z;
break;
default:
red = v;
green = x;
blue = y;
// red = v;
// green = y;
// blue = z;
}
}
public void fromRgb(double pRed, double pGreen, double pBlue) {
hue = 1.0;
saturation = 0.0;
red = limitVal(pRed, 0.0, 1.0);
green = limitVal(pGreen, 0.0, 1.0);
blue = limitVal(pBlue, 0.0, 1.0);
double max = Math.max(red, Math.max(green, blue));
double min = Math.min(red, Math.min(green, blue));
luminosity = (min + max) / 2.0;
if (Math.abs(luminosity) <= MathLib.EPSILON)
return;
saturation = max - min;
if (Math.abs(saturation) <= MathLib.EPSILON)
return;
saturation /= ((luminosity) <= 0.5) ? (min + max) : (2.0 - max - min);
if (Math.abs(red - max) < MathLib.EPSILON) {
hue = ((green == min) ? 5.0 + (max - blue) / (max - min) : 1.0 - (max - green) / (max - min));
}
else {
if (Math.abs(green - max) < MathLib.EPSILON) {
hue = ((blue == min) ? 1.0 + (max - red) / (max - min) : 3.0 - (max - blue) / (max - min));
}
else {
hue = ((red == min) ? 3.0 + (max - green) / (max - min) : 5.0 - (max - red) / (max - min));
}
}
hue /= 6.0;
}
private double limitVal(double pValue, double pMin, double pMax) {
return pValue < pMin ? pMin : pValue > pMax ? pMax : pValue;
}
public double getRed() {
return red;
}
public double getGreen() {
return green;
}
public double getBlue() {
return blue;
}
public double getHue() {
return hue;
}
public double getSaturation() {
return saturation;
}
public double getLuminosity() {
return luminosity;
}
}
}