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 }