Skip to content

Commit 072e6f4

Browse files
committed
Detect the desired size from the path or Query parameters.
Currently Icons can be organized in folders like /icons/16x16, /icons/32x32 and so on for high resolution support. Currently if one want to replace such structure with SVG it is required to scale down the SVG to 16x16 pixel document size as otherwise they get rendered at there native size (what usually is much larger). As it is not really desirable to restrict the size of the SVG design for technical reasons, JFace now can detect two cases: 1) the SVG is places in a folder with "classic" folder layout the size is extracted and passed down as a hint for dynamic sizable icons 2) one can additionally add a query parameter, e.g. if I have an icon like /icons/obj16/search.svg and I have two places where I want to use it one for a toolbar (16x16) and once for a Wizard Images (usually 128x128) I can use the url bundle:/example.id/icons/obj16/search.svg?size=16x16 and bundle:/example.id/icons/obj16/search.svg?size=128x128 to accomplish this task without the need to even store two SVGs
1 parent 447e516 commit 072e6f4

File tree

10 files changed

+798
-4
lines changed

10 files changed

+798
-4
lines changed

bundles/org.eclipse.jface/META-INF/MANIFEST.MF

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ Export-Package: org.eclipse.jface,
3434
org.eclipse.jface.window,
3535
org.eclipse.jface.wizard,
3636
org.eclipse.jface.wizard.images
37-
Require-Bundle: org.eclipse.swt;bundle-version="[3.126.0,4.0.0)";visibility:=reexport,
37+
Require-Bundle: org.eclipse.swt;bundle-version="[3.132.0,3.134.0)";visibility:=reexport,
3838
org.eclipse.core.commands;bundle-version="[3.4.0,4.0.0)";visibility:=reexport,
3939
org.eclipse.equinox.common;bundle-version="[3.18.0,4.0.0)",
4040
org.eclipse.equinox.bidi;bundle-version="[0.10.0,2.0.0)";resolution:=optional
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2025 Christoph Läubrich and others.
3+
*
4+
* This program and the accompanying materials
5+
* are made available under the terms of the Eclipse Public License 2.0
6+
* which accompanies this distribution, and is available at
7+
* https://www.eclipse.org/legal/epl-2.0/
8+
*
9+
* SPDX-License-Identifier: EPL-2.0
10+
*
11+
* Contributors:
12+
* Christoph Läubrich - initial API and implementation
13+
*******************************************************************************/
14+
package org.eclipse.jface.resource;
15+
16+
import java.net.URL;
17+
import java.util.function.Supplier;
18+
import java.util.regex.Matcher;
19+
import java.util.regex.Pattern;
20+
21+
import org.eclipse.swt.graphics.Point;
22+
23+
class URLHintProvider implements Supplier<Point> {
24+
25+
private static final Pattern QUERY_PATTERN = Pattern.compile("&size=(\\d+)x(\\d+)"); //$NON-NLS-1$
26+
private static final Pattern PATH_PATTERN = Pattern.compile("/(\\d+)x(\\d+)/"); //$NON-NLS-1$
27+
28+
private URL url;
29+
30+
public URLHintProvider(URL url) {
31+
this.url = url;
32+
}
33+
34+
@Override
35+
public Point get() {
36+
String query = url.getQuery();
37+
Matcher matcher;
38+
if (query != null && !query.isEmpty()) {
39+
matcher = QUERY_PATTERN.matcher("&" + query); //$NON-NLS-1$
40+
} else {
41+
String path = url.getPath();
42+
matcher = PATH_PATTERN.matcher(path);
43+
}
44+
if (matcher.find()) {
45+
return new Point(Integer.parseInt(matcher.group(1)), Integer.parseInt(matcher.group(2)));
46+
}
47+
return null;
48+
}
49+
50+
}

bundles/org.eclipse.jface/src/org/eclipse/jface/resource/URLImageDescriptor.java

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import java.net.MalformedURLException;
2626
import java.net.URL;
2727
import java.util.function.Function;
28+
import java.util.function.Supplier;
2829
import java.util.regex.Matcher;
2930
import java.util.regex.Pattern;
3031

@@ -42,6 +43,7 @@
4243
import org.eclipse.swt.graphics.ImageDataProvider;
4344
import org.eclipse.swt.graphics.ImageFileNameProvider;
4445
import org.eclipse.swt.graphics.ImageLoader;
46+
import org.eclipse.swt.graphics.Point;
4547
import org.eclipse.swt.internal.DPIUtil.ElementAtZoom;
4648
import org.eclipse.swt.internal.NativeImageLoader;
4749
import org.eclipse.swt.internal.image.FileFormat;
@@ -59,6 +61,11 @@ private ImageFileNameProvider createURLImageFileNameProvider() {
5961
if (tempURL != null) {
6062
final boolean logIOException = zoom == 100;
6163
if (zoom == 100) {
64+
// Do not use file path if URL has query parameters (e.g., size hints)
65+
// because getFilePath() strips the query and size hints would be lost
66+
if (tempURL.getQuery() != null) {
67+
return null;
68+
}
6269
// Do not take this path if the image file can be scaled up dynamically.
6370
// The calling image will do that itself!
6471
return getFilePath(tempURL, logIOException);
@@ -133,7 +140,7 @@ private static <R> R getZoomedImageSource(URL url, String urlString, int zoom, F
133140
private static ImageData getImageData(URL url, int fileZoom, int targetZoom) {
134141
try (InputStream in = getStream(url)) {
135142
if (in != null) {
136-
return loadImageFromStream(new BufferedInputStream(in), fileZoom, targetZoom);
143+
return loadImageFromStream(new BufferedInputStream(in), fileZoom, targetZoom, new URLHintProvider(url));
137144
}
138145
} catch (SWTException e) {
139146
if (e.code != SWT.ERROR_INVALID_IMAGE) {
@@ -147,7 +154,13 @@ private static ImageData getImageData(URL url, int fileZoom, int targetZoom) {
147154
}
148155

149156
@SuppressWarnings("restriction")
150-
private static ImageData loadImageFromStream(InputStream stream, int fileZoom, int targetZoom) {
157+
private static ImageData loadImageFromStream(InputStream stream, int fileZoom, int targetZoom,
158+
Supplier<Point> hintProvider) {
159+
Point hintSize = hintProvider.get();
160+
if (hintSize != null) {
161+
return NativeImageLoader.load(stream, new ImageLoader(), hintSize.x * targetZoom / fileZoom,
162+
hintSize.y * targetZoom / fileZoom);
163+
}
151164
return NativeImageLoader.load(new ElementAtZoom<>(stream, fileZoom), new ImageLoader(), targetZoom).get(0)
152165
.element();
153166
}
@@ -169,8 +182,14 @@ private static InputStream getStream(URL url) {
169182
if (InternalPolicy.OSGI_AVAILABLE) {
170183
url = resolvePathVariables(url);
171184
}
185+
// For file: URLs, strip query parameters before opening the stream
186+
// Query parameters are used for size hints but are not valid in file paths
187+
if (FILE_PROTOCOL.equalsIgnoreCase(url.getProtocol()) && url.getQuery() != null) {
188+
url = new URL(url.getProtocol(), url.getHost(), url.getPort(), url.getPath());
189+
}
172190
return url.openStream();
173191
} catch (IOException e) {
192+
e.printStackTrace();
174193
if (InternalPolicy.DEBUG_LOG_URL_IMAGE_DESCRIPTOR_MISSING_2x) {
175194
String path = url.getPath();
176195
if (path.endsWith("@2x.png") || path.endsWith("@1.5x.png")) { //$NON-NLS-1$ //$NON-NLS-2$

0 commit comments

Comments
 (0)