diff --git a/build.xml b/build.xml
index 8146da263..1b9be0763 100644
--- a/build.xml
+++ b/build.xml
@@ -905,7 +905,7 @@ Build-Date: ${build.tstamp}
         </taskdef>
         <checkstyle config="${checkstyle.dir}/josm_checks.xml">
             <fileset dir="${base.dir}/src/org/openstreetmap/josm" includes="**/*.java"
-                excludes="gui/mappaint/mapcss/parsergen/*.java"/>
+                excludes="gui/mappaint/mapcss/parsergen/*.java,images/**"/>
             <fileset dir="${base.dir}/test" includes="**/*.java"/>
             <fileset dir="${base.dir}/scripts" includes="**/*.java"/>
             <formatter type="plain"/>
@@ -1148,6 +1148,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.SvgDeepBatchConverter" failonerror="true">
+            <classpath refid="radiance-photon.classpath"/>
+            <classpath path="${tools.dir}/radiance-photon"/>
+            <arg value="sourceRootFolder=resources/images/"/>
+            <arg value="outputRootFolder=src/org/openstreetmap/josm/images"/>
+            <arg value="outputRootPackageName=org.openstreetmap.josm.images"/>
+            <arg value="outputClassNamePrefix=img_"/>
+            <arg value="outputLanguage=java"/>
+            <arg value="templateFile=/JosmSvgIcon.templ"/>
+        </java>
+    </target>
 </project>
diff --git a/ivy.xml b/ivy.xml
index e9888acb7..f0febed81 100644
--- a/ivy.xml
+++ b/ivy.xml
@@ -26,6 +26,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="org.openstreetmap.jmapviewer" name="jmapviewer" rev="2.13" conf="sources->sources"/>
         <dependency org="javax.json" name="javax.json-api" rev="1.1.4" conf="sources->sources"/>
diff --git a/ivysettings.xml b/ivysettings.xml
index c8451d120..f77ea6a6f 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="josm-nexus-snapshots" m2compatible="true" root="https://josm.openstreetmap.de/nexus/content/repositories/snapshots/" />
       <ibiblio name="jcenter" m2compatible="true" root="https://jcenter.bintray.com/" />
diff --git a/src/org/openstreetmap/josm/data/osm/visitor/paint/StyledMapRenderer.java b/src/org/openstreetmap/josm/data/osm/visitor/paint/StyledMapRenderer.java
index c477314de..9b6dad7b7 100644
--- a/src/org/openstreetmap/josm/data/osm/visitor/paint/StyledMapRenderer.java
+++ b/src/org/openstreetmap/josm/data/osm/visitor/paint/StyledMapRenderer.java
@@ -86,6 +86,7 @@
 import org.openstreetmap.josm.tools.HiDPISupport;
 import org.openstreetmap.josm.tools.ImageProvider;
 import org.openstreetmap.josm.tools.JosmRuntimeException;
+import org.openstreetmap.josm.tools.JosmSvgIcon;
 import org.openstreetmap.josm.tools.Logging;
 import org.openstreetmap.josm.tools.ShapeClipper;
 import org.openstreetmap.josm.tools.Utils;
@@ -839,7 +840,13 @@ private void drawIcon(MapViewPoint p, MapImage img, boolean disabled, boolean se
         temporaryGraphics.rotate(theta);
         int drawX = -img.getWidth() / 2 + img.offsetX;
         int drawY = -img.getHeight() / 2 + img.offsetY;
-        temporaryGraphics.drawImage(img.getImage(disabled), drawX, drawY, nc);
+        final JosmSvgIcon icon = img.getJosmSvgIcon();
+        if (icon != null) {
+            Logging.trace("drawIcon {0}", icon);
+            icon.paintIcon(nc, temporaryGraphics, drawX, drawY);
+        } else {
+            temporaryGraphics.drawImage(img.getImage(disabled), drawX, drawY, nc);
+        }
         if (selected || member) {
             selectionDrawer.accept(temporaryGraphics, new Rectangle2D.Double(drawX - 2d, drawY - 2d, img.getWidth() + 4d, img.getHeight() + 4d));
         }
diff --git a/src/org/openstreetmap/josm/gui/mappaint/MapPaintStyles.java b/src/org/openstreetmap/josm/gui/mappaint/MapPaintStyles.java
index 22944e1f7..a3678c34e 100644
--- a/src/org/openstreetmap/josm/gui/mappaint/MapPaintStyles.java
+++ b/src/org/openstreetmap/josm/gui/mappaint/MapPaintStyles.java
@@ -24,6 +24,7 @@
 import org.openstreetmap.josm.io.FileWatcher;
 import org.openstreetmap.josm.spi.preferences.Config;
 import org.openstreetmap.josm.tools.ImageProvider;
+import org.openstreetmap.josm.tools.ImageResource;
 import org.openstreetmap.josm.tools.ListenerList;
 import org.openstreetmap.josm.tools.Logging;
 import org.openstreetmap.josm.tools.Stopwatch;
@@ -199,13 +200,13 @@ public static ImageIcon getIcon(IconReference ref, int width, int height) {
      *   - josm's default icon
      *  can be null if the defaults are turned off by user
      */
-    public static ImageIcon getNoIconIcon(StyleSource source) {
+    public static ImageResource getNoIconIcon(StyleSource source) {
         return new ImageProvider("presets/misc/no_icon")
                 .setDirs(getIconSourceDirs(source))
                 .setId("mappaint."+source.getPrefName())
                 .setArchive(source.zipIcons)
                 .setInArchiveDir(source.getZipEntryDirName())
-                .setOptional(true).get();
+                .setOptional(true).getResource();
     }
 
     /**
diff --git a/src/org/openstreetmap/josm/gui/mappaint/styleelement/MapImage.java b/src/org/openstreetmap/josm/gui/mappaint/styleelement/MapImage.java
index 6bc869d67..a5823e62a 100644
--- a/src/org/openstreetmap/josm/gui/mappaint/styleelement/MapImage.java
+++ b/src/org/openstreetmap/josm/gui/mappaint/styleelement/MapImage.java
@@ -13,8 +13,6 @@
 import java.util.concurrent.ExecutionException;
 import java.util.function.Consumer;
 
-import javax.swing.ImageIcon;
-
 import org.openstreetmap.josm.gui.MainApplication;
 import org.openstreetmap.josm.gui.MapView;
 import org.openstreetmap.josm.gui.mappaint.MapPaintStyles;
@@ -24,6 +22,7 @@
 import org.openstreetmap.josm.gui.util.GuiHelper;
 import org.openstreetmap.josm.tools.ImageProvider;
 import org.openstreetmap.josm.tools.ImageResource;
+import org.openstreetmap.josm.tools.JosmSvgIcon;
 import org.openstreetmap.josm.tools.Logging;
 import org.openstreetmap.josm.tools.Utils;
 
@@ -33,12 +32,12 @@
 public class MapImage {
 
     private static final int MAX_SIZE = 48;
+    private static final ImageResource TEMPORARY = new ImageProvider("clock").getResource();
 
     /**
-     * ImageIcon can change while the image is loading.
+     * {@link ImageResource} can change while the image is loading.
      */
-    private Image img;
-    private ImageResource imageResource;
+    private ImageResource imageResource = TEMPORARY;
 
     /**
      * The alpha (opacity) value of the image. It is multiplied to the image alpha channel.
@@ -74,8 +73,6 @@
      */
     public int offsetY;
 
-    private boolean temporary;
-
     /**
      * A cache that holds a disabled (gray) version of this image
      */
@@ -134,12 +131,16 @@ public ImageResource getImageResource() {
         return imageResource;
     }
 
+    public JosmSvgIcon getJosmSvgIcon() {
+        return imageResource.getJosmSvgIcon();
+    }
+
     private Image getDisabled() {
         if (disabledImgCache != null)
             return disabledImgCache;
-        if (img == null)
+        if (isTemporary())
             getImage(); // fix #7498 ?
-        Image disImg = GuiHelper.getDisabledImage(img);
+        Image disImg = GuiHelper.getDisabledImage(getImage());
         if (disImg instanceof BufferedImage) {
             disabledImgCache = (BufferedImage) disImg;
         } else {
@@ -152,17 +153,10 @@ private Image getDisabled() {
     }
 
     private Image getImage() {
-        if (img != null)
-            return img;
-        temporary = false;
-        loadImage();
-        synchronized (this) {
-            if (img == null) {
-                img = ImageProvider.get("clock").getImage();
-                temporary = true;
-            }
+        if (isTemporary()) {
+            loadImage();
         }
-        return img;
+        return rescale(imageResource.getImageIcon(new Dimension(width, height)).getImage());
     }
 
     private CompletableFuture<Void> load(Consumer<? super ImageResource> action) {
@@ -183,21 +177,14 @@ private Image getImage() {
     private CompletableFuture<Void> loadImage() {
         return load(result -> {
             synchronized (this) {
-                imageResource = result;
-                if (result == null) {
-                    source.logWarning(tr("Failed to locate image ''{0}''", name));
-                    ImageIcon noIcon = MapPaintStyles.getNoIconIcon(source);
-                    img = noIcon == null ? null : noIcon.getImage();
-                } else {
-                    img = rescale(result.getImageIcon(new Dimension(width, height)).getImage());
-                }
+                final boolean temporary = isTemporary(); // setImageResource changes isTemporary
+                setImageResource(result);
                 if (temporary) {
                     disabledImgCache = null;
                     MapView mapView = MainApplication.getMap().mapView;
                     mapView.preferenceChanged(null); // otherwise repaint is ignored, because layer hasn't changed
                     mapView.repaint();
                 }
-                temporary = false;
             }
         });
     }
@@ -210,19 +197,29 @@ private Image getImage() {
     private CompletableFuture<Void> loadImageResource() {
         return load(result -> {
             synchronized (this) {
-                imageResource = result;
-                if (result == null) {
-                    source.logWarning(tr("Failed to locate image ''{0}''", name));
-                }
+                setImageResource(result);
             }
         });
     }
 
+    private void setImageResource(ImageResource result) {
+        if (result == null) {
+            source.logWarning(tr("Failed to locate image ''{0}''", name));
+            imageResource = MapPaintStyles.getNoIconIcon(source);
+        } else {
+            imageResource = result;
+        }
+    }
+
     /**
      * Gets the image width
      * @return The real image width
      */
     public int getWidth() {
+        final JosmSvgIcon icon = getJosmSvgIcon();
+        if (icon != null) {
+            return icon.getIconWidth();
+        }
         return getImage().getWidth(null);
     }
 
@@ -231,6 +228,10 @@ public int getWidth() {
      * @return The real image height
      */
     public int getHeight() {
+        final JosmSvgIcon icon = getJosmSvgIcon();
+        if (icon != null) {
+            return icon.getIconHeight();
+        }
         return getImage().getHeight(null);
     }
 
@@ -247,13 +248,13 @@ public float getAlphaFloat() {
      * @return {@code true} if image is not completely loaded and getImage() returns a temporary image
      */
     public boolean isTemporary() {
-        return temporary;
+        return imageResource == TEMPORARY;
     }
 
     protected class MapImageBoxProvider implements BoxProvider {
         @Override
         public BoxProviderResult get() {
-            return new BoxProviderResult(box(), temporary);
+            return new BoxProviderResult(box(), isTemporary());
         }
 
         private Rectangle box() {
@@ -281,7 +282,7 @@ public boolean equals(Object obj) {
             if (obj instanceof MapImageBoxProvider) {
                 MapImageBoxProvider other = (MapImageBoxProvider) obj;
                 return MapImage.this.equals(other.getParent());
-            } else if (temporary) {
+            } else if (isTemporary()) {
                 return false;
             } else {
                 final BoxProvider other = (BoxProvider) obj;
diff --git a/src/org/openstreetmap/josm/tools/ImageProvider.java b/src/org/openstreetmap/josm/tools/ImageProvider.java
index 89de2b570..2b167c594 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;
@@ -853,6 +854,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 = getJosmSvgIcon();
+            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 +965,24 @@ private ImageResource getIfAvailableImpl() {
         return null;
     }
 
+    private ImageResource getJosmSvgIcon() {
+        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 JosmSvgIcon icon = (JosmSvgIcon) imageClass.getConstructor(int.class, int.class).newInstance(16, 16);
+
+            Logging.trace("ImageProvider: For image {0}, using {1}", name, imageClass);
+            return new ImageResource(icon);
+
+        } catch (Exception ex) {
+            Logging.warn(ex);
+        }
+        return null;
+    }
+
     /**
      * Internal implementation of the image request for URL's.
      *
@@ -1640,34 +1668,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 +2135,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..482be937b 100644
--- a/src/org/openstreetmap/josm/tools/ImageResource.java
+++ b/src/org/openstreetmap/josm/tools/ImageResource.java
@@ -3,10 +3,12 @@
 
 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;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Consumer;
 
 import javax.swing.AbstractAction;
 import javax.swing.Action;
@@ -35,6 +37,8 @@
      * SVG diagram information in case of SVG vector image.
      */
     private SVGDiagram svg;
+
+    private JosmSvgIcon josmSvgIcon;
     /**
      * Use this dimension to request original file dimension.
      */
@@ -70,6 +74,11 @@ public ImageResource(SVGDiagram svg) {
         this.svg = svg;
     }
 
+    public ImageResource(JosmSvgIcon josmSvgIcon) {
+        CheckParameterUtil.ensureParameterNotNull(josmSvgIcon);
+        this.josmSvgIcon = josmSvgIcon;
+    }
+
     /**
      * Constructs a new {@code ImageResource} from another one and sets overlays.
      * @param res the existing resource
@@ -78,6 +87,7 @@ public ImageResource(SVGDiagram svg) {
      */
     public ImageResource(ImageResource res, List<ImageOverlay> overlayInfo) {
         this.svg = res.svg;
+        this.josmSvgIcon = res.josmSvgIcon;
         this.baseImage = res.baseImage;
         this.overlayInfo = overlayInfo;
     }
@@ -94,19 +104,20 @@ public ImageResource setDisabled(boolean disabled) {
         return this;
     }
 
+    private void attachImageIcon(ImageProvider.ImageSizes size, Consumer<Icon> iconConsumer) {
+        iconConsumer.accept(josmSvgIcon != null
+                ? josmSvgIcon.withDimension(size.getVirtualWidth(), size.getVirtualHeight())
+                : getImageIconBounded(size.getImageDimension()));
+    }
+
     /**
      * Set both icons of an Action
      * @param a The action for the icons
      * @since 10369
      */
     public void attachImageIcon(AbstractAction a) {
-        Dimension iconDimension = ImageProvider.ImageSizes.SMALLICON.getImageDimension();
-        ImageIcon icon = getImageIconBounded(iconDimension);
-        a.putValue(Action.SMALL_ICON, icon);
-
-        iconDimension = ImageProvider.ImageSizes.LARGEICON.getImageDimension();
-        icon = getImageIconBounded(iconDimension);
-        a.putValue(Action.LARGE_ICON_KEY, icon);
+        attachImageIcon(ImageProvider.ImageSizes.SMALLICON, icon -> a.putValue(Action.SMALL_ICON, icon));
+        attachImageIcon(ImageProvider.ImageSizes.LARGEICON, icon -> a.putValue(Action.LARGE_ICON_KEY, icon));
     }
 
     /**
@@ -158,9 +169,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 (josmSvgIcon != null) {
+                final Dimension2D dimension = new ImageProvider.ComputedDimension(
+                        dim, josmSvgIcon.getIconWidth(), josmSvgIcon.getIconHeight());
+                img = josmSvgIcon.withDimension((int) dimension.getWidth(), (int) dimension.getHeight()).toImage();
+            } else if (svg != null) {
+                img = ImageProvider.createImageFromSvg(svg, dim);
                 if (img == null) {
                     return null;
                 }
@@ -248,7 +262,10 @@ public ImageIcon getImageIconBounded(Dimension maxSize, boolean multiResolution)
         float sourceHeight;
         int maxWidth = maxSize.width;
         int maxHeight = maxSize.height;
-        if (svg != null) {
+        if (josmSvgIcon != null) {
+            sourceHeight = josmSvgIcon.getIconHeight();
+            sourceWidth = josmSvgIcon.getIconWidth();
+        } else if (svg != null) {
             sourceWidth = svg.getWidth();
             sourceHeight = svg.getHeight();
         } else {
@@ -292,6 +309,10 @@ public ImageIcon getPaddedIcon(Dimension iconSize) {
         return new ImageIcon(image);
     }
 
+    public JosmSvgIcon getJosmSvgIcon() {
+        return josmSvgIcon;
+    }
+
     @Override
     public String toString() {
         return "ImageResource ["
diff --git a/src/org/openstreetmap/josm/tools/JosmSvgIcon.java b/src/org/openstreetmap/josm/tools/JosmSvgIcon.java
new file mode 100644
index 000000000..a1deadfaf
--- /dev/null
+++ b/src/org/openstreetmap/josm/tools/JosmSvgIcon.java
@@ -0,0 +1,57 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.tools;
+
+import java.awt.image.BufferedImage;
+
+import javax.swing.Icon;
+
+import org.pushingpixels.neon.api.NeonCortex;
+
+/**
+ * An SVG icon as compiled Java class.
+ */
+public interface JosmSvgIcon extends Icon {
+
+    /**
+     * Creates a copy this this icon with the specified dimension.
+     *
+     * @param width  the width of the copy
+     * @param height the height of the copy
+     * @return a copy this this icon with the specified dimension.
+     */
+    JosmSvgIcon withDimension(int width, int height);
+
+    /**
+     * Returns the X of the bounding box of the original SVG image.
+     *
+     * @return The X of the bounding box of the original SVG image.
+     */
+    double getOrigX();
+
+    /**
+     * Returns the Y of the bounding box of the original SVG image.
+     *
+     * @return The Y of the bounding box of the original SVG image.
+     */
+    double getOrigY();
+
+    /**
+     * Returns the width of the bounding box of the original SVG image.
+     *
+     * @return The width of the bounding box of the original SVG image.
+     */
+    double getOrigWidth();
+
+    /**
+     * Returns the height of the bounding box of the original SVG image.
+     *
+     * @return The height of the bounding box of the original SVG image.
+     */
+    double getOrigHeight();
+
+    default BufferedImage toImage() {
+        BufferedImage result = NeonCortex.getBlankImage(this.getIconWidth(), this.getIconHeight());
+        this.paintIcon(null, result.getGraphics(), 0, 0);
+        return result;
+    }
+}
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>
diff --git a/tools/radiance-photon/JosmSvgIcon.templ b/tools/radiance-photon/JosmSvgIcon.templ
new file mode 100644
index 000000000..b06a68356
--- /dev/null
+++ b/tools/radiance-photon/JosmSvgIcon.templ
@@ -0,0 +1,138 @@
+TOKEN_PACKAGE
+
+import java.awt.*;
+import java.awt.geom.*;
+import java.awt.image.BufferedImage;
+import java.io.*;
+import java.lang.ref.WeakReference;
+import java.util.Base64;
+import java.util.Stack;
+import javax.imageio.ImageIO;
+
+/**
+ * This class has been automatically generated using <a
+ * href="https://github.com/kirill-grouchnikov/radiance">Photon SVG transcoder</a>.
+ */
+public class TOKEN_CLASSNAME implements org.openstreetmap.josm.tools.JosmSvgIcon {
+    private Shape shape = null;
+    private GeneralPath generalPath = null;
+    private Paint paint = null;
+    private Stroke stroke = null;
+    private Shape clip = null;
+    private Stack<AffineTransform> transformsStack = new Stack<>();
+
+    TOKEN_RASTER_CODE
+
+    TOKEN_PAINTING_CODE
+
+    @SuppressWarnings("unused")
+    private void innerPaint(Graphics2D g) {
+        float origAlpha = 1.0f;
+        Composite origComposite = g.getComposite();
+        if (origComposite instanceof AlphaComposite) {
+            AlphaComposite origAlphaComposite =
+                    (AlphaComposite) origComposite;
+            if (origAlphaComposite.getRule() == AlphaComposite.SRC_OVER) {
+                origAlpha = origAlphaComposite.getAlpha();
+            }
+        }
+
+        TOKEN_PAINTING_INVOCATIONS
+
+        shape = null;
+        generalPath = null;
+        paint = null;
+        stroke = null;
+        clip = null;
+        transformsStack.clear();
+    }
+
+    @Override
+    public double getOrigX() {
+        return TOKEN_ORIG_X;
+    }
+
+    @Override
+    public double getOrigY() {
+        return TOKEN_ORIG_Y;
+    }
+
+    @Override
+    public double getOrigWidth() {
+        return TOKEN_ORIG_WIDTH;
+    }
+
+    @Override
+    public double getOrigHeight() {
+        return TOKEN_ORIG_HEIGHT;
+    }
+
+    /**
+     * The current width of this resizable icon.
+     */
+    private final int width;
+
+    /**
+     * The current height of this resizable icon.
+     */
+    private final int height;
+
+    /**
+     * Creates a new transcoded SVG image with the original dimension.
+     */
+    public TOKEN_CLASSNAME() {
+        this.width = (int) getOrigWidth();
+        this.height = (int) getOrigHeight();
+    }
+
+    /**
+     * Creates a new transcoded SVG image with the specified dimension.
+     *
+     * @param width  the width of the icon
+     * @param height the height of the icon
+     */
+    public TOKEN_CLASSNAME(int width, int height) {
+        this.width = width;
+        this.height = height;
+    }
+
+    @Override
+    public int getIconHeight() {
+        return height;
+    }
+
+    @Override
+    public int getIconWidth() {
+        return width;
+    }
+
+    @Override
+    public void paintIcon(Component c, Graphics g, int x, int y) {
+        g.translate(x, y);
+
+        double coef1 = (double) this.width / getOrigWidth();
+        double coef2 = (double) this.height / getOrigHeight();
+        double coef = Math.min(coef1, coef2);
+        g.clipRect(0, 0, this.width, this.height);
+        ((Graphics2D) g).scale(coef, coef);
+        ((Graphics2D) g).translate(-getOrigX(), -getOrigY());
+        if (coef1 != coef2) {
+            if (coef1 < coef2) {
+                int extraDy = (int) ((getOrigWidth() - getOrigHeight()) / 2.0);
+                g.translate(0, extraDy);
+            } else {
+                int extraDx = (int) ((getOrigHeight() - getOrigWidth()) / 2.0);
+                g.translate(extraDx, 0);
+            }
+        }
+        Graphics2D gInner = (Graphics2D) g.create();
+        innerPaint(gInner);
+        gInner.dispose();
+        g.dispose();
+    }
+
+    @Override
+    public TOKEN_CLASSNAME withDimension(int width, int height) {
+        return new TOKEN_CLASSNAME(width, height);
+    }
+}
