// 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.Color; import java.awt.Dimension; 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.util.ArrayList; import java.util.List; import javax.swing.BorderFactory; import javax.swing.JButton; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import org.infinity.gui.ColorGrid; import org.infinity.gui.ViewerUtil; import org.infinity.gui.ColorGrid.MouseOverEvent; import org.infinity.gui.ColorGrid.MouseOverListener; import org.infinity.util.Misc; /** * The base class for filters that manipulate on color/pixel level. */ public abstract class BamFilterBaseColor extends BamFilterBase { protected BamFilterBaseColor(ConvertToBam parent, String name, String desc) { super(parent, name, desc, Type.COLOR); } /** * Applies the filter to the specified BufferedImage object. * The returned BufferedImage object can either ne the modified source image or a new copy. * @param frame The BufferedImage object to modify. * @return The resulting BufferedImage object. */ public abstract BufferedImage process(BufferedImage frame) throws Exception; /** Parses a list of palette indices from a parameter string of the format "[idx1,idx2,...]". */ protected int[] decodeColorList(String param) { int[] indices = null; if (param != null && param.matches("\\[.*\\]")) { String colorString = param.substring(1, param.length() - 1).trim(); if (!colorString.isEmpty()) { String[] colors = colorString.split(","); indices = new int[colors.length]; for (int i = 0; i < colors.length; i++) { indices[i] = Misc.toNumber(colors[i], -1); if (indices[i] < 0 || indices[i] > 255) { indices = null; break; } } } else { indices = new int[0]; } } return indices; } /** Converts a list of palette indices into a parameter string. */ protected String encodeColorList(int[] indices) { StringBuilder sb = new StringBuilder(); sb.append('['); if (indices != null) { for (int i = 0; i < indices.length; i++) { if (i > 0) { sb.append(','); } sb.append(indices[i]); } } sb.append(']'); return sb.toString(); } //-------------------------- INNER CLASSES -------------------------- /** * Lets you select multiple color entries from the palette. It can be used to exclude the * selected colors from filtering. */ public static class ExcludeColorsPanel extends JPanel implements MouseOverListener, ActionListener { private static final String FmtInfoRGB = "%1$d %2$d %3$d"; private static final String FmtInfoHexRGB = "#%1$02X%2$02X%3$02X"; private static final int[] AllColorIndices = new int[256]; static { for (int i = 0; i < AllColorIndices.length; i++) { AllColorIndices[i] = i; } } private final List<ChangeListener> listChangeListeners = new ArrayList<ChangeListener>(); private ColorGrid cgPalette; private JLabel lInfoIndex, lInfoRGB, lInfoHexRGB; private JButton bSelectAll, bSelectNone, bSelectInvert; public ExcludeColorsPanel(int[] palette) { super(new GridBagLayout()); init(); updatePalette(palette); } /** * Adds a ChangeListener to the listener list. * ChangeListeners will be notified whenever the color selection changes. */ public void addChangeListener(ChangeListener l) { if (l != null) { if (listChangeListeners.indexOf(l) < 0) { listChangeListeners.add(l); } } } /** Returns an array of all ChangeListeners added to this object. */ public ChangeListener[] getChangeListeners() { ChangeListener[] retVal = new ChangeListener[listChangeListeners.size()]; for (int i = 0; i < listChangeListeners.size(); i++) { retVal[i] = listChangeListeners.get(i); } return retVal; } /** Removes a ChangeListener from the listener list. */ public void removeChangeListener(ChangeListener l) { if (l != null) { int idx = listChangeListeners.indexOf(l); if (idx >= 0) { listChangeListeners.remove(idx); } } } /** Applies the specified palette to the color grid component. */ public void updatePalette(int[] palette) { for (int i = 0; i < cgPalette.getColorCount(); i++) { if (palette != null && i < palette.length) { cgPalette.setColor(i, new Color(palette[i])); } else { cgPalette.setColor(i, Color.BLACK); } } } /** Returns the selected color indices as an array of integers. */ public int[] getSelectedIndices() { return cgPalette.getSelectedIndices(); } /** Returns whether the specified color index has been selected. */ public boolean isSelectedIndex(int index) { return cgPalette.isSelectedIndex(index); } /** Selects the specified color indices. Previous selections will be cleared. */ public void setSelectedIndices(int[] indices) { cgPalette.clearSelection(); if (indices != null) { cgPalette.setSelectedIndices(indices); } } //--------------------- Begin Interface MouseOverListener --------------------- @Override public void mouseOver(MouseOverEvent event) { if (event.getSource() == cgPalette) { updateInfoBox(event.getColorIndex()); } } //--------------------- End Interface MouseOverListener --------------------- //--------------------- Begin Interface ActionListener --------------------- @Override public void actionPerformed(ActionEvent event) { if (event.getSource() == cgPalette) { fireChangeListener(); } else if (event.getSource() == bSelectAll) { cgPalette.setSelectedIndices(AllColorIndices); fireChangeListener(); } else if (event.getSource() == bSelectNone) { cgPalette.clearSelection(); fireChangeListener(); } else if (event.getSource() == bSelectInvert) { int[] selectedIndices = cgPalette.getSelectedIndices(); int[] indices = new int[cgPalette.getColorCount() - selectedIndices.length]; for (int i = 0, ofs = 0; i < cgPalette.getColorCount(); i++) { int idx = -1; for (int j = 0; j < selectedIndices.length; j++) { if (i == selectedIndices[j]) { idx = j; break; } } if (idx < 0) { indices[ofs++] = i; } } cgPalette.setSelectedIndices(indices); fireChangeListener(); } } //--------------------- End Interface ActionListener --------------------- private void init() { GridBagConstraints c = new GridBagConstraints(); // creating palette section cgPalette = new ColorGrid(256); cgPalette.setColorEntryHorizontalGap(4); cgPalette.setColorEntryVerticalGap(4); cgPalette.setSelectionMode(ColorGrid.SELECTION_MULTIPLE); cgPalette.setSelectionFrame(ColorGrid.Frame.SINGLE_LINE); cgPalette.addMouseOverListener(this); cgPalette.addActionListener(this); JPanel pPalette = new JPanel(new GridBagLayout()); pPalette.setBorder(BorderFactory.createTitledBorder("Palette ")); c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(0, 4, 2, 4), 0, 0); pPalette.add(cgPalette, c); // creating information panel JPanel pInfo = new JPanel(new GridBagLayout()); pInfo.setBorder(BorderFactory.createTitledBorder("Information ")); JLabel lInfoIndexTitle = new JLabel("Index:"); JLabel lInfoRGBTitle = new JLabel("RGB:"); JLabel lInfoHexRGBTitle = new JLabel("Hex:"); // XXX: making sure that the initial size of the components is big enough to hold all valid data lInfoIndex = new JLabel("1999"); lInfoRGB = new JLabel(String.format(FmtInfoRGB, 1999, 1999, 1999)); lInfoHexRGB = new JLabel(String.format(FmtInfoHexRGB, 0xAAA, 0xAAA, 0xAAA)); c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(0, 4, 0, 0), 0, 0); pInfo.add(lInfoIndexTitle, c); c = ViewerUtil.setGBC(c, 1, 0, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(0, 8, 0, 4), 0, 0); pInfo.add(lInfoIndex, c); c = ViewerUtil.setGBC(c, 0, 1, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(4, 4, 0, 0), 0, 0); pInfo.add(lInfoRGBTitle, c); c = ViewerUtil.setGBC(c, 1, 1, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(4, 8, 0, 4), 0, 0); pInfo.add(lInfoRGB, c); c = ViewerUtil.setGBC(c, 0, 2, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(4, 4, 4, 0), 0, 0); pInfo.add(lInfoHexRGBTitle, c); c = ViewerUtil.setGBC(c, 1, 2, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(4, 8, 4, 4), 0, 0); pInfo.add(lInfoHexRGB, c); // creating button section JPanel pButtons = new JPanel(new GridBagLayout()); bSelectAll = new JButton("Select all"); bSelectAll.setMnemonic('a'); bSelectAll.addActionListener(this); bSelectNone = new JButton("Select none"); bSelectNone.setMnemonic('n'); bSelectNone.addActionListener(this); bSelectInvert = new JButton("Invert selection"); bSelectInvert.setMnemonic('i'); bSelectInvert.addActionListener(this); c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.HORIZONTAL, new Insets(4, 4, 0, 4), 0, 0); pButtons.add(bSelectAll, c); c = ViewerUtil.setGBC(c, 0, 1, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.HORIZONTAL, new Insets(4, 4, 0, 4), 0, 0); pButtons.add(bSelectNone, c); c = ViewerUtil.setGBC(c, 0, 2, 1, 1, 1.0, 1.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.HORIZONTAL, new Insets(4, 4, 4, 4), 0, 0); pButtons.add(bSelectInvert, c); // putting sidebar together JPanel pSideBar = new JPanel(new GridBagLayout()); c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0); pSideBar.add(pInfo, c); c = ViewerUtil.setGBC(c, 0, 1, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.HORIZONTAL, new Insets(8, 0, 0, 0), 0, 0); pSideBar.add(pButtons, c); // putting all together JPanel pMain = new JPanel(new GridBagLayout()); c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0); pMain.add(pPalette, c); c = ViewerUtil.setGBC(c, 1, 0, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.HORIZONTAL, new Insets(0, 4, 0, 0), 0, 0); pMain.add(pSideBar, c); // and adding to main panel c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 1.0, 1.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.BOTH, new Insets(4, 4, 4, 4), 0, 0); add(pMain, c); updateInfoBox(-1); } // Updates the information panel private void updateInfoBox(int index) { if (index >= 0 && index < cgPalette.getColorCount()) { Color c = cgPalette.getColor(index); setLabelText(lInfoIndex, Integer.toString(index), null); setLabelText(lInfoRGB, String.format(FmtInfoRGB, c.getRed(), c.getGreen(), c.getBlue()), null); setLabelText(lInfoHexRGB, String.format(FmtInfoHexRGB, c.getRed(), c.getGreen(), c.getBlue()), null); } else { setLabelText(lInfoIndex, "", null); setLabelText(lInfoRGB, "", null); setLabelText(lInfoHexRGB, "", null); } } // Sets a new text to the specified JLabel component while retaining its preferred size private void setLabelText(JLabel c, String text, Dimension d) { if (c != null) { if (text == null) text = ""; if (d == null) { d = c.getPreferredSize(); } c.setText(text); c.setPreferredSize(d); } } private void fireChangeListener() { ChangeEvent event = new ChangeEvent(this); for (int i = 0; i < listChangeListeners.size(); i++) { listChangeListeners.get(i).stateChanged(event); } } } }