1 /* 2 * Copyright (c) 2018, 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. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 package test.com.sun.marlin; 26 27 import java.awt.Color; 28 import java.io.File; 29 import java.io.IOException; 30 import java.util.concurrent.CountDownLatch; 31 import java.util.concurrent.TimeUnit; 32 import java.util.concurrent.atomic.AtomicBoolean; 33 import javafx.application.Application; 34 import javafx.application.Platform; 35 import javafx.geometry.Rectangle2D; 36 import javafx.scene.Group; 37 import javafx.scene.Scene; 38 import javafx.scene.SnapshotParameters; 39 import javafx.scene.image.PixelReader; 40 import javafx.scene.image.WritableImage; 41 import javafx.scene.shape.LineTo; 42 import javafx.scene.shape.MoveTo; 43 import javafx.scene.shape.Path; 44 import javafx.scene.shape.StrokeLineCap; 45 import javafx.scene.shape.StrokeLineJoin; 46 import javafx.scene.transform.Transform; 47 import javafx.stage.Stage; 48 import junit.framework.AssertionFailedError; 49 import org.junit.AfterClass; 50 import static org.junit.Assert.assertEquals; 51 import static org.junit.Assert.fail; 52 import org.junit.BeforeClass; 53 import org.junit.Test; 54 import test.util.Util; 55 import static test.util.Util.TIMEOUT; 56 57 /** 58 * Scaled Line Clipping rendering test 59 */ 60 public class ScaleClipTest { 61 62 static final int SIZE = 50; 63 64 enum SCALE_MODE { 65 ORTHO, 66 NON_ORTHO, 67 COMPLEX 68 }; 69 70 // Used to launch the application before running any test 71 private static final CountDownLatch launchLatch = new CountDownLatch(1); 72 73 // Singleton Application instance 74 static MyApp myApp; 75 76 // Application class. An instance is created and initialized before running 77 // the first test, and it lives through the execution of all tests. 78 public static class MyApp extends Application { 79 80 Stage stage = null; 81 82 public MyApp() { 83 super(); 84 } 85 86 @Override 87 public void init() { 88 ScaleClipTest.myApp = this; 89 } 90 91 @Override 92 public void start(Stage primaryStage) throws Exception { 93 this.stage = primaryStage; 94 95 stage.setScene(new Scene(new Group())); 96 stage.setTitle("ScaleClipTest"); 97 stage.show(); 98 99 launchLatch.countDown(); 100 } 101 } 102 103 @BeforeClass 104 public static void setupOnce() { 105 // Start the Application 106 new Thread(() -> Application.launch(MyApp.class, (String[]) null)).start(); 107 108 try { 109 if (!launchLatch.await(TIMEOUT, TimeUnit.MILLISECONDS)) { 110 throw new AssertionFailedError("Timeout waiting for Application to launch"); 111 } 112 113 } catch (InterruptedException ex) { 114 AssertionFailedError err = new AssertionFailedError("Unexpected exception"); 115 err.initCause(ex); 116 throw err; 117 } 118 119 assertEquals(0, launchLatch.getCount()); 120 121 System.out.println("ScaleClipTest: size = " + SIZE); 122 } 123 124 @AfterClass 125 public static void teardownOnce() { 126 Platform.exit(); 127 } 128 129 @Test(timeout = 10000) 130 public void TestNegativeScaleClipPath() throws InterruptedException { 131 final AtomicBoolean fail = new AtomicBoolean(); 132 133 for (SCALE_MODE mode : SCALE_MODE.values()) { 134 Util.runAndWait(() -> { 135 try { 136 testNegativeScale(mode); 137 } catch (AssertionError ae) { 138 System.err.println("testNegativeScale[" + mode + "] failed:"); 139 ae.printStackTrace(); 140 fail.set(true); 141 } 142 }); 143 } 144 145 // Fail at the end: 146 if (fail.get()) { 147 fail("TestNegativeScaleClipPath has failures."); 148 } 149 } 150 151 @Test(timeout = 10000) 152 public void TestMarginScaleClipPath() throws InterruptedException { 153 final AtomicBoolean fail = new AtomicBoolean(); 154 155 // testMarginScale: 156 for (SCALE_MODE mode : SCALE_MODE.values()) { 157 Util.runAndWait(() -> { 158 try { 159 testMarginScale(mode); 160 } catch (AssertionError ae) { 161 System.err.println("testMarginScale[" + mode + "] failed:"); 162 ae.printStackTrace(); 163 fail.set(true); 164 } 165 }); 166 } 167 168 // Fail at the end: 169 if (fail.get()) { 170 fail("TestMarginScaleClipPath has failures."); 171 } 172 } 173 174 private void testNegativeScale(final SCALE_MODE mode) { 175 176 // Bug in TransformingPathConsumer2D.adjustClipScale() 177 // non ortho scale only 178 final double scale = -1.0; 179 180 final Transform t; 181 switch (mode) { 182 default: 183 case ORTHO: 184 t = Transform.scale(scale, scale); 185 break; 186 case NON_ORTHO: 187 t = Transform.scale(scale, scale + 1e-5); 188 break; 189 case COMPLEX: 190 t = Transform.affine(scale, 1e-4, 1e-4, scale, 0, 0); 191 break; 192 } 193 194 final Path p = new Path(); 195 p.getElements().addAll( 196 new MoveTo(scale * 10, scale * 10), 197 new LineTo(scale * (SIZE - 10), scale * (SIZE - 10)) 198 ); 199 200 // Set cap/join to reduce clip margin: 201 p.setFill(null); 202 p.setStroke(javafx.scene.paint.Color.BLACK); 203 p.setStrokeWidth(2); 204 p.setStrokeLineCap(StrokeLineCap.BUTT); 205 p.setStrokeLineJoin(StrokeLineJoin.BEVEL); 206 207 Scene scene = new Scene(new Group(p)); 208 myApp.stage.setScene(scene); 209 210 final SnapshotParameters sp = new SnapshotParameters(); 211 sp.setViewport(new Rectangle2D(0, 0, SIZE, SIZE)); 212 sp.setTransform(t); 213 214 final WritableImage img = scene.getRoot().snapshot(sp, new WritableImage(SIZE, SIZE)); 215 216 // Check image: 217 // 25, 25 = black 218 checkPixel(img.getPixelReader(), 25, 25, Color.BLACK.getRGB()); 219 } 220 221 private static void testMarginScale(final SCALE_MODE mode) { 222 223 // Bug in Stroker.init() 224 // ortho scale only: scale used twice ! 225 final double scale = 1e-2; 226 227 final Transform t; 228 switch (mode) { 229 default: 230 case ORTHO: 231 t = Transform.scale(scale, scale); 232 break; 233 case NON_ORTHO: 234 t = Transform.scale(scale, scale + 1e-5); 235 break; 236 case COMPLEX: 237 t = Transform.affine(scale, 1e-4, 1e-4, scale, 0, 0); 238 break; 239 } 240 241 final double invScale = 1.0 / scale; 242 243 final Path p = new Path(); 244 p.getElements().addAll( 245 new MoveTo(invScale * -0.5, invScale * 10), 246 new LineTo(invScale * -0.5, invScale * (SIZE - 10)) 247 ); 248 249 // Set cap/join to reduce clip margin: 250 p.setFill(null); 251 p.setStroke(javafx.scene.paint.Color.BLACK); 252 p.setStrokeWidth(3.0 * invScale); 253 p.setStrokeLineCap(StrokeLineCap.BUTT); 254 p.setStrokeLineJoin(StrokeLineJoin.BEVEL); 255 256 Scene scene = new Scene(new Group(p)); 257 myApp.stage.setScene(scene); 258 259 final SnapshotParameters sp = new SnapshotParameters(); 260 sp.setViewport(new Rectangle2D(0, 0, SIZE, SIZE)); 261 sp.setTransform(t); 262 263 final WritableImage img = scene.getRoot().snapshot(sp, new WritableImage(SIZE, SIZE)); 264 265 // Check image: 266 // 0, 25 = black 267 checkPixel(img.getPixelReader(), 0, 25, Color.BLACK.getRGB()); 268 } 269 270 private static void checkPixel(final PixelReader pr, 271 final int x, final int y, 272 final int expected) { 273 274 final int rgb = pr.getArgb(x, y); 275 if (rgb != expected) { 276 fail("bad pixel at (" + x + ", " + y 277 + ") = " + rgb + " expected: " + expected); 278 } 279 } 280 }