/* * Copyright (c) 1997, 2011, Oracle and/or its affiliates. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * - Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * - Neither the name of Oracle nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* * This source code is provided to illustrate the usage of a given feature * or technique and has been deliberately simplified. Additional steps * required for a production-quality application, such as security checks, * input validation and proper error handling, might not be present in * this sample code. */ import java.applet.Applet; import java.awt.AWTEvent; import java.awt.BorderLayout; import java.awt.Button; import java.awt.Canvas; import java.awt.Choice; import java.awt.Color; import java.awt.Dimension; import java.awt.FlowLayout; import java.awt.FontMetrics; import java.awt.Frame; import java.awt.Graphics; import java.awt.Image; import java.awt.Label; import java.awt.LayoutManager; import java.awt.Panel; import java.awt.TextField; import java.awt.Toolkit; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyEvent; import java.awt.event.TextEvent; import java.awt.image.ColorModel; import java.awt.image.MemoryImageSource; enum DitherMethod { NOOP, RED, GREEN, BLUE, ALPHA, SATURATION }; @SuppressWarnings("serial") public class DitherTest extends Applet implements Runnable { private Thread runner; private DitherControls XControls; private DitherControls YControls; private DitherCanvas canvas; public static void main(String args[]) { Frame f = new Frame("DitherTest"); DitherTest ditherTest = new DitherTest(); ditherTest.init(); f.add("Center", ditherTest); f.pack(); f.setVisible(true); ditherTest.start(); } @Override public void init() { String xspec = null, yspec = null; int xvals[] = new int[2]; int yvals[] = new int[2]; try { xspec = getParameter("xaxis"); yspec = getParameter("yaxis"); } catch (NullPointerException ignored) { //only occurs if run as application } if (xspec == null) { xspec = "red"; } if (yspec == null) { yspec = "blue"; } DitherMethod xmethod = colorMethod(xspec, xvals); DitherMethod ymethod = colorMethod(yspec, yvals); setLayout(new BorderLayout()); XControls = new DitherControls(this, xvals[0], xvals[1], xmethod, false); YControls = new DitherControls(this, yvals[0], yvals[1], ymethod, true); YControls.addRenderButton(); add("North", XControls); add("South", YControls); add("Center", canvas = new DitherCanvas()); } private DitherMethod colorMethod(String s, int vals[]) { DitherMethod method = DitherMethod.NOOP; if (s == null) { s = ""; } String lower = s.toLowerCase(); for (DitherMethod m : DitherMethod.values()) { if (lower.startsWith(m.toString().toLowerCase())) { method = m; lower = lower.substring(m.toString().length()); } } if (method == DitherMethod.NOOP) { vals[0] = 0; vals[1] = 0; return method; } int begval = 0; int endval = 255; try { int dash = lower.indexOf('-'); if (dash < 0) { endval = Integer.parseInt(lower); } else { begval = Integer.parseInt(lower.substring(0, dash)); endval = Integer.parseInt(lower.substring(dash + 1)); } } catch (NumberFormatException ignored) { } if (begval < 0) { begval = 0; } else if (begval > 255) { begval = 255; } if (endval < 0) { endval = 0; } else if (endval > 255) { endval = 255; } vals[0] = begval; vals[1] = endval; return method; } /** * Calculates and returns the image. Halts the calculation and returns * null if the Applet is stopped during the calculation. */ private Image calculateImage() { Thread me = Thread.currentThread(); int width = canvas.getSize().width; int height = canvas.getSize().height; int xvals[] = new int[2]; int yvals[] = new int[2]; int xmethod = XControls.getParams(xvals); int ymethod = YControls.getParams(yvals); int pixels[] = new int[width * height]; int c[] = new int[4]; //temporarily holds R,G,B,A information int index = 0; for (int j = 0; j < height; j++) { for (int i = 0; i < width; i++) { c[0] = c[1] = c[2] = 0; c[3] = 255; if (xmethod < ymethod) { applyMethod(c, xmethod, i, width, xvals); applyMethod(c, ymethod, j, height, yvals); } else { applyMethod(c, ymethod, j, height, yvals); applyMethod(c, xmethod, i, width, xvals); } pixels[index++] = ((c[3] << 24) | (c[0] << 16) | (c[1] << 8) | c[2]); } // Poll once per row to see if we've been told to stop. if (runner != me) { return null; } } return createImage(new MemoryImageSource(width, height, ColorModel.getRGBdefault(), pixels, 0, width)); } private void applyMethod(int c[], int methodIndex, int step, int total, int vals[]) { DitherMethod method = DitherMethod.values()[methodIndex]; if (method == DitherMethod.NOOP) { return; } int val = ((total < 2) ? vals[0] : vals[0] + ((vals[1] - vals[0]) * step / (total - 1))); switch (method) { case RED: c[0] = val; break; case GREEN: c[1] = val; break; case BLUE: c[2] = val; break; case ALPHA: c[3] = val; break; case SATURATION: int max = Math.max(Math.max(c[0], c[1]), c[2]); int min = max * (255 - val) / 255; if (c[0] == 0) { c[0] = min; } if (c[1] == 0) { c[1] = min; } if (c[2] == 0) { c[2] = min; } break; } } @Override public void start() { runner = new Thread(this); runner.start(); } @Override public void run() { canvas.setImage(null); // Wipe previous image Image img = calculateImage(); if (img != null && runner == Thread.currentThread()) { canvas.setImage(img); } } @Override public void stop() { runner = null; } @Override public void destroy() { remove(XControls); remove(YControls); remove(canvas); } @Override public String getAppletInfo() { return "An interactive demonstration of dithering."; } @Override public String[][] getParameterInfo() { String[][] info = { { "xaxis", "{RED, GREEN, BLUE, ALPHA, SATURATION}", "The color of the Y axis. Default is RED." }, { "yaxis", "{RED, GREEN, BLUE, ALPHA, SATURATION}", "The color of the X axis. Default is BLUE." } }; return info; } } @SuppressWarnings("serial") class DitherCanvas extends Canvas { private Image img; private static String calcString = "Calculating..."; @Override public void paint(Graphics g) { int w = getSize().width; int h = getSize().height; if (img == null) { super.paint(g); g.setColor(Color.black); FontMetrics fm = g.getFontMetrics(); int x = (w - fm.stringWidth(calcString)) / 2; int y = h / 2; g.drawString(calcString, x, y); } else { g.drawImage(img, 0, 0, w, h, this); } } @Override public void update(Graphics g) { paint(g); } @Override public Dimension getMinimumSize() { return new Dimension(20, 20); } @Override public Dimension getPreferredSize() { return new Dimension(200, 200); } public Image getImage() { return img; } public void setImage(Image img) { this.img = img; repaint(); } } @SuppressWarnings("serial") class DitherControls extends Panel implements ActionListener { private CardinalTextField start; private CardinalTextField end; private Button button; private Choice choice; private DitherTest applet; private static LayoutManager dcLayout = new FlowLayout(FlowLayout.CENTER, 10, 5); public DitherControls(DitherTest app, int s, int e, DitherMethod type, boolean vertical) { applet = app; setLayout(dcLayout); add(new Label(vertical ? "Vertical" : "Horizontal")); add(choice = new Choice()); for (DitherMethod m : DitherMethod.values()) { choice.addItem(m.toString().substring(0, 1) + m.toString().substring(1).toLowerCase()); } choice.select(type.ordinal()); add(start = new CardinalTextField(Integer.toString(s), 4)); add(end = new CardinalTextField(Integer.toString(e), 4)); } /* puts on the button */ public void addRenderButton() { add(button = new Button("New Image")); button.addActionListener(this); } /* retrieves data from the user input fields */ public int getParams(int vals[]) { try { vals[0] = scale(Integer.parseInt(start.getText())); } catch (NumberFormatException nfe) { vals[0] = 0; } try { vals[1] = scale(Integer.parseInt(end.getText())); } catch (NumberFormatException nfe) { vals[1] = 255; } return choice.getSelectedIndex(); } /* fits the number between 0 and 255 inclusive */ private int scale(int number) { if (number < 0) { number = 0; } else if (number > 255) { number = 255; } return number; } /* called when user clicks the button */ @Override public void actionPerformed(ActionEvent e) { if (e.getSource() == button) { applet.start(); } } } @SuppressWarnings("serial") class CardinalTextField extends TextField { String oldText = null; public CardinalTextField(String text, int columns) { super(text, columns); enableEvents(AWTEvent.KEY_EVENT_MASK | AWTEvent.TEXT_EVENT_MASK); oldText = getText(); } // Consume non-digit KeyTyped events // Note that processTextEvent kind of eliminates the need for this // function, but this is neater, since ideally, it would prevent // the text from appearing at all. Sigh. See bugid 4100317/4114565. // @Override protected void processEvent(AWTEvent evt) { int id = evt.getID(); if (id != KeyEvent.KEY_TYPED) { super.processEvent(evt); return; } KeyEvent kevt = (KeyEvent) evt; char c = kevt.getKeyChar(); // Digits, backspace, and delete are okay // Note that the minus sign is not allowed (neither is decimal) if (Character.isDigit(c) || (c == '\b') || (c == '\u007f')) { super.processEvent(evt); return; } Toolkit.getDefaultToolkit().beep(); kevt.consume(); } // Should consume TextEvents for non-integer Strings // Store away the text in the tf for every TextEvent // so we can revert to it on a TextEvent (paste, or // legal key in the wrong location) with bad text // // Note: it would be easy to extend this to an eight-bit // TextField (range 0-255), but I'll leave it as-is. // @Override protected void processTextEvent(TextEvent te) { // The empty string is okay, too String newText = getText(); if (newText.equals("") || textIsCardinal(newText)) { oldText = newText; super.processTextEvent(te); return; } Toolkit.getDefaultToolkit().beep(); setText(oldText); } // Returns true for Cardinal (non-negative) numbers // Note that the empty string is not allowed private boolean textIsCardinal(String textToCheck) { try { return Integer.parseInt(textToCheck, 10) >= 0; } catch (NumberFormatException nfe) { return false; } } }