Subject: [PATCH] fix #18131 - antialias SVGs, including the logo
---
Index: src/org/openstreetmap/josm/tools/ImageProvider.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/org/openstreetmap/josm/tools/ImageProvider.java b/src/org/openstreetmap/josm/tools/ImageProvider.java
--- a/src/org/openstreetmap/josm/tools/ImageProvider.java	(revision 7647079db3f9dec07c901606c3806818ddfe53df)
+++ b/src/org/openstreetmap/josm/tools/ImageProvider.java	(date 1789814167728)
@@ -82,6 +82,7 @@
 import com.github.weisj.jsvg.parser.DocumentLimits;
 import com.github.weisj.jsvg.parser.LoaderContext;
 import com.github.weisj.jsvg.parser.SVGLoader;
+import com.github.weisj.jsvg.renderer.SVGRenderingHints;
 
 /**
  * Helper class to support the application with images.
@@ -1477,6 +1478,12 @@
             return null;
         }
         return resizeMode.createBufferedImage(dim, new Dimension((int) sourceWidth, (int) sourceHeight), g -> {
+            // Without soft clipping, jsvg applies clip paths through Graphics2D#clip, which is not antialiased and
+            // therefore produces jagged edges wherever an image uses clip-path, see #18131. Soft clipping requires
+            // accurate mask/clip rendering, otherwise the clip is realized as a paint that the clipped group's own
+            // paints override, which drops parts of the image.
+            g.setRenderingHint(SVGRenderingHints.KEY_SOFT_CLIPPING, SVGRenderingHints.VALUE_SOFT_CLIPPING_ON);
+            g.setRenderingHint(SVGRenderingHints.KEY_MASK_CLIP_RENDERING, SVGRenderingHints.VALUE_MASK_CLIP_RENDERING_ACCURACY);
             svg.render(null, g);
         }, null);
     }
Index: test/functional/org/openstreetmap/josm/tools/ImageProviderTest.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/test/functional/org/openstreetmap/josm/tools/ImageProviderTest.java b/test/functional/org/openstreetmap/josm/tools/ImageProviderTest.java
--- a/test/functional/org/openstreetmap/josm/tools/ImageProviderTest.java	(revision 7647079db3f9dec07c901606c3806818ddfe53df)
+++ b/test/functional/org/openstreetmap/josm/tools/ImageProviderTest.java	(date 1789813667097)
@@ -5,6 +5,7 @@
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.openstreetmap.josm.testutils.ImageTestUtils.assertImageEquals;
 
 import java.awt.Dimension;
@@ -130,6 +131,39 @@
         assertDoesNotThrow(() -> OsmPrimitiveImageProvider.getResource(node, Collections.emptyList()));
     }
 
+    /**
+     * Non-regression test for ticket <a href="https://josm.openstreetmap.de/ticket/18131">#18131</a>
+     * <p>
+     * The JOSM logo is assembled from clipped paths. Unless the SVG renderer clips with antialiasing,
+     * the outline of the logo shows jagged steps.
+     */
+    @Test
+    void testTicket18131() {
+        ImageIcon icon = new ImageProvider("logo").setSize(new Dimension(256, 256)).get();
+        assertNotNull(icon);
+        BufferedImage image = (BufferedImage) icon.getImage();
+        // For every column, look at the topmost visible pixel: a fully opaque one means a hard, aliased edge
+        int opaqueEdges = 0;
+        int blendedEdges = 0;
+        for (int x = 0; x < image.getWidth(); x++) {
+            for (int y = 0; y < image.getHeight(); y++) {
+                int alpha = image.getRGB(x, y) >>> 24;
+                if (alpha == 0) {
+                    continue;
+                }
+                if (alpha == 0xff) {
+                    opaqueEdges++;
+                } else {
+                    blendedEdges++;
+                }
+                break;
+            }
+        }
+        assertTrue(opaqueEdges * 4 < blendedEdges,
+                "expected an antialiased logo outline, but found " + opaqueEdges + " aliased and "
+                        + blendedEdges + " antialiased edge pixels");
+    }
+
     /**
      * Test fetching an image using {@code data:} URL.
      */
