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