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 
  35 import java.awt.*;
  36 import java.awt.MultipleGradientPaint.ColorSpaceType;
  37 import java.awt.MultipleGradientPaint.CycleMethod;
  38 import java.awt.geom.*;
  39 import java.awt.image.*;
  40 import java.io.File;
  41 import java.util.Arrays;
  42 import javax.imageio.ImageIO;
  43 
  44 public 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 }