1/* 2 * Copyright (c) 1997, 2011, Oracle and/or its affiliates. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions 6 * are met: 7 * 8 * - Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 11 * - Redistributions in binary form must reproduce the above copyright 12 * notice, this list of conditions and the following disclaimer in the 13 * documentation and/or other materials provided with the distribution. 14 * 15 * - Neither the name of Oracle nor the names of its 16 * contributors may be used to endorse or promote products derived 17 * from this software without specific prior written permission. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 20 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 21 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 22 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 23 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 24 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 25 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 26 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 27 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 28 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 29 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 */ 31 32/* 33 * This source code is provided to illustrate the usage of a given feature 34 * or technique and has been deliberately simplified. Additional steps 35 * required for a production-quality application, such as security checks, 36 * input validation and proper error handling, might not be present in 37 * this sample code. 38 */ 39 40 41 42import java.applet.Applet; 43import java.awt.AWTEvent; 44import java.awt.BorderLayout; 45import java.awt.Button; 46import java.awt.Canvas; 47import java.awt.Choice; 48import java.awt.Color; 49import java.awt.Dimension; 50import java.awt.FlowLayout; 51import java.awt.FontMetrics; 52import java.awt.Frame; 53import java.awt.Graphics; 54import java.awt.Image; 55import java.awt.Label; 56import java.awt.LayoutManager; 57import java.awt.Panel; 58import java.awt.TextField; 59import java.awt.Toolkit; 60import java.awt.event.ActionEvent; 61import java.awt.event.ActionListener; 62import java.awt.event.KeyEvent; 63import java.awt.event.TextEvent; 64import java.awt.image.ColorModel; 65import java.awt.image.MemoryImageSource; 66 67 68enum DitherMethod { 69 70 NOOP, RED, GREEN, BLUE, ALPHA, SATURATION 71}; 72 73 74@SuppressWarnings("serial") 75public class DitherTest extends Applet implements Runnable { 76 77 private Thread runner; 78 private DitherControls XControls; 79 private DitherControls YControls; 80 private DitherCanvas canvas; 81 82 public static void main(String args[]) { 83 Frame f = new Frame("DitherTest"); 84 DitherTest ditherTest = new DitherTest(); 85 ditherTest.init(); 86 f.add("Center", ditherTest); 87 f.pack(); 88 f.setVisible(true); 89 ditherTest.start(); 90 } 91 92 @Override 93 public void init() { 94 String xspec = null, yspec = null; 95 int xvals[] = new int[2]; 96 int yvals[] = new int[2]; 97 98 try { 99 xspec = getParameter("xaxis"); 100 yspec = getParameter("yaxis"); 101 } catch (NullPointerException ignored) { 102 //only occurs if run as application 103 } 104 105 if (xspec == null) { 106 xspec = "red"; 107 } 108 if (yspec == null) { 109 yspec = "blue"; 110 } 111 DitherMethod xmethod = colorMethod(xspec, xvals); 112 DitherMethod ymethod = colorMethod(yspec, yvals); 113 114 setLayout(new BorderLayout()); 115 XControls = new DitherControls(this, xvals[0], xvals[1], 116 xmethod, false); 117 YControls = new DitherControls(this, yvals[0], yvals[1], 118 ymethod, true); 119 YControls.addRenderButton(); 120 add("North", XControls); 121 add("South", YControls); 122 add("Center", canvas = new DitherCanvas()); 123 } 124 125 private DitherMethod colorMethod(String s, int vals[]) { 126 DitherMethod method = DitherMethod.NOOP; 127 if (s == null) { 128 s = ""; 129 } 130 String lower = s.toLowerCase(); 131 132 for (DitherMethod m : DitherMethod.values()) { 133 if (lower.startsWith(m.toString().toLowerCase())) { 134 method = m; 135 lower = lower.substring(m.toString().length()); 136 } 137 } 138 if (method == DitherMethod.NOOP) { 139 vals[0] = 0; 140 vals[1] = 0; 141 return method; 142 } 143 int begval = 0; 144 int endval = 255; 145 try { 146 int dash = lower.indexOf('-'); 147 if (dash < 0) { 148 endval = Integer.parseInt(lower); 149 } else { 150 begval = Integer.parseInt(lower.substring(0, dash)); 151 endval = Integer.parseInt(lower.substring(dash + 1)); 152 } 153 } catch (NumberFormatException ignored) { 154 } 155 156 if (begval < 0) { 157 begval = 0; 158 } else if (begval > 255) { 159 begval = 255; 160 } 161 162 if (endval < 0) { 163 endval = 0; 164 } else if (endval > 255) { 165 endval = 255; 166 } 167 168 vals[0] = begval; 169 vals[1] = endval; 170 return method; 171 } 172 173 /** 174 * Calculates and returns the image. Halts the calculation and returns 175 * null if the Applet is stopped during the calculation. 176 */ 177 private Image calculateImage() { 178 Thread me = Thread.currentThread(); 179 180 int width = canvas.getSize().width; 181 int height = canvas.getSize().height; 182 int xvals[] = new int[2]; 183 int yvals[] = new int[2]; 184 int xmethod = XControls.getParams(xvals); 185 int ymethod = YControls.getParams(yvals); 186 int pixels[] = new int[width * height]; 187 int c[] = new int[4]; //temporarily holds R,G,B,A information 188 int index = 0; 189 for (int j = 0; j < height; j++) { 190 for (int i = 0; i < width; i++) { 191 c[0] = c[1] = c[2] = 0; 192 c[3] = 255; 193 if (xmethod < ymethod) { 194 applyMethod(c, xmethod, i, width, xvals); 195 applyMethod(c, ymethod, j, height, yvals); 196 } else { 197 applyMethod(c, ymethod, j, height, yvals); 198 applyMethod(c, xmethod, i, width, xvals); 199 } 200 pixels[index++] = ((c[3] << 24) | (c[0] << 16) | (c[1] << 8) 201 | c[2]); 202 } 203 204 // Poll once per row to see if we've been told to stop. 205 if (runner != me) { 206 return null; 207 } 208 } 209 return createImage(new MemoryImageSource(width, height, 210 ColorModel.getRGBdefault(), pixels, 0, width)); 211 } 212 213 private void applyMethod(int c[], int methodIndex, int step, 214 int total, int vals[]) { 215 DitherMethod method = DitherMethod.values()[methodIndex]; 216 if (method == DitherMethod.NOOP) { 217 return; 218 } 219 int val = ((total < 2) 220 ? vals[0] 221 : vals[0] + ((vals[1] - vals[0]) * step / (total - 1))); 222 switch (method) { 223 case RED: 224 c[0] = val; 225 break; 226 case GREEN: 227 c[1] = val; 228 break; 229 case BLUE: 230 c[2] = val; 231 break; 232 case ALPHA: 233 c[3] = val; 234 break; 235 case SATURATION: 236 int max = Math.max(Math.max(c[0], c[1]), c[2]); 237 int min = max * (255 - val) / 255; 238 if (c[0] == 0) { 239 c[0] = min; 240 } 241 if (c[1] == 0) { 242 c[1] = min; 243 } 244 if (c[2] == 0) { 245 c[2] = min; 246 } 247 break; 248 } 249 } 250 251 @Override 252 public void start() { 253 runner = new Thread(this); 254 runner.start(); 255 } 256 257 @Override 258 public void run() { 259 canvas.setImage(null); // Wipe previous image 260 Image img = calculateImage(); 261 if (img != null && runner == Thread.currentThread()) { 262 canvas.setImage(img); 263 } 264 } 265 266 @Override 267 public void stop() { 268 runner = null; 269 } 270 271 @Override 272 public void destroy() { 273 remove(XControls); 274 remove(YControls); 275 remove(canvas); 276 } 277 278 @Override 279 public String getAppletInfo() { 280 return "An interactive demonstration of dithering."; 281 } 282 283 @Override 284 public String[][] getParameterInfo() { 285 String[][] info = { 286 { "xaxis", "{RED, GREEN, BLUE, ALPHA, SATURATION}", 287 "The color of the Y axis. Default is RED." }, 288 { "yaxis", "{RED, GREEN, BLUE, ALPHA, SATURATION}", 289 "The color of the X axis. Default is BLUE." } 290 }; 291 return info; 292 } 293} 294 295 296@SuppressWarnings("serial") 297class DitherCanvas extends Canvas { 298 299 private Image img; 300 private static String calcString = "Calculating..."; 301 302 @Override 303 public void paint(Graphics g) { 304 int w = getSize().width; 305 int h = getSize().height; 306 if (img == null) { 307 super.paint(g); 308 g.setColor(Color.black); 309 FontMetrics fm = g.getFontMetrics(); 310 int x = (w - fm.stringWidth(calcString)) / 2; 311 int y = h / 2; 312 g.drawString(calcString, x, y); 313 } else { 314 g.drawImage(img, 0, 0, w, h, this); 315 } 316 } 317 318 @Override 319 public void update(Graphics g) { 320 paint(g); 321 } 322 323 @Override 324 public Dimension getMinimumSize() { 325 return new Dimension(20, 20); 326 } 327 328 @Override 329 public Dimension getPreferredSize() { 330 return new Dimension(200, 200); 331 } 332 333 public Image getImage() { 334 return img; 335 } 336 337 public void setImage(Image img) { 338 this.img = img; 339 repaint(); 340 } 341} 342 343 344@SuppressWarnings("serial") 345class DitherControls extends Panel implements ActionListener { 346 347 private CardinalTextField start; 348 private CardinalTextField end; 349 private Button button; 350 private Choice choice; 351 private DitherTest applet; 352 private static LayoutManager dcLayout = new FlowLayout(FlowLayout.CENTER, 353 10, 5); 354 355 public DitherControls(DitherTest app, int s, int e, DitherMethod type, 356 boolean vertical) { 357 applet = app; 358 setLayout(dcLayout); 359 add(new Label(vertical ? "Vertical" : "Horizontal")); 360 add(choice = new Choice()); 361 for (DitherMethod m : DitherMethod.values()) { 362 choice.addItem(m.toString().substring(0, 1) 363 + m.toString().substring(1).toLowerCase()); 364 } 365 choice.select(type.ordinal()); 366 add(start = new CardinalTextField(Integer.toString(s), 4)); 367 add(end = new CardinalTextField(Integer.toString(e), 4)); 368 } 369 370 /* puts on the button */ 371 public void addRenderButton() { 372 add(button = new Button("New Image")); 373 button.addActionListener(this); 374 } 375 376 /* retrieves data from the user input fields */ 377 public int getParams(int vals[]) { 378 try { 379 vals[0] = scale(Integer.parseInt(start.getText())); 380 } catch (NumberFormatException nfe) { 381 vals[0] = 0; 382 } 383 try { 384 vals[1] = scale(Integer.parseInt(end.getText())); 385 } catch (NumberFormatException nfe) { 386 vals[1] = 255; 387 } 388 return choice.getSelectedIndex(); 389 } 390 391 /* fits the number between 0 and 255 inclusive */ 392 private int scale(int number) { 393 if (number < 0) { 394 number = 0; 395 } else if (number > 255) { 396 number = 255; 397 } 398 return number; 399 } 400 401 /* called when user clicks the button */ 402 @Override 403 public void actionPerformed(ActionEvent e) { 404 if (e.getSource() == button) { 405 applet.start(); 406 } 407 } 408} 409 410 411@SuppressWarnings("serial") 412class CardinalTextField extends TextField { 413 414 String oldText = null; 415 416 public CardinalTextField(String text, int columns) { 417 super(text, columns); 418 enableEvents(AWTEvent.KEY_EVENT_MASK | AWTEvent.TEXT_EVENT_MASK); 419 oldText = getText(); 420 } 421 422 // Consume non-digit KeyTyped events 423 // Note that processTextEvent kind of eliminates the need for this 424 // function, but this is neater, since ideally, it would prevent 425 // the text from appearing at all. Sigh. See bugid 4100317/4114565. 426 // 427 @Override 428 protected void processEvent(AWTEvent evt) { 429 int id = evt.getID(); 430 if (id != KeyEvent.KEY_TYPED) { 431 super.processEvent(evt); 432 return; 433 } 434 435 KeyEvent kevt = (KeyEvent) evt; 436 char c = kevt.getKeyChar(); 437 438 // Digits, backspace, and delete are okay 439 // Note that the minus sign is not allowed (neither is decimal) 440 if (Character.isDigit(c) || (c == '\b') || (c == '\u007f')) { 441 super.processEvent(evt); 442 return; 443 } 444 445 Toolkit.getDefaultToolkit().beep(); 446 kevt.consume(); 447 } 448 449 // Should consume TextEvents for non-integer Strings 450 // Store away the text in the tf for every TextEvent 451 // so we can revert to it on a TextEvent (paste, or 452 // legal key in the wrong location) with bad text 453 // 454 // Note: it would be easy to extend this to an eight-bit 455 // TextField (range 0-255), but I'll leave it as-is. 456 // 457 @Override 458 protected void processTextEvent(TextEvent te) { 459 // The empty string is okay, too 460 String newText = getText(); 461 if (newText.equals("") || textIsCardinal(newText)) { 462 oldText = newText; 463 super.processTextEvent(te); 464 return; 465 } 466 467 Toolkit.getDefaultToolkit().beep(); 468 setText(oldText); 469 } 470 471 // Returns true for Cardinal (non-negative) numbers 472 // Note that the empty string is not allowed 473 private boolean textIsCardinal(String textToCheck) { 474 try { 475 return Integer.parseInt(textToCheck, 10) >= 0; 476 } catch (NumberFormatException nfe) { 477 return false; 478 } 479 } 480} 481