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