Skip to content

Commit c5f8d31

Browse files
committed
Add support for SVG images
SWT currently loads icons exclusively as raster graphics (e.g., PNGs) without support for vector formats like SVG (except for Linux). A major drawback of raster graphics is their inability to scale without degrading image quality. Additionally, generating icons of different sizes requires manually rasterization of SVGs as a preparatory step, leading to unnecessary effort and many icon files. This change introduces support for vector graphics in images, enabling SVGs to be used for images. An SVG rasterizer can be provided via an SWT fragment. This change adds an according fragment based on the JSVG library. An according FileFormat implementation is added, which utilized a present SVG rasterization fragment to rasterize images for the desired zoom factor. Fixes #1438 Revert "Add support for SVG images" This reverts commit e03b214dc664848d0e1723a1ec80b4a419d2bd59. Introduce functionality for icon disablement with SVGs merge new API (flag parameter) into existing API ImageDataProvider functionality is ignored for now.
1 parent 22e8829 commit c5f8d31

File tree

10 files changed

+172
-24
lines changed

10 files changed

+172
-24
lines changed

bundles/org.eclipse.swt.svg/src/org/eclipse/swt/svg/JSVGRasterizer.java

+104-1
Original file line numberDiff line numberDiff line change
@@ -35,15 +35,30 @@
3535
import java.awt.RenderingHints.Key;
3636
import java.awt.image.BufferedImage;
3737
import java.awt.image.DataBufferInt;
38+
import java.io.ByteArrayInputStream;
39+
import java.io.ByteArrayOutputStream;
3840
import java.io.IOException;
3941
import java.io.InputStream;
42+
import java.io.OutputStream;
4043
import java.util.Map;
4144

4245
import org.eclipse.swt.SWT;
4346
import org.eclipse.swt.graphics.ImageData;
4447
import org.eclipse.swt.graphics.PaletteData;
4548
import org.eclipse.swt.internal.image.SVGRasterizer;
4649

50+
import javax.xml.parsers.DocumentBuilder;
51+
import javax.xml.parsers.DocumentBuilderFactory;
52+
import javax.xml.parsers.ParserConfigurationException;
53+
import javax.xml.transform.Transformer;
54+
import javax.xml.transform.TransformerException;
55+
import javax.xml.transform.TransformerFactory;
56+
import javax.xml.transform.dom.DOMSource;
57+
import javax.xml.transform.stream.StreamResult;
58+
import org.w3c.dom.Document;
59+
import org.w3c.dom.Element;
60+
import org.xml.sax.SAXException;
61+
4762
import com.github.weisj.jsvg.SVGDocument;
4863
import com.github.weisj.jsvg.geometry.size.FloatSize;
4964
import com.github.weisj.jsvg.parser.LoaderContext;
@@ -71,7 +86,19 @@ public class JSVGRasterizer implements SVGRasterizer {
7186
);
7287

7388
@Override
74-
public ImageData rasterizeSVG(InputStream inputStream, int zoom) throws IOException {
89+
public ImageData rasterizeSVG(InputStream inputStream, int zoom, int flag) throws IOException {
90+
switch(flag) {
91+
case SWT.IMAGE_DISABLE:
92+
inputStream = applyDisabledLook(inputStream);
93+
break;
94+
case SWT.IMAGE_GRAY:
95+
inputStream = applyGrayLook(inputStream);
96+
break;
97+
case SWT.IMAGE_COPY:
98+
break;
99+
default:
100+
SWT.error(SWT.ERROR_INVALID_IMAGE);
101+
}
75102
SVGDocument svgDocument = loadSVG(inputStream);
76103
if (svgDocument == null) {
77104
SWT.error(SWT.ERROR_INVALID_IMAGE);
@@ -133,4 +160,80 @@ private ImageData convertToSWTImageData(BufferedImage rasterizedImage) {
133160
}
134161
return imageData;
135162
}
163+
164+
private static InputStream applyDisabledLook(InputStream svgInputStream) throws IOException {
165+
Document svgDocument = parseSVG(svgInputStream);
166+
addDisabledFilter(svgDocument);
167+
try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
168+
writeSVG(svgDocument, outputStream);
169+
return new ByteArrayInputStream(outputStream.toByteArray());
170+
}
171+
}
172+
173+
private static InputStream applyGrayLook(InputStream svgInputStream) throws IOException {
174+
Document svgDocument = parseSVG(svgInputStream);
175+
addGrayFilter(svgDocument);
176+
try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
177+
writeSVG(svgDocument, outputStream);
178+
return new ByteArrayInputStream(outputStream.toByteArray());
179+
}
180+
}
181+
182+
private static Document parseSVG(InputStream inputStream) throws IOException {
183+
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
184+
DocumentBuilder builder;
185+
try {
186+
builder = factory.newDocumentBuilder();
187+
return builder.parse(inputStream);
188+
} catch (SAXException | IOException | ParserConfigurationException e) {
189+
throw new IOException(e.getMessage());
190+
}
191+
}
192+
193+
private static void addDisabledFilter(Document document) {
194+
addFilter(document, 0.64f, 0.4f);
195+
}
196+
197+
private static void addGrayFilter(Document document) {
198+
addFilter(document, 0.64f, 0.1f);
199+
}
200+
201+
private static void addFilter(Document document, float slope, float intercept) {
202+
Element defs = (Element) document.getElementsByTagName("defs").item(0);
203+
if (defs == null) {
204+
defs = document.createElement("defs");
205+
document.getDocumentElement().appendChild(defs);
206+
}
207+
208+
Element filter = document.createElement("filter");
209+
filter.setAttribute("id", "customizedLook");
210+
211+
Element colorMatrix = document.createElement("feColorMatrix");
212+
colorMatrix.setAttribute("type", "saturate");
213+
colorMatrix.setAttribute("values", "0");
214+
filter.appendChild(colorMatrix);
215+
216+
Element componentTransfer = document.createElement("feComponentTransfer");
217+
for (String channel : new String[] { "R", "G", "B" }) {
218+
Element func = document.createElement("feFunc" + channel);
219+
func.setAttribute("type", "linear");
220+
func.setAttribute("slope", Float.toString(slope));
221+
func.setAttribute("intercept", Float.toString(intercept));
222+
componentTransfer.appendChild(func);
223+
}
224+
filter.appendChild(componentTransfer);
225+
defs.appendChild(filter);
226+
document.getDocumentElement().setAttribute("filter", "url(#customizedLook)");
227+
}
228+
229+
private static void writeSVG(Document document, OutputStream outputStream) throws IOException {
230+
TransformerFactory transformerFactory = TransformerFactory.newInstance();
231+
Transformer transformer;
232+
try {
233+
transformer = transformerFactory.newTransformer();
234+
transformer.transform(new DOMSource(document), new StreamResult(outputStream));
235+
} catch (TransformerException e) {
236+
throw new IOException(e.getMessage());
237+
}
238+
}
136239
}

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

+4-4
Original file line numberDiff line numberDiff line change
@@ -37,14 +37,14 @@ public static ImageData load(String filename) {
3737
return data[0];
3838
}
3939

40-
public static ElementAtZoom<ImageData> load(InputStream stream, int fileZoom, int targetZoom) {
41-
List<ElementAtZoom<ImageData>> data = new ImageLoader().load(stream, fileZoom, targetZoom);
40+
public static ElementAtZoom<ImageData> load(InputStream stream, int fileZoom, int targetZoom, int flag) {
41+
List<ElementAtZoom<ImageData>> data = new ImageLoader().load(stream, fileZoom, targetZoom, flag);
4242
if (data.isEmpty()) SWT.error(SWT.ERROR_INVALID_IMAGE);
4343
return data.get(0);
4444
}
4545

46-
public static ElementAtZoom<ImageData> load(String filename, int fileZoom, int targetZoom) {
47-
List<ElementAtZoom<ImageData>> data = new ImageLoader().load(filename, fileZoom, targetZoom);
46+
public static ElementAtZoom<ImageData> load(String filename, int fileZoom, int targetZoom, int flag) {
47+
List<ElementAtZoom<ImageData>> data = new ImageLoader().load(filename, fileZoom, targetZoom, flag);
4848
if (data.isEmpty()) SWT.error(SWT.ERROR_INVALID_IMAGE);
4949
return data.get(0);
5050
}

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

+13
Original file line numberDiff line numberDiff line change
@@ -43,4 +43,17 @@ public interface ImageDataProvider {
4343
*/
4444
ImageData getImageData (int zoom);
4545

46+
// /**
47+
// * @since 4.0
48+
// */
49+
// default ImageData getCustomizedImageData(int zoom, int flag) {
50+
// throw new UnsupportedOperationException();
51+
// }
52+
//
53+
// /**
54+
// * @since 4.0
55+
// */
56+
// default boolean supportsRasterizationFlag(int flag) {
57+
// return false;
58+
// }
4659
}

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

+6-6
Original file line numberDiff line numberDiff line change
@@ -150,14 +150,14 @@ void reset() {
150150
* </ul>
151151
*/
152152
public ImageData[] load(InputStream stream) {
153-
load(stream, FileFormat.DEFAULT_ZOOM, FileFormat.DEFAULT_ZOOM);
153+
load(stream, FileFormat.DEFAULT_ZOOM, FileFormat.DEFAULT_ZOOM, SWT.IMAGE_COPY);
154154
return data;
155155
}
156156

157-
List<ElementAtZoom<ImageData>> load(InputStream stream, int fileZoom, int targetZoom) {
157+
List<ElementAtZoom<ImageData>> load(InputStream stream, int fileZoom, int targetZoom, int flag) {
158158
if (stream == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
159159
reset();
160-
List<ElementAtZoom<ImageData>> images = InternalImageLoader.load(stream, this, fileZoom, targetZoom);
160+
List<ElementAtZoom<ImageData>> images = InternalImageLoader.load(stream, this, fileZoom, targetZoom, flag);
161161
data = images.stream().map(ElementAtZoom::element).toArray(ImageData[]::new);
162162
return images;
163163
}
@@ -181,14 +181,14 @@ List<ElementAtZoom<ImageData>> load(InputStream stream, int fileZoom, int target
181181
* </ul>
182182
*/
183183
public ImageData[] load(String filename) {
184-
load(filename, FileFormat.DEFAULT_ZOOM, FileFormat.DEFAULT_ZOOM);
184+
load(filename, FileFormat.DEFAULT_ZOOM, FileFormat.DEFAULT_ZOOM, SWT.IMAGE_COPY);
185185
return data;
186186
}
187187

188-
List<ElementAtZoom<ImageData>> load(String filename, int fileZoom, int targetZoom) {
188+
List<ElementAtZoom<ImageData>> load(String filename, int fileZoom, int targetZoom, int flag) {
189189
if (filename == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
190190
try (InputStream stream = new FileInputStream(filename)) {
191-
return load(stream, fileZoom, targetZoom);
191+
return load(stream, fileZoom, targetZoom, flag);
192192
} catch (IOException e) {
193193
SWT.error(SWT.ERROR_IO, e);
194194
}

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

+6-6
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ static abstract class StaticImageFileFormat extends FileFormat {
8282
abstract ImageData[] loadFromByteStream();
8383

8484
@Override
85-
List<ElementAtZoom<ImageData>> loadFromByteStream(int fileZoom, int targetZoom) {
85+
List<ElementAtZoom<ImageData>> loadFromByteStream(int fileZoom, int targetZoom, int flag) {
8686
return Arrays.stream(loadFromByteStream()).map(d -> new ElementAtZoom<>(d, fileZoom)).toList();
8787
}
8888
}
@@ -102,16 +102,16 @@ List<ElementAtZoom<ImageData>> loadFromByteStream(int fileZoom, int targetZoom)
102102
* Format that do not implement {@link StaticImageFileFormat} MUST return
103103
* {@link ImageData} with the specified {@code targetZoom}.
104104
*/
105-
abstract List<ElementAtZoom<ImageData>> loadFromByteStream(int fileZoom, int targetZoom);
105+
abstract List<ElementAtZoom<ImageData>> loadFromByteStream(int fileZoom, int targetZoom, int flag);
106106

107107
/**
108108
* Read the specified input stream, and return the
109109
* device independent image array represented by the stream.
110110
*/
111-
public List<ElementAtZoom<ImageData>> loadFromStream(LEDataInputStream stream, int fileZoom, int targetZoom) {
111+
public List<ElementAtZoom<ImageData>> loadFromStream(LEDataInputStream stream, int fileZoom, int targetZoom, int flag) {
112112
try {
113113
inputStream = stream;
114-
return loadFromByteStream(fileZoom, targetZoom);
114+
return loadFromByteStream(fileZoom, targetZoom, flag);
115115
} catch (Exception e) {
116116
if (e instanceof IOException) {
117117
SWT.error(SWT.ERROR_IO, e);
@@ -126,14 +126,14 @@ public List<ElementAtZoom<ImageData>> loadFromStream(LEDataInputStream stream, i
126126
* Read the specified input stream using the specified loader, and
127127
* return the device independent image array represented by the stream.
128128
*/
129-
public static List<ElementAtZoom<ImageData>> load(InputStream is, ImageLoader loader, int fileZoom, int targetZoom) {
129+
public static List<ElementAtZoom<ImageData>> load(InputStream is, ImageLoader loader, int fileZoom, int targetZoom, int flag) {
130130
LEDataInputStream stream = new LEDataInputStream(is);
131131
FileFormat fileFormat = determineFileFormat(stream).orElseGet(() -> {
132132
SWT.error(SWT.ERROR_UNSUPPORTED_FORMAT);
133133
return null;
134134
});
135135
fileFormat.loader = loader;
136-
return fileFormat.loadFromStream(stream, fileZoom, targetZoom);
136+
return fileFormat.loadFromStream(stream, fileZoom, targetZoom, flag);
137137
}
138138

139139
/**

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -43,15 +43,15 @@ boolean isFileFormat(LEDataInputStream stream) throws IOException {
4343
}
4444

4545
@Override
46-
List<ElementAtZoom<ImageData>> loadFromByteStream(int fileZoom, int targetZoom) {
46+
List<ElementAtZoom<ImageData>> loadFromByteStream(int fileZoom, int targetZoom, int flag) {
4747
if (RASTERIZER == null) {
4848
SWT.error(SWT.ERROR_UNSUPPORTED_FORMAT, null, " [No SVG rasterizer found]");
4949
}
5050
if (targetZoom <= 0) {
5151
SWT.error(SWT.ERROR_INVALID_ARGUMENT, null, " [Cannot rasterize SVG for zoom <= 0]");
5252
}
5353
try {
54-
ImageData rasterizedImageData = RASTERIZER.rasterizeSVG(inputStream, 100 * targetZoom / fileZoom);
54+
ImageData rasterizedImageData = RASTERIZER.rasterizeSVG(inputStream, 100 * targetZoom / fileZoom, flag);
5555
return List.of(new ElementAtZoom<>(rasterizedImageData, targetZoom));
5656
} catch (IOException e) {
5757
SWT.error(SWT.ERROR_INVALID_IMAGE, e);

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -31,5 +31,5 @@ public interface SVGRasterizer {
3131
* @return the {@link ImageData} for the rasterized image, or {@code null} if
3232
* the input is not a valid SVG file or cannot be processed.
3333
*/
34-
public ImageData rasterizeSVG(InputStream stream, int zoom) throws IOException;
34+
public ImageData rasterizeSVG(InputStream stream, int zoom, int flag) throws IOException;
3535
}

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ ImageData loadIcon(int[] iconHeader) {
133133
StaticImageFileFormat png = new PNGFileFormat();
134134
if (png.isFileFormat(inputStream)) {
135135
png.loader = this.loader;
136-
return png.loadFromStream(inputStream, DEFAULT_ZOOM, DEFAULT_ZOOM).get(0).element();
136+
return png.loadFromStream(inputStream, DEFAULT_ZOOM, DEFAULT_ZOOM, SWT.IMAGE_COPY).get(0).element();
137137
}
138138
} catch (Exception e) {
139139
}

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

+33-1
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,9 @@ public Image(Device device, Image srcImage, int flag) {
247247
long srcImageHandle = win32_getHandle(srcImage, getZoom());
248248
switch (flag) {
249249
case SWT.IMAGE_COPY: {
250+
if(createWithSVG(device, flag)) {
251+
break;
252+
}
250253
switch (type) {
251254
case SWT.BITMAP:
252255
/* Get the HDC for the device */
@@ -282,12 +285,18 @@ public Image(Device device, Image srcImage, int flag) {
282285
break;
283286
}
284287
case SWT.IMAGE_DISABLE: {
288+
if(createWithSVG(device, flag)) {
289+
break;
290+
}
285291
ImageData data = srcImage.getImageData(srcImage.getZoom());
286292
ImageData newData = applyDisableImageData(data, rect.height, rect.width);
287293
init (newData, getZoom());
288294
break;
289295
}
290296
case SWT.IMAGE_GRAY: {
297+
if(createWithSVG(device, flag)) {
298+
break;
299+
}
291300
ImageData data = srcImage.getImageData(srcImage.getZoom());
292301
ImageData newData = applyGrayImageData(data, rect.height, rect.width);
293302
init (newData, getZoom());
@@ -300,6 +309,29 @@ public Image(Device device, Image srcImage, int flag) {
300309
this.device.registerResourceWithZoomSupport(this);
301310
}
302311

312+
private boolean createWithSVG(Device device, int flag) {
313+
if (imageProvider instanceof DynamicImageProviderWrapper dynamicImageProvider) {
314+
if (dynamicImageProvider.getProvider() instanceof ImageFileNameProvider imageFileNameProvider) {
315+
ElementAtZoom<String> fileName = DPIUtil.validateAndGetImagePathAtZoom(imageFileNameProvider, getZoom());
316+
// if (fileName.element().endsWith(".svg")) {
317+
ElementAtZoom<ImageData> imageData = ImageDataLoader.load(fileName.element(), fileName.zoom(), getZoom(),
318+
flag);
319+
init(imageData.element(), getZoom());
320+
return true;
321+
// }
322+
}
323+
// else if (imageProvider.getProvider() instanceof ImageDataProvider imageDataProvider) {
324+
// if (imageDataProvider.supportsRasterizationFlag(flag)) {
325+
// ImageData data = imageDataProvider.getCustomizedImageData(getZoom(), flag);
326+
// ElementAtZoom<ImageData> imageData = new ElementAtZoom<>(data, getZoom());
327+
// init(imageData.element(), getZoom());
328+
// return true;
329+
// }
330+
// }
331+
}
332+
return false;
333+
}
334+
303335
/**
304336
* Constructs an empty instance of this class with the
305337
* width and height of the specified rectangle. The result
@@ -2221,7 +2253,7 @@ ImageData getImageData(int zoom) {
22212253

22222254
ElementAtZoom<ImageData> imageDataAtZoom;
22232255
if (nativeInitializedImage == null) {
2224-
imageDataAtZoom = ImageDataLoader.load(fileForZoom.element(), fileForZoom.zoom(), zoom);
2256+
imageDataAtZoom = ImageDataLoader.load(fileForZoom.element(), fileForZoom.zoom(), zoom, SWT.IMAGE_COPY);
22252257
} else {
22262258
imageDataAtZoom = new ElementAtZoom<>(nativeInitializedImage.getImageData(), fileForZoom.zoom());
22272259
destroyHandleForZoom(zoom);

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@
2121

2222
class InternalImageLoader {
2323

24-
static List<ElementAtZoom<ImageData>> load(InputStream stream, ImageLoader imageLoader, int fileZoom, int targetZoom) {
25-
return FileFormat.load(stream, imageLoader, fileZoom, targetZoom);
24+
static List<ElementAtZoom<ImageData>> load(InputStream stream, ImageLoader imageLoader, int fileZoom, int targetZoom, int flag) {
25+
return FileFormat.load(stream, imageLoader, fileZoom, targetZoom, flag);
2626
}
2727

2828
static void save(OutputStream stream, int format, ImageLoader imageLoader) {

0 commit comments

Comments
 (0)