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  * @test
  25  * @key headful
  26  * @bug 6514990
  27  * @summary Verifies that calling
  28  * Graphics2D.drawImage(BufferedImage, BufferedImageOp, x, y) to an
  29  * OpenGL-accelerated destination produces the same results when performed
  30  * in software via BufferedImageOp.filter().
  31  * @run main/othervm -Dsun.java2d.opengl=True DrawBufImgOp -ignore
  32  * @author campbelc
  33  */
  34 
  35 import java.awt.*;
  36 import java.awt.image.*;
  37 import java.io.File;
  38 import javax.imageio.ImageIO;
  39 
  40 /**
  41  * REMIND: This testcase was originally intended to automatically compare
  42  * the results of the software BufferedImageOp implementations against
  43  * the OGL-accelerated codepaths.  However, there are just too many open
  44  * bugs in the mediaLib-based codepaths (see below), which means that
  45  * creating the reference image may cause crashes or exceptions,
  46  * and even if we work around those cases using the "-ignore" flag,
  47  * the visual results of the reference image are often buggy as well
  48  * (so the comparison will fail even though the OGL results are correct).
  49  * Therefore, for now we will run the testcase with the "-ignore" flag
  50  * but without the "-compare" flag, so at least it will be checking for
  51  * any exceptions/crashes in the OGL code.  When we fix all of the
  52  * outstanding bugs with the software codepaths, we can remove the
  53  * "-ignore" flag and maybe even restore the "-compare" flag.  In the
  54  * meantime, it stil functions well as a manual testcase (with either
  55  * the "-show" or "-dump" options).
  56  */
  57 public class DrawBufImgOp extends Canvas {
  58 
  59     private static final int TESTW = 600;
  60     private static final int TESTH = 500;
  61     private static boolean done;
  62 
  63     /*
  64      * If true, skips tests that are known to trigger bugs (which in
  65      * turn may cause crashes, exceptions, or other artifacts).
  66      */
  67     private static boolean ignore;
  68 
  69     // Test both pow2 and non-pow2 sized images
  70     private static final int[] srcSizes = { 32, 17 };
  71     private static final int[] srcTypes = {
  72         BufferedImage.TYPE_INT_RGB,
  73         BufferedImage.TYPE_INT_ARGB,
  74         BufferedImage.TYPE_INT_ARGB_PRE,
  75         BufferedImage.TYPE_INT_BGR,
  76         BufferedImage.TYPE_3BYTE_BGR,
  77         BufferedImage.TYPE_4BYTE_ABGR,
  78         BufferedImage.TYPE_USHORT_565_RGB,
  79         BufferedImage.TYPE_BYTE_GRAY,
  80         BufferedImage.TYPE_USHORT_GRAY,
  81     };
  82 
  83     private static final RescaleOp
  84         rescale1band, rescale3band, rescale4band;
  85     private static final LookupOp
  86         lookup1bandbyte, lookup3bandbyte, lookup4bandbyte;
  87     private static final LookupOp
  88         lookup1bandshort, lookup3bandshort, lookup4bandshort;
  89     private static final ConvolveOp
  90         convolve3x3zero, convolve5x5zero, convolve7x7zero;
  91     private static final ConvolveOp
  92         convolve3x3noop, convolve5x5noop, convolve7x7noop;
  93 
  94     static {
  95         rescale1band = new RescaleOp(0.5f, 10.0f, null);
  96         rescale3band = new RescaleOp(
  97             new float[] {  0.6f,  0.4f, 0.6f },
  98             new float[] { 10.0f, -3.0f, 5.0f },
  99             null);
 100         rescale4band = new RescaleOp(
 101             new float[] {  0.6f, 0.4f, 0.6f, 0.9f },
 102             new float[] { -1.0f, 5.0f, 3.0f, 1.0f },
 103             null);
 104 
 105         // REMIND: we should probably test non-zero offsets, but that
 106         // would require massaging the source image data to avoid going
 107         // outside the lookup table array bounds
 108         int offset = 0;
 109         {
 110             byte invert[] = new byte[256];
 111             byte halved[] = new byte[256];
 112             for (int j = 0; j < 256 ; j++) {
 113                 invert[j] = (byte) (255-j);
 114                 halved[j] = (byte) (j / 2);
 115             }
 116             ByteLookupTable lut1 = new ByteLookupTable(offset, invert);
 117             lookup1bandbyte = new LookupOp(lut1, null);
 118             ByteLookupTable lut3 =
 119                 new ByteLookupTable(offset,
 120                                     new byte[][] {invert, halved, invert});
 121             lookup3bandbyte = new LookupOp(lut3, null);
 122             ByteLookupTable lut4 =
 123                 new ByteLookupTable(offset,
 124                                new byte[][] {invert, halved, invert, halved});
 125             lookup4bandbyte = new LookupOp(lut4, null);
 126         }
 127 
 128         {
 129             short invert[] = new short[256];
 130             short halved[] = new short[256];
 131             for (int j = 0; j < 256 ; j++) {
 132                 invert[j] = (short) ((255-j) * 255);
 133                 halved[j] = (short) ((j / 2) * 255);
 134             }
 135             ShortLookupTable lut1 = new ShortLookupTable(offset, invert);
 136             lookup1bandshort = new LookupOp(lut1, null);
 137             ShortLookupTable lut3 =
 138                 new ShortLookupTable(offset,
 139                                      new short[][] {invert, halved, invert});
 140             lookup3bandshort = new LookupOp(lut3, null);
 141             ShortLookupTable lut4 =
 142                 new ShortLookupTable(offset,
 143                               new short[][] {invert, halved, invert, halved});
 144             lookup4bandshort = new LookupOp(lut4, null);
 145         }
 146 
 147         // 3x3 blur
 148         float[] data3 = {
 149             0.1f, 0.1f, 0.1f,
 150             0.1f, 0.2f, 0.1f,
 151             0.1f, 0.1f, 0.1f,
 152         };
 153         Kernel k3 = new Kernel(3, 3, data3);
 154 
 155         // 5x5 edge
 156         float[] data5 = {
 157             -1.0f, -1.0f, -1.0f, -1.0f, -1.0f,
 158             -1.0f, -1.0f, -1.0f, -1.0f, -1.0f,
 159             -1.0f, -1.0f, 24.0f, -1.0f, -1.0f,
 160             -1.0f, -1.0f, -1.0f, -1.0f, -1.0f,
 161             -1.0f, -1.0f, -1.0f, -1.0f, -1.0f,
 162         };
 163         Kernel k5 = new Kernel(5, 5, data5);
 164 
 165         // 7x7 blur
 166         float[] data7 = {
 167             0.02f, 0.02f, 0.02f, 0.02f, 0.02f, 0.02f, 0.02f,
 168             0.02f, 0.02f, 0.02f, 0.02f, 0.02f, 0.02f, 0.02f,
 169             0.02f, 0.02f, 0.02f, 0.02f, 0.02f, 0.02f, 0.02f,
 170             0.02f, 0.02f, 0.02f, 0.02f, 0.02f, 0.02f, 0.02f,
 171             0.02f, 0.02f, 0.02f, 0.02f, 0.02f, 0.02f, 0.02f,
 172             0.02f, 0.02f, 0.02f, 0.02f, 0.02f, 0.02f, 0.02f,
 173             0.02f, 0.02f, 0.02f, 0.02f, 0.02f, 0.02f, 0.02f,
 174         };
 175         Kernel k7 = new Kernel(7, 7, data7);
 176 
 177         convolve3x3zero = new ConvolveOp(k3, ConvolveOp.EDGE_ZERO_FILL, null);
 178         convolve5x5zero = new ConvolveOp(k5, ConvolveOp.EDGE_ZERO_FILL, null);
 179         convolve7x7zero = new ConvolveOp(k7, ConvolveOp.EDGE_ZERO_FILL, null);
 180 
 181         convolve3x3noop = new ConvolveOp(k3, ConvolveOp.EDGE_NO_OP, null);
 182         convolve5x5noop = new ConvolveOp(k5, ConvolveOp.EDGE_NO_OP, null);
 183         convolve7x7noop = new ConvolveOp(k7, ConvolveOp.EDGE_NO_OP, null);
 184     }
 185 
 186     public void paint(Graphics g) {
 187         synchronized (this) {
 188             if (done) {
 189                 return;
 190             }
 191         }
 192 
 193         VolatileImage vimg = createVolatileImage(TESTW, TESTH);
 194         vimg.validate(getGraphicsConfiguration());
 195 
 196         Graphics2D g2d = vimg.createGraphics();
 197         renderTest(g2d);
 198         g2d.dispose();
 199 
 200         g.drawImage(vimg, 0, 0, null);
 201 
 202         Toolkit.getDefaultToolkit().sync();
 203 
 204         synchronized (this) {
 205             done = true;
 206             notifyAll();
 207         }
 208     }
 209 
 210     /*
 211      * foreach source image size (once with pow2, once with non-pow2)
 212      *
 213      *   foreach BufferedImage type
 214      *
 215      *     RescaleOp (1 band)
 216      *     RescaleOp (3 bands, if src has 3 bands)
 217      *     RescaleOp (4 bands, if src has 4 bands)
 218      *
 219      *     foreach LookupTable type (once with ByteLUT, once with ShortLUT)
 220      *       LookupOp (1 band)
 221      *       LookupOp (3 bands, if src has 3 bands)
 222      *       LookupOp (4 bands, if src has 4 bands)
 223      *
 224      *     foreach edge condition (once with ZERO_FILL, once with EDGE_NO_OP)
 225      *       ConvolveOp (3x3)
 226      *       ConvolveOp (5x5)
 227      *       ConvolveOp (7x7)
 228      */
 229     private void renderTest(Graphics2D g2d) {
 230         g2d.setColor(Color.white);
 231         g2d.fillRect(0, 0, TESTW, TESTH);
 232 
 233         int yorig = 2;
 234         int xinc = 34;
 235         int yinc = srcSizes[0] + srcSizes[1] + 2 + 2;
 236 
 237         for (int srcType : srcTypes) {
 238             int y = yorig;
 239 
 240             for (int srcSize : srcSizes) {
 241                 int x = 2;
 242                 System.out.printf("type=%d size=%d\n", srcType, srcSize);
 243 
 244                 BufferedImage srcImg = makeSourceImage(srcSize, srcType);
 245                 ColorModel srcCM = srcImg.getColorModel();
 246 
 247                 // RescaleOp
 248                 g2d.drawImage(srcImg, rescale1band, x, y);
 249                 x += xinc;
 250                 // REMIND: 3-band RescaleOp.filter() throws IAE for images
 251                 //         that contain an alpha channel (bug to be filed)
 252                 if (srcCM.getNumColorComponents() == 3 &&
 253                     !(ignore && srcCM.hasAlpha()))
 254                 {
 255                     g2d.drawImage(srcImg, rescale3band, x, y);
 256                 }
 257                 x += xinc;
 258                 if (srcCM.getNumComponents() == 4) {
 259                     g2d.drawImage(srcImg, rescale4band, x, y);
 260                 }
 261                 x += xinc;
 262 
 263                 // LookupOp
 264                 // REMIND: Our LUTs are only 256 elements long, so won't
 265                 //         currently work with USHORT_GRAY data
 266                 if (srcType != BufferedImage.TYPE_USHORT_GRAY) {
 267                     g2d.drawImage(srcImg, lookup1bandbyte, x, y);
 268                     x += xinc;
 269                     if (srcCM.getNumColorComponents() == 3) {
 270                         g2d.drawImage(srcImg, lookup3bandbyte, x, y);
 271                     }
 272                     x += xinc;
 273                     if (srcCM.getNumComponents() == 4) {
 274                         g2d.drawImage(srcImg, lookup4bandbyte, x, y);
 275                     }
 276                     x += xinc;
 277 
 278                     // REMIND: LookupOp.createCompatibleDestImage() throws
 279                     //         IAE for 3BYTE_BGR/4BYTE_ABGR (bug to be filed)
 280                     if (!(ignore &&
 281                           (srcType == BufferedImage.TYPE_3BYTE_BGR ||
 282                            srcType == BufferedImage.TYPE_4BYTE_ABGR)))
 283                     {
 284                         g2d.drawImage(srcImg, lookup1bandshort, x, y);
 285                         x += xinc;
 286                         // REMIND: 3-band LookupOp.filter() throws IAE for
 287                         //         images that contain an alpha channel
 288                         //         (bug to be filed)
 289                         if (srcCM.getNumColorComponents() == 3 &&
 290                             !(ignore && srcCM.hasAlpha()))
 291                         {
 292                             g2d.drawImage(srcImg, lookup3bandshort, x, y);
 293                         }
 294                         x += xinc;
 295                         if (srcCM.getNumComponents() == 4) {
 296                             g2d.drawImage(srcImg, lookup4bandshort, x, y);
 297                         }
 298                         x += xinc;
 299                     } else {
 300                         x += 3*xinc;
 301                     }
 302                 } else {
 303                     x += 6*xinc;
 304                 }
 305 
 306                 // ConvolveOp
 307                 // REMIND: ConvolveOp.filter() throws ImagingOpException
 308                 //         for 3BYTE_BGR (see 4957775)
 309                 if (srcType != BufferedImage.TYPE_3BYTE_BGR) {
 310                     g2d.drawImage(srcImg, convolve3x3zero, x, y);
 311                     x += xinc;
 312                     g2d.drawImage(srcImg, convolve5x5zero, x, y);
 313                     x += xinc;
 314                     g2d.drawImage(srcImg, convolve7x7zero, x, y);
 315                     x += xinc;
 316 
 317                     g2d.drawImage(srcImg, convolve3x3noop, x, y);
 318                     x += xinc;
 319                     g2d.drawImage(srcImg, convolve5x5noop, x, y);
 320                     x += xinc;
 321                     g2d.drawImage(srcImg, convolve7x7noop, x, y);
 322                     x += xinc;
 323                 } else {
 324                     x += 6*xinc;
 325                 }
 326 
 327                 y += srcSize + 2;
 328             }
 329 
 330             yorig += yinc;
 331         }
 332     }
 333 
 334     private BufferedImage makeSourceImage(int size, int type) {
 335         int s2 = size/2;
 336         BufferedImage img = new BufferedImage(size, size, type);
 337         Graphics2D g2d = img.createGraphics();
 338         g2d.setComposite(AlphaComposite.Src);
 339         g2d.setColor(Color.orange);
 340         g2d.fillRect(0, 0, size, size);
 341         g2d.setColor(Color.red);
 342         g2d.fillRect(0, 0, s2, s2);
 343         g2d.setColor(Color.green);
 344         g2d.fillRect(s2, 0, s2, s2);
 345         g2d.setColor(Color.blue);
 346         g2d.fillRect(0, s2, s2, s2);
 347         g2d.setColor(new Color(255, 255, 0, 128));
 348         g2d.fillRect(s2, s2, s2, s2);
 349         g2d.setColor(Color.pink);
 350         g2d.fillOval(s2-3, s2-3, 6, 6);
 351         g2d.dispose();
 352         return img;
 353     }
 354 
 355     public BufferedImage makeReferenceImage() {
 356         BufferedImage img = new BufferedImage(TESTW, TESTH,
 357                                               BufferedImage.TYPE_INT_RGB);
 358         Graphics2D g2d = img.createGraphics();
 359         renderTest(g2d);
 360         g2d.dispose();
 361         return img;
 362     }
 363 
 364     public Dimension getPreferredSize() {
 365         return new Dimension(TESTW, TESTH);
 366     }
 367 
 368     private static void compareImages(BufferedImage refImg,
 369                                       BufferedImage testImg,
 370                                       int tolerance)
 371     {
 372         int x1 = 0;
 373         int y1 = 0;
 374         int x2 = refImg.getWidth();
 375         int y2 = refImg.getHeight();
 376 
 377         for (int y = y1; y < y2; y++) {
 378             for (int x = x1; x < x2; x++) {
 379                 Color expected = new Color(refImg.getRGB(x, y));
 380                 Color actual   = new Color(testImg.getRGB(x, y));
 381                 if (!isSameColor(expected, actual, tolerance)) {
 382                     throw new RuntimeException("Test failed at x="+x+" y="+y+
 383                                                " (expected="+expected+
 384                                                " actual="+actual+
 385                                                ")");
 386                 }
 387             }
 388         }
 389     }
 390 
 391     private static boolean isSameColor(Color c1, Color c2, int e) {
 392         int r1 = c1.getRed();
 393         int g1 = c1.getGreen();
 394         int b1 = c1.getBlue();
 395         int r2 = c2.getRed();
 396         int g2 = c2.getGreen();
 397         int b2 = c2.getBlue();
 398         int rmin = Math.max(r2-e, 0);
 399         int gmin = Math.max(g2-e, 0);
 400         int bmin = Math.max(b2-e, 0);
 401         int rmax = Math.min(r2+e, 255);
 402         int gmax = Math.min(g2+e, 255);
 403         int bmax = Math.min(b2+e, 255);
 404         if (r1 >= rmin && r1 <= rmax &&
 405             g1 >= gmin && g1 <= gmax &&
 406             b1 >= bmin && b1 <= bmax)
 407         {
 408             return true;
 409         }
 410         return false;
 411     }
 412 
 413     public static void main(String[] args) throws Exception {
 414         boolean show = false;
 415         boolean dump = false;
 416         boolean compare = false;
 417 
 418         for (String arg : args) {
 419             if (arg.equals("-show")) {
 420                 show = true;
 421             } else if (arg.equals("-dump")) {
 422                 dump = true;
 423             } else if (arg.equals("-compare")) {
 424                 compare = true;
 425             } else if (arg.equals("-ignore")) {
 426                 ignore = true;
 427             }
 428         }
 429 
 430         DrawBufImgOp test = new DrawBufImgOp();
 431         Frame frame = new Frame();
 432         frame.add(test);
 433         frame.pack();
 434         frame.setVisible(true);
 435 
 436         // Wait until the component's been painted
 437         synchronized (test) {
 438             while (!done) {
 439                 try {
 440                     test.wait();
 441                 } catch (InterruptedException e) {
 442                     throw new RuntimeException("Failed: Interrupted");
 443                 }
 444             }
 445         }
 446 
 447         GraphicsConfiguration gc = frame.getGraphicsConfiguration();
 448         if (gc.getColorModel() instanceof IndexColorModel) {
 449             System.out.println("IndexColorModel detected: " +
 450                                "test considered PASSED");
 451             frame.dispose();
 452             return;
 453         }
 454 
 455         // Grab the screen region
 456         BufferedImage capture = null;
 457         try {
 458             Robot robot = new Robot();
 459             Point pt1 = test.getLocationOnScreen();
 460             Rectangle rect = new Rectangle(pt1.x, pt1.y, TESTW, TESTH);
 461             capture = robot.createScreenCapture(rect);
 462         } catch (Exception e) {
 463             throw new RuntimeException("Problems creating Robot");
 464         } finally {
 465             if (!show) {
 466                 frame.dispose();
 467             }
 468         }
 469 
 470         // Compare the images (allow for +/- 1 bit differences in color comps)
 471         if (dump || compare) {
 472             BufferedImage ref = test.makeReferenceImage();
 473             if (dump) {
 474                 ImageIO.write(ref,     "png",
 475                               new File("DrawBufImgOp.ref.png"));
 476                 ImageIO.write(capture, "png",
 477                               new File("DrawBufImgOp.cap.png"));
 478             }
 479             if (compare) {
 480                 test.compareImages(ref, capture, 1);
 481             }
 482         }
 483     }
 484 }