/*
JWildfire - an image and animation processor written in Java
Copyright (C) 1995-2011 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.transform;
import org.jwildfire.base.Property;
import org.jwildfire.base.PropertyCategory;
import org.jwildfire.base.mathlib.MathLib;
import org.jwildfire.image.SimpleHDRImage;
import org.jwildfire.image.WFImage;
import org.jwildfire.swing.Buffer;
import org.jwildfire.swing.Buffer.BufferType;
import org.jwildfire.swing.HDRImageBufferComboBoxEditor;
import com.l2fprod.common.beans.editor.ComboBoxPropertyEditor;
public class HDRComposeTransformer extends Transformer {
public enum MergeMode {
ADD, BLUE, DARKEN, GREEN, HSL_ADD, LIGHTEN, MULTIPLY, NORMAL, RED, SUBTRACT
}
@Property(description = "Merge mode", editorClass = MergeModeEditor.class)
private MergeMode mergeMode = MergeMode.NORMAL;
@Property(category = PropertyCategory.PRIMARY, description = "Foreground image", editorClass = HDRImageBufferComboBoxEditor.class)
private Buffer foreground;
@Property(category = PropertyCategory.PRIMARY, description = "Foreground transparency")
private double transparency = 0.0;
@Property(category = PropertyCategory.PRIMARY, description = "Foreground intensity")
private double intensity = 1.0;
@Override
protected void performImageTransformation(WFImage pImg) {
SimpleHDRImage fgImg = foreground.getHDRImage();
SimpleHDRImage bgImg = (SimpleHDRImage) pImg;
if ((fgImg.getImageWidth() != bgImg.getImageWidth()) || (fgImg.getImageHeight() != bgImg.getImageHeight())) {
throw new IllegalArgumentException();
}
SimpleHDRImage res = new SimpleHDRImage(fgImg.getImageWidth(), fgImg.getImageHeight());
float fgRGB[] = new float[3];
float bgRGB[] = new float[3];
float r, g, b;
float trans = (float) transparency * 0.01f;
if (trans < 0.0f) {
trans = 0.0f;
}
else if (trans > 1.0f) {
trans = 1.0f;
}
float invTrans = 1.0f - trans;
float fgScale = (float) intensity;
float bgScale = (float) intensity;
float fgRed, fgGreen, fgBlue;
float bgRed, bgGreen, bgBlue;
float mergedRed, mergedGreen, mergedBlue;
HSLTransformer fgHSL = new HSLTransformer();
HSLTransformer bgHSL = new HSLTransformer();
HSLTransformer mergedHSL = new HSLTransformer();
float lum[] = new float[2];
fgImg.getMinMaxLum(lum);
float fgLumMin = lum[0];
float fgLumMax = lum[1];
if ((fgLumMax - fgLumMin) < MathLib.EPSILON) {
fgLumMax = fgLumMin + (float) MathLib.EPSILON;
}
bgImg.getMinMaxLum(lum);
float bgLumMin = lum[0];
float bgLumMax = lum[1];
if ((bgLumMax - bgLumMin) < MathLib.EPSILON) {
bgLumMax = bgLumMin + (float) MathLib.EPSILON;
}
bgScale *= (fgLumMax - fgLumMin) / (bgLumMax - bgLumMin);
for (int i = 0; i < fgImg.getImageHeight(); i++) {
for (int j = 0; j < fgImg.getImageWidth(); j++) {
fgImg.getRGBValues(fgRGB, j, i);
fgRed = fgRGB[0] * fgScale;
fgGreen = fgRGB[1] * fgScale;
fgBlue = fgRGB[2] * fgScale;
bgRed = bgRGB[0] * bgScale;
bgGreen = bgRGB[1] * bgScale;
bgBlue = bgRGB[2] * bgScale;
bgImg.getRGBValues(bgRGB, j, i);
switch (mergeMode) {
case MULTIPLY:
mergedRed = fgRed * bgRed;
mergedGreen = fgGreen * bgGreen;
mergedBlue = fgBlue * bgBlue;
break;
case ADD:
mergedRed = (fgRed + bgRed) * 0.5f;
mergedGreen = (fgGreen + bgGreen) * 0.5f;
mergedBlue = (fgBlue + bgBlue) * 0.5f;
break;
case SUBTRACT:
mergedRed = bgRed - fgRed;
if (mergedRed < 0.0f) {
mergedRed = 0.0f;
}
mergedGreen = bgGreen - fgGreen;
if (mergedGreen < 0.0f) {
mergedGreen = 0.0f;
}
mergedBlue = bgBlue - fgBlue;
if (mergedBlue < 0.0f) {
mergedBlue = 0.0f;
}
break;
case RED:
mergedRed = fgRed;
mergedGreen = bgGreen;
mergedBlue = bgBlue;
break;
case GREEN:
mergedRed = bgRed;
mergedGreen = fgGreen;
mergedBlue = bgBlue;
break;
case BLUE:
mergedRed = bgRed;
mergedGreen = bgGreen;
mergedBlue = fgBlue;
break;
case LIGHTEN: {
float fgLum = SimpleHDRImage.calcLum(fgRed, fgGreen, fgBlue);
float bgLum = SimpleHDRImage.calcLum(bgRed, bgGreen, bgBlue);
if (fgLum > bgLum) {
mergedRed = fgRed;
mergedGreen = fgGreen;
mergedBlue = fgBlue;
}
else {
mergedRed = bgRed;
mergedGreen = bgGreen;
mergedBlue = bgBlue;
}
}
break;
case DARKEN: {
float fgLum = SimpleHDRImage.calcLum(fgRed, fgGreen, fgBlue);
float bgLum = SimpleHDRImage.calcLum(bgRed, bgGreen, bgBlue);
if (fgLum < bgLum) {
mergedRed = fgRed;
mergedGreen = fgGreen;
mergedBlue = fgBlue;
}
else {
mergedRed = bgRed;
mergedGreen = bgGreen;
mergedBlue = bgBlue;
}
}
break;
case HSL_ADD:
fgHSL.setRGB(fgRed, fgGreen, fgBlue);
bgHSL.setRGB(bgRed, bgGreen, bgBlue);
mergedHSL.setHSL(fgHSL.getHue() + bgHSL.getHue(), fgHSL.getSaturation() + bgHSL.getSaturation(), fgHSL.getLuminosity() + bgHSL.getLuminosity(), fgHSL.getAmp());
mergedRed = mergedHSL.getRed();
mergedGreen = mergedHSL.getGreen();
mergedBlue = mergedHSL.getBlue();
break;
default:
mergedRed = fgRed;
mergedGreen = fgGreen;
mergedBlue = fgBlue;
break;
}
r = mergedRed * invTrans + bgRed * trans;
g = mergedGreen * invTrans + bgGreen * trans;
b = mergedBlue * invTrans + bgBlue * trans;
res.setRGB(j, i, r, g, b);
}
}
((SimpleHDRImage) pImg).assignImage(res);
}
@Override
public void initDefaultParams(WFImage pImg) {
mergeMode = MergeMode.NORMAL;
}
public MergeMode getMergeMode() {
return mergeMode;
}
public void setMergeMode(MergeMode mergeMode) {
this.mergeMode = mergeMode;
}
public Buffer getForeground() {
return foreground;
}
public void setForeground(Buffer foreground) {
this.foreground = foreground;
}
public double getTransparency() {
return transparency;
}
public void setTransparency(double transparency) {
this.transparency = transparency;
}
@Override
public BufferType getBufferType() {
return BufferType.HDR_IMAGE;
}
@Override
public boolean supports3DOutput() {
return false;
}
@Override
public boolean acceptsInputBufferType(BufferType pBufferType) {
return pBufferType.equals(BufferType.HDR_IMAGE);
}
public double getIntensity() {
return intensity;
}
public void setIntensity(double intensity) {
this.intensity = intensity;
}
public class HSLTransformer {
private float red, green, blue, amp;
private float hue, saturation, luminosity;
private static final float EPSILON = 0.000001f;
private float _max(float x, float y) {
return (((x) > (y)) ? (x) : (y));
}
private float _min(float x, float y) {
return (((x) < (y)) ? (x) : (y));
}
public void setRGB(float pRed, float pGreen, float pBlue) {
this.red = pRed;
this.green = pGreen;
this.blue = pBlue;
this.hue = 1.0f;
this.saturation = 0.0f;
this.luminosity = 0.0f;
this.amp = _max(_max(pRed, pGreen), pBlue);
if (amp < 0.00001f) {
return;
}
float r = pRed / amp;
float g = pGreen / amp;
float b = pBlue / amp;
float max = _max(r, _max(g, b));
float min = _min(r, _min(g, b));
this.luminosity = (min + max) / 2.0f;
if (Math.abs(this.luminosity) <= EPSILON) {
return;
}
this.saturation = max - min;
if (Math.abs(this.saturation) <= EPSILON) {
return;
}
this.saturation /= ((this.luminosity) <= 0.5f) ? (min + max) : (2.0f - max - min);
if (Math.abs(r - max) < EPSILON) {
this.hue = ((g == min) ? 5.0f + (max - b) / (max - min) : 1.0f - (max - g) / (max - min));
}
else {
if (Math.abs(g - max) < EPSILON) {
this.hue = ((b == min) ? 1.0f + (max - r) / (max - min) : 3.0f - (max - b) / (max - min));
}
else {
this.hue = ((r == min) ? 3.0f + (max - g) / (max - min) : 5.0f - (max - r) / (max - min));
}
}
this.hue /= 6.0f;
}
private float limit_int(float pVal) {
if (pVal < 0.0f) {
return 0.0f;
}
else if (pVal > 1.0f) {
return 1.0f;
}
else {
return pVal;
}
}
public void setHSL(float pHue, float pSaturation, float pLuminosity, float amp) {
this.luminosity = limit_int(pLuminosity);
this.saturation = limit_int(pSaturation);
this.hue = limit_int(pHue);
this.amp = amp;
float v = (luminosity <= 0.5f) ? (luminosity * (1.0f + saturation))
: (luminosity + saturation - luminosity * saturation);
if (v <= 0) {
this.red = 0.0f;
this.green = 0.0f;
this.blue = 0.0f;
this.amp = 0.0f;
return;
}
this.hue *= 6.0f;
if (this.hue < 0.0f)
this.hue = 0.0f;
else if (this.hue > 6.0f)
this.hue = 6.0f;
float y = this.luminosity + this.luminosity - v;
float x = y + (v - y) * (this.hue - (int) this.hue);
float z = v - (v - y) * (this.hue - (int) this.hue);
float r, g, b;
switch ((int) hue) {
case 0:
r = v;
g = x;
b = y;
break;
case 1:
r = z;
g = v;
b = y;
break;
case 2:
r = y;
g = v;
b = x;
break;
case 3:
r = y;
g = z;
b = v;
break;
case 4:
r = x;
g = y;
b = v;
break;
case 5:
r = v;
g = y;
b = z;
break;
default:
r = v;
g = y;
b = z;
}
this.red = r * this.amp;
this.green = g * this.amp;
this.blue = b * this.amp;
}
public float getRed() {
return red;
}
public float getGreen() {
return green;
}
public float getBlue() {
return blue;
}
public float getHue() {
return hue;
}
public float getSaturation() {
return saturation;
}
public float getLuminosity() {
return luminosity;
}
public float getAmp() {
return amp;
}
}
public static class MergeModeEditor extends ComboBoxPropertyEditor {
public MergeModeEditor() {
super();
setAvailableValues(new MergeMode[] { MergeMode.ADD, MergeMode.BLUE, MergeMode.DARKEN, MergeMode.GREEN, MergeMode.HSL_ADD, MergeMode.LIGHTEN, MergeMode.MULTIPLY, MergeMode.NORMAL, MergeMode.RED, MergeMode.SUBTRACT });
}
}
}