1 /*
   2  * Copyright (c) 2014, 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 import java.awt.AWTException;
  25 import java.awt.Canvas;
  26 import java.awt.Color;
  27 import java.awt.Component;
  28 import java.awt.Dimension;
  29 import java.awt.Frame;
  30 import java.awt.Graphics;
  31 import java.awt.Graphics2D;
  32 import java.awt.GraphicsConfiguration;
  33 import java.awt.GraphicsEnvironment;
  34 import java.awt.HeadlessException;
  35 import java.awt.Rectangle;
  36 import java.awt.Robot;
  37 import java.awt.Toolkit;
  38 import java.awt.event.WindowAdapter;
  39 import java.awt.event.WindowEvent;
  40 import java.awt.image.BufferedImage;
  41 import java.awt.image.VolatileImage;
  42 import java.io.File;
  43 import java.io.IOException;
  44 import java.util.HashSet;
  45 import javax.imageio.ImageIO;
  46 import sun.awt.ConstrainableGraphics;
  47 
  48 /**
  49  * @test
  50  * @key headful
  51  * @bug 6335200 6419610
  52  * @summary Tests that we don't render anything if specific empty clip is set
  53  * @author Dmitri.Trembovetski@Sun.COM: area=Graphics
  54  * @modules java.desktop/sun.awt
  55  * @run main EmptyClipRenderingTest
  56  * @run main/othervm -Dsun.java2d.noddraw=true EmptyClipRenderingTest
  57  * @run main/othervm -Dsun.java2d.pmoffscreen=true EmptyClipRenderingTest
  58  * @run main/othervm -Dsun.java2d.opengl=true EmptyClipRenderingTest
  59  */
  60 public class EmptyClipRenderingTest {
  61     static final int IMG_W = 400;
  62     static final int IMG_H = 400;
  63 
  64     // generated rectangles
  65     static HashSet<Rectangle> rects;
  66 
  67     volatile boolean isActivated = false;
  68     volatile boolean isPainted;
  69     private static boolean showErrors = false;
  70 
  71     public EmptyClipRenderingTest() {
  72         // initialize clip/render region rectangles
  73         initClips();
  74 
  75         HashSet<RuntimeException> errors = new HashSet<RuntimeException>();
  76 
  77         BufferedImage screenResult = testOnscreen();
  78         try {
  79             testResult(screenResult, "Screen");
  80         } catch (RuntimeException e) {
  81             errors.add(e);
  82         }
  83 
  84         BufferedImage destBI =
  85             new BufferedImage(IMG_W, IMG_H, BufferedImage.TYPE_INT_RGB);
  86         runTest((Graphics2D)destBI.getGraphics());
  87         try {
  88             testResult(destBI, "BufferedImage");
  89         } catch (RuntimeException e) {
  90             errors.add(e);
  91         }
  92 
  93         GraphicsConfiguration gc =
  94                 GraphicsEnvironment.getLocalGraphicsEnvironment().
  95                 getDefaultScreenDevice().getDefaultConfiguration();
  96         VolatileImage destVI = gc.createCompatibleVolatileImage(IMG_W, IMG_H);
  97         destVI.validate(gc);
  98         runTest((Graphics2D)destVI.getGraphics());
  99         try {
 100             testResult(destVI.getSnapshot(), "VolatileImage");
 101         } catch (RuntimeException e) {
 102             errors.add(e);
 103         }
 104 
 105         if (errors.isEmpty()) {
 106             System.err.println("Test PASSED.");
 107         } else {
 108             for (RuntimeException re : errors) {
 109                 re.printStackTrace();
 110             }
 111             if (showErrors) {
 112                 System.err.println("Test FAILED: "+ errors.size() +
 113                                    " subtest failures.");
 114             } else {
 115                 throw new RuntimeException("Test FAILED: "+ errors.size() +
 116                                            " subtest failures.");
 117             }
 118         }
 119     }
 120 
 121     /**
 122      * Recursively adds 4 new rectangles: two vertical and two horizontal
 123      * based on the passed rectangle area. The area is then shrunk and the
 124      * process repeated for smaller area.
 125      */
 126     private static void add4Rects(HashSet<Rectangle> rects, Rectangle area) {
 127         if (area.width < 10 || area.height < 10) {
 128             rects.add(area);
 129             return;
 130         }
 131         // two vertical rects
 132         rects.add(new Rectangle(area.x, area.y, 5, area.height));
 133         rects.add(new Rectangle(area.x + area.width - 5, area.y, 5, area.height));
 134         // two horizontal rects
 135         int width = area.width - 2*(5 + 1);
 136         rects.add(new Rectangle(area.x+6, area.y, width, 5));
 137         rects.add(new Rectangle(area.x+6, area.y + area.height - 5, width, 5));
 138         // reduce the area and repeat
 139         area.grow(-6, -6);
 140         add4Rects(rects, area);
 141     }
 142 
 143     /**
 144      * Generate a bunch of non-intersecting rectangles
 145      */
 146     private static void initClips() {
 147         rects = new HashSet<Rectangle>();
 148         add4Rects(rects, new Rectangle(0, 0, IMG_W, IMG_H));
 149         System.err.println("Total number of test rects: " + rects.size());
 150     }
 151 
 152     /**
 153      * Render the pattern to the screen, capture the output with robot and
 154      * return it.
 155      */
 156     private BufferedImage testOnscreen() throws HeadlessException {
 157         final Canvas destComponent;
 158         final Object lock = new Object();
 159         Frame f = new Frame("Test Frame");
 160         f.setUndecorated(true);
 161         f.add(destComponent = new Canvas() {
 162             public void paint(Graphics g) {
 163                 isPainted = true;
 164             }
 165             public Dimension getPreferredSize() {
 166                 return new Dimension(IMG_W, IMG_H);
 167             }
 168         });
 169         f.addWindowListener(new WindowAdapter() {
 170             public void windowActivated(WindowEvent e) {
 171                 if (!isActivated) {
 172                     synchronized (lock) {
 173                         isActivated = true;
 174                         lock.notify();
 175                     }
 176                 }
 177             }
 178         });
 179         f.pack();
 180         f.setLocationRelativeTo(null);
 181         f.setVisible(true);
 182         synchronized(lock) {
 183             while (!isActivated) {
 184                 try {
 185                     lock.wait(100);
 186                 } catch (InterruptedException ex) {
 187                     ex.printStackTrace();
 188                 }
 189             }
 190         }
 191         Robot r;
 192         try {
 193             r = new Robot();
 194         } catch (AWTException ex) {
 195             throw new RuntimeException("Can't create Robot");
 196         }
 197         BufferedImage bi;
 198         int attempt = 0;
 199         do {
 200             if (++attempt > 10) {
 201                 throw new RuntimeException("Too many attempts: " + attempt);
 202             }
 203             isPainted = false;
 204             runTest((Graphics2D) destComponent.getGraphics());
 205             r.waitForIdle();
 206             Toolkit.getDefaultToolkit().sync();
 207             bi = r.createScreenCapture(
 208                     new Rectangle(destComponent.getLocationOnScreen().x,
 209                             destComponent.getLocationOnScreen().y,
 210                             destComponent.getWidth(),
 211                             destComponent.getHeight()));
 212         } while (isPainted);
 213         f.setVisible(false);
 214         f.dispose();
 215         return bi;
 216     }
 217 
 218     /**
 219      * Run the test: cycle through all the rectangles, use one as clip and
 220      * another as the area to render to.
 221      * Set the clip in the same way Swing does it when repainting:
 222      * first constrain the graphics to the damaged area, and repaint everything
 223      */
 224     void runTest(Graphics2D destGraphics) {
 225         destGraphics.setColor(Color.black);
 226         destGraphics.fillRect(0, 0, IMG_W, IMG_H);
 227 
 228         destGraphics.setColor(Color.red);
 229         for (Rectangle clip : rects) {
 230             Graphics2D g2d = (Graphics2D)destGraphics.create();
 231             g2d.setColor(Color.red);
 232             // mimic what swing does in BufferStrategyPaintManager
 233             if (g2d instanceof ConstrainableGraphics) {
 234                 ((ConstrainableGraphics)g2d).constrain(clip.x, clip.y,
 235                                                        clip.width, clip.height);
 236             }
 237             g2d.setClip(clip);
 238 
 239             for (Rectangle renderRegion : rects) {
 240                 if (renderRegion != clip) {
 241                     // from CellRendererPane's paintComponent
 242                     Graphics2D rG = (Graphics2D)
 243                         g2d.create(renderRegion.x, renderRegion.y,
 244                                    renderRegion.width, renderRegion.height);
 245                     rG.fillRect(0,0, renderRegion.width, renderRegion.height);
 246                 }
 247             }
 248         }
 249     }
 250 
 251     void testResult(final BufferedImage bi, final String desc) {
 252         for (int y = 0; y < bi.getHeight(); y++) {
 253             for (int x = 0; x < bi.getWidth(); x++) {
 254                 if (bi.getRGB(x, y) != Color.black.getRGB()) {
 255                     if (showErrors) {
 256                         Frame f = new Frame("Error: " + desc);
 257                         f.add(new Component() {
 258                             public void paint(Graphics g) {
 259                                 g.drawImage(bi, 0, 0, null);
 260                             }
 261                             public Dimension getPreferredSize() {
 262                                 return new Dimension(bi.getWidth(),
 263                                                      bi.getHeight());
 264                             }
 265                         });
 266                         f.pack();
 267                         f.setVisible(true);
 268                     }
 269                     try {
 270                         String fileName =
 271                             "EmptyClipRenderingTest_"+desc+"_res.png";
 272                         System.out.println("Writing resulting image: "+fileName);
 273                         ImageIO.write(bi, "png", new File(fileName));
 274                     } catch (IOException ex) {
 275                         ex.printStackTrace();
 276                     }
 277                     throw new RuntimeException("Dest: "+desc+
 278                         " was rendered to at x="+
 279                         x + " y=" + y +
 280                         " pixel="+Integer.toHexString(bi.getRGB(x,y)));
 281                 }
 282             }
 283         }
 284     }
 285 
 286     public static void main(String argv[]) {
 287         for (String arg : argv) {
 288             if (arg.equals("-show")) {
 289                 showErrors = true;
 290             } else {
 291                 usage("Incorrect argument:" + arg);
 292             }
 293         }
 294         new EmptyClipRenderingTest();
 295     }
 296 
 297     private static void usage(String string) {
 298         System.out.println(string);
 299         System.out.println("Usage: EmptyClipRenderingTest [-show]");
 300     }
 301 }