From 1850455da35a1fbdca8581d54f44d52bd1666798 Mon Sep 17 00:00:00 2001 From: Mathieu Baudier Date: Sat, 7 May 2022 11:52:29 +0200 Subject: [PATCH] Introduce dynamic icon generation from SVG --- .../argeo/cms/swt/osgi/BundleSvgTheme.java | 105 ++++++++++++++++++ .../src/org/argeo/cms/media/SvgToPng.java | 64 +++++++++++ sdk/argeo-build | 2 +- 3 files changed, 170 insertions(+), 1 deletion(-) create mode 100644 eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/osgi/BundleSvgTheme.java create mode 100644 org.argeo.cms/src/org/argeo/cms/media/SvgToPng.java diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/osgi/BundleSvgTheme.java b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/osgi/BundleSvgTheme.java new file mode 100644 index 000000000..e65f226e2 --- /dev/null +++ b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/osgi/BundleSvgTheme.java @@ -0,0 +1,105 @@ +package org.argeo.cms.swt.osgi; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.lang.System.Logger; +import java.lang.System.Logger.Level; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import org.apache.batik.transcoder.TranscoderException; +import org.apache.batik.transcoder.TranscoderInput; +import org.apache.batik.transcoder.TranscoderOutput; +import org.apache.batik.transcoder.image.ImageTranscoder; +import org.apache.batik.transcoder.image.PNGTranscoder; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.graphics.ImageData; +import org.eclipse.swt.widgets.Display; +import org.osgi.framework.BundleContext; + +/** Theme which can dynamically create icons from SVG data. */ +public class BundleSvgTheme extends BundleCmsSwtTheme { + private final static Logger logger = System.getLogger(BundleSvgTheme.class.getName()); + + private Map> imageCache = Collections.synchronizedMap(new HashMap<>()); + + private Map transcoders = Collections.synchronizedMap(new HashMap<>()); + + @Override + public Image getIcon(String name, Integer preferredSize) { + String path = "icons/types/svg/" + name + ".svg"; + return createImageFromSvg(path, preferredSize); + } + + protected Image createImageFromSvg(String path, Integer preferredSize) { + Image image = null; + if (imageCache.containsKey(path)) { + image = imageCache.get(path).get(preferredSize); + } + if (image != null) + return image; + ImageData imageData = loadFromSvg(path, preferredSize); + image = new Image(Display.getDefault(), imageData); + if (!imageCache.containsKey(path)) + imageCache.put(path, Collections.synchronizedMap(new HashMap<>())); + imageCache.get(path).put(preferredSize, image); + return image; + } + + protected ImageData loadFromSvg(String path, int size) { + ImageTranscoder transcoder = null; + synchronized (this) { + transcoder = transcoders.get(size); + if (transcoder == null) { + transcoder = new PNGTranscoder(); + transcoder.addTranscodingHint(PNGTranscoder.KEY_WIDTH, (float) size); + transcoder.addTranscodingHint(PNGTranscoder.KEY_HEIGHT, (float) size); + transcoders.put(size, transcoder); + } + } + ImageData imageData; + try (InputStream in = getResourceAsStream(path); ByteArrayOutputStream out = new ByteArrayOutputStream();) { + if (in == null) + throw new IllegalArgumentException(path + " not found"); + TranscoderInput input = new TranscoderInput(in); + TranscoderOutput output = new TranscoderOutput(out); + transcoder.transcode(input, output); + try (InputStream imageIn = new ByteArrayInputStream(out.toByteArray())) { + imageData = new ImageData(imageIn); + } + logger.log(Level.DEBUG, () -> "Generated " + size + "x" + size + " PNG icon from " + path); + } catch (IOException | TranscoderException e) { + throw new RuntimeException("Cannot transcode SVG " + path, e); + } + + return imageData; + } + + @Override + public void init(BundleContext bundleContext, Map properties) { + super.init(bundleContext, properties); + + // preload all icons +// paths: for (String p : getImagesPaths()) { +// if (!p.endsWith(".svg")) +// continue paths; +// createImageFromSvg(p, getDefaultIconSize()); +// } + } + + @Override + public void destroy(BundleContext bundleContext, Map properties) { + Display display = Display.getDefault(); + if (display != null) + for (String path : imageCache.keySet()) { + for (Image image : imageCache.get(path).values()) { + display.syncExec(() -> image.dispose()); + } + } + super.destroy(bundleContext, properties); + } + +} diff --git a/org.argeo.cms/src/org/argeo/cms/media/SvgToPng.java b/org.argeo.cms/src/org/argeo/cms/media/SvgToPng.java new file mode 100644 index 000000000..eadefa593 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/media/SvgToPng.java @@ -0,0 +1,64 @@ +package org.argeo.cms.media; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.Reader; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +import org.apache.batik.transcoder.TranscoderException; +import org.apache.batik.transcoder.TranscoderInput; +import org.apache.batik.transcoder.TranscoderOutput; +import org.apache.batik.transcoder.image.ImageTranscoder; +import org.apache.batik.transcoder.image.PNGTranscoder; +import org.apache.commons.io.FilenameUtils; + +public class SvgToPng { + + public void convertSvgDir(Path sourceDir, Path targetDir, int width) { + System.out.println("##\n## " + width + "px - " + sourceDir+"\n##"); + try { + if (targetDir == null) + targetDir = sourceDir.getParent().resolve(Integer.toString(width)); + Files.createDirectories(targetDir); + + PNGTranscoder transcoder = new PNGTranscoder(); + // transcoder.addTranscodingHint(ImageTranscoder.KEY_BACKGROUND_COLOR, + // Color.WHITE); + transcoder.addTranscodingHint(PNGTranscoder.KEY_WIDTH, (float) width); + transcoder.addTranscodingHint(PNGTranscoder.KEY_HEIGHT, (float) width); + + for (Path source : Files.newDirectoryStream(sourceDir, "*.svg")) { + String baseName = FilenameUtils.getBaseName(source.toString()); + Path target = targetDir.resolve(baseName + ".png"); + convertSvgFile(transcoder, source, target); + } + } catch (IOException | TranscoderException e) { + throw new IllegalStateException("Cannot convert from " + sourceDir + " to " + targetDir, e); + } + + } + + protected void convertSvgFile(ImageTranscoder transcoder, Path source, Path target) + throws IOException, TranscoderException { + try (Reader reader = Files.newBufferedReader(source); OutputStream out = Files.newOutputStream(target);) { + TranscoderInput input = new TranscoderInput(reader); +// BufferedImage image = transcoder.createImage(32, 32); + TranscoderOutput output = new TranscoderOutput(out); + transcoder.transcode(input, output); + System.out.println(source.getFileName() + " -> " + target); + } + } + + public static void main(String[] args) throws Exception { + + Path path = Paths.get(args[0]); + + SvgToPng svgToPng = new SvgToPng(); + svgToPng.convertSvgDir(path, null, 16); + svgToPng.convertSvgDir(path, null, 32); + svgToPng.convertSvgDir(path, null, 64); + svgToPng.convertSvgDir(path, null, 96); + } +} diff --git a/sdk/argeo-build b/sdk/argeo-build index 2cb8a40fe..0d5780a13 160000 --- a/sdk/argeo-build +++ b/sdk/argeo-build @@ -1 +1 @@ -Subproject commit 2cb8a40febfe1b42ab2aebecbf6e0ec06b2c5e4d +Subproject commit 0d5780a1346ab30294998a894fe8b63d1de9f5d6 -- 2.30.2