import javafx.application.Application; import javafx.scene.Scene; import javafx.scene.canvas.Canvas; import javafx.scene.canvas.GraphicsContext; import javafx.scene.image.Image; import javafx.scene.layout.VBox; import javafx.scene.paint.Color; import javafx.scene.paint.ImagePattern; import javafx.scene.text.Font; import javafx.stage.Stage; import javax.xml.bind.DatatypeConverter; import java.io.ByteArrayInputStream; import javafx.scene.Group; import javafx.scene.shape.Rectangle; public class ImagePatternTest extends Application { static int RECT_WIDTH = 200; static int RECT_HEIGHT = 100; GraphicsContext context; public static void main(String[] args) { launch(args); } @Override public void start(Stage primaryStage) throws Exception { primaryStage.setScene(createScene()); primaryStage.show(); } Scene createScene() { // The pattern image has 2x2 size with the top left and bottom right pixels set to yellow // and the other two set to transparent. So, being shifted by 1 pixel, they together should // provide a solid yellow color. String base64Image = "iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAIAAAD91JpzAAAABnRSTlMAAAAAAABupgeRAAAABmJLR0QA/wD/AP+gvaeTAAAAEUlEQVR42mP4/58BCv7/ZwAAHfAD/abwPj4AAAAASUVORK5CYII="; byte[] imageBytes = DatatypeConverter.parseBase64Binary(base64Image); Canvas canvas = new Canvas(600, 400); context = canvas.getGraphicsContext2D(); context.setFont(Font.font(16)); Group nodes = new Group(); // first, try to draw patterns that use separate images sharing same byte data // and observe correct solid yellow color fill int x = 0, y = 100; context.setFill(Color.BLACK); context.fillText("Correct - separate images", x, y / 2); Image separateImage1 = createImage(imageBytes); Image separateImage2 = createImage(imageBytes); drawPatterns(nodes, separateImage1, separateImage2, x, y); // second, try to draw patterns that share the image itself // and observe incorrect red-and-yellow chequered fill x = 300; context.setFill(Color.BLACK); context.fillText("Incorrect - shared image", x, y / 2); Image sharedImage = createImage(imageBytes); drawPatterns(nodes, sharedImage, sharedImage, x, y); VBox root = new VBox(); Scene scene = new Scene(root); root.getChildren().addAll(canvas, nodes); return scene; } Image createImage(byte[] imageBytes) { return new Image(new ByteArrayInputStream(imageBytes)); } /** * Fills a rectangle with red background then tiles two image patterns subsequently, * shifting the second one 1 pixel right. The rectangle size is set in static class fields. * @param image1 image for the 1st pattern to fill * @param image2 image for the 2nd pattern to fill * @param x x coordinate of the rectangle to fill * @param y y coordinate of the rectangle to fill */ void drawPatterns(Group nodes, Image image1, Image image2, int x, int y) { context.setFill(Color.RED); context.fillRect(x, y, RECT_WIDTH, RECT_HEIGHT); Rectangle rect = new Rectangle(x, y, RECT_HEIGHT, RECT_HEIGHT); rect.setFill(Color.RED); nodes.getChildren().add(rect); drawPattern(nodes, image1, 0, x, y); drawPattern(nodes, image2, 1, x, y); // shifted 1 pixel right } /** * Fills a rectangle with a tiled image pattern. The rectangle size is set in static class fields. * @param image image to tile * @param anchorX the horizontal anchor point for image pattern * @param x x coordinate of the rectangle to fill * @param y y coordinate of the rectangle to fill */ void drawPattern(Group nodes, Image image, double anchorX, double x, double y) { ImagePattern pattern = new ImagePattern( image, anchorX, 0, image.getWidth(), image.getHeight(), false); context.setFill(pattern); context.fillRect(x, y, RECT_WIDTH, RECT_HEIGHT); Rectangle rectangle = new Rectangle(x, y, RECT_WIDTH, RECT_HEIGHT); rectangle.setFill(pattern); nodes.getChildren().add(rectangle); } }