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