Skip to content

Commit 01766e0

Browse files
committed
Adding DynamicallySizableImageDataProvider Interface
Implementations of DynamicallySizableImageDataProvider can provide image data at a requested targetHeight and targetWidth. The behavior of GC#drawImage() is updated: when drawing a dynamically sizeable image, it requests a handle at the target size. If the image can be loaded at that size, it is used directly; otherwise, the image is loaded as before and scaled using zoom. In the Windows implementation, if the source or destination width/height is set to zero, the image will be drawn fully, avoiding the need to call getBounds() to determine the image size.
1 parent bd7b7dc commit 01766e0

File tree

9 files changed

+273
-31
lines changed

9 files changed

+273
-31
lines changed

bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/graphics/Image.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1820,11 +1820,12 @@ public String toString () {
18201820
* @param imageData the imageData which is used to draw the scaled Image
18211821
* @param width the width of the original image
18221822
* @param height the height of the original image
1823-
* @param scaleFactor the factor with which the image is supposed to be scaled
1823+
* @param targetWidth the width to which the image is supposed to be scaled
1824+
* @param targetHeight the height to which the image is supposed to be scaled
18241825
*
18251826
* @noreference This method is not intended to be referenced by clients.
18261827
*/
1827-
public static void drawScaled(GC gc, ImageData imageData, int width, int height, float scaleFactor) {
1828+
public static void drawAtTargetSize(GC gc, ImageData imageData, int width, int height, int targetWidth, int targetHeight) {
18281829
StrictChecks.runWithStrictChecksDisabled(() -> {
18291830
Image imageToDraw = new Image(gc.device, (ImageDataProvider) zoom -> imageData);
18301831
gc.drawImage(imageToDraw, 0, 0, CocoaDPIUtil.pixelToPoint(width), CocoaDPIUtil.pixelToPoint(height),
@@ -1833,8 +1834,7 @@ public static void drawScaled(GC gc, ImageData imageData, int width, int height,
18331834
* avoiding rounding errors. Nevertheless, we still have some rounding errors
18341835
* due to the point-based API GC#drawImage(..).
18351836
*/
1836-
0, 0, Math.round(CocoaDPIUtil.pixelToPoint(width * scaleFactor)),
1837-
Math.round(CocoaDPIUtil.pixelToPoint(height * scaleFactor)));
1837+
0, 0, targetWidth, targetHeight);
18381838
imageToDraw.dispose();
18391839
});
18401840
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2025 Vector Informatik GmbH and others.
3+
*
4+
* This program and the accompanying materials are made available under the terms of the Eclipse
5+
* Public License 2.0 which accompanies this distribution, and is available at
6+
* https://www.eclipse.org/legal/epl-2.0/
7+
*
8+
* SPDX-License-Identifier: EPL-2.0
9+
*
10+
* Contributors:
11+
* Michael Bangas (Vector Informatik GmbH) - initial API and implementation
12+
*******************************************************************************/
13+
package org.eclipse.swt.graphics;
14+
15+
/**
16+
* @since 3.132
17+
*/
18+
public interface ImageDataAtSizeProvider extends ImageDataProvider {
19+
20+
ImageData getImageData(int targetWidth, int targetHeight);
21+
22+
}

bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/graphics/ImageDataLoader.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,14 @@ public static boolean canLoadAtZoom(String filename, int fileZoom, int targetZoo
4545
return ImageLoader.canLoadAtZoom(filename, fileZoom, targetZoom);
4646
}
4747

48+
static boolean isDynamicallySizable(String filename) {
49+
return ImageLoader.isDynamicallySizable(filename);
50+
}
51+
52+
static boolean isDynamicallySizable(InputStream stream) {
53+
return ImageLoader.isDynamicallySizable(stream);
54+
}
55+
4856
public static ElementAtZoom<ImageData> loadByZoom(InputStream stream, int fileZoom, int targetZoom) {
4957
List<ElementAtZoom<ImageData>> data = new ImageLoader().loadByZoom(stream, fileZoom, targetZoom);
5058
if (data.isEmpty()) SWT.error(SWT.ERROR_INVALID_IMAGE);

bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/graphics/ImageLoader.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,19 @@ static boolean canLoadAtZoom(String filename, int fileZoom, int targetZoom) {
229229
return false;
230230
}
231231

232+
static boolean isDynamicallySizable(String filename) {
233+
try (InputStream stream = new FileInputStream(filename)) {
234+
return FileFormat.isDynamicallySizableFormat(stream);
235+
} catch (IOException e) {
236+
SWT.error(SWT.ERROR_IO, e);
237+
}
238+
return false;
239+
}
240+
241+
static boolean isDynamicallySizable(InputStream stream) {
242+
return FileFormat.isDynamicallySizableFormat(stream);
243+
}
244+
232245
/**
233246
* Saves the image data in this ImageLoader to the specified stream.
234247
* The format parameter can have one of the following values:

bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/internal/DPIUtil.java

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,14 +146,24 @@ public static ImageData autoScaleImageData (Device device, final ImageData image
146146
int height = imageData.height;
147147
int scaledWidth = Math.round (width * scaleFactor);
148148
int scaledHeight = Math.round (height * scaleFactor);
149+
return scaleImage(device, imageData, Image::drawAtTargetSize, width, height, scaledWidth, scaledHeight);
150+
}
151+
152+
@FunctionalInterface
153+
private interface ImageDrawFunction {
154+
void draw(GC gc, ImageData imageData, int originalWidth, int originalHeight, int targetWidth, int targetHeight);
155+
}
156+
157+
private static ImageData scaleImage(Device device, final ImageData imageData, ImageDrawFunction drawFunction, int width, int height,
158+
int scaledWidth, int scaledHeight) {
149159
int defaultZoomLevel = 100;
150160
boolean useSmoothScaling = isSmoothScalingEnabled() && imageData.getTransparencyType() != SWT.TRANSPARENCY_MASK;
151161
if (useSmoothScaling) {
152162
ImageGcDrawer drawer = new ImageGcDrawer() {
153163
@Override
154164
public void drawOn(GC gc, int imageWidth, int imageHeight) {
155165
gc.setAntialias (SWT.ON);
156-
Image.drawScaled(gc, imageData, width, height, scaleFactor);
166+
drawFunction.draw(gc, imageData, width, height, imageWidth, imageHeight);
157167
};
158168

159169
@Override
@@ -170,6 +180,10 @@ public int getGcStyle() {
170180
}
171181
}
172182

183+
public static ImageData autoScaleImageData(Device device, final ImageData imageData, int targetWidth, int targetHeight) {
184+
return scaleImage(device, imageData, Image::drawAtTargetSize, imageData.width, imageData.height, targetWidth, targetHeight);
185+
}
186+
173187
public static boolean isSmoothScalingEnabled() {
174188
return autoScaleMethod == AutoScaleMethod.SMOOTH;
175189
}

bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/internal/image/FileFormat.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,16 @@ private static Optional<FileFormat> determineFileFormat(LEDataInputStream stream
7373
private static final int MAX_SIGNATURE_BYTES = 18 + 2; // e.g. Win-BMP or OS2-BMP plus a safety-margin
7474

7575
public static boolean isDynamicallySizableFormat(InputStream is) {
76-
Optional<FileFormat> format = determineFileFormat(new LEDataInputStream(is, MAX_SIGNATURE_BYTES));
76+
if (!is.markSupported()) {
77+
is = new BufferedInputStream(is);
78+
}
79+
is.mark(MAX_SIGNATURE_BYTES);
80+
Optional<FileFormat> format = determineFileFormat(new LEDataInputStream(is, MAX_SIGNATURE_BYTES));
81+
try {
82+
is.reset();
83+
} catch (IOException e) {
84+
e.printStackTrace();
85+
}
7786
return format.isPresent() && !(format.get() instanceof StaticImageFileFormat);
7887
}
7988

bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/graphics/Image.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1581,11 +1581,12 @@ public String toString () {
15811581
* @param imageData the imageData which is used to draw the scaled Image
15821582
* @param width the width of the original image
15831583
* @param height the height of the original image
1584-
* @param scaleFactor the factor with which the image is supposed to be scaled
1584+
* @param targetWidth the width to which the image is supposed to be scaled
1585+
* @param targetHeight the height to which the image is supposed to be scaled
15851586
*
15861587
* @noreference This method is not intended to be referenced by clients.
15871588
*/
1588-
public static void drawScaled(GC gc, ImageData imageData, int width, int height, float scaleFactor) {
1589+
public static void drawAtTargetSize(GC gc, ImageData imageData, int width, int height, int targetWidth, int targetHeight) {
15891590
StrictChecks.runWithStrictChecksDisabled(() -> {
15901591
Image imageToDraw = new Image(gc.device, (ImageDataProvider) zoom -> imageData);
15911592
gc.drawImage(imageToDraw, 0, 0, width, height,
@@ -1594,7 +1595,7 @@ public static void drawScaled(GC gc, ImageData imageData, int width, int height,
15941595
* avoiding rounding errors. Nevertheless, we still have some rounding errors
15951596
* due to the point-based API GC#drawImage(..).
15961597
*/
1597-
0, 0, Math.round(width * scaleFactor), Math.round(height * scaleFactor));
1598+
0, 0, targetWidth, targetHeight);
15981599
imageToDraw.dispose();
15991600
});
16001601
}

bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/graphics/GC.java

Lines changed: 57 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1053,7 +1053,8 @@ void apply() {
10531053

10541054
private void drawImageInPixels(Image image, Point location) {
10551055
if (image.isDisposed()) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
1056-
drawImage(image, 0, 0, -1, -1, location.x, location.y, -1, -1, true, getZoom());
1056+
long handle = Image.win32_getHandle(image, getZoom());
1057+
drawImage(image, 0, 0, -1, -1, location.x, location.y, -1, -1, true, handle);
10571058
}
10581059
}
10591060

@@ -1091,7 +1092,10 @@ private void drawImageInPixels(Image image, Point location) {
10911092
*/
10921093
public void drawImage (Image image, int srcX, int srcY, int srcWidth, int srcHeight, int destX, int destY, int destWidth, int destHeight) {
10931094
checkNonDisposed();
1094-
if (srcWidth == 0 || srcHeight == 0 || destWidth == 0 || destHeight == 0) return;
1095+
if (image == null && (srcWidth == 0 || srcHeight == 0)) return;
1096+
// Skip drawing only if exactly one source dimension is zero
1097+
if ((srcWidth == 0) ^ (srcHeight == 0)) return;
1098+
if (destWidth == 0 || destHeight == 0) return;
10951099
if (srcX < 0 || srcY < 0 || srcWidth < 0 || srcHeight < 0 || destWidth < 0 || destHeight < 0) {
10961100
SWT.error (SWT.ERROR_INVALID_ARGUMENT);
10971101
}
@@ -1156,8 +1160,8 @@ private int calculateZoomForImage(int gcZoom, int srcWidth, int srcHeight, int d
11561160
private void drawImage(Image image, int srcX, int srcY, int srcWidth, int srcHeight, int destX, int destY,
11571161
int destWidth, int destHeight, int imageZoom, int scaledImageZoom) {
11581162
Rectangle src = Win32DPIUtils.pointToPixel(drawable, new Rectangle(srcX, srcY, srcWidth, srcHeight), scaledImageZoom);
1159-
Rectangle dest = Win32DPIUtils.pointToPixel(drawable, new Rectangle(destX, destY, destWidth, destHeight), imageZoom);
1160-
if (scaledImageZoom != 100) {
1163+
Rectangle destPixels = Win32DPIUtils.pointToPixel(drawable, new Rectangle(destX, destY, destWidth, destHeight), imageZoom);
1164+
if (scaledImageZoom % 100 != 0) {
11611165
/*
11621166
* This is a HACK! Due to rounding errors at fractional scale factors,
11631167
* the coordinates may be slightly off. The workaround is to restrict
@@ -1174,7 +1178,46 @@ private void drawImage(Image image, int srcX, int srcY, int srcWidth, int srcHei
11741178
}
11751179
}
11761180
}
1177-
drawImage(image, src.x, src.y, src.width, src.height, dest.x, dest.y, dest.width, dest.height, false, scaledImageZoom);
1181+
int targetWidth;
1182+
int targetHeight;
1183+
Rectangle scaledSrc;
1184+
if (srcWidth == 0 && srcHeight == 0) {
1185+
scaledSrc = new Rectangle(srcX, srcY, srcWidth, srcHeight);
1186+
targetWidth = destWidth;
1187+
targetHeight = destHeight;
1188+
} else {
1189+
float widthScalingFactor = (float) destWidth / srcWidth;
1190+
float heightScalingFactor = (float) destHeight / srcHeight;
1191+
Rectangle fullImageBounds = image.getBounds();
1192+
targetWidth = Math.round(fullImageBounds.width * widthScalingFactor);
1193+
targetHeight = Math.round(fullImageBounds.height * heightScalingFactor);
1194+
1195+
scaledSrc = new Rectangle(Math.round(src.x * widthScalingFactor),
1196+
Math.round(src.y * heightScalingFactor), Math.round(src.width * widthScalingFactor),
1197+
Math.round(src.height * heightScalingFactor));
1198+
}
1199+
Point targetSize = Win32DPIUtils.pointToPixel(drawable, new Point(targetWidth, targetHeight), scaledImageZoom);
1200+
image.executeOnImageHandleAtSizeOrZoom((tempHandle, handleSize) -> {
1201+
Rectangle srcRect = computeSourceRectangle(handleSize, targetSize, scaledSrc, src);
1202+
drawImage(image, srcRect.x, srcRect.y, srcRect.width, srcRect.height, destPixels.x, destPixels.y, destPixels.width,
1203+
destPixels.height, false, tempHandle);
1204+
}, targetSize.x, targetSize.y, scaledImageZoom);
1205+
}
1206+
1207+
/**
1208+
* Selects the source rectangle for drawing. If the requested width matches the image size,
1209+
* uses the scaled rectangle, otherwise the unscaled one.
1210+
*/
1211+
private Rectangle computeSourceRectangle(Point imageSize, Point requestedSize, Rectangle scaledSrc,
1212+
Rectangle unScaledSrc) {
1213+
1214+
Rectangle src = ((imageSize.x == requestedSize.x) && (imageSize.y == requestedSize.y)) ? scaledSrc : unScaledSrc;
1215+
// If both width and height are zero, use the full image
1216+
// (avoids forcing a getBounds() call inside drawImage)
1217+
if (src.width == 0 && src.height == 0) {
1218+
return new Rectangle(0, 0, imageSize.x, imageSize.y);
1219+
}
1220+
return src;
11781221
}
11791222

11801223
private class DrawImageToImageOperation extends ImageOperation {
@@ -1191,18 +1234,20 @@ private class DrawImageToImageOperation extends ImageOperation {
11911234

11921235
@Override
11931236
void apply() {
1194-
drawImage(getImage(), source.x, source.y, source.width, source.height, destination.x, destination.y, destination.width, destination.height, simple, getZoom());
1237+
long handle = Image.win32_getHandle(getImage(), getZoom());
1238+
drawImage(getImage(), source.x, source.y, source.width, source.height, destination.x, destination.y, destination.width, destination.height, simple, handle);
11951239
}
11961240
}
11971241

1198-
private void drawImage(Image srcImage, int srcX, int srcY, int srcWidth, int srcHeight, int destX, int destY, int destWidth, int destHeight, boolean simple, int imageZoom) {
1242+
private void drawImage(Image srcImage, int srcX, int srcY, int srcWidth, int srcHeight, int destX, int destY, int destWidth, int destHeight, boolean simple, long tempImageHandle) {
11991243
if (data.gdipGraphics != 0) {
12001244
//TODO - cache bitmap
1201-
long [] gdipImage = srcImage.createGdipImage(imageZoom);
1245+
long [] gdipImage = srcImage.createGdipImageFromHandle(tempImageHandle);
1246+
BITMAP bm = new BITMAP();
1247+
OS.GetObject(tempImageHandle, BITMAP.sizeof, bm);
12021248
long img = gdipImage[0];
12031249
int imgWidth = Gdip.Image_GetWidth(img);
12041250
int imgHeight = Gdip.Image_GetHeight(img);
1205-
12061251
if (simple) {
12071252
srcWidth = destWidth = imgWidth;
12081253
srcHeight = destHeight = imgHeight;
@@ -1253,13 +1298,13 @@ private void drawImage(Image srcImage, int srcX, int srcY, int srcWidth, int src
12531298
}
12541299
return;
12551300
}
1256-
long imageHandle = srcImage.getHandle(imageZoom, data.nativeZoom);
12571301
switch (srcImage.type) {
12581302
case SWT.BITMAP:
1259-
drawBitmap(srcImage, imageHandle, srcX, srcY, srcWidth, srcHeight, destX, destY, destWidth, destHeight, simple);
1303+
drawBitmap(srcImage, tempImageHandle, srcX, srcY, srcWidth, srcHeight, destX, destY, destWidth, destHeight,
1304+
simple);
12601305
break;
12611306
case SWT.ICON:
1262-
drawIcon(imageHandle, srcX, srcY, srcWidth, srcHeight, destX, destY, destWidth, destHeight, simple);
1307+
drawIcon(tempImageHandle, srcX, srcY, srcWidth, srcHeight, destX, destY, destWidth, destHeight, simple);
12631308
break;
12641309
}
12651310
}

0 commit comments

Comments
 (0)