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