diff --git a/src/java/com/threerings/media/tools/RecolorImage.java b/src/java/com/threerings/media/tools/RecolorImage.java index ef2510f07..e5cef8182 100644 --- a/src/java/com/threerings/media/tools/RecolorImage.java +++ b/src/java/com/threerings/media/tools/RecolorImage.java @@ -1,5 +1,5 @@ // -// $Id: RecolorImage.java,v 1.6 2004/08/27 02:12:46 mdb Exp $ +// $Id$ // // Narya library - tools for developing networked games // Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved @@ -23,7 +23,11 @@ package com.threerings.media.tools; import java.awt.BorderLayout; import java.awt.Color; +import java.awt.Graphics; import java.awt.Rectangle; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.Collections; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseAdapter; @@ -31,6 +35,7 @@ import java.awt.event.MouseEvent; import java.awt.image.BufferedImage; import java.io.File; +import java.io.FileInputStream; import java.io.IOException; import javax.imageio.ImageIO; @@ -38,17 +43,27 @@ import javax.imageio.ImageIO; import javax.swing.BorderFactory; import javax.swing.ImageIcon; import javax.swing.JButton; +import javax.swing.JComboBox; import javax.swing.JFileChooser; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JScrollPane; +import javax.swing.JSlider; import javax.swing.JTextField; +import javax.swing.JToggleButton; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; import com.samskivert.swing.HGroupLayout; +import com.samskivert.swing.IntField; import com.samskivert.swing.VGroupLayout; +import com.samskivert.swing.event.DocumentAdapter; import com.samskivert.swing.util.SwingUtil; +import com.samskivert.util.Interator; +import com.threerings.media.image.ColorPository; +import com.threerings.media.image.Colorization; import com.threerings.media.image.ImageUtil; /** @@ -70,6 +85,7 @@ public class RecolorImage extends JPanel images.add(_newImage = new JLabel()); add(new JScrollPane(images)); + // Image file JPanel file = new JPanel(new HGroupLayout(HGroupLayout.STRETCH)); file.add(new JLabel("Image file:"), HGroupLayout.FIXED); file.add(_imagePath = new JTextField()); @@ -78,11 +94,39 @@ public class RecolorImage extends JPanel browse.setActionCommand("browse"); browse.addActionListener(this); file.add(browse, HGroupLayout.FIXED); + JButton reload = new JButton("Reload"); + reload.setActionCommand("reload"); + reload.addActionListener(this); + file.add(reload, HGroupLayout.FIXED); add(file, VGroupLayout.FIXED); + // Colorization file + JPanel colFile = new JPanel(new HGroupLayout(HGroupLayout.STRETCH)); + colFile.add(new JLabel("Colorize file:"), HGroupLayout.FIXED); + colFile.add(_colFilePath = new JTextField()); + _colFilePath.setEditable(false); + browse = new JButton("Browse..."); + browse.setActionCommand("browse_colorize"); + browse.addActionListener(this); + colFile.add(browse, HGroupLayout.FIXED); + add(colFile, VGroupLayout.FIXED); + + JPanel colMode = new JPanel(new HGroupLayout(HGroupLayout.STRETCH)); + colMode.add(_mode = new JToggleButton("All Colorizations")); + colMode.add(_classList = new JComboBox()); + ActionListener al = new ActionListener () { + public void actionPerformed (ActionEvent ae) { + convert(); + } + }; + _mode.addActionListener(al); + _classList.addActionListener(al); + + add(colMode, VGroupLayout.FIXED); + JPanel controls = new JPanel(new HGroupLayout(HGroupLayout.STRETCH)); controls.add(new JLabel("Source color:"), HGroupLayout.FIXED); - controls.add(_source = new JTextField()); + controls.add(_source = new JTextField("FF0000")); _colorLabel = new JPanel(); _colorLabel.setSize(48, 48); _colorLabel.setOpaque(true); @@ -98,17 +142,17 @@ public class RecolorImage extends JPanel HGroupLayout hlay = new HGroupLayout(HGroupLayout.STRETCH); JPanel dists = new JPanel(hlay); dists.add(new JLabel("HSV distances:"), HGroupLayout.FIXED); - dists.add(_hueD = new JTextField("0.05")); - dists.add(_saturationD = new JTextField("0.8")); - dists.add(_valueD = new JTextField("0.6")); + dists.add(_hueD = new SliderAndLabel(0.0f, 1.0f, 0.05f)); + dists.add(_saturationD = new SliderAndLabel(0.0f, 1.0f, 0.8f)); + dists.add(_valueD = new SliderAndLabel(0.0f, 1.0f, 0.6f)); add(dists, VGroupLayout.FIXED); hlay = new HGroupLayout(HGroupLayout.STRETCH); JPanel offsets = new JPanel(hlay); offsets.add(new JLabel("HSV offsets:"), HGroupLayout.FIXED); - offsets.add(_hueO = new JTextField("0.1")); - offsets.add(_saturationO = new JTextField("0.0")); - offsets.add(_valueO = new JTextField("0.0")); + offsets.add(_hueO = new SliderAndLabel(-1.0f, 1.0f, 0.1f)); + offsets.add(_saturationO = new SliderAndLabel(-1.0f, 1.0f, 0.0f)); + offsets.add(_valueO = new SliderAndLabel(-1.0f, 1.0f, 0.0f)); add(offsets, VGroupLayout.FIXED); add(_status = new JTextField(), VGroupLayout.FIXED); @@ -134,40 +178,98 @@ public class RecolorImage extends JPanel String cwd = System.getProperty("user.dir"); if (cwd == null) { _chooser = new JFileChooser(); + _colChooser = new JFileChooser(); } else { _chooser = new JFileChooser(cwd); + _colChooser = new JFileChooser(cwd); } } + /** + * Performs colorizations. + */ + protected void convert () + { + if (_image == null) { + return; + } + + // obtain the target color and offset + try { + BufferedImage image; + if (_mode.isSelected()) { + // All recolorings from file. + image = getAllRecolors(); + if (image == null) { + return; + } + } else { + // Normal recoloring + int color = Integer.parseInt(_source.getText(), 16); + + float hueD = _hueD.getValue(); + float satD = _saturationD.getValue(); + float valD = _valueD.getValue(); + float[] dists = new float[] { hueD, satD, valD }; + + float hueO = _hueO.getValue(); + float satO = _saturationO.getValue(); + float valO = _valueO.getValue(); + float[] offsets = new float[] { hueO, satO, valO }; + + image = ImageUtil.recolorImage( + _image, new Color(color), dists, offsets); + } + _newImage.setIcon(new ImageIcon(image)); + _status.setText("Recolored image."); + repaint(); + + } catch (NumberFormatException nfe) { + _status.setText("Invalid value: " + nfe.getMessage()); + } + } + + /** + * Gets an image with all recolorings of the selection colorization class. + */ + public BufferedImage getAllRecolors () + { + if (_colRepo == null) { + return null; + } + + ColorPository.ClassRecord colClass = + _colRepo.getClassRecord((String)_classList.getSelectedItem()); + int classId = colClass.classId; + + ArrayList imgs = new ArrayList(); + Interator iter = colClass.colors.keys(); + BufferedImage img = new BufferedImage(_image.getWidth(), + _image.getHeight()*colClass.colors.size(), + BufferedImage.TYPE_INT_ARGB); + Graphics gfx = img.getGraphics(); + int y = 0; + + while (iter.hasNext()) { + Colorization coloriz = + _colRepo.getColorization(classId, iter.nextInt()); + BufferedImage subImg = ImageUtil.recolorImage( + _image, coloriz.rootColor, coloriz.range, coloriz.offsets); + + gfx.drawImage(subImg, 0, y, null, null); + + y += subImg.getHeight(); + } + + return img; + } + public void actionPerformed (ActionEvent event) { String cmd = event.getActionCommand(); if (cmd.equals("convert")) { - // obtain the target color and offset - try { - int color = Integer.parseInt(_source.getText(), 16); - - float hueD = Float.parseFloat(_hueD.getText()); - float satD = Float.parseFloat(_saturationD.getText()); - float valD = Float.parseFloat(_valueD.getText()); - float[] dists = new float[] { hueD, satD, valD }; - - float hueO = Float.parseFloat(_hueO.getText()); - float satO = Float.parseFloat(_saturationO.getText()); - float valO = Float.parseFloat(_valueO.getText()); - float[] offsets = new float[] { hueO, satO, valO }; - - BufferedImage image = ImageUtil.recolorImage( - _image, new Color(color), dists, offsets); - _newImage.setIcon(new ImageIcon(image)); - _status.setText("Recolored image."); - repaint(); - - } catch (NumberFormatException nfe) { - _status.setText("Invalid value: " + nfe.getMessage()); - } - + convert(); } else if (cmd.equals("update")) { // obtain the target color and offset try { @@ -177,9 +279,9 @@ public class RecolorImage extends JPanel float[] thsv = rgbToHSV(target); // set the offsets based on the differences - _hueO.setText("" + (thsv[0] - shsv[0])); - _saturationO.setText("" + (thsv[1] - shsv[1])); - _valueO.setText("" + (thsv[2] - shsv[2])); + _hueO.setValue(thsv[0] - shsv[0]); + _saturationO.setValue(thsv[1] - shsv[1]); + _valueO.setValue(thsv[2] - shsv[2]); } catch (NumberFormatException nfe) { _status.setText("Invalid value: " + nfe.getMessage()); @@ -190,6 +292,13 @@ public class RecolorImage extends JPanel if (result == JFileChooser.APPROVE_OPTION) { setImage(_chooser.getSelectedFile()); } + } else if (cmd.equals("reload")) { + setImage(_chooser.getSelectedFile()); + } else if (cmd.equals("browse_colorize")) { + int result = _colChooser.showOpenDialog(this); + if (result == JFileChooser.APPROVE_OPTION) { + setColorizeFile(_colChooser.getSelectedFile()); + } } } @@ -205,6 +314,37 @@ public class RecolorImage extends JPanel } } + /** + * Loads up the colorization classes from the specified file. + */ + public void setColorizeFile (File path) + { + try { + _colRepo = + ColorPository.loadColorPository(new FileInputStream(path)); + + _classList.removeAllItems(); + Iterator iter = _colRepo.enumerateClasses(); + ArrayList names = new ArrayList(); + while (iter.hasNext()) { + String str = ((ColorPository.ClassRecord)iter.next()).name; + names.add(str); + } + + Collections.sort(names); + + iter = names.iterator(); + while (iter.hasNext()) { + _classList.addItem((String)iter.next()); + } + + _classList.setSelectedIndex(0); + _colFilePath.setText(path.getPath()); + } catch (Exception ex) { + _status.setText("Error opening colorization file: " + ex); + } + } + protected static float[] rgbToHSV (int rgb) { float[] hsv = new float[3]; @@ -229,6 +369,48 @@ public class RecolorImage extends JPanel } } + /** + * Class with linked slider and label arranged vertically. + */ + protected class SliderAndLabel extends JPanel + { + public SliderAndLabel (float minf, float maxf, float valuef) + { + int min = (int)(minf*CONVERSION); + int max = (int)(maxf*CONVERSION); + int value = (int)(valuef*CONVERSION); + setLayout(new VGroupLayout(VGroupLayout.STRETCH)); + _intField = new JLabel(String.valueOf(value/CONVERSION)); + _slider = new JSlider(min, max, value); + + _slider.addChangeListener(new ChangeListener() { + public void stateChanged (ChangeEvent ce) { + _intField.setText(String.valueOf( + ((float)_slider.getValue())/CONVERSION)); + + convert(); + } + }); + add(_intField); + add(_slider); + } + + public float getValue () + { + return _slider.getValue()/CONVERSION; + } + + public void setValue (float val) + { + _slider.setValue((int)(val*CONVERSION)); + } + + protected JSlider _slider; + protected JLabel _intField; + + protected final static float CONVERSION = 1000.0f; + } + public static void main (String[] args) { try { @@ -255,7 +437,9 @@ public class RecolorImage extends JPanel protected BufferedImage _image; protected JFileChooser _chooser; + protected JFileChooser _colChooser; protected JTextField _imagePath; + protected JTextField _colFilePath; protected JLabel _oldImage; protected JLabel _newImage; @@ -264,16 +448,21 @@ public class RecolorImage extends JPanel protected JTextField _source; protected JTextField _target; - protected JTextField _hueO; - protected JTextField _saturationO; - protected JTextField _valueO; + protected SliderAndLabel _hueO; + protected SliderAndLabel _saturationO; + protected SliderAndLabel _valueO; - protected JTextField _hueD; - protected JTextField _saturationD; - protected JTextField _valueD; + protected SliderAndLabel _hueD; + protected SliderAndLabel _saturationD; + protected SliderAndLabel _valueD; protected JTextField _status; + protected JComboBox _classList; + protected JToggleButton _mode; + + protected ColorPository _colRepo; + protected static final String IMAGE_PATH = // "bundles/components/pirate/torso/regular/standing.png"; "bundles/components/pirate/head/regular/standing.png";