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.robot.javafx.scene.canvas;
  26 
  27 import java.io.FileInputStream;
  28 import java.net.URL;
  29 
  30 import javafx.application.Application;
  31 import javafx.application.Platform;
  32 import javafx.scene.Scene;
  33 import javafx.scene.canvas.Canvas;
  34 import javafx.scene.canvas.GraphicsContext;
  35 import javafx.scene.image.Image;
  36 import javafx.scene.layout.VBox;
  37 import javafx.scene.paint.Color;
  38 import javafx.scene.robot.Robot;
  39 import javafx.stage.Stage;
  40 import javafx.stage.StageStyle;
  41 import javafx.stage.WindowEvent;
  42 
  43 import java.util.concurrent.CountDownLatch;
  44 import java.util.concurrent.TimeUnit;
  45 
  46 import org.junit.AfterClass;
  47 import org.junit.Assert;
  48 import org.junit.BeforeClass;
  49 import org.junit.Test;
  50 import static org.junit.Assert.fail;
  51 
  52 import test.util.Util;
  53 
  54 // 1. An image, half white half black sized 10 X 10 is used in test.
  55 // 2. The image is drawn on a 20 times scaled up canvas(200 X 200).
  56 
  57 public class ImageSmoothingDrawTest {
  58 
  59     static CountDownLatch startupLatch = new CountDownLatch(1);
  60     static Robot robot;
  61     static ImageCanvas imageCanvas;
  62     static volatile Stage stage;
  63     static volatile Scene scene;
  64     Color lastWhitePixelColor;
  65     Color whitePixelColor;
  66     static final int scaleFactor = 20;
  67 
  68     public void getPixelColors() {
  69         int lastWhiteX = (int) (stage.getX() + scene.getX() +
  70             imageCanvas.getLayoutX() + (imageCanvas.getWidth() / 2) - 2);
  71         int heightCenter = (int) (stage.getY() + scene.getY() +
  72             imageCanvas.getLayoutY() + (imageCanvas.getHeight() / 2));
  73 
  74         Util.runAndWait(() -> {
  75             lastWhitePixelColor = robot.getPixelColor(lastWhiteX, heightCenter);
  76             whitePixelColor = robot.getPixelColor(
  77                 lastWhiteX - (imageCanvas.getWidth() / 4), heightCenter);
  78         });
  79     }
  80 
  81     // 3. When image smoothing is enabled, linear filtering is applied
  82     // at the center where white and black colors meet.
  83     // 4. A width of few pixels at the center would not be white or black.
  84     // 5. Color of second last white side pixel, should NOT be white.
  85     @Test
  86     public void testImageSmoothingEnabled() {
  87         imageCanvas.setImageSmoothing(true);
  88         Util.sleep(1000); // Wait for image to be drawn
  89         getPixelColors();
  90         Assert.assertEquals(Color.WHITE, whitePixelColor);
  91         Assert.assertFalse(whitePixelColor.equals(lastWhitePixelColor));
  92     }
  93 
  94     // 6. When image smoothing is disabled, filtering is NOT applied
  95     // where at the center where white and black colors meet.
  96     // 7. As filtering is NOT applied, all white pixels remain white
  97     // and black remain black.
  98     // 8. Color of second last white side pixel, should remain white.
  99     @Test
 100     public void testImageSmoothingDisabled() {
 101         imageCanvas.setImageSmoothing(false);
 102         Util.sleep(1000); // Wait for image to be drawn
 103         getPixelColors();
 104         Assert.assertEquals(Color.WHITE, whitePixelColor);
 105         Assert.assertEquals(whitePixelColor, lastWhitePixelColor);
 106     }
 107 
 108     @BeforeClass
 109     public static void initFX() {
 110         new Thread(() -> Application.launch(TestApp.class, (String[])null)).start();
 111         waitForLatch(startupLatch, 10, "Timeout waiting for FX runtime to start");
 112     }
 113 
 114     @AfterClass
 115     public static void exit() {
 116         Platform.runLater(() -> {
 117             stage.hide();
 118         });
 119         Platform.exit();
 120     }
 121 
 122     public static class TestApp extends Application {
 123         @Override
 124         public void start(Stage primaryStage) throws Exception {
 125             robot = new Robot();
 126             stage = primaryStage;
 127             // Create 20 times scaled canvas.
 128             URL resource = this.getClass().getResource("image_smoothing_draw_test.png");
 129             FileInputStream inFile = new FileInputStream(resource.getFile());
 130             Image whiteBlack = new Image(inFile);
 131             inFile.close();
 132             imageCanvas = new ImageCanvas(whiteBlack,
 133                 whiteBlack.getWidth() * scaleFactor,
 134                 whiteBlack.getHeight() * scaleFactor);
 135 
 136             VBox root = new VBox();
 137             root.getChildren().add(imageCanvas);
 138 
 139             scene = new Scene(root);
 140             stage.setScene(scene);
 141             stage.initStyle(StageStyle.UNDECORATED);
 142             stage.addEventHandler(WindowEvent.WINDOW_SHOWN, e ->
 143                     Platform.runLater(startupLatch::countDown));
 144             stage.setAlwaysOnTop(true);
 145             stage.show();
 146         }
 147     }
 148 
 149     public static void waitForLatch(CountDownLatch latch, int seconds, String msg) {
 150         try {
 151             if (!latch.await(seconds, TimeUnit.SECONDS)) {
 152                 fail(msg);
 153             }
 154         } catch (Exception ex) {
 155             fail("Unexpected exception: " + ex);
 156         }
 157     }
 158 
 159     static class ImageCanvas extends Canvas {
 160         private Image image = null;
 161 
 162         public ImageCanvas(Image img, double width, double height) {
 163             super(width, height);
 164             image = img;
 165         }
 166 
 167         public void render() {
 168             GraphicsContext gc = getGraphicsContext2D();
 169             if (image != null) {
 170                 gc.drawImage(image,
 171                     0, 0, image.getWidth(), image.getHeight(),
 172                     0, 0, getWidth(), getHeight());
 173             }
 174         }
 175 
 176         public void setImageSmoothing(boolean smooth) {
 177             GraphicsContext gc = getGraphicsContext2D();
 178             gc.setImageSmoothing(smooth);
 179             render();
 180         }
 181     }
 182 }