// 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; /** * Color filter: adjust brightness, contrast and gamma. */ public class BamFilterColorBCG extends BamFilterBaseColor implements ChangeListener, ActionListener { private static final String FilterName = "Brightness/Contrast/Gamma"; private static final String FilterDesc = "This filter provides controls for adjusting brightness, " + "contrast and gamma"; private static final double GammaScaleFactor = 100.0; // the scale factor for gamma slider private JSlider sliderBrightness, sliderContrast, sliderGamma; private JSpinner spinnerBrightness, spinnerContrast, spinnerGamma; private ButtonPopupWindow bpwExclude; private BamFilterBaseColor.ExcludeColorsPanel pExcludeColors; public static String getFilterName() { return FilterName; } public static String getFilterDesc() { return FilterDesc; } public BamFilterColorBCG(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(sliderBrightness.getValue()).append(';'); sb.append(sliderContrast.getValue()).append(';'); sb.append(((SpinnerNumberModel)spinnerGamma.getModel()).getNumber().doubleValue()).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 bValue = Integer.MIN_VALUE; Integer cValue = Integer.MIN_VALUE; Double gValue = Double.MIN_VALUE; int[] indices = null; // parsing configuration data if (params.length > 0) { // set brightness value bValue = decodeNumber(params[0], sliderBrightness.getMinimum(), sliderBrightness.getMaximum(), Integer.MIN_VALUE); if (bValue == Integer.MIN_VALUE) { return false; } } if (params.length > 1) { // set contrast value cValue = decodeNumber(params[1], sliderContrast.getMinimum(), sliderContrast.getMaximum(), Integer.MIN_VALUE); if (cValue == Integer.MIN_VALUE) { return false; } } if (params.length > 2) { // set gamma value double min = ((Number)((SpinnerNumberModel)spinnerGamma.getModel()).getMinimum()).doubleValue(); double max = ((Number)((SpinnerNumberModel)spinnerGamma.getModel()).getMaximum()).doubleValue(); gValue = decodeDouble(params[2], min, max, Double.MIN_VALUE); if (gValue == Double.MIN_VALUE) { return false; } } if (params.length > 3) { indices = decodeColorList(params[3]); if (indices == null) { return false; } } // applying configuration data if (bValue != Integer.MIN_VALUE) { sliderBrightness.setValue(bValue); } if (cValue != Integer.MIN_VALUE) { sliderContrast.setValue(cValue); } if (gValue != Double.MIN_VALUE) { sliderGamma.setValue((int)(gValue * GammaScaleFactor)); } 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 lb = new JLabel("Brightness:"); JLabel lc = new JLabel("Contrast:"); JLabel lg = new JLabel("Gamma:"); sliderBrightness = new JSlider(SwingConstants.HORIZONTAL, -100, 100, 0); sliderBrightness.addChangeListener(this); sliderContrast = new JSlider(SwingConstants.HORIZONTAL, -100, 100, 0); sliderContrast.addChangeListener(this); sliderGamma = new JSlider(SwingConstants.HORIZONTAL, 1, 500, 100); sliderGamma.addChangeListener(this); spinnerBrightness = new JSpinner(new SpinnerNumberModel(sliderBrightness.getValue(), sliderBrightness.getMinimum(), sliderBrightness.getMaximum(), 1)); spinnerBrightness.addChangeListener(this); spinnerContrast = new JSpinner(new SpinnerNumberModel(sliderContrast.getValue(), sliderContrast.getMinimum(), sliderContrast.getMaximum(), 1)); spinnerContrast.addChangeListener(this); spinnerGamma = new JSpinner(new SpinnerNumberModel((double)sliderGamma.getValue() / GammaScaleFactor, (double)sliderGamma.getMinimum() / GammaScaleFactor, (double)sliderGamma.getMaximum() / GammaScaleFactor, 0.1)); spinnerGamma.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(lb, 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(sliderBrightness, 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(spinnerBrightness, 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(lc, 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(sliderContrast, 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(spinnerContrast, 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(lg, 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(sliderGamma, 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(spinnerGamma, 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() == sliderBrightness) { spinnerBrightness.setValue(Integer.valueOf(sliderBrightness.getValue())); if (sliderBrightness.getModel().getValueIsAdjusting() == false) { fireChangeListener(); } } else if (event.getSource() == sliderContrast) { spinnerContrast.setValue(Integer.valueOf(sliderContrast.getValue())); if (sliderContrast.getModel().getValueIsAdjusting() == false) { fireChangeListener(); } } else if (event.getSource() == sliderGamma) { spinnerGamma.setValue(Double.valueOf((double)sliderGamma.getValue() / GammaScaleFactor)); if (sliderGamma.getModel().getValueIsAdjusting() == false) { fireChangeListener(); } } else if (event.getSource() == spinnerBrightness) { sliderBrightness.setValue(((Integer)spinnerBrightness.getValue()).intValue()); } else if (event.getSource() == spinnerContrast) { sliderContrast.setValue(((Integer)spinnerContrast.getValue()).intValue()); } else if (event.getSource() == spinnerGamma) { double v = ((Double)spinnerGamma.getValue()).doubleValue() * GammaScaleFactor; sliderGamma.setValue((int)v); } } //--------------------- 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]; } // brightness in range [-100, 100] float brightness = ((Integer)spinnerBrightness.getValue()).floatValue() / 100.0f; // contrast in range [-100, 100] float contrast = (((Integer)spinnerContrast.getValue()).floatValue() + 100.0f) / 100.0f; // gamma in range [0.01, 5.0] float gamma2 = 1.0f / ((Double)spinnerGamma.getValue()).floatValue(); for (int i = 0; i < buffer.length; i++) { if ((cm == null || (cm != null && !pExcludeColors.isSelectedIndex(i))) && (buffer[i] & 0xff000000) != 0) { // extracting color channels 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; // applying brightness if (brightness != 0.0f) { fr += brightness; fg += brightness; fb += brightness; if (fr < 0.0f) fr = 0.0f; else if (fr > 1.0f) fr = 1.0f; if (fg < 0.0f) fg = 0.0f; else if (fg > 1.0f) fg = 1.0f; if (fb < 0.0f) fb = 0.0f; else if (fb > 1.0f) fb = 1.0f; } // applying contrast if (contrast != 0.0f) { fr = ((fr - 0.5f) * contrast) + 0.5f; fg = ((fg - 0.5f) * contrast) + 0.5f; fb = ((fb - 0.5f) * contrast) + 0.5f; if (fr < 0.0f) fr = 0.0f; else if (fr > 1.0f) fr = 1.0f; if (fg < 0.0f) fg = 0.0f; else if (fg > 1.0f) fg = 1.0f; if (fb < 0.0f) fb = 0.0f; else if (fb > 1.0f) fb = 1.0f; } // applying gamma if (gamma2 != 1.0f) { fr = (float)Math.pow((double)fr, gamma2); fg = (float)Math.pow((double)fg, gamma2); fb = (float)Math.pow((double)fb, gamma2); if (fr < 0.0f) fr = 0.0f; else if (fr > 1.0f) fr = 1.0f; if (fg < 0.0f) fg = 0.0f; else if (fg > 1.0f) fg = 1.0f; if (fb < 0.0f) fb = 0.0f; else if (fb > 1.0f) fb = 1.0f; } // reverting to int RGB int ir = (int)((fr * fa) * 255.0f); if (ir < 0) ir = 0; else if (ir > 255) ir = 255; int ig = (int)((fg * fa) * 255.0f); if (ig < 0) ig = 0; else if (ig > 255) ig = 255; int ib = (int)((fb * fa) * 255.0f); if (ib < 0) ib = 0; else if (ib > 255) ib = 255; buffer[i] = (buffer[i] & 0xff000000) | (ir << 16) | (ig << 8) | ib; } } 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; } }