From a8a3a2c33c67a2049f032d273fbd93e511f8fc40 Mon Sep 17 00:00:00 2001 From: jvos Date: Tue, 20 Aug 2019 10:43:13 +0200 Subject: [PATCH 1/2] Added tag 13+13 for changeset dde636c6219c From 0438ea89b2fe7aaa916232a57127cf445e34ba65 Mon Sep 17 00:00:00 2001 From: kcr Date: Mon, 26 Aug 2019 11:23:23 -0700 Subject: [PATCH 2/2] 8229890: WritableImage update fails for empty region Reviewed-by: kcr, arapte Contributed-by: mp@jugs.org --- .../java/javafx/scene/image/PixelBuffer.java | 16 +- .../javafx/scene/image/PixelBufferTest.java | 11 ++ .../image/WritableImageFromBufferTest.java | 167 ++++++++++++++++++ 3 files changed, 187 insertions(+), 7 deletions(-) create mode 100644 tests/system/src/test/java/test/javafx/scene/image/WritableImageFromBufferTest.java diff --git a/modules/javafx.graphics/src/main/java/javafx/scene/image/PixelBuffer.java b/modules/javafx.graphics/src/main/java/javafx/scene/image/PixelBuffer.java index 099de9fa61..8c267ebbcb 100644 --- a/modules/javafx.graphics/src/main/java/javafx/scene/image/PixelBuffer.java +++ b/modules/javafx.graphics/src/main/java/javafx/scene/image/PixelBuffer.java @@ -189,15 +189,17 @@ public void updateBuffer(Callback, Rectangle2D> callback) { Toolkit.getToolkit().checkFxUserThread(); Objects.requireNonNull(callback, "callback must not be null."); Rectangle2D rect2D = callback.call(this); - Rectangle rect = null; if (rect2D != null) { - int x1 = (int) Math.floor(rect2D.getMinX()); - int y1 = (int) Math.floor(rect2D.getMinY()); - int x2 = (int) Math.ceil(rect2D.getMaxX()); - int y2 = (int) Math.ceil(rect2D.getMaxY()); - rect = new Rectangle(x1, y1, x2 - x1, y2 - y1); + if (rect2D.getWidth() > 0 && rect2D.getHeight() > 0) { + int x1 = (int) Math.floor(rect2D.getMinX()); + int y1 = (int) Math.floor(rect2D.getMinY()); + int x2 = (int) Math.ceil(rect2D.getMaxX()); + int y2 = (int) Math.ceil(rect2D.getMaxY()); + bufferDirty(new Rectangle(x1, y1, x2 - x1, y2 - y1)); + } + } else { + bufferDirty(null); } - bufferDirty(rect); } private void bufferDirty(Rectangle rect) { diff --git a/modules/javafx.graphics/src/test/java/test/javafx/scene/image/PixelBufferTest.java b/modules/javafx.graphics/src/test/java/test/javafx/scene/image/PixelBufferTest.java index 602ecbbab5..b89a6e8ecd 100644 --- a/modules/javafx.graphics/src/test/java/test/javafx/scene/image/PixelBufferTest.java +++ b/modules/javafx.graphics/src/test/java/test/javafx/scene/image/PixelBufferTest.java @@ -133,6 +133,17 @@ public void testUpdatePixelBufferPartialBufferUpdate() { pixelBuffer.updateBuffer(callback); } + @Test + public void testUpdatePixelBufferEmptyBufferUpdate() { + // This test verifies that an empty dirty region does not cause any exception + PixelBuffer pixelBuffer = new PixelBuffer<>(WIDTH, HEIGHT, BYTE_BUFFER, BYTE_BGRA_PRE_PF); + Callback, Rectangle2D> callback = pixBuf -> { + // Assuming no pixels were modified. + return Rectangle2D.EMPTY; + }; + pixelBuffer.updateBuffer(callback); + } + @Test public void testUpdatePixelBufferCallbackNull() { try { diff --git a/tests/system/src/test/java/test/javafx/scene/image/WritableImageFromBufferTest.java b/tests/system/src/test/java/test/javafx/scene/image/WritableImageFromBufferTest.java new file mode 100644 index 0000000000..09c181e2f2 --- /dev/null +++ b/tests/system/src/test/java/test/javafx/scene/image/WritableImageFromBufferTest.java @@ -0,0 +1,167 @@ +/* + * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package test.javafx.scene.image; + +import java.awt.Color; +import java.awt.Graphics2D; +import java.awt.image.BufferedImage; +import java.awt.image.DataBuffer; +import java.awt.image.DataBufferInt; +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.nio.IntBuffer; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import javafx.application.Application; +import javafx.application.Platform; +import javafx.geometry.Rectangle2D; +import javafx.scene.Scene; +import javafx.scene.image.ImageView; +import javafx.scene.image.PixelBuffer; +import javafx.scene.image.PixelFormat; +import javafx.scene.image.WritableImage; +import javafx.scene.layout.StackPane; +import javafx.stage.Stage; +import javafx.stage.WindowEvent; +import test.util.Util; + +/** + * This test verifies the fix for JDK-8229890. + * This test ensures that the callback provided to the {@link javafx.scene.image.PixelBuffer#updateBuffer()} + * method may return {@link javafx.geometry.Rectangle2D.Empty} in order to + * indicate that no update is necessary and no exception is thrown. + */ +public class WritableImageFromBufferTest { + + static CountDownLatch startupLatch; + + private static final int IMG_WIDTH = 600; + private static final int IMG_HEIGHT = 400; + + private PixelBuffer pixelBuffer; + private Graphics2D g2d; + private WritableImage fxImage; + + private static Scene scene; + + public static class TestApp extends Application { + @Override + public void start(Stage primaryStage) throws Exception { + scene = new Scene(new StackPane(), IMG_WIDTH, IMG_HEIGHT); + primaryStage.addEventHandler(WindowEvent.WINDOW_SHOWN, event -> startupLatch.countDown()); + primaryStage.setScene(scene); + primaryStage.show(); + } + } + + @Before + public void setUp() throws Exception { + BufferedImage awtImage = new BufferedImage(IMG_WIDTH, IMG_HEIGHT, BufferedImage.TYPE_INT_ARGB_PRE); + g2d = (Graphics2D) awtImage.getGraphics(); + + DataBuffer db = awtImage.getRaster().getDataBuffer(); + DataBufferInt dbi = (DataBufferInt) db; + int[] rawInts = dbi.getData(); + IntBuffer ib = IntBuffer.wrap(rawInts); + + PixelFormat pixelFormat = PixelFormat.getIntArgbPreInstance(); + pixelBuffer = new PixelBuffer<>(IMG_WIDTH, IMG_HEIGHT, ib, pixelFormat); + fxImage = new WritableImage(pixelBuffer); + } + + @Test + public void test() throws InterruptedException { + PrintStream defaultErrorStream = System.err; + ByteArrayOutputStream out = new ByteArrayOutputStream(); + System.setErr(new PrintStream(out, true)); + + Thread.sleep(1000); + Util.runAndWait(() -> { + StackPane root = (StackPane)scene.getRoot(); + root.getChildren().add(new ImageView(fxImage)); + requestFullUpdate(); + }); + Thread.sleep(100); + Util.runAndWait(() -> { + requestEmptyUpdate(); // This will fail without the fix. + }); + Thread.sleep(100); + Util.runAndWait(() -> { + requestPartialUpdate(); + }); + Thread.sleep(100); + + System.setErr(defaultErrorStream); + Assert.assertEquals("No error should be thrown", "", out.toString()); + } + + private void requestFullUpdate() { + // This call should work before and after the fix. + pixelBuffer.updateBuffer(pb -> { + g2d.setBackground(Color.decode("#FF0000")); + g2d.clearRect(0, 0, IMG_WIDTH, IMG_HEIGHT); + return null; + }); + } + + private void requestEmptyUpdate() { + // This call should fail without the fix and pass after the fix. + pixelBuffer.updateBuffer(pb -> { + // Nothing to do. + return Rectangle2D.EMPTY; + }); + } + + private void requestPartialUpdate() { + // This call should work before and after the fix. + pixelBuffer.updateBuffer(pb -> { + g2d.setBackground(Color.decode("#0000FF")); + g2d.clearRect(0, 0, IMG_WIDTH / 2, IMG_HEIGHT); + return new Rectangle2D(0, 0, IMG_WIDTH / 2, IMG_HEIGHT); + }); + } + + @BeforeClass + public static void initFX() throws Exception { + startupLatch = new CountDownLatch(1); + new Thread(() -> Application.launch(TestApp.class, (String[])null)).start(); + Assert.assertTrue("Timeout waiting for FX runtime to start", + startupLatch.await(15, TimeUnit.SECONDS)); + } + + @AfterClass + public static void tearDown() { + Platform.exit(); + } + +}