Skip to content

Allow GC to draw images in arbitrary sizes #2

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -72,34 +72,44 @@ public class JSVGRasterizer implements SVGRasterizer {

@Override
public ImageData rasterizeSVG(InputStream inputStream, int zoom) throws IOException {
SVGDocument svgDocument = loadSVG(inputStream);
if (svgDocument == null) {
SWT.error(SWT.ERROR_INVALID_IMAGE);
}
SVGDocument svgDocument = loadAndValidateSVG(inputStream);
BufferedImage rasterizedImage = renderSVG(svgDocument, zoom);
return convertToSWTImageData(rasterizedImage);
}

private SVGDocument loadSVG(InputStream inputStream) {
return SVG_LOADER.load(inputStream, null, LoaderContext.createDefault());

@Override
public ImageData rasterizeSVG(InputStream inputStream, int width, int height) throws IOException {
SVGDocument svgDocument = loadAndValidateSVG(inputStream);
BufferedImage rasterizedImage = renderSVG(svgDocument, width, height);
return convertToSWTImageData(rasterizedImage);
}

private SVGDocument loadAndValidateSVG(InputStream inputStream) throws IOException {
SVGDocument svgDocument = SVG_LOADER.load(inputStream, null, LoaderContext.createDefault());
if (svgDocument == null) {
SWT.error(SWT.ERROR_INVALID_IMAGE);
}
return svgDocument;
}

private BufferedImage renderSVG(SVGDocument svgDocument, int zoom) {
FloatSize sourceImageSize = svgDocument.size();
float scalingFactor = zoom / 100.0f;
BufferedImage image = createImageBase(svgDocument, scalingFactor);
Graphics2D g = configureRenderingOptions(scalingFactor, image);
int targetImageWidth = calculateTargetWidth(scalingFactor, sourceImageSize);
int targetImageHeight = calculateTargetHeight(scalingFactor, sourceImageSize);
return renderSVG(svgDocument, targetImageWidth, targetImageHeight);
}

private BufferedImage renderSVG(SVGDocument svgDocument, int width, int height) {
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
float widthScalingFactor = width / svgDocument.size().width;
float heightScalingFactor = height / svgDocument.size().height;
Graphics2D g = configureRenderingOptions(widthScalingFactor, heightScalingFactor, image);
svgDocument.render(null, g);
g.dispose();
return image;
}

private BufferedImage createImageBase(SVGDocument svgDocument, float scalingFactor) {
FloatSize sourceImageSize = svgDocument.size();
int targetImageWidth = calculateTargetWidth(scalingFactor, sourceImageSize);
int targetImageHeight = calculateTargetHeight(scalingFactor, sourceImageSize);
return new BufferedImage(targetImageWidth, targetImageHeight, BufferedImage.TYPE_INT_ARGB);
}

private int calculateTargetWidth(float scalingFactor, FloatSize sourceImageSize) {
double sourceImageWidth = sourceImageSize.getWidth();
return (int) Math.round(sourceImageWidth * scalingFactor);
Expand All @@ -110,10 +120,10 @@ private int calculateTargetHeight(float scalingFactor, FloatSize sourceImageSize
return (int) Math.round(sourceImageHeight * scalingFactor);
}

private Graphics2D configureRenderingOptions(float scalingFactor, BufferedImage image) {
private Graphics2D configureRenderingOptions(float widthScalingFactor, float heightScalingFactor, BufferedImage image) {
Graphics2D g = image.createGraphics();
g.setRenderingHints(RENDERING_HINTS);
g.scale(scalingFactor, scalingFactor);
g.scale(widthScalingFactor, heightScalingFactor);
return g;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*******************************************************************************
* Copyright (c) 2025 Vector Informatik GmbH and others.
*
* This program and the accompanying materials are made available under the terms of the Eclipse
* Public License 2.0 which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Michael Bangas (Vector Informatik GmbH) - initial API and implementation
*******************************************************************************/
package org.eclipse.swt.graphics;

import java.util.*;

/**
* @since 3.131
*/
public interface ImageDataAtSizeProvider extends ImageDataProvider {

Optional<ImageData> getImageData(int targetWidth, int targetHeight);

}
Original file line number Diff line number Diff line change
Expand Up @@ -41,20 +41,32 @@ public static boolean canLoadAtZoom(InputStream stream, int fileZoom, int target
return ImageLoader.canLoadAtZoom(stream, fileZoom, targetZoom);
}

public static ElementAtZoom<ImageData> load(InputStream stream, int fileZoom, int targetZoom) {
List<ElementAtZoom<ImageData>> data = new ImageLoader().load(stream, fileZoom, targetZoom);
if (data.isEmpty()) SWT.error(SWT.ERROR_INVALID_IMAGE);
return data.get(0);
}

public static boolean canLoadAtZoom(String filename, int fileZoom, int targetZoom) {
return ImageLoader.canLoadAtZoom(filename, fileZoom, targetZoom);
}

public static ElementAtZoom<ImageData> load(String filename, int fileZoom, int targetZoom) {
List<ElementAtZoom<ImageData>> data = new ImageLoader().load(filename, fileZoom, targetZoom);
public static ElementAtZoom<ImageData> loadByZoom(InputStream stream, int fileZoom, int targetZoom) {
List<ElementAtZoom<ImageData>> data = new ImageLoader().loadByZoom(stream, fileZoom, targetZoom);
if (data.isEmpty()) SWT.error(SWT.ERROR_INVALID_IMAGE);
return data.get(0);
}

public static ElementAtZoom<ImageData> loadByZoom(String filename, int fileZoom, int targetZoom) {
List<ElementAtZoom<ImageData>> data = new ImageLoader().loadByZoom(filename, fileZoom, targetZoom);
if (data.isEmpty()) SWT.error(SWT.ERROR_INVALID_IMAGE);
return data.get(0);
}

public static ImageData loadByTargetSize(InputStream stream, int targetWidth, int targetHeight) {
ImageData data = new ImageLoader().loadByTargetSize(stream, targetWidth, targetHeight);
if (data == null) SWT.error(SWT.ERROR_INVALID_IMAGE);
return data;
}

public static ImageData loadByTargetSize(String filename, int targetWidth, int targetHeight) {
ImageData data = new ImageLoader().loadByTargetSize(filename, targetWidth, targetHeight);
if (data == null) SWT.error(SWT.ERROR_INVALID_IMAGE);
return data;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*******************************************************************************
* Copyright (c) 2025 Vector Informatik GmbH and others.
*
* This program and the accompanying materials are made available under the terms of the Eclipse
* Public License 2.0 which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Michael Bangas (Vector Informatik GmbH) - initial API and implementation
*******************************************************************************/
package org.eclipse.swt.graphics;

import java.util.*;

/**
* @since 3.131
*/
public interface ImageFileNameAtSizeProvider extends ImageFileNameProvider {

/**
* Returns the image file path most suitable for rendering at the given target size.
*
*/
Optional<String> getImagePath(int targetWidth, int targetHeight);

}
Original file line number Diff line number Diff line change
Expand Up @@ -151,18 +151,26 @@ void reset() {
* </ul>
*/
public ImageData[] load(InputStream stream) {
load(stream, FileFormat.DEFAULT_ZOOM, FileFormat.DEFAULT_ZOOM);
loadByZoom(stream, FileFormat.DEFAULT_ZOOM, FileFormat.DEFAULT_ZOOM);
return data;
}

List<ElementAtZoom<ImageData>> load(InputStream stream, int fileZoom, int targetZoom) {
List<ElementAtZoom<ImageData>> loadByZoom(InputStream stream, int fileZoom, int targetZoom) {
if (stream == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
reset();
List<ElementAtZoom<ImageData>> images = NativeImageLoader.load(new ElementAtZoom<>(stream, fileZoom), this, targetZoom);
data = images.stream().map(ElementAtZoom::element).toArray(ImageData[]::new);
return images;
}

ImageData loadByTargetSize(InputStream stream, int targetWidth, int targetHeight) {
if (stream == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
reset();
ImageData image = NativeImageLoader.load(stream, this, targetWidth, targetHeight);
data = new ImageData[] {image};
return image;
}

static boolean canLoadAtZoom(InputStream stream, int fileZoom, int targetZoom) {
if (stream == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
return FileFormat.canLoadAtZoom(new ElementAtZoom<>(stream, fileZoom), targetZoom);
Expand All @@ -187,14 +195,24 @@ static boolean canLoadAtZoom(InputStream stream, int fileZoom, int targetZoom) {
* </ul>
*/
public ImageData[] load(String filename) {
load(filename, FileFormat.DEFAULT_ZOOM, FileFormat.DEFAULT_ZOOM);
loadByZoom(filename, FileFormat.DEFAULT_ZOOM, FileFormat.DEFAULT_ZOOM);
return data;
}

List<ElementAtZoom<ImageData>> load(String filename, int fileZoom, int targetZoom) {
List<ElementAtZoom<ImageData>> loadByZoom(String filename, int fileZoom, int targetZoom) {
if (filename == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
try (InputStream stream = new FileInputStream(filename)) {
return loadByZoom(stream, fileZoom, targetZoom);
} catch (IOException e) {
SWT.error(SWT.ERROR_IO, e);
}
return null;
}

ImageData loadByTargetSize(String filename, int targetWidth, int targetHeight) {
if (filename == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
try (InputStream stream = new FileInputStream(filename)) {
return load(stream, fileZoom, targetZoom);
return loadByTargetSize(stream, targetWidth, targetHeight);
} catch (IOException e) {
SWT.error(SWT.ERROR_IO, e);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,34 +144,49 @@ public static ImageData scaleImageData (Device device, final ElementAtZoom<Image
return scaleImageData(device, elementAtZoom.element(), targetZoom, elementAtZoom.zoom());
}

public static ImageData autoScaleImageData (Device device, final ImageData imageData, float scaleFactor) {
// Guards are already implemented in callers: if (deviceZoom == 100 || imageData == null || scaleFactor == 1.0f) return imageData;
int width = imageData.width;
int height = imageData.height;
int scaledWidth = Math.round (width * scaleFactor);
int scaledHeight = Math.round (height * scaleFactor);
public static ImageData autoScaleImageData(Device device, final ImageData imageData, float scaleFactor) {
int targetWidth = Math.round(imageData.width * scaleFactor);
int targetHeight = Math.round(imageData.height * scaleFactor);
return scaleImage(device, imageData, imageData.width, imageData.height, targetWidth, targetHeight, Image::drawAtTargetSize);
}

public static ImageData autoScaleImageData(Device device, final ImageData imageData, int targetWidth, int targetHeight) {
return scaleImage(device, imageData, imageData.width, imageData.height, targetWidth, targetHeight, Image::drawAtTargetSize);
}

@FunctionalInterface
private interface ImageDrawFunction {
void draw(GC gc, Image original, int originalWidth, int originalHeight, int targetWidth, int targetHeight);
}

private static ImageData scaleImage(Device device, ImageData imageData, int originalWidth, int originalHeight,
int targetWidth, int targetHeight, ImageDrawFunction drawFunction) {

boolean useSmoothScaling = isSmoothScalingEnabled() && imageData.getTransparencyType() != SWT.TRANSPARENCY_MASK;

if (useSmoothScaling) {
Image original = new Image (device, (ImageDataProvider) zoom -> imageData);
ImageGcDrawer drawer = new ImageGcDrawer() {
Image original = new Image(device, (ImageDataProvider) zoom -> imageData);

ImageGcDrawer drawer = new ImageGcDrawer() {
@Override
public void drawOn(GC gc, int imageWidth, int imageHeight) {
gc.setAntialias (SWT.ON);
Image.drawScaled(gc, original, width, height, scaleFactor);
};
gc.setAntialias(SWT.ON);
drawFunction.draw(gc, original, originalWidth, originalHeight, targetWidth, targetHeight);
}

@Override
public int getGcStyle() {
return SWT.TRANSPARENT;
}
};
Image resultImage = new Image (device, drawer, scaledWidth, scaledHeight);
ImageData result = resultImage.getImageData (100);
original.dispose ();
resultImage.dispose ();

Image resultImage = new Image(device, drawer, targetWidth, targetHeight);
ImageData result = resultImage.getImageData(100);
original.dispose();
resultImage.dispose();
return result;
} else {
return imageData.scaledTo (scaledWidth, scaledHeight);
return imageData.scaledTo(targetWidth, targetHeight);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,11 @@ static abstract class StaticImageFileFormat extends FileFormat {
List<ElementAtZoom<ImageData>> loadFromByteStream(int fileZoom, int targetZoom) {
return Arrays.stream(loadFromByteStream()).map(d -> new ElementAtZoom<>(d, fileZoom)).toList();
}

@Override
ImageData loadFromByteStreamByTargetSize(int targetWidth, int targetHeight) {
return loadFromByteStream()[0];
}
}

LEDataInputStream inputStream;
Expand All @@ -104,6 +109,8 @@ List<ElementAtZoom<ImageData>> loadFromByteStream(int fileZoom, int targetZoom)
*/
abstract List<ElementAtZoom<ImageData>> loadFromByteStream(int fileZoom, int targetZoom);

abstract ImageData loadFromByteStreamByTargetSize(int targetWidth, int targetHeight);

/**
* Read the specified input stream, and return the
* device independent image array represented by the stream.
Expand All @@ -122,6 +129,20 @@ public List<ElementAtZoom<ImageData>> loadFromStream(LEDataInputStream stream, i
}
}

public ImageData loadFromStreamByTargetSize(LEDataInputStream stream, int targetWidth, int targetHeight) {
try {
inputStream = stream;
return loadFromByteStreamByTargetSize(targetWidth, targetHeight);
} catch (Exception e) {
if (e instanceof IOException) {
SWT.error(SWT.ERROR_IO, e);
} else {
SWT.error(SWT.ERROR_INVALID_IMAGE, e);
}
return null;
}
}

/**
* Read the specified input stream using the specified loader, and
* return the device independent image array represented by the stream.
Expand All @@ -136,6 +157,16 @@ public static List<ElementAtZoom<ImageData>> load(ElementAtZoom<InputStream> is,
return fileFormat.loadFromStream(stream, is.zoom(), targetZoom);
}

public static ImageData load(InputStream is, ImageLoader loader, int targetWidth, int targetHeight) {
LEDataInputStream stream = new LEDataInputStream(is);
FileFormat fileFormat = determineFileFormat(stream).orElseGet(() -> {
SWT.error(SWT.ERROR_UNSUPPORTED_FORMAT);
return null;
});
fileFormat.loader = loader;
return fileFormat.loadFromStreamByTargetSize(stream, targetWidth, targetHeight);
}

public static boolean canLoadAtZoom(ElementAtZoom<InputStream> is, int targetZoom) {
return is.zoom() == targetZoom || isDynamicallySizableFormat(is.element());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,26 @@ List<ElementAtZoom<ImageData>> loadFromByteStream(int fileZoom, int targetZoom)
}
}

@Override
ImageData loadFromByteStreamByTargetSize(int targetWidth, int targetHeight) {
if (RASTERIZER == null) {
SWT.error(SWT.ERROR_UNSUPPORTED_FORMAT, null, " [No SVG rasterizer found]");
}
if (targetWidth <= 0 || targetHeight <= 0) {
SWT.error(SWT.ERROR_INVALID_ARGUMENT, null, " [Cannot rasterize SVG for targetWidth or targetHeight <= 0]");
}
if (targetHeight <= 0 || targetHeight <= 0) {
SWT.error(SWT.ERROR_INVALID_ARGUMENT, null, " [Cannot rasterize SVG for targetHeight or targetHeight <= 0]");
}
try {
ImageData rasterizedImageData = RASTERIZER.rasterizeSVG(inputStream, targetWidth, targetHeight);
return rasterizedImageData;
} catch (IOException e) {
SWT.error(SWT.ERROR_INVALID_IMAGE, e);
return null;
}
}

@Override
void unloadIntoByteStream(ImageLoader loader) {
throw new UnsupportedOperationException();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,6 @@ public interface SVGRasterizer {
* the input is not a valid SVG file or cannot be processed.
*/
public ImageData rasterizeSVG(InputStream stream, int zoom) throws IOException;

public ImageData rasterizeSVG(InputStream stream, int width, int height) throws IOException;
}
Loading
Loading