commit 6b04f8fb548874b9139abc4bb08ac675b6a66829
Author: Simon Legner <Simon.Legner@gmail.com>
Date:   2020-03-22 14:35:14 +0100

    see #16733 - Use radiance-photon for transcoding SVG icons to Java classes

diff --git a/build.xml b/build.xml
index 465f7df38..e75e4b6b8 100644
--- a/build.xml
+++ b/build.xml
@@ -1187,6 +1187,21 @@ Build-Date: ${build.tstamp}
         <ivy:retrieve pattern="${lib.dir}/compile/[artifact]-[type].[ext]" conf="compile"/>
         <ivy:retrieve pattern="${lib.dir}/runtime/[artifact]-[type].[ext]" conf="runtime"/>
         <ivy:retrieve pattern="${lib.dir}/sources/[artifact]-[type].[ext]" conf="sources"/>
+        <ivy:retrieve file="${tools.ivy}" pattern="${lib.dir}/radiance-photon/[artifact]-[type].[ext]" conf="radiance-photon"/>
         <ivy:retrieve pattern="${lib.dir}/tools/[artifact]-[type].[ext]" conf="javacc,checkstyle" file="${tools.ivy}"/>
     </target>
+    <target name="transform-svg" depends="resolve">
+        <ivy:cachepath file="${tools.ivy}" pathid="radiance-photon.classpath" conf="radiance-photon"/>
+        <mkdir dir="src/org/openstreetmap/josm/images"/>
+        <java classname="org.pushingpixels.photon.api.transcoder.SvgBatchConverter" failonerror="true">
+            <classpath refid="radiance-photon.classpath"/>
+            <arg value="sourceFolder=resources/images/"/>
+            <arg value="maxDepth=10"/>
+            <arg value="outputFolder=src/org/openstreetmap/josm/images"/>
+            <arg value="outputPackageName=org.openstreetmap.josm.images"/>
+            <arg value="outputClassNamePrefix=img_"/>
+            <arg value="outputLanguage=java"/>
+            <arg value="templateFile=/org/pushingpixels/photon/api/transcoder/java/SvgTranscoderTemplateResizable.templ"/>
+        </java>
+    </target>
 </project>
diff --git a/ivy.xml b/ivy.xml
index 8e76fba07..f3ad56601 100644
--- a/ivy.xml
+++ b/ivy.xml
@@ -24,6 +24,7 @@
         <dependency org="org.tukaani" name="xz" rev="1.8" conf="api->default"/>
         <dependency org="com.drewnoakes" name="metadata-extractor" rev="2.13.0" conf="api->default"/>
         <dependency org="ch.poole" name="OpeningHoursParser" rev="0.21.1" conf="api->default"/>
+        <dependency org="org.pushing-pixels" name="radiance-neon" rev="3.0-SNAPSHOT" conf="api->default"/>
         <!-- sources->sources -->
         <dependency org="javax.json" name="javax.json-api" rev="1.1.4" conf="sources->sources"/>
         <dependency org="org.glassfish" name="javax.json" rev="1.1.4" conf="sources->sources"/>
diff --git a/ivysettings.xml b/ivysettings.xml
index 3cd393631..e919942b1 100644
--- a/ivysettings.xml
+++ b/ivysettings.xml
@@ -2,8 +2,13 @@
 <!-- License: GPL. For details, see LICENSE file. -->
 <ivysettings>
   <settings defaultResolver="chain"/>
+  <property name="m2-pattern" value="${user.home}/.m2/repository/[organisation]/[module]/[revision]/[module]-[revision](-[classifier]).[ext]" override="false" />
   <resolvers>
     <chain name="chain">
+      <filesystem name="local-maven2" m2compatible="true" >
+        <artifact pattern="${m2-pattern}"/>
+        <ivy pattern="${m2-pattern}"/>
+      </filesystem>
       <ibiblio name="josm-nexus" m2compatible="true" root="https://josm.openstreetmap.de/nexus/content/repositories/public/" />
       <ibiblio name="jcenter" m2compatible="true" root="https://jcenter.bintray.com/" />
     </chain>
diff --git a/src/org/openstreetmap/josm/tools/ImageProvider.java b/src/org/openstreetmap/josm/tools/ImageProvider.java
index 89de2b570..15b1f2f44 100644
--- a/src/org/openstreetmap/josm/tools/ImageProvider.java
+++ b/src/org/openstreetmap/josm/tools/ImageProvider.java
@@ -15,6 +15,7 @@
 import java.awt.RenderingHints;
 import java.awt.Toolkit;
 import java.awt.Transparency;
+import java.awt.geom.Dimension2D;
 import java.awt.image.BufferedImage;
 import java.awt.image.ColorModel;
 import java.awt.image.FilteredImageSource;
@@ -81,6 +82,7 @@
 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresets;
 import org.openstreetmap.josm.io.CachedFile;
 import org.openstreetmap.josm.spi.preferences.Config;
+import org.pushingpixels.neon.api.icon.ResizableIcon;
 import org.w3c.dom.Element;
 import org.w3c.dom.Node;
 import org.w3c.dom.NodeList;
@@ -853,6 +855,15 @@ private ImageResource getIfAvailableImpl() {
         // for example in loops in map entries (ie freeze when such entry is retrieved)
 
         String prefix = isDisabled ? "dis:" : "";
+
+        if (Utils.hasExtension(name, "svg")) {
+            final ImageResource ir = getResizableIcon();
+            if (ir != null) {
+                cache.put(prefix + name, ir);
+                return ir;
+            }
+        }
+
         if (name.startsWith("data:")) {
             String url = name;
             ImageResource ir = cache.get(prefix + url);
@@ -955,6 +966,25 @@ private ImageResource getIfAvailableImpl() {
         return null;
     }
 
+    private ImageResource getResizableIcon() {
+        try {
+            // rewrite foo/bar/baz.svg to org.openstreetmap.josm.images.foo.bar.img_baz (class is prefixed with img_)
+            final String[] nameParts = name.replaceAll("[ -]", "_").split("/");
+            nameParts[nameParts.length - 1] = "img_" + nameParts[nameParts.length - 1].replaceFirst(".svg$", "");
+            final String className = "org.openstreetmap.josm.images." + String.join(".", nameParts);
+            final Class<?> imageClass = Class.forName(className);
+            final ResizableIcon.Factory factory = (ResizableIcon.Factory) imageClass.getDeclaredMethod("factory").invoke(null);
+
+            Logging.info("ImageProvider: For image {0}, using {1}", name, imageClass);
+            final ResizableIcon resizableIcon = factory.createNewIcon();
+            return new ImageResource(resizableIcon);
+
+        } catch (Exception ex) {
+            Logging.warn(ex);
+        }
+        return null;
+    }
+
     /**
      * Internal implementation of the image request for URL's.
      *
@@ -1640,34 +1670,18 @@ public static BufferedImage createImageFromSvg(SVGDiagram svg, Dimension dim) {
         }
         final float sourceWidth = svg.getWidth();
         final float sourceHeight = svg.getHeight();
-        final float realWidth;
-        final float realHeight;
-        if (dim.width >= 0) {
-            realWidth = dim.width;
-            if (dim.height >= 0) {
-                realHeight = dim.height;
-            } else {
-                realHeight = sourceHeight * realWidth / sourceWidth;
-            }
-        } else if (dim.height >= 0) {
-            realHeight = dim.height;
-            realWidth = sourceWidth * realHeight / sourceHeight;
-        } else {
-            realWidth = GuiSizesHelper.getSizeDpiAdjusted(sourceWidth);
-            realHeight = GuiSizesHelper.getSizeDpiAdjusted(sourceHeight);
-        }
 
-        int roundedWidth = Math.round(realWidth);
-        int roundedHeight = Math.round(realHeight);
+        final Dimension2D realDimension = new ComputedDimension(dim, sourceWidth, sourceHeight);
+        int roundedWidth = (int) Math.round(realDimension.getWidth());
+        int roundedHeight = (int) Math.round(realDimension.getHeight());
         if (roundedWidth <= 0 || roundedHeight <= 0 || roundedWidth >= Integer.MAX_VALUE || roundedHeight >= Integer.MAX_VALUE) {
-            Logging.error("createImageFromSvg: {0} {1} realWidth={2} realHeight={3}",
-                    svg.getXMLBase(), dim, Float.toString(realWidth), Float.toString(realHeight));
+            Logging.error("createImageFromSvg: {0} {1} realDimension={2}", svg.getXMLBase(), dim, realDimension);
             return null;
         }
         BufferedImage img = new BufferedImage(roundedWidth, roundedHeight, BufferedImage.TYPE_INT_ARGB);
         Graphics2D g = img.createGraphics();
         g.setClip(0, 0, img.getWidth(), img.getHeight());
-        g.scale(realWidth / sourceWidth, realHeight / sourceHeight);
+        g.scale((float) realDimension.getWidth() / sourceWidth, (float) realDimension.getHeight() / sourceHeight);
         g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
         try {
             synchronized (getSvgUniverse()) {
@@ -2123,4 +2137,47 @@ public String toString() {
                 + (archive != null ? "archive=" + archive + ", " : "")
                 + (inArchiveDir != null && !inArchiveDir.isEmpty() ? "inArchiveDir=" + inArchiveDir : "") + ']').replaceAll(", \\]", "]");
     }
+
+    static class ComputedDimension extends Dimension2D {
+        private final float realWidth;
+        private final float realHeight;
+
+        ComputedDimension(Dimension dim, float sourceWidth, float sourceHeight) {
+            dim = GuiSizesHelper.getDimensionDpiAdjusted(dim);
+            if (dim.width >= 0) {
+                realWidth = dim.width;
+                if (dim.height >= 0) {
+                    realHeight = dim.height;
+                } else {
+                    realHeight = sourceHeight * realWidth / sourceWidth;
+                }
+            } else if (dim.height >= 0) {
+                realHeight = dim.height;
+                realWidth = sourceWidth * realHeight / sourceHeight;
+            } else {
+                realWidth = GuiSizesHelper.getSizeDpiAdjusted(sourceWidth);
+                realHeight = GuiSizesHelper.getSizeDpiAdjusted(sourceHeight);
+            }
+        }
+
+        @Override
+        public double getWidth() {
+            return realWidth;
+        }
+
+        @Override
+        public double getHeight() {
+            return realHeight;
+        }
+
+        @Override
+        public void setSize(double width, double height) {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public String toString() {
+            return "ComputedDimension{realWidth=" + realWidth + ", realHeight=" + realHeight + '}';
+        }
+    }
 }
diff --git a/src/org/openstreetmap/josm/tools/ImageResource.java b/src/org/openstreetmap/josm/tools/ImageResource.java
index 56f5d4f87..62301ddfb 100644
--- a/src/org/openstreetmap/josm/tools/ImageResource.java
+++ b/src/org/openstreetmap/josm/tools/ImageResource.java
@@ -3,6 +3,7 @@
 
 import java.awt.Dimension;
 import java.awt.Image;
+import java.awt.geom.Dimension2D;
 import java.awt.image.BufferedImage;
 import java.util.List;
 import java.util.Map;
@@ -16,6 +17,7 @@
 import javax.swing.UIManager;
 
 import com.kitfox.svg.SVGDiagram;
+import org.pushingpixels.neon.api.icon.ResizableIcon;
 
 /**
  * Holds data for one particular image.
@@ -35,6 +37,10 @@
      * SVG diagram information in case of SVG vector image.
      */
     private SVGDiagram svg;
+
+    private ResizableIcon resizableIcon;
+    private short resizableIconWidth;
+    private short resizableIconHeight;
     /**
      * Use this dimension to request original file dimension.
      */
@@ -70,6 +76,13 @@ public ImageResource(SVGDiagram svg) {
         this.svg = svg;
     }
 
+    public ImageResource(ResizableIcon resizableIcon) {
+        CheckParameterUtil.ensureParameterNotNull(resizableIcon);
+        this.resizableIcon = resizableIcon;
+        this.resizableIconWidth = (short) resizableIcon.getIconWidth();
+        this.resizableIconHeight = (short) resizableIcon.getIconHeight();
+    }
+
     /**
      * Constructs a new {@code ImageResource} from another one and sets overlays.
      * @param res the existing resource
@@ -78,6 +91,9 @@ public ImageResource(SVGDiagram svg) {
      */
     public ImageResource(ImageResource res, List<ImageOverlay> overlayInfo) {
         this.svg = res.svg;
+        this.resizableIcon = res.resizableIcon;
+        this.resizableIconWidth = res.resizableIconWidth;
+        this.resizableIconHeight = res.resizableIconHeight;
         this.baseImage = res.baseImage;
         this.overlayInfo = overlayInfo;
     }
@@ -158,9 +174,12 @@ public ImageIcon getImageIcon(Dimension dim, boolean multiResolution) {
                 () -> dim + " is invalid");
         BufferedImage img = imgCache.get(dim);
         if (img == null) {
-            if (svg != null) {
-                Dimension realDim = GuiSizesHelper.getDimensionDpiAdjusted(dim);
-                img = ImageProvider.createImageFromSvg(svg, realDim);
+            if (resizableIcon != null) {
+                final Dimension2D dimension = new ImageProvider.ComputedDimension(dim, resizableIconWidth, resizableIconHeight);
+                resizableIcon.setDimension(new Dimension((int) dimension.getWidth(), (int) dimension.getHeight()));
+                img = resizableIcon.toImage();
+            } else if (svg != null) {
+                img = ImageProvider.createImageFromSvg(svg, dim);
                 if (img == null) {
                     return null;
                 }
@@ -248,7 +267,10 @@ public ImageIcon getImageIconBounded(Dimension maxSize, boolean multiResolution)
         float sourceHeight;
         int maxWidth = maxSize.width;
         int maxHeight = maxSize.height;
-        if (svg != null) {
+        if (resizableIcon != null) {
+            sourceHeight = resizableIconHeight;
+            sourceWidth = resizableIconWidth;
+        } else if (svg != null) {
             sourceWidth = svg.getWidth();
             sourceHeight = svg.getHeight();
         } else {
diff --git a/tools/ivy.xml b/tools/ivy.xml
index dd4fc3b9b..f3c990041 100644
--- a/tools/ivy.xml
+++ b/tools/ivy.xml
@@ -8,6 +8,7 @@
         <conf name="proguard" description="Everything needed for running ProGuard"/>
         <conf name="pmd" description="Everything needed for running PMD"/>
         <conf name="spotbugs" description="Everything needed for running SpotBugs"/>
+        <conf name="radiance-photon" description="Everything needed for running radiance-photon"/>
     </configurations>
     <dependencies>
         <!-- javacc->default -->
@@ -26,5 +27,10 @@
         <!-- spotbugs->default -->
         <dependency org="com.github.spotbugs" name="spotbugs" rev="3.1.12" conf="spotbugs->default"/>
         <dependency org="com.github.spotbugs" name="spotbugs-ant" rev="3.1.12" conf="spotbugs->default"/>
+        <!-- radiance-photon->default -->
+        <dependency org="org.pushing-pixels" name="radiance-photon" rev="3.0-SNAPSHOT" conf="radiance-photon->default"/>
+        <dependency org="org.apache.xmlgraphics" name="batik-all" rev="1.12" conf="radiance-photon->default">
+            <artifact name="batik-all" type="pom" ext="pom"/>
+        </dependency>
     </dependencies>
 </ivy-module>
