mirror of
https://github.com/processing/processing4.git
synced 2026-02-03 13:49:18 +01:00
595 lines
16 KiB
Java
595 lines
16 KiB
Java
/* -*- mode: jde; c-basic-offset: 2; indent-tabs-mode: nil -*- */
|
|
|
|
/*
|
|
Part of the Processing project - http://processing.org
|
|
|
|
Copyright (c) 2006 Ben Fry and Casey Reas
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 2 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program 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 General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software Foundation,
|
|
Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*/
|
|
|
|
package processing.app.tools;
|
|
|
|
import processing.app.*;
|
|
import processing.core.*;
|
|
|
|
import java.awt.*;
|
|
import java.awt.event.*;
|
|
import javax.swing.*;
|
|
import javax.swing.border.*;
|
|
import javax.swing.event.*;
|
|
import javax.swing.text.*;
|
|
|
|
|
|
/**
|
|
* Color picker tool for the Tools menu.
|
|
* <p/>
|
|
* Using the keyboard shortcuts, you can copy/paste the values for the
|
|
* colors and paste them into your program. We didn't do any sort of
|
|
* auto-insert of colorMode() or fill() or stroke() code cuz we couldn't
|
|
* decide on a good way to do this.. your contributions welcome).
|
|
* <p/>
|
|
* There is currently a bug that causes the drawing area to show up
|
|
* slightly large on Windows and Linux. This is an annoyance but less
|
|
* important than me fixing some other stuff. If someone were to track
|
|
* down what's going wrong, I'd be super grateful.
|
|
* (<A HREF="http://dev.processing.org/bugs/show_bug.cgi?id=310">Bug 310</A>)
|
|
*/
|
|
public class ColorPicker implements DocumentListener {
|
|
|
|
Editor editor;
|
|
JFrame frame;
|
|
|
|
int hue, saturation, brightness; // range 360, 100, 100
|
|
int red, green, blue; // range 256, 256, 256
|
|
|
|
ColorRange range;
|
|
ColorSlider slider;
|
|
|
|
JTextField hueField, saturationField, brightnessField;
|
|
JTextField redField, greenField, blueField;
|
|
|
|
JTextField hexField;
|
|
|
|
JPanel colorPanel;
|
|
|
|
|
|
public ColorPicker(Editor editor) {
|
|
this.editor = editor;
|
|
|
|
frame = new JFrame("Color Picker");
|
|
frame.getContentPane().setLayout(new BorderLayout());
|
|
|
|
Box box = Box.createHorizontalBox();
|
|
box.setBorder(new EmptyBorder(12, 12, 12, 12));
|
|
|
|
range = new ColorRange();
|
|
range.init();
|
|
JPanel rangePanel = new JPanel();
|
|
rangePanel.setLayout(new BorderLayout());
|
|
rangePanel.setBorder(BorderFactory.createBevelBorder(BevelBorder.LOWERED));
|
|
rangePanel.add(range, BorderLayout.CENTER);
|
|
//range.setSize(256, 256); // doesn't help, pack() trashes
|
|
box.add(rangePanel);
|
|
box.add(Box.createHorizontalStrut(10));
|
|
|
|
slider = new ColorSlider();
|
|
slider.init();
|
|
JPanel sliderPanel = new JPanel();
|
|
sliderPanel.setLayout(new BorderLayout());
|
|
sliderPanel.setBorder(BorderFactory.createBevelBorder(BevelBorder.LOWERED));
|
|
//slider.setSize(256, 20); // doesn't help, pack() trashes
|
|
sliderPanel.add(slider, BorderLayout.CENTER);
|
|
box.add(sliderPanel);
|
|
box.add(Box.createHorizontalStrut(10));
|
|
|
|
box.add(createColorFields());
|
|
box.add(Box.createHorizontalStrut(10));
|
|
|
|
frame.getContentPane().add(box, BorderLayout.CENTER);
|
|
frame.pack();
|
|
frame.setResizable(false);
|
|
|
|
// these don't help either.. they fix the component size but
|
|
// leave a gap where the component is located
|
|
//range.setSize(256, 256);
|
|
//slider.setSize(256, 20);
|
|
|
|
Dimension size = frame.getSize();
|
|
Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
|
|
frame.setLocation((screen.width - size.width) / 2,
|
|
(screen.height - size.height) / 2);
|
|
|
|
frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
|
|
frame.addWindowListener(new WindowAdapter() {
|
|
public void windowClosing(WindowEvent e) {
|
|
frame.hide();
|
|
}
|
|
});
|
|
Base.registerWindowCloseKeys(frame.getRootPane(), new ActionListener() {
|
|
public void actionPerformed(ActionEvent actionEvent) {
|
|
frame.hide();
|
|
}
|
|
});
|
|
|
|
hueField.getDocument().addDocumentListener(this);
|
|
saturationField.getDocument().addDocumentListener(this);
|
|
brightnessField.getDocument().addDocumentListener(this);
|
|
redField.getDocument().addDocumentListener(this);
|
|
greenField.getDocument().addDocumentListener(this);
|
|
blueField.getDocument().addDocumentListener(this);
|
|
hexField.getDocument().addDocumentListener(this);
|
|
|
|
hexField.setText("FFFFFF");
|
|
}
|
|
|
|
|
|
public void show() {
|
|
frame.show();
|
|
frame.setCursor(Cursor.CROSSHAIR_CURSOR);
|
|
}
|
|
|
|
|
|
public void changedUpdate(DocumentEvent e) {
|
|
//System.out.println("changed");
|
|
}
|
|
|
|
public void removeUpdate(DocumentEvent e) {
|
|
//System.out.println("remove");
|
|
}
|
|
|
|
|
|
boolean updating;
|
|
|
|
public void insertUpdate(DocumentEvent e) {
|
|
if (updating) return; // don't update forever recursively
|
|
updating = true;
|
|
|
|
Document doc = e.getDocument();
|
|
if (doc == hueField.getDocument()) {
|
|
hue = bounded(hue, hueField, 359);
|
|
updateRGB();
|
|
updateHex();
|
|
|
|
} else if (doc == saturationField.getDocument()) {
|
|
saturation = bounded(saturation, saturationField, 99);
|
|
updateRGB();
|
|
updateHex();
|
|
|
|
} else if (doc == brightnessField.getDocument()) {
|
|
brightness = bounded(brightness, brightnessField, 99);
|
|
updateRGB();
|
|
updateHex();
|
|
|
|
} else if (doc == redField.getDocument()) {
|
|
red = bounded(red, redField, 255);
|
|
updateHSB();
|
|
updateHex();
|
|
|
|
} else if (doc == greenField.getDocument()) {
|
|
green = bounded(green, greenField, 255);
|
|
updateHSB();
|
|
updateHex();
|
|
|
|
} else if (doc == blueField.getDocument()) {
|
|
blue = bounded(blue, blueField, 255);
|
|
updateHSB();
|
|
updateHex();
|
|
|
|
} else if (doc == hexField.getDocument()) {
|
|
String str = hexField.getText();
|
|
while (str.length() < 6) {
|
|
str += "0";
|
|
}
|
|
if (str.length() > 6) {
|
|
str = str.substring(0, 6);
|
|
}
|
|
updateRGB2(Integer.parseInt(str, 16));
|
|
updateHSB();
|
|
}
|
|
range.redraw();
|
|
slider.redraw();
|
|
colorPanel.repaint();
|
|
updating = false;
|
|
}
|
|
|
|
|
|
/**
|
|
* Set the RGB values based on the current HSB values.
|
|
*/
|
|
protected void updateRGB() {
|
|
int rgb = Color.HSBtoRGB((float)hue / 359f,
|
|
(float)saturation / 99f,
|
|
(float)brightness / 99f);
|
|
updateRGB2(rgb);
|
|
}
|
|
|
|
|
|
/**
|
|
* Set the RGB values based on a calculated ARGB int.
|
|
* Used by both updateRGB() to set the color from the HSB values,
|
|
* and by updateHex(), to unpack the hex colors and assign them.
|
|
*/
|
|
protected void updateRGB2(int rgb) {
|
|
red = (rgb >> 16) & 0xff;
|
|
green = (rgb >> 8) & 0xff;
|
|
blue = rgb & 0xff;
|
|
|
|
redField.setText(String.valueOf(red));
|
|
greenField.setText(String.valueOf(green));
|
|
blueField.setText(String.valueOf(blue));
|
|
}
|
|
|
|
|
|
/**
|
|
* Set the HSB values based on the current RGB values.
|
|
*/
|
|
protected void updateHSB() {
|
|
float hsb[] = new float[3];
|
|
Color.RGBtoHSB(red, green, blue, hsb);
|
|
|
|
hue = (int) (hsb[0] * 359.0f);
|
|
saturation = (int) (hsb[1] * 99.0f);
|
|
brightness = (int) (hsb[2] * 99.0f);
|
|
|
|
hueField.setText(String.valueOf(hue));
|
|
saturationField.setText(String.valueOf(saturation));
|
|
brightnessField.setText(String.valueOf(brightness));
|
|
}
|
|
|
|
|
|
protected void updateHex() {
|
|
hexField.setText(PApplet.hex(red, 2) +
|
|
PApplet.hex(green, 2) +
|
|
PApplet.hex(blue, 2));
|
|
}
|
|
|
|
|
|
/**
|
|
* Get the bounded value for a specific range. If the value is outside
|
|
* the max, you can't edit right away, so just act as if it's already
|
|
* been bounded and return the bounded value, then fire an event to set
|
|
* it to the value that was just returned.
|
|
*/
|
|
protected int bounded(int current, final JTextField field, final int max) {
|
|
String text = field.getText();
|
|
if (text.length() == 0) {
|
|
return 0;
|
|
}
|
|
try {
|
|
int value = Integer.parseInt(text);
|
|
if (value > max) {
|
|
SwingUtilities.invokeLater(new Runnable() {
|
|
public void run() {
|
|
field.setText(String.valueOf(max));
|
|
}
|
|
});
|
|
return max;
|
|
}
|
|
return value;
|
|
|
|
} catch (NumberFormatException e) {
|
|
return current; // should not be reachable
|
|
}
|
|
}
|
|
|
|
|
|
protected Container createColorFields() {
|
|
Box box = Box.createVerticalBox();
|
|
|
|
colorPanel = new JPanel() {
|
|
public void paintComponent(Graphics g) {
|
|
g.setColor(new Color(red, green, blue));
|
|
Dimension size = getSize();
|
|
g.fillRect(0, 0, size.width, size.height);
|
|
}
|
|
};
|
|
colorPanel.setBorder(BorderFactory.createBevelBorder(BevelBorder.LOWERED));
|
|
Dimension dim = new Dimension(60, 40);
|
|
colorPanel.setMinimumSize(dim);
|
|
//colorPanel.setMaximumSize(dim);
|
|
//colorPanel.setPreferredSize(dim);
|
|
box.add(colorPanel);
|
|
box.add(Box.createVerticalStrut(10));
|
|
|
|
Box row;
|
|
|
|
row = Box.createHorizontalBox();
|
|
row.add(createFixedLabel("H:"));
|
|
row.add(hueField = new NumberField(4, false));
|
|
row.add(new JLabel(" \u00B0")); // degree symbol
|
|
row.add(Box.createHorizontalGlue());
|
|
box.add(row);
|
|
box.add(Box.createVerticalStrut(5));
|
|
|
|
row = Box.createHorizontalBox();
|
|
row.add(createFixedLabel("S:"));
|
|
row.add(saturationField = new NumberField(4, false));
|
|
row.add(new JLabel(" %"));
|
|
row.add(Box.createHorizontalGlue());
|
|
box.add(row);
|
|
box.add(Box.createVerticalStrut(5));
|
|
|
|
row = Box.createHorizontalBox();
|
|
row.add(createFixedLabel("B:"));
|
|
row.add(brightnessField = new NumberField(4, false));
|
|
row.add(new JLabel(" %"));
|
|
row.add(Box.createHorizontalGlue());
|
|
box.add(row);
|
|
box.add(Box.createVerticalStrut(10));
|
|
|
|
//
|
|
|
|
row = Box.createHorizontalBox();
|
|
row.add(createFixedLabel("R:"));
|
|
row.add(redField = new NumberField(4, false));
|
|
row.add(Box.createHorizontalGlue());
|
|
box.add(row);
|
|
box.add(Box.createVerticalStrut(5));
|
|
|
|
row = Box.createHorizontalBox();
|
|
row.add(createFixedLabel("G:"));
|
|
row.add(greenField = new NumberField(4, false));
|
|
row.add(Box.createHorizontalGlue());
|
|
box.add(row);
|
|
box.add(Box.createVerticalStrut(5));
|
|
|
|
row = Box.createHorizontalBox();
|
|
row.add(createFixedLabel("B:"));
|
|
row.add(blueField = new NumberField(4, false));
|
|
row.add(Box.createHorizontalGlue());
|
|
box.add(row);
|
|
box.add(Box.createVerticalStrut(10));
|
|
|
|
//
|
|
|
|
row = Box.createHorizontalBox();
|
|
row.add(createFixedLabel("#"));
|
|
row.add(hexField = new NumberField(5, true));
|
|
row.add(Box.createHorizontalGlue());
|
|
box.add(row);
|
|
box.add(Box.createVerticalStrut(10));
|
|
|
|
box.add(Box.createVerticalGlue());
|
|
return box;
|
|
}
|
|
|
|
|
|
int labelH;
|
|
|
|
/**
|
|
* return a label of a fixed width
|
|
*/
|
|
protected JLabel createFixedLabel(String title) {
|
|
JLabel label = new JLabel(title);
|
|
if (labelH == 0) {
|
|
labelH = label.getPreferredSize().height;
|
|
}
|
|
Dimension dim = new Dimension(20, labelH);
|
|
label.setPreferredSize(dim);
|
|
label.setMinimumSize(dim);
|
|
label.setMaximumSize(dim);
|
|
return label;
|
|
}
|
|
|
|
|
|
public class ColorRange extends PApplet {
|
|
|
|
static final int WIDE = 256;
|
|
static final int HIGH = 256;
|
|
|
|
int lastX, lastY;
|
|
|
|
|
|
public void setup() {
|
|
size(WIDE, HIGH, P3D);
|
|
noLoop();
|
|
|
|
colorMode(HSB, 360, 256, 256);
|
|
noFill();
|
|
rectMode(CENTER);
|
|
}
|
|
|
|
public void draw() {
|
|
if ((g == null) || (g.pixels == null)) return;
|
|
if ((width != WIDE) || (height < HIGH)) {
|
|
//System.out.println("bad size " + width + " " + height);
|
|
return;
|
|
}
|
|
|
|
int index = 0;
|
|
for (int j = 0; j < 256; j++) {
|
|
for (int i = 0; i < 256; i++) {
|
|
g.pixels[index++] = color(hue, i, 255 - j);
|
|
}
|
|
}
|
|
|
|
stroke((brightness > 50) ? 0 : 255);
|
|
rect(lastX, lastY, 9, 9);
|
|
}
|
|
|
|
public void mousePressed() {
|
|
updateMouse();
|
|
}
|
|
|
|
public void mouseDragged() {
|
|
updateMouse();
|
|
}
|
|
|
|
public void updateMouse() {
|
|
if ((mouseX >= 0) && (mouseX < 256) &&
|
|
(mouseY >= 0) && (mouseY < 256)) {
|
|
int nsaturation = (int) (100 * (mouseX / 255.0f));
|
|
int nbrightness = 100 - ((int) (100 * (mouseY / 255.0f)));
|
|
saturationField.setText(String.valueOf(nsaturation));
|
|
brightnessField.setText(String.valueOf(nbrightness));
|
|
|
|
lastX = mouseX;
|
|
lastY = mouseY;
|
|
}
|
|
}
|
|
|
|
public Dimension getPreferredSize() {
|
|
//System.out.println("getting pref " + WIDE + " " + HIGH);
|
|
return new Dimension(WIDE, HIGH);
|
|
}
|
|
|
|
public Dimension getMinimumSize() {
|
|
//System.out.println("getting min " + WIDE + " " + HIGH);
|
|
return new Dimension(WIDE, HIGH);
|
|
}
|
|
|
|
public Dimension getMaximumSize() {
|
|
//System.out.println("getting max " + WIDE + " " + HIGH);
|
|
return new Dimension(WIDE, HIGH);
|
|
}
|
|
}
|
|
|
|
|
|
public class ColorSlider extends PApplet {
|
|
|
|
static final int WIDE = 20;
|
|
static final int HIGH = 256;
|
|
|
|
public void setup() {
|
|
size(WIDE, HIGH, P3D);
|
|
colorMode(HSB, 255, 100, 100);
|
|
noLoop();
|
|
}
|
|
|
|
public void draw() {
|
|
if ((g == null) || (g.pixels == null)) return;
|
|
if ((width != WIDE) || (height < HIGH)) {
|
|
//System.out.println("bad size " + width + " " + height);
|
|
return;
|
|
}
|
|
|
|
int index = 0;
|
|
int sel = 255 - (int) (255 * (hue / 359f));
|
|
for (int j = 0; j < 256; j++) {
|
|
int c = color(255 - j, 100, 100);
|
|
if (j == sel) c = 0xFF000000;
|
|
for (int i = 0; i < WIDE; i++) {
|
|
g.pixels[index++] = c;
|
|
}
|
|
}
|
|
}
|
|
|
|
public void mousePressed() {
|
|
updateMouse();
|
|
}
|
|
|
|
public void mouseDragged() {
|
|
updateMouse();
|
|
}
|
|
|
|
public void updateMouse() {
|
|
if ((mouseX >= 0) && (mouseX < 256) &&
|
|
(mouseY >= 0) && (mouseY < 256)) {
|
|
int nhue = 359 - (int) (359 * (mouseY / 255.0f));
|
|
hueField.setText(String.valueOf(nhue));
|
|
}
|
|
}
|
|
|
|
public Dimension getPreferredSize() {
|
|
//System.out.println("s getting pref " + WIDE + " " + HIGH);
|
|
return new Dimension(WIDE, HIGH);
|
|
}
|
|
|
|
public Dimension getMinimumSize() {
|
|
//System.out.println("s getting min " + WIDE + " " + HIGH);
|
|
return new Dimension(WIDE, HIGH);
|
|
}
|
|
|
|
public Dimension getMaximumSize() {
|
|
//System.out.println("s getting max " + WIDE + " " + HIGH);
|
|
return new Dimension(WIDE, HIGH);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Extension of JTextField that only allows numbers
|
|
*/
|
|
class NumberField extends JTextField {
|
|
|
|
public boolean allowHex;
|
|
|
|
public NumberField(int cols, boolean allowHex) {
|
|
super(cols);
|
|
this.allowHex = allowHex;
|
|
}
|
|
|
|
protected Document createDefaultModel() {
|
|
return new NumberDocument(this);
|
|
}
|
|
|
|
public Dimension getPreferredSize() {
|
|
if (!allowHex) {
|
|
return new Dimension(35, super.getPreferredSize().height);
|
|
}
|
|
return super.getPreferredSize();
|
|
}
|
|
|
|
public Dimension getMinimumSize() {
|
|
return getPreferredSize();
|
|
}
|
|
|
|
public Dimension getMaximumSize() {
|
|
return getPreferredSize();
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Document model to go with JTextField that only allows numbers.
|
|
*/
|
|
class NumberDocument extends PlainDocument {
|
|
|
|
NumberField parentField;
|
|
|
|
public NumberDocument(NumberField parentField) {
|
|
this.parentField = parentField;
|
|
//System.out.println("setting parent to " + parentPicker);
|
|
}
|
|
|
|
public void insertString(int offs, String str, AttributeSet a)
|
|
throws BadLocationException {
|
|
|
|
if (str == null) return;
|
|
|
|
char chars[] = str.toCharArray();
|
|
int charCount = 0;
|
|
// remove any non-digit chars
|
|
for (int i = 0; i < chars.length; i++) {
|
|
boolean ok = Character.isDigit(chars[i]);
|
|
if (parentField.allowHex) {
|
|
if ((chars[i] >= 'A') && (chars[i] <= 'F')) ok = true;
|
|
if ((chars[i] >= 'a') && (chars[i] <= 'f')) ok = true;
|
|
}
|
|
if (ok) {
|
|
if (charCount != i) { // shift if necessary
|
|
chars[charCount] = chars[i];
|
|
}
|
|
charCount++;
|
|
}
|
|
}
|
|
super.insertString(offs, new String(chars, 0, charCount), a);
|
|
// can't call any sort of methods on the enclosing class here
|
|
// seems to have something to do with how Document objects are set up
|
|
}
|
|
}
|
|
}
|