// Near Infinity - An Infinity Engine Browser and Editor
// Copyright (C) 2001 - 2005 Jon Olav Hauglid
// See LICENSE.txt for license information
package org.infinity.gui.converter;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.awt.image.DataBuffer;
import java.awt.image.DataBufferByte;
import java.awt.image.DataBufferInt;
import java.awt.image.IndexColorModel;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.JSpinner;
import javax.swing.SpinnerNumberModel;
import javax.swing.SwingConstants;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import org.infinity.gui.ButtonPopupWindow;
import org.infinity.gui.ViewerUtil;
import org.infinity.icon.Icons;
import org.infinity.resource.graphics.PseudoBamDecoder.PseudoBamFrameEntry;
/**
* ColorFilter: adjust hue, saturation and lightness.
*/
public class BamFilterColorHSL extends BamFilterBaseColor
implements ChangeListener, ActionListener
{
private static final String FilterName = "Hue/Saturation/Lightness";
private static final String FilterDesc = "This filter provides controls for adjusting hue, " +
"saturation and lightness";
private JSlider sliderHue, sliderSaturation, sliderLightness;
private JSpinner spinnerHue, spinnerSaturation, spinnerLightness;
private ButtonPopupWindow bpwExclude;
private BamFilterBaseColor.ExcludeColorsPanel pExcludeColors;
public static String getFilterName() { return FilterName; }
public static String getFilterDesc() { return FilterDesc; }
public BamFilterColorHSL(ConvertToBam parent)
{
super(parent, FilterName, FilterDesc);
}
@Override
public BufferedImage process(BufferedImage frame) throws Exception
{
return applyEffect(frame);
}
@Override
public PseudoBamFrameEntry updatePreview(PseudoBamFrameEntry entry)
{
if (entry != null) {
entry.setFrame(applyEffect(entry.getFrame()));
}
return entry;
}
@Override
public void updateControls()
{
bpwExclude.setEnabled(getConverter().isBamV1Selected());
}
@Override
public String getConfiguration()
{
StringBuilder sb = new StringBuilder();
sb.append(sliderHue.getValue()).append(';');
sb.append(sliderSaturation.getValue()).append(';');
sb.append(sliderLightness.getValue()).append(';');
sb.append(encodeColorList(pExcludeColors.getSelectedIndices()));
return sb.toString();
}
@Override
public boolean setConfiguration(String config)
{
if (config != null) {
config = config.trim();
if (!config.isEmpty()) {
String[] params = config.trim().split(";");
Integer hValue = Integer.MIN_VALUE;
Integer sValue = Integer.MIN_VALUE;
Integer lValue = Integer.MIN_VALUE;
int[] indices = null;
// parsing configuration data
if (params.length > 0) { // set hue value
hValue = decodeNumber(params[0], sliderHue.getMinimum(), sliderHue.getMaximum(), Integer.MIN_VALUE);
if (hValue == Integer.MIN_VALUE) {
return false;
}
}
if (params.length > 1) { // set saturation value
sValue = decodeNumber(params[1], sliderSaturation.getMinimum(), sliderSaturation.getMaximum(), Integer.MIN_VALUE);
if (sValue == Integer.MIN_VALUE) {
return false;
}
}
if (params.length > 2) { // set lightness value
lValue = decodeNumber(params[2], sliderLightness.getMinimum(), sliderLightness.getMaximum(), Integer.MIN_VALUE);
if (lValue == Integer.MIN_VALUE) {
return false;
}
}
if (params.length > 3) {
indices = decodeColorList(params[3]);
if (indices == null) {
return false;
}
}
// applying configuration data
if (hValue != Integer.MIN_VALUE) {
sliderHue.setValue(hValue);
}
if (sValue != Integer.MIN_VALUE) {
sliderSaturation.setValue(sValue);
}
if (lValue != Integer.MIN_VALUE) {
sliderLightness.setValue(lValue);
}
if (indices != null) {
pExcludeColors.setSelectedIndices(indices);
}
}
return true;
}
return false;
}
@Override
protected JPanel loadControls()
{
GridBagConstraints c = new GridBagConstraints();
JLabel l1 = new JLabel("Exclude colors:");
pExcludeColors = new BamFilterBaseColor.ExcludeColorsPanel(
getConverter().getPaletteDialog().getPalette(getConverter().getPaletteDialog().getPaletteType()));
pExcludeColors.addChangeListener(this);
bpwExclude = new ButtonPopupWindow("Palette", Icons.getIcon(Icons.ICON_ARROW_DOWN_15), pExcludeColors);
bpwExclude.setIconTextGap(8);
bpwExclude.addActionListener(this);
bpwExclude.setEnabled(getConverter().isBamV1Selected());
JPanel pExclude = new JPanel(new GridBagLayout());
ViewerUtil.setGBC(c, 0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START,
GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0);
pExclude.add(l1, c);
ViewerUtil.setGBC(c, 1, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START,
GridBagConstraints.NONE, new Insets(0, 8, 0, 0), 0, 0);
pExclude.add(bpwExclude, c);
ViewerUtil.setGBC(c, 2, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START,
GridBagConstraints.NONE, new Insets(0, 4, 0, 0), 0, 0);
pExclude.add(new JPanel(), c);
JLabel lh = new JLabel("Hue:");
JLabel ls = new JLabel("Saturation:");
JLabel ll = new JLabel("Lightness:");
sliderHue = new JSlider(SwingConstants.HORIZONTAL, -180, 180, 0);
sliderHue.addChangeListener(this);
sliderSaturation = new JSlider(SwingConstants.HORIZONTAL, -100, 100, 0);
sliderSaturation.addChangeListener(this);
sliderLightness = new JSlider(SwingConstants.HORIZONTAL, -100, 100, 0);
sliderLightness.addChangeListener(this);
spinnerHue = new JSpinner(new SpinnerNumberModel(sliderHue.getValue(),
sliderHue.getMinimum(),
sliderHue.getMaximum(), 1));
spinnerHue.addChangeListener(this);
spinnerSaturation = new JSpinner(new SpinnerNumberModel(sliderSaturation.getValue(),
sliderSaturation.getMinimum(),
sliderSaturation.getMaximum(), 1));
spinnerSaturation.addChangeListener(this);
spinnerLightness = new JSpinner(new SpinnerNumberModel(sliderLightness.getValue(),
sliderLightness.getMinimum() ,
sliderLightness.getMaximum(), 1));
spinnerLightness.addChangeListener(this);
JPanel p = new JPanel(new GridBagLayout());
ViewerUtil.setGBC(c, 0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START,
GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0);
p.add(lh, c);
ViewerUtil.setGBC(c, 1, 0, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START,
GridBagConstraints.HORIZONTAL, new Insets(0, 4, 0, 0), 0, 0);
p.add(sliderHue, c);
ViewerUtil.setGBC(c, 2, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START,
GridBagConstraints.HORIZONTAL, new Insets(0, 4, 0, 0), 0, 0);
p.add(spinnerHue, c);
ViewerUtil.setGBC(c, 0, 1, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START,
GridBagConstraints.NONE, new Insets(4, 0, 0, 0), 0, 0);
p.add(ls, c);
ViewerUtil.setGBC(c, 1, 1, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START,
GridBagConstraints.HORIZONTAL, new Insets(4, 4, 0, 0), 0, 0);
p.add(sliderSaturation, c);
ViewerUtil.setGBC(c, 2, 1, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START,
GridBagConstraints.HORIZONTAL, new Insets(4, 4, 0, 0), 0, 0);
p.add(spinnerSaturation, c);
ViewerUtil.setGBC(c, 0, 2, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START,
GridBagConstraints.NONE, new Insets(4, 0, 0, 0), 0, 0);
p.add(ll, c);
ViewerUtil.setGBC(c, 1, 2, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START,
GridBagConstraints.HORIZONTAL, new Insets(4, 4, 0, 0), 0, 0);
p.add(sliderLightness, c);
ViewerUtil.setGBC(c, 2, 2, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START,
GridBagConstraints.HORIZONTAL, new Insets(4, 4, 0, 0), 0, 0);
p.add(spinnerLightness, c);
ViewerUtil.setGBC(c, 0, 3, 3, 1, 0.0, 0.0, GridBagConstraints.LINE_START,
GridBagConstraints.NONE, new Insets(8, 0, 0, 0), 0, 0);
p.add(pExclude, c);
JPanel panel = new JPanel(new GridBagLayout());
ViewerUtil.setGBC(c, 0, 0, 1, 1, 1.0, 1.0, GridBagConstraints.CENTER,
GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0);
panel.add(p, c);
return panel;
}
//--------------------- Begin Interface ChangeListener ---------------------
@Override
public void stateChanged(ChangeEvent event)
{
if (event.getSource() == pExcludeColors) {
fireChangeListener();
} else if (event.getSource() == sliderHue) {
spinnerHue.setValue(Integer.valueOf(sliderHue.getValue()));
if (sliderHue.getModel().getValueIsAdjusting() == false) {
fireChangeListener();
}
} else if (event.getSource() == sliderSaturation) {
spinnerSaturation.setValue(Integer.valueOf(sliderSaturation.getValue()));
if (sliderSaturation.getModel().getValueIsAdjusting() == false) {
fireChangeListener();
}
} else if (event.getSource() == sliderLightness) {
spinnerLightness.setValue(Integer.valueOf(sliderLightness.getValue()));
if (sliderLightness.getModel().getValueIsAdjusting() == false) {
fireChangeListener();
}
} else if (event.getSource() == spinnerHue) {
sliderHue.setValue(((Integer)spinnerHue.getValue()).intValue());
} else if (event.getSource() == spinnerSaturation) {
sliderSaturation.setValue(((Integer)spinnerSaturation.getValue()).intValue());
} else if (event.getSource() == spinnerLightness) {
sliderLightness.setValue(((Integer)spinnerLightness.getValue()).intValue());
}
}
//--------------------- End Interface ChangeListener ---------------------
//--------------------- Begin Interface ActionListener ---------------------
@Override
public void actionPerformed(ActionEvent event)
{
if (event.getSource() == bpwExclude) {
pExcludeColors.updatePalette(getConverter().getPaletteDialog().getPalette(
getConverter().getPaletteDialog().getPaletteType()));
}
}
//--------------------- End Interface ActionListener ---------------------
private BufferedImage applyEffect(BufferedImage srcImage)
{
if (srcImage != null) {
int[] buffer;
IndexColorModel cm = null;
if (srcImage.getType() == BufferedImage.TYPE_BYTE_INDEXED) {
// paletted image
cm = (IndexColorModel)srcImage.getColorModel();
buffer = new int[1 << cm.getPixelSize()];
cm.getRGBs(buffer);
// applying proper alpha
if (!cm.hasAlpha()) {
final int Green = 0x0000ff00;
boolean greenFound = false;
for (int i = 0; i < buffer.length; i++) {
if (!greenFound && buffer[i] == Green) {
greenFound = true;
buffer[i] &= 0x00ffffff;
} else {
buffer[i] |= 0xff000000;
}
}
}
} else if (srcImage.getRaster().getDataBuffer().getDataType() == DataBuffer.TYPE_INT) {
// truecolor image
buffer = ((DataBufferInt)srcImage.getRaster().getDataBuffer()).getData();
} else {
buffer = new int[0];
}
// hue in range [-180, 180]
float hue = ((Integer)spinnerHue.getValue()).floatValue() / 360.0f;
// saturation in range [-100, 100]
float saturation = ((Integer)spinnerSaturation.getValue()).floatValue() / 100.0f;
// lightness in range [-100, 100]
float lightness = ((Integer)spinnerLightness.getValue()).floatValue() / 100.0f;
for (int i = 0; i < buffer.length; i++) {
if ((cm == null || (cm != null && !pExcludeColors.isSelectedIndex(i))) &&
(buffer[i] & 0xff000000) != 0) {
// convert RGB -> HSL
float fa = (float)((buffer[i] >>> 24) & 0xff) / 255.0f;
float fr = ((float)((buffer[i] >>> 16) & 0xff) / 255.0f) / fa;
float fg = ((float)((buffer[i] >>> 8) & 0xff) / 255.0f) / fa;
float fb = ((float)(buffer[i] & 0xff) / 255.0f) / fa;
float cmin = fr; if (fg < cmin) cmin = fg; if (fb < cmin) cmin = fb;
float cmax = fr; if (fg > cmax) cmax = fg; if (fb > cmax) cmax = fb;
float cdelta = cmax - cmin;
float cdelta2 = cdelta / 2.0f;
float h, s, l;
l = (cmax + cmin) / 2.0f;
if (cdelta == 0.0f) {
h = 0.0f;
s = 0.0f;
} else {
if (l < 0.5f) {
s = cdelta / (cmax + cmin);
} else {
s = cdelta / (2.0f - cmax - cmin);
}
float dr = (((cmax - fr) / 6.0f) + cdelta2) / cdelta;
float dg = (((cmax - fg) / 6.0f) + cdelta2) / cdelta;
float db = (((cmax - fb) / 6.0f) + cdelta2) / cdelta;
if (fr == cmax) {
h = db - dg;
} else if (fg == cmax) {
h = (1.0f / 3.0f) + dr - db;
} else {
h = (2.0f / 3.0f) + dg - dr;
}
if (h < 0.0f) h += 1.0f; else if (h > 1.0f) h -= 1.0f;
}
// applying adjustments
h += hue;
s += saturation;
l += lightness;
if (h < 0.0f) h += 1.0f; else if (h > 1.0f) h -= 1.0f;
if (s < 0.0f) s = 0.0f; else if (s > 1.0f) s = 1.0f;
if (l < 0.0f) l = 0.0f; else if (l > 1.0f) l = 1.0f;
// converting HSL -> RGB
if (s == 0.0f) {
// achromatic
int v = (int)(l * 255.0f);
buffer[i] = (buffer[i] & 0xff000000) | (v << 16) | (v << 8) | v;
} else {
float f2 = (l < 0.5f) ? l * (1.0f + s) : (l + s) - (s * l);
float f1 = 2.0f * l - f2;
float res;
// red
float t = h + (1.0f / 3.0f);
if (t < 0.0f) t += 1.0f; else if (t > 1.0f) t -= 1.0f;
if ((6.0f * t) < 1.0f) {
res = f1 + (f2 - f1) * 6.0f * t;
} else if ((2.0f * t) < 1.0f) {
res = f2;
} else if ((3.0f * t) < 2.0f) {
res = f1 + (f2 - f1) * ((2.0f / 3.0f) - t) * 6.0f;
} else {
res = f1;
}
int r = (int)(res * 255.0f * fa);
// green
t = h;
if ((6.0f * t) < 1.0f) {
res = f1 + (f2 - f1) * 6.0f * t;
} else if ((2.0f * t) < 1.0f) {
res = f2;
} else if ((3.0f * t) < 2.0f) {
res = f1 + (f2 - f1) * ((2.0f / 3.0f) - t) * 6.0f;
} else {
res = f1;
}
int g = (int)(res * 255.0f * fa);
// blue
t = h - (1.0f / 3.0f);
if (t < 0.0f) t += 1.0f; else if (t > 1.0f) t -= 1.0f;
if ((6.0f * t) < 1.0f) {
res = f1 + (f2 - f1) * 6.0f * t;
} else if ((2.0f * t) < 1.0f) {
res = f2;
} else if ((3.0f * t) < 2.0f) {
res = f1 + (f2 - f1) * ((2.0f / 3.0f) - t) * 6.0f;
} else {
res = f1;
}
int b = (int)(res * 255.0f * fa);
if (r < 0) r = 0; else if (r > 255) r = 255;
if (g < 0) g = 0; else if (g > 255) g = 255;
if (b < 0) b = 0; else if (b > 255) b = 255;
buffer[i] = (buffer[i] & 0xff000000) | (r << 16) | (g << 8) | b;
}
}
}
if (cm != null) {
// recreating paletted image
IndexColorModel cm2 = new IndexColorModel(cm.getPixelSize(), buffer.length, buffer, 0,
cm.hasAlpha(), cm.getTransparentPixel(), DataBuffer.TYPE_BYTE);
int width = srcImage.getWidth();
int height = srcImage.getHeight();
BufferedImage dstImage = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_INDEXED, cm2);
byte[] srcPixels = ((DataBufferByte)srcImage.getRaster().getDataBuffer()).getData();
byte[] dstPixels = ((DataBufferByte)dstImage.getRaster().getDataBuffer()).getData();
System.arraycopy(srcPixels, 0, dstPixels, 0, srcPixels.length);
srcImage = dstImage;
srcPixels = null; dstPixels = null;
}
}
return srcImage;
}
}