GradientPaints.java revision 14851:980da45565c8
1/*
2 * Copyright (c) 2007, 2016, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.
8 *
9 * This code is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12 * version 2 for more details (a copy is included in the LICENSE file that
13 * accompanied this code).
14 *
15 * You should have received a copy of the GNU General Public License version
16 * 2 along with this work; if not, write to the Free Software Foundation,
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18 *
19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20 * or visit www.oracle.com if you need additional information or have any
21 * questions.
22 */
23
24/*
25 * @test
26 * @key headful
27 * @bug 6521533 6525997 7102282
28 * @summary Verifies that the OGL-accelerated codepaths for GradientPaint,
29 * LinearGradientPaint, and RadialGradientPaint produce results that are
30 * sufficiently close to those produced by the software codepaths.
31 * @run main/othervm -Dsun.java2d.uiScale=1 -Dsun.java2d.opengl=True GradientPaints
32 * @author campbelc
33 */
34
35import java.awt.*;
36import java.awt.MultipleGradientPaint.ColorSpaceType;
37import java.awt.MultipleGradientPaint.CycleMethod;
38import java.awt.geom.*;
39import java.awt.image.*;
40import java.io.File;
41import java.util.Arrays;
42import javax.imageio.ImageIO;
43
44public class GradientPaints extends Canvas {
45
46    private static final int TESTW = 600;
47    private static final int TESTH = 500;
48
49    /*
50     * We expect slight differences in rendering between the OpenGL and
51     * software pipelines due to algorithmic and rounding differences.
52     * The purpose of this test is just to make sure that the OGL pipeline
53     * is producing results that are "reasonably" consistent with those
54     * produced in software, so we will allow +/-TOLERANCE differences
55     * in each component.  When comparing the test and reference images,
56     * we add up the number of pixels that fall outside this tolerance
57     * range and if the sum is larger than some percentage of the total
58     * number of pixels.
59     *
60     * REMIND: Note that we have separate thresholds for linear and radial
61     * gradients because the visible differences between OGL and software
62     * are more apparent in the radial cases.  In the future we should try
63     * to reduce the number of mismatches between the two approaches, but
64     * for now the visible differences are slight enough to not cause worry.
65     */
66    private static final int TOLERANCE = 5;
67    private static final int ALLOWED_MISMATCHES_LINEAR =
68        (int)(TESTW * TESTH * 0.18);
69    private static final int ALLOWED_MISMATCHES_RADIAL =
70        (int)(TESTW * TESTH * 0.45);
71
72    private static boolean done;
73    private static boolean verbose;
74
75    private static final Color[] COLORS = {
76        new Color(0, 0, 0),
77        new Color(128, 128, 128),
78        new Color(255, 0, 0),
79        new Color(255, 255, 0),
80        new Color(0, 255, 0),
81        new Color(0, 255, 255),
82        new Color(128, 0, 255),
83        new Color(128, 128, 128),
84    };
85
86    private static enum PaintType {BASIC, LINEAR, RADIAL};
87    private static enum XformType {IDENTITY, TRANSLATE, SCALE, SHEAR, ROTATE};
88    private static final int[] numStopsArray = {2, 4, 7};
89    private static final Object[] hints = {
90        RenderingHints.VALUE_ANTIALIAS_OFF,
91        RenderingHints.VALUE_ANTIALIAS_ON,
92    };
93
94    public void paint(Graphics g) {
95        synchronized (this) {
96            if (!done) {
97                done = true;
98                notifyAll();
99            }
100        }
101    }
102
103    private void testOne(BufferedImage refImg, VolatileImage testImg) {
104        Graphics2D gref  = refImg.createGraphics();
105        Graphics2D gtest = testImg.createGraphics();
106        Paint paint =
107            makePaint(PaintType.RADIAL, CycleMethod.REPEAT,
108                      ColorSpaceType.SRGB, XformType.IDENTITY, 7);
109        Object aahint = hints[0];
110        renderTest(gref,  paint, aahint);
111        renderTest(gtest, paint, aahint);
112        Toolkit.getDefaultToolkit().sync();
113        compareImages(refImg, testImg.getSnapshot(),
114                      TOLERANCE, 0, "");
115        gref.dispose();
116        gtest.dispose();
117    }
118
119    private void testAll(Graphics gscreen,
120                         BufferedImage refImg, VolatileImage testImg)
121    {
122        Graphics2D gref  = refImg.createGraphics();
123        Graphics2D gtest = testImg.createGraphics();
124        for (PaintType paintType : PaintType.values()) {
125            for (CycleMethod cycleMethod : CycleMethod.values()) {
126                for (ColorSpaceType colorSpace : ColorSpaceType.values()) {
127                    for (XformType xform : XformType.values()) {
128                        for (Object aahint : hints) {
129                            for (int numStops : numStopsArray) {
130                                Paint paint =
131                                    makePaint(paintType, cycleMethod,
132                                              colorSpace, xform, numStops);
133                                String msg =
134                                    "type=" + paintType +
135                                    " cycleMethod=" + cycleMethod +
136                                    " colorSpace=" + colorSpace +
137                                    " xformType=" + xform +
138                                    " numStops=" + numStops +
139                                    " aa=" + aahint;
140                                renderTest(gref,  paint, aahint);
141                                renderTest(gtest, paint, aahint);
142                                gscreen.drawImage(testImg, 0, 0, null);
143                                Toolkit.getDefaultToolkit().sync();
144                                int allowedMismatches =
145                                    paintType == PaintType.RADIAL ?
146                                    ALLOWED_MISMATCHES_RADIAL :
147                                    ALLOWED_MISMATCHES_LINEAR;
148                                compareImages(refImg, testImg.getSnapshot(),
149                                              TOLERANCE, allowedMismatches,
150                                              msg);
151                            }
152                        }
153                    }
154                }
155            }
156        }
157        gref.dispose();
158        gtest.dispose();
159    }
160
161    private Paint makePaint(PaintType paintType,
162                            CycleMethod cycleMethod,
163                            ColorSpaceType colorSpace,
164                            XformType xformType, int numStops)
165    {
166        int startX   = TESTW/6;
167        int startY   = TESTH/6;
168        int endX     = TESTW/2;
169        int endY     = TESTH/2;
170        int ctrX     = TESTW/2;
171        int ctrY     = TESTH/2;
172        int focusX   = ctrX + 20;
173        int focusY   = ctrY + 20;
174        float radius = 100.0f;
175        Paint paint;
176        AffineTransform transform;
177
178        Color[] colors = Arrays.copyOf(COLORS, numStops);
179        float[] fractions = new float[colors.length];
180        for (int i = 0; i < fractions.length; i++) {
181            fractions[i] = ((float)i) / (fractions.length-1);
182        }
183
184        switch (xformType) {
185        default:
186        case IDENTITY:
187            transform = new AffineTransform();
188            break;
189        case TRANSLATE:
190            transform = AffineTransform.getTranslateInstance(2, 2);
191            break;
192        case SCALE:
193            transform = AffineTransform.getScaleInstance(1.2, 1.4);
194            break;
195        case SHEAR:
196            transform = AffineTransform.getShearInstance(0.1, 0.1);
197            break;
198        case ROTATE:
199            transform = AffineTransform.getRotateInstance(Math.PI / 4,
200                                                          getWidth()/2,
201                                                          getHeight()/2);
202            break;
203        }
204
205        switch (paintType) {
206        case BASIC:
207            boolean cyclic = (cycleMethod != CycleMethod.NO_CYCLE);
208            paint =
209                new GradientPaint(startX, startY, Color.RED,
210                                  endX, endY, Color.BLUE, cyclic);
211            break;
212
213        default:
214        case LINEAR:
215            paint =
216                new LinearGradientPaint(new Point2D.Float(startX, startY),
217                                        new Point2D.Float(endX, endY),
218                                        fractions, colors,
219                                        cycleMethod, colorSpace,
220                                        transform);
221            break;
222
223        case RADIAL:
224            paint =
225                new RadialGradientPaint(new Point2D.Float(ctrX, ctrY),
226                                        radius,
227                                        new Point2D.Float(focusX, focusY),
228                                        fractions, colors,
229                                        cycleMethod, colorSpace,
230                                        transform);
231            break;
232        }
233
234        return paint;
235    }
236
237    private void renderTest(Graphics2D g2d, Paint p, Object aahint) {
238        g2d.setColor(Color.white);
239        g2d.fillRect(0, 0, TESTW, TESTH);
240        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, aahint);
241        g2d.setPaint(p);
242        g2d.fillOval(0, 0, TESTW, TESTH);
243    }
244
245    public Dimension getPreferredSize() {
246        return new Dimension(TESTW, TESTH);
247    }
248
249    private static void compareImages(BufferedImage refImg,
250                                      BufferedImage testImg,
251                                      int tolerance, int allowedMismatches,
252                                      String msg)
253    {
254        int numMismatches = 0;
255        int x1 = 0;
256        int y1 = 0;
257        int x2 = refImg.getWidth();
258        int y2 = refImg.getHeight();
259
260        for (int y = y1; y < y2; y++) {
261            for (int x = x1; x < x2; x++) {
262                Color expected = new Color(refImg.getRGB(x, y));
263                Color actual   = new Color(testImg.getRGB(x, y));
264                if (!isSameColor(expected, actual, tolerance)) {
265                    numMismatches++;
266                }
267            }
268        }
269
270        if (verbose) {
271            System.out.println(msg);
272        }
273        if (numMismatches > allowedMismatches) {
274            try {
275                ImageIO.write(refImg,  "png",
276                              new File("GradientPaints.ref.png"));
277                ImageIO.write(testImg, "png",
278                              new File("GradientPaints.cap.png"));
279            } catch (Exception e) {
280            }
281            if (!verbose) {
282                System.err.println(msg);
283            }
284            throw new RuntimeException("Test failed: Number of mismatches (" +
285                                       numMismatches +
286                                       ") exceeds limit (" +
287                                       allowedMismatches +
288                                       ") with tolerance=" +
289                                       tolerance);
290        }
291    }
292
293    private static boolean isSameColor(Color c1, Color c2, int e) {
294        int r1 = c1.getRed();
295        int g1 = c1.getGreen();
296        int b1 = c1.getBlue();
297        int r2 = c2.getRed();
298        int g2 = c2.getGreen();
299        int b2 = c2.getBlue();
300        int rmin = Math.max(r2-e, 0);
301        int gmin = Math.max(g2-e, 0);
302        int bmin = Math.max(b2-e, 0);
303        int rmax = Math.min(r2+e, 255);
304        int gmax = Math.min(g2+e, 255);
305        int bmax = Math.min(b2+e, 255);
306        if (r1 >= rmin && r1 <= rmax &&
307            g1 >= gmin && g1 <= gmax &&
308            b1 >= bmin && b1 <= bmax)
309        {
310            return true;
311        }
312        return false;
313    }
314
315    public static void main(String[] args) {
316        if (args.length == 1 && args[0].equals("-verbose")) {
317            verbose = true;
318        }
319
320        GradientPaints test = new GradientPaints();
321        Frame frame = new Frame();
322        frame.add(test);
323        frame.pack();
324        frame.setVisible(true);
325
326        // Wait until the component's been painted
327        synchronized (test) {
328            while (!done) {
329                try {
330                    test.wait();
331                } catch (InterruptedException e) {
332                    throw new RuntimeException("Failed: Interrupted");
333                }
334            }
335        }
336
337        GraphicsConfiguration gc = frame.getGraphicsConfiguration();
338        if (gc.getColorModel() instanceof IndexColorModel) {
339            System.out.println("IndexColorModel detected: " +
340                               "test considered PASSED");
341            frame.dispose();
342            return;
343        }
344
345        BufferedImage refImg =
346            new BufferedImage(TESTW, TESTH, BufferedImage.TYPE_INT_RGB);
347        VolatileImage testImg = frame.createVolatileImage(TESTW, TESTH);
348        testImg.validate(gc);
349
350        try {
351            test.testAll(test.getGraphics(), refImg, testImg);
352        } finally {
353            frame.dispose();
354        }
355    }
356}
357