From 1452b5c14261cf36d05afbbaf2de2f45a71b0bf1 Mon Sep 17 00:00:00 2001 From: John Hendrikx Date: Mon, 18 Aug 2025 11:33:25 +0200 Subject: [PATCH 1/6] Support background loading of raw input streams - Fixed generics (mix up of two ImageLoader types) - Removed unused code for handling headers, methods, request parameters - Use long for progress as streams max exceed 2 GB - Improved documentation of Image regarding background loading --- .../runtime/async/AbstractAsyncOperation.java | 18 +-- .../runtime/async/AbstractRemoteResource.java | 106 ++++-------------- .../runtime/async/AsyncOperationListener.java | 2 +- .../java/com/sun/javafx/tk/DummyToolkit.java | 7 +- .../main/java/com/sun/javafx/tk/Toolkit.java | 9 +- .../javafx/tk/quantum/PrismImageLoader2.java | 16 +-- .../sun/javafx/tk/quantum/QuantumToolkit.java | 43 ++++++- .../main/java/javafx/scene/image/Image.java | 100 +++++++++++++++-- .../javafx/pgstub/StubImageLoaderFactory.java | 4 +- .../com/sun/javafx/pgstub/StubToolkit.java | 10 +- .../test/javafx/scene/image/ImageTest.java | 41 ++++++- 11 files changed, 232 insertions(+), 124 deletions(-) diff --git a/modules/javafx.graphics/src/main/java/com/sun/javafx/runtime/async/AbstractAsyncOperation.java b/modules/javafx.graphics/src/main/java/com/sun/javafx/runtime/async/AbstractAsyncOperation.java index 871c03d0d62..18b77433b94 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/javafx/runtime/async/AbstractAsyncOperation.java +++ b/modules/javafx.graphics/src/main/java/com/sun/javafx/runtime/async/AbstractAsyncOperation.java @@ -46,10 +46,10 @@ public abstract class AbstractAsyncOperation implements AsyncOperation, Callable { protected final FutureTask future; - protected final AsyncOperationListener listener; + protected final AsyncOperationListener listener; - private int progressGranularity = 100; - private int progressMax, lastProgress, progressIncrement, nextProgress, bytesRead; + private long progressGranularity = 100; + private long progressMax, lastProgress, progressIncrement, nextProgress, bytesRead; protected AbstractAsyncOperation(final AsyncOperationListener listener) { this.listener = listener; @@ -109,8 +109,8 @@ public void start() { } protected void notifyProgress() { - final int last = lastProgress; - final int max = progressMax; + final long last = lastProgress; + final long max = progressMax; Platform.runLater(() -> listener.onProgress(last, max)); } @@ -123,11 +123,11 @@ protected void addProgress(int amount) { } } - protected int getProgressMax() { + protected long getProgressMax() { return progressMax; } - protected void setProgressMax(int progressMax) { + protected void setProgressMax(long progressMax) { if (progressMax == 0) { progressIncrement = progressGranularity; } @@ -145,11 +145,11 @@ else if (progressMax == -1) { notifyProgress(); } - protected int getProgressGranularity() { + protected long getProgressGranularity() { return progressGranularity; } - protected void setProgressGranularity(int progressGranularity) { + protected void setProgressGranularity(long progressGranularity) { this.progressGranularity = progressGranularity; progressIncrement = progressMax / progressGranularity; nextProgress = ((lastProgress / progressIncrement) + 1) * progressIncrement; diff --git a/modules/javafx.graphics/src/main/java/com/sun/javafx/runtime/async/AbstractRemoteResource.java b/modules/javafx.graphics/src/main/java/com/sun/javafx/runtime/async/AbstractRemoteResource.java index 8e71d34d77b..5662b999437 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/javafx/runtime/async/AbstractRemoteResource.java +++ b/modules/javafx.graphics/src/main/java/com/sun/javafx/runtime/async/AbstractRemoteResource.java @@ -29,14 +29,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.InterruptedIOException; -import java.io.OutputStream; -import java.net.HttpURLConnection; -import java.net.URL; -import java.net.URLConnection; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; +import java.util.Objects; /** * Abstract base class for representing remote resources identified by a URL. Subclasses may plug in arbitrary @@ -46,72 +39,39 @@ */ public abstract class AbstractRemoteResource extends AbstractAsyncOperation { - protected final String url; - protected final String method; - protected final String outboundContent; - protected int fileSize; - private Map headers = new HashMap<>(); - private Map> responseHeaders = new HashMap<>(); - - protected AbstractRemoteResource(String url, AsyncOperationListener listener) { - this(url, "GET", listener); + /** + * @param stream an {@link InputStream}, cannot be {@code null} + * @param size the size of the stream, or -1 if unknown + */ + public record SizedStream(InputStream stream, long size) {} + + /** + * An interface to provide a stream with a known (or unknown) size that + * allows {@link IOException} to be thrown. + */ + public interface SizedStreamSupplier { + SizedStream get() throws IOException; } - protected AbstractRemoteResource(String url, String method, AsyncOperationListener listener) { - this(url, method, null, listener); - } + private final SizedStreamSupplier sizedStreamSupplier; - protected AbstractRemoteResource(String url, String method, String outboundContent, AsyncOperationListener listener) { + protected AbstractRemoteResource(SizedStreamSupplier sizedStreamSupplier, AsyncOperationListener listener) { super(listener); - this.url = url; - this.method = method; - this.outboundContent = outboundContent; + + this.sizedStreamSupplier = Objects.requireNonNull(sizedStreamSupplier, "sizedStreamSupplier"); } - protected abstract T processStream(InputStream stream) throws IOException; + protected abstract T processStream(InputStream stream); @Override public T call() throws IOException { - URL u = new URL(url); - InputStream stream = null; - final String protocol = u.getProtocol(); - if(protocol.equals("http") || protocol.equals("https")) { - HttpURLConnection conn = (HttpURLConnection) u.openConnection(); - conn.setRequestMethod(method); - conn.setDoInput(true); - - for (Map.Entry entry : headers.entrySet()) { - String key = entry.getKey(); - String value = entry.getValue(); - if (value != null && !value.equals("")) - conn.setRequestProperty(key, value); - } - if(outboundContent != null && method.equals("POST")) { - conn.setDoOutput(true); - byte[] outBytes = outboundContent.getBytes("utf-8"); - conn.setRequestProperty("Content-Length", String.valueOf(outBytes.length)); - OutputStream out = conn.getOutputStream(); - out.write(outBytes); - out.close(); - } - conn.connect(); - fileSize = conn.getContentLength(); - setProgressMax(fileSize); - responseHeaders = conn.getHeaderFields(); - - stream = new ProgressInputStream(conn.getInputStream()); - } else { // protocol is something other than http... - URLConnection con = u.openConnection(); - setProgressMax(con.getContentLength()); - stream = new ProgressInputStream(con.getInputStream()); - } + SizedStream sizedStream = sizedStreamSupplier.get(); + + setProgressMax(sizedStream.size); - try { + try (ProgressInputStream stream = new ProgressInputStream(sizedStream.stream)) { return processStream(stream); } - finally { - stream.close(); - } } protected class ProgressInputStream extends BufferedInputStream { @@ -146,26 +106,4 @@ public int read(byte b[]) throws IOException { return bytes; } } - - public void setHeader(String header, String value) { - headers.put(header, value); - } - - public String getResponseHeader(String header) { - String value = null; - List list = responseHeaders.get(header); - // return a csv of the strings. - if(list != null) { - StringBuilder sb = new StringBuilder(); - Iterator iter = list.iterator(); - while(iter.hasNext()) { - sb.append(iter.next()); - if(iter.hasNext()) { - sb.append(','); - } - } - value = sb.toString(); - } - return value; - } } diff --git a/modules/javafx.graphics/src/main/java/com/sun/javafx/runtime/async/AsyncOperationListener.java b/modules/javafx.graphics/src/main/java/com/sun/javafx/runtime/async/AsyncOperationListener.java index 93994a27cad..5a9cd356847 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/javafx/runtime/async/AsyncOperationListener.java +++ b/modules/javafx.graphics/src/main/java/com/sun/javafx/runtime/async/AsyncOperationListener.java @@ -30,7 +30,7 @@ * */ public interface AsyncOperationListener { - public void onProgress(int progressValue, int progressMax); + public void onProgress(long progressValue, long progressMax); public void onCompletion(V value); public void onCancel(); public void onException(Exception e); diff --git a/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/DummyToolkit.java b/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/DummyToolkit.java index 0d214e91a31..223662485de 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/DummyToolkit.java +++ b/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/DummyToolkit.java @@ -131,7 +131,12 @@ public ImageLoader loadImage(InputStream stream, double width, double height, bo } @Override - public AsyncOperation loadImageAsync(AsyncOperationListener listener, String url, double width, double height, boolean preserveRatio, boolean smooth) { + public AsyncOperation loadImageAsync(AsyncOperationListener listener, String url, double width, double height, boolean preserveRatio, boolean smooth) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public AsyncOperation loadImageAsync(AsyncOperationListener listener, InputStream stream, double width, double height, boolean preserveRatio, boolean smooth) { throw new UnsupportedOperationException("Not supported yet."); } diff --git a/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/Toolkit.java b/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/Toolkit.java index 2873c65de16..db2240bc7c2 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/Toolkit.java +++ b/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/Toolkit.java @@ -519,12 +519,17 @@ public abstract ImageLoader loadImage(InputStream stream, boolean preserveRatio, boolean smooth); public abstract AsyncOperation loadImageAsync( - AsyncOperationListener listener, + AsyncOperationListener listener, String url, double width, double height, boolean preserveRatio, boolean smooth); - + public abstract AsyncOperation loadImageAsync( + AsyncOperationListener listener, + InputStream stream, + double width, double height, + boolean preserveRatio, + boolean smooth); /* * The loadPlatformImage method supports the following image types: * - an object returned by the renderToImage method diff --git a/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/PrismImageLoader2.java b/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/PrismImageLoader2.java index 7663fb7a512..e88579d84b6 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/PrismImageLoader2.java +++ b/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/PrismImageLoader2.java @@ -25,7 +25,6 @@ package com.sun.javafx.tk.quantum; -import java.io.IOException; import java.io.InputStream; import com.sun.javafx.iio.ImageFrame; @@ -227,7 +226,7 @@ public void imageLoadMetaData(ImageLoader loader, ImageMetadata metadata) { } static final class AsyncImageLoader - extends AbstractRemoteResource + extends AbstractRemoteResource { private static final ExecutorService BG_LOADING_EXECUTOR = createExecutor(); @@ -237,11 +236,11 @@ static final class AsyncImageLoader boolean smooth; public AsyncImageLoader( - AsyncOperationListener listener, - String url, + AsyncOperationListener listener, + SizedStreamSupplier sizedStreamSupplier, double width, double height, boolean preserveRatio, boolean smooth) { - super(url, listener); + super(sizedStreamSupplier, listener); this.width = width; this.height = height; this.preserveRatio = preserveRatio; @@ -249,15 +248,10 @@ public AsyncImageLoader( } @Override - protected PrismImageLoader2 processStream(InputStream stream) throws IOException { + protected PrismImageLoader2 processStream(InputStream stream) { return new PrismImageLoader2(stream, width, height, preserveRatio, smooth); } - @Override - public PrismImageLoader2 call() throws IOException { - return AsyncImageLoader.super.call(); - } - @Override public void start() { BG_LOADING_EXECUTOR.execute(future); diff --git a/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/QuantumToolkit.java b/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/QuantumToolkit.java index 4481b6b3ce9..39cffc97a8e 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/QuantumToolkit.java +++ b/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/QuantumToolkit.java @@ -57,6 +57,9 @@ import javafx.stage.Window; import java.io.File; import java.io.InputStream; +import java.net.HttpURLConnection; +import java.net.URL; +import java.net.URLConnection; import java.nio.Buffer; import java.nio.ByteBuffer; import java.nio.IntBuffer; @@ -92,6 +95,8 @@ import com.sun.javafx.perf.PerformanceTracker; import com.sun.javafx.runtime.async.AbstractRemoteResource; import com.sun.javafx.runtime.async.AsyncOperationListener; +import com.sun.javafx.runtime.async.AbstractRemoteResource.SizedStream; +import com.sun.javafx.runtime.async.AbstractRemoteResource.SizedStreamSupplier; import com.sun.javafx.scene.text.TextLayoutFactory; import com.sun.javafx.sg.prism.NGNode; import com.sun.javafx.tk.CompletionListener; @@ -813,10 +818,42 @@ private float getMaxRenderScale() { return new PrismImageLoader2(stream, width, height, preserveRatio, smooth); } - @Override public AbstractRemoteResource loadImageAsync( - AsyncOperationListener listener, String url, + @Override public AbstractRemoteResource loadImageAsync( + AsyncOperationListener listener, String url, double width, double height, boolean preserveRatio, boolean smooth) { - return new PrismImageLoader2.AsyncImageLoader(listener, url, width, height, preserveRatio, smooth); + + SizedStreamSupplier sizedStreamSupplier = () -> { + URL u = new URL(url); + String protocol = u.getProtocol(); + + if(protocol.equals("http") || protocol.equals("https")) { + HttpURLConnection conn = (HttpURLConnection) u.openConnection(); + + conn.setRequestMethod("GET"); + conn.connect(); + + long size = conn.getContentLengthLong(); + + return new SizedStream(conn.getInputStream(), size); + } + + // protocol is something other than http... + URLConnection conn = u.openConnection(); + long size = conn.getContentLengthLong(); + + return new SizedStream(conn.getInputStream(), size); + }; + + return new PrismImageLoader2.AsyncImageLoader(listener, sizedStreamSupplier, width, height, preserveRatio, smooth); + } + + @Override public AbstractRemoteResource loadImageAsync( + AsyncOperationListener listener, InputStream stream, + double width, double height, boolean preserveRatio, boolean smooth) { + + SizedStreamSupplier sizedStreamSupplier = () -> new SizedStream(stream, -1); + + return new PrismImageLoader2.AsyncImageLoader(listener, sizedStreamSupplier, width, height, preserveRatio, smooth); } // Note that this method should only be called by PlatformImpl.runLater diff --git a/modules/javafx.graphics/src/main/java/javafx/scene/image/Image.java b/modules/javafx.graphics/src/main/java/javafx/scene/image/Image.java index 90b07c71d91..b739b708b5a 100644 --- a/modules/javafx.graphics/src/main/java/javafx/scene/image/Image.java +++ b/modules/javafx.graphics/src/main/java/javafx/scene/image/Image.java @@ -620,6 +620,9 @@ public String getName() { /** * Constructs an {@code Image} with content loaded from the specified URL. + *

+ * The image loading is performed immediately and is completed after this + * constructor returns. * * @param url a resource path, file path, or URL * @throws NullPointerException if {@code url} is null @@ -633,10 +636,14 @@ public Image(@NamedArg("url") String url) { /** * Constructs an {@code Image} with content loaded from the specified URL * using the specified parameters. + *

+ * If loading in the background is requested, then the progress property can + * be monitored for loading progress. Otherwise, the image loading is performed + * immediately and is completed after this constructor returns. * * @param url a resource path, file path, or URL * @param backgroundLoading indicates whether the image - * is being loaded in the background + * should be loaded in the background * @throws NullPointerException if {@code url} is null * @throws IllegalArgumentException if {@code url} is invalid or unsupported */ @@ -648,6 +655,9 @@ public Image(@NamedArg("url") String url, @NamedArg("backgroundLoading") boolean /** * Constructs an {@code Image} with content loaded from the specified URL * using the specified parameters. + *

+ * The image loading is performed immediately and is completed after this + * constructor returns. * * @param url a resource path, file path, or URL * @param requestedWidth the image's bounding box width @@ -671,6 +681,10 @@ public Image(@NamedArg("url") String url, @NamedArg("requestedWidth") double req /** * Constructs an {@code Image} with content loaded from the specified URL * using the specified parameters. + *

+ * If loading in the background is requested, then the progress property can + * be monitored for loading progress. Otherwise, the image loading is performed + * immediately and is completed after this constructor returns. * * @param url a resource path, file path, or URL * @param requestedWidth the image's bounding box width @@ -682,7 +696,7 @@ public Image(@NamedArg("url") String url, @NamedArg("requestedWidth") double req * algorithm or a faster one when scaling this image to fit within * the specified bounding box * @param backgroundLoading indicates whether the image - * is being loaded in the background + * should be loaded in the background * @throws NullPointerException if {@code url} is null * @throws IllegalArgumentException if {@code url} is invalid or unsupported */ @@ -701,6 +715,9 @@ public Image( /** * Constructs an {@code Image} with content loaded from the specified * input stream. + *

+ * The image loading is performed immediately and is completed after this + * constructor returns. * * @param is the stream from which to load the image * @throws NullPointerException if input stream is null @@ -710,8 +727,29 @@ public Image(@NamedArg("is") InputStream is) { initialize(null); } + /** + * Constructs an {@code Image} with content loaded from the specified + * input stream. + *

+ * If loading in the background is requested, then the progress property can + * be monitored for loading progress. Otherwise, the image loading is performed + * immediately and is completed after this constructor returns. + * + * @param is the stream from which to load the image + * @param backgroundLoading indicates whether the image + * should be loaded in the background + * @throws NullPointerException if input stream is null + */ + public Image(@NamedArg("is") InputStream is, @NamedArg("backgroundLoading") boolean backgroundLoading) { + this(null, validateInputStream(is), 0, 0, false, false, backgroundLoading); + initialize(null); + } + /** * Constructs a new {@code Image} with the specified parameters. + *

+ * The image loading is performed immediately and is completed after this + * constructor returns. * * @param is the stream from which to load the image * @param requestedWidth the image's bounding box width @@ -731,6 +769,33 @@ public Image(@NamedArg("is") InputStream is, @NamedArg("requestedWidth") double initialize(null); } + /** + * Constructs a new {@code Image} with the specified parameters. + *

+ * If loading in the background is requested, then the progress property can + * be monitored for loading progress. Otherwise, the image loading is performed + * immediately and is completed after this constructor returns. + * + * @param is the stream from which to load the image + * @param requestedWidth the image's bounding box width + * @param requestedHeight the image's bounding box height + * @param preserveRatio indicates whether to preserve the aspect ratio of + * the original image when scaling to fit the image within the + * specified bounding box + * @param smooth indicates whether to use a better quality filtering + * algorithm or a faster one when scaling this image to fit within + * the specified bounding box + * @param backgroundLoading indicates whether the image + * should be loaded in the background + * @throws NullPointerException if input stream is null + */ + public Image(@NamedArg("is") InputStream is, @NamedArg("requestedWidth") double requestedWidth, @NamedArg("requestedHeight") double requestedHeight, + @NamedArg("preserveRatio") boolean preserveRatio, @NamedArg("smooth") boolean smooth, @NamedArg("backgroundLoading") boolean backgroundLoading) { + this(null, validateInputStream(is), requestedWidth, requestedHeight, + preserveRatio, smooth, backgroundLoading); + initialize(null); + } + /** * Package private internal constructor used only by {@link WritableImage}. * The dimensions must both be positive numbers (> 0). @@ -810,7 +875,7 @@ private void initialize(Object externalImage) { // object (e.g. a BufferedImage in the case of the Swing profile) ImageLoader loader = loadPlatformImage(externalImage); finishImage(loader); - } else if (isBackgroundLoading() && (inputSource == null)) { + } else if (isBackgroundLoading()) { // Load image in the background. loadInBackground(); } else { @@ -1047,7 +1112,7 @@ public void onCompletion(ImageLoader value) { } @Override - public void onProgress(int cur, int max) { + public void onProgress(long cur, long max) { if (max > 0) { double curProgress = (double) cur / max; if ((curProgress < 1) && (curProgress >= (getProgress() + 0.1))) { @@ -1065,9 +1130,19 @@ public void cancel() { } private AsyncOperation constructPeer() { - return loadImageAsync(this, url, - requestedWidth, requestedHeight, - preserveRatio, smooth); + if(inputSource == null) { + return loadImageAsync( + this, url, + requestedWidth, requestedHeight, + preserveRatio, smooth + ); + } + + return loadImageAsync( + this, inputSource, + requestedWidth, requestedHeight, + preserveRatio, smooth + ); } } @@ -1088,7 +1163,7 @@ private static ImageLoader loadImage( } private static AsyncOperation loadImageAsync( - AsyncOperationListener listener, + AsyncOperationListener listener, String url, double width, double height, boolean preserveRatio, boolean smooth) { return Toolkit.getToolkit().loadImageAsync(listener, url, @@ -1096,6 +1171,15 @@ private static AsyncOperation loadImageAsync( preserveRatio, smooth); } + private static AsyncOperation loadImageAsync( + AsyncOperationListener listener, + InputStream stream, double width, double height, + boolean preserveRatio, boolean smooth) { + return Toolkit.getToolkit().loadImageAsync(listener, stream, + width, height, + preserveRatio, smooth); + } + private static ImageLoader loadPlatformImage(Object platformImage) { return Toolkit.getToolkit().loadPlatformImage(platformImage); } diff --git a/modules/javafx.graphics/src/test/java/test/com/sun/javafx/pgstub/StubImageLoaderFactory.java b/modules/javafx.graphics/src/test/java/test/com/sun/javafx/pgstub/StubImageLoaderFactory.java index 6146a03f09b..8d70677517c 100644 --- a/modules/javafx.graphics/src/test/java/test/com/sun/javafx/pgstub/StubImageLoaderFactory.java +++ b/modules/javafx.graphics/src/test/java/test/com/sun/javafx/pgstub/StubImageLoaderFactory.java @@ -112,8 +112,8 @@ public ImageLoader createImageLoader(final Object source, } public AsyncOperation createAsyncImageLoader( - final AsyncOperationListener listener, - final String url, final double loadWidth, final double loadHeight, + final AsyncOperationListener listener, + final Object url, final double loadWidth, final double loadHeight, final boolean preserveRatio, final boolean smooth) { final ImageLoader imageLoader = createImageLoader(url, loadWidth, loadHeight, diff --git a/modules/javafx.graphics/src/test/java/test/com/sun/javafx/pgstub/StubToolkit.java b/modules/javafx.graphics/src/test/java/test/com/sun/javafx/pgstub/StubToolkit.java index b7842c82374..cfe5d05a4d3 100644 --- a/modules/javafx.graphics/src/test/java/test/com/sun/javafx/pgstub/StubToolkit.java +++ b/modules/javafx.graphics/src/test/java/test/com/sun/javafx/pgstub/StubToolkit.java @@ -602,12 +602,20 @@ public ImageLoader loadImage(InputStream stream, double width, double height, @Override public AsyncOperation loadImageAsync( - AsyncOperationListener listener, String url, double width, double height, + AsyncOperationListener listener, String url, double width, double height, boolean preserveRatio, boolean smooth) { return imageLoaderFactory.createAsyncImageLoader( listener, url, width, height, preserveRatio, smooth); } + @Override + public AsyncOperation loadImageAsync( + AsyncOperationListener listener, InputStream stream, double width, double height, + boolean preserveRatio, boolean smooth) { + return imageLoaderFactory.createAsyncImageLoader( + listener, stream, width, height, preserveRatio, smooth); + } + @Override public ImageLoader loadPlatformImage(Object platformImage) { return imageLoaderFactory.createImageLoader(platformImage, diff --git a/modules/javafx.graphics/src/test/java/test/javafx/scene/image/ImageTest.java b/modules/javafx.graphics/src/test/java/test/javafx/scene/image/ImageTest.java index 35ef30faa3f..f8a50df4a60 100644 --- a/modules/javafx.graphics/src/test/java/test/javafx/scene/image/ImageTest.java +++ b/modules/javafx.graphics/src/test/java/test/javafx/scene/image/ImageTest.java @@ -33,12 +33,13 @@ import test.com.sun.javafx.pgstub.StubToolkit; import test.com.sun.javafx.test.PropertyInvalidationCounter; import com.sun.javafx.tk.Toolkit; -import javafx.beans.InvalidationListener; import java.io.ByteArrayInputStream; import java.io.InputStream; import java.util.LinkedList; import java.util.Queue; + +import javafx.beans.InvalidationListener; import javafx.scene.image.Image; import javafx.scene.image.ImageShim; @@ -154,6 +155,35 @@ public void loadImageAsyncProgressTest() { assertTrue(p3 == p4); } + @Test + public void loadImageStreamAsyncProgressTest() { + final InputStream stream = new ByteArrayInputStream(new byte[0]); + registerImage(stream, 200, 100); + + final Image image = new Image(stream, true); + + final StubAsyncImageLoader lastAsyncImageLoader = + getLastAsyncImageLoader(); + + lastAsyncImageLoader.setProgress(0, 100); + final float p1 = (float) image.getProgress(); + + lastAsyncImageLoader.setProgress(33, 100); + final float p2 = (float) image.getProgress(); + + lastAsyncImageLoader.setProgress(66, 100); + final float p3 = (float) image.getProgress(); + + lastAsyncImageLoader.setProgress(200, 100); + final float p4 = (float) image.getProgress(); + + lastAsyncImageLoader.finish(); + + assertTrue(p1 < p2); + assertTrue(p2 < p3); + assertTrue(p3 == p4); + } + /* @Test public void loadImageAsyncPlaceholderTest() { @@ -528,7 +558,7 @@ public void createImageFromNullUrlTest() { @Test public void createImageAsyncFromNullUrlTest() { assertThrows(NullPointerException.class, () -> { - new Image(null, true); + new Image((String)null, true); }); } @@ -539,6 +569,13 @@ public void createImageFromNullInputStreamTest() { }); } + @Test + public void createImageAsynFromNullInputStreamTest() { + assertThrows(NullPointerException.class, () -> { + new Image((InputStream) null, true); + }); + } + @Test public void createImageFromEmptyUrlTest() { assertThrows(IllegalArgumentException.class, () -> { From 63e652e5a5f19ca83fe74cc44b157b6b99d3985c Mon Sep 17 00:00:00 2001 From: John Hendrikx Date: Mon, 18 Aug 2025 22:12:32 +0200 Subject: [PATCH 2/6] Add since tags --- .../javafx.graphics/src/main/java/javafx/scene/image/Image.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/javafx.graphics/src/main/java/javafx/scene/image/Image.java b/modules/javafx.graphics/src/main/java/javafx/scene/image/Image.java index b739b708b5a..30e1fa0fdf4 100644 --- a/modules/javafx.graphics/src/main/java/javafx/scene/image/Image.java +++ b/modules/javafx.graphics/src/main/java/javafx/scene/image/Image.java @@ -739,6 +739,7 @@ public Image(@NamedArg("is") InputStream is) { * @param backgroundLoading indicates whether the image * should be loaded in the background * @throws NullPointerException if input stream is null + * @since 26 */ public Image(@NamedArg("is") InputStream is, @NamedArg("backgroundLoading") boolean backgroundLoading) { this(null, validateInputStream(is), 0, 0, false, false, backgroundLoading); @@ -788,6 +789,7 @@ public Image(@NamedArg("is") InputStream is, @NamedArg("requestedWidth") double * @param backgroundLoading indicates whether the image * should be loaded in the background * @throws NullPointerException if input stream is null + * @since 26 */ public Image(@NamedArg("is") InputStream is, @NamedArg("requestedWidth") double requestedWidth, @NamedArg("requestedHeight") double requestedHeight, @NamedArg("preserveRatio") boolean preserveRatio, @NamedArg("smooth") boolean smooth, @NamedArg("backgroundLoading") boolean backgroundLoading) { From cef710cd9b40de77a20d5d6ebe6e0f120a0015b6 Mon Sep 17 00:00:00 2001 From: John Hendrikx Date: Wed, 20 Aug 2025 15:42:07 +0200 Subject: [PATCH 3/6] Remove wrapping --- .../src/main/java/javafx/scene/image/Image.java | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/modules/javafx.graphics/src/main/java/javafx/scene/image/Image.java b/modules/javafx.graphics/src/main/java/javafx/scene/image/Image.java index 30e1fa0fdf4..1c3fd279eb3 100644 --- a/modules/javafx.graphics/src/main/java/javafx/scene/image/Image.java +++ b/modules/javafx.graphics/src/main/java/javafx/scene/image/Image.java @@ -1133,18 +1133,10 @@ public void cancel() { private AsyncOperation constructPeer() { if(inputSource == null) { - return loadImageAsync( - this, url, - requestedWidth, requestedHeight, - preserveRatio, smooth - ); + return loadImageAsync(this, url, requestedWidth, requestedHeight, preserveRatio, smooth); } - return loadImageAsync( - this, inputSource, - requestedWidth, requestedHeight, - preserveRatio, smooth - ); + return loadImageAsync(this, inputSource, requestedWidth, requestedHeight, preserveRatio, smooth); } } From 2e23626e8a19dd6ae1262145ab081587cb8fc8c6 Mon Sep 17 00:00:00 2001 From: John Hendrikx Date: Wed, 20 Aug 2025 15:44:05 +0200 Subject: [PATCH 4/6] Add links to progress property in javadoc --- .../src/main/java/javafx/scene/image/Image.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/javafx.graphics/src/main/java/javafx/scene/image/Image.java b/modules/javafx.graphics/src/main/java/javafx/scene/image/Image.java index 1c3fd279eb3..bfa1913c4ee 100644 --- a/modules/javafx.graphics/src/main/java/javafx/scene/image/Image.java +++ b/modules/javafx.graphics/src/main/java/javafx/scene/image/Image.java @@ -637,7 +637,7 @@ public Image(@NamedArg("url") String url) { * Constructs an {@code Image} with content loaded from the specified URL * using the specified parameters. *

- * If loading in the background is requested, then the progress property can + * If loading in the background is requested, then the {@link #progressProperty() progress} property can * be monitored for loading progress. Otherwise, the image loading is performed * immediately and is completed after this constructor returns. * @@ -682,7 +682,7 @@ public Image(@NamedArg("url") String url, @NamedArg("requestedWidth") double req * Constructs an {@code Image} with content loaded from the specified URL * using the specified parameters. *

- * If loading in the background is requested, then the progress property can + * If loading in the background is requested, then the {@link #progressProperty() progress} property can * be monitored for loading progress. Otherwise, the image loading is performed * immediately and is completed after this constructor returns. * @@ -731,7 +731,7 @@ public Image(@NamedArg("is") InputStream is) { * Constructs an {@code Image} with content loaded from the specified * input stream. *

- * If loading in the background is requested, then the progress property can + * If loading in the background is requested, then the {@link #progressProperty() progress} property can * be monitored for loading progress. Otherwise, the image loading is performed * immediately and is completed after this constructor returns. * @@ -773,7 +773,7 @@ public Image(@NamedArg("is") InputStream is, @NamedArg("requestedWidth") double /** * Constructs a new {@code Image} with the specified parameters. *

- * If loading in the background is requested, then the progress property can + * If loading in the background is requested, then the {@link #progressProperty() progress} property can * be monitored for loading progress. Otherwise, the image loading is performed * immediately and is completed after this constructor returns. * From 65bc250741f8aef39d68f1fcf069a80bd5f066e8 Mon Sep 17 00:00:00 2001 From: John Hendrikx Date: Thu, 21 Aug 2025 01:27:42 +0200 Subject: [PATCH 5/6] Fix typo --- .../src/test/java/test/javafx/scene/image/ImageTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/javafx.graphics/src/test/java/test/javafx/scene/image/ImageTest.java b/modules/javafx.graphics/src/test/java/test/javafx/scene/image/ImageTest.java index f8a50df4a60..715be0fcd13 100644 --- a/modules/javafx.graphics/src/test/java/test/javafx/scene/image/ImageTest.java +++ b/modules/javafx.graphics/src/test/java/test/javafx/scene/image/ImageTest.java @@ -570,7 +570,7 @@ public void createImageFromNullInputStreamTest() { } @Test - public void createImageAsynFromNullInputStreamTest() { + public void createImageAsyncFromNullInputStreamTest() { assertThrows(NullPointerException.class, () -> { new Image((InputStream) null, true); }); From 6494c52adcdebe2d4d0f6387a5a075523d400782 Mon Sep 17 00:00:00 2001 From: John Hendrikx Date: Sat, 23 Aug 2025 16:23:33 +0200 Subject: [PATCH 6/6] Clarify documentation with regards to stream closing --- .../main/java/javafx/scene/image/Image.java | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/modules/javafx.graphics/src/main/java/javafx/scene/image/Image.java b/modules/javafx.graphics/src/main/java/javafx/scene/image/Image.java index bfa1913c4ee..4638db87810 100644 --- a/modules/javafx.graphics/src/main/java/javafx/scene/image/Image.java +++ b/modules/javafx.graphics/src/main/java/javafx/scene/image/Image.java @@ -717,7 +717,7 @@ public Image( * input stream. *

* The image loading is performed immediately and is completed after this - * constructor returns. + * constructor returns. The stream is consumed but not closed. * * @param is the stream from which to load the image * @throws NullPointerException if input stream is null @@ -731,9 +731,12 @@ public Image(@NamedArg("is") InputStream is) { * Constructs an {@code Image} with content loaded from the specified * input stream. *

- * If loading in the background is requested, then the {@link #progressProperty() progress} property can - * be monitored for loading progress. Otherwise, the image loading is performed - * immediately and is completed after this constructor returns. + * If {@code backgroundLoading} is {@code true}, the {@link #progressProperty() progress} property + * can be monitored for loading progress. The stream will be consumed asynchronously; + * the caller must not read from or close it. It will be closed automatically when loading completes. + *

+ * If {@code backgroundLoading} is {@code false}, the image is loaded immediately and + * completed when this constructor returns. The stream is consumed but not closed. * * @param is the stream from which to load the image * @param backgroundLoading indicates whether the image @@ -750,7 +753,7 @@ public Image(@NamedArg("is") InputStream is, @NamedArg("backgroundLoading") bool * Constructs a new {@code Image} with the specified parameters. *

* The image loading is performed immediately and is completed after this - * constructor returns. + * constructor returns. The stream is consumed but not closed. * * @param is the stream from which to load the image * @param requestedWidth the image's bounding box width @@ -773,9 +776,12 @@ public Image(@NamedArg("is") InputStream is, @NamedArg("requestedWidth") double /** * Constructs a new {@code Image} with the specified parameters. *

- * If loading in the background is requested, then the {@link #progressProperty() progress} property can - * be monitored for loading progress. Otherwise, the image loading is performed - * immediately and is completed after this constructor returns. + * If {@code backgroundLoading} is {@code true}, the {@link #progressProperty() progress} property + * can be monitored for loading progress. The stream will be consumed asynchronously; + * the caller must not read from or close it. It will be closed automatically when loading completes. + *

+ * If {@code backgroundLoading} is {@code false}, the image is loaded immediately and + * completed when this constructor returns. The stream is consumed but not closed. * * @param is the stream from which to load the image * @param requestedWidth the image's bounding box width