blackears / svgSalamander

153 stars 56 forks source link

Can't get a proper antialiased SVG rendering #44

Open don-vip opened 5 years ago

don-vip commented 5 years ago

I can't get a proper antialiased rendering of our SVG logo: https://josm.openstreetmap.de/export/15290/josm/trunk/images/logo.svg

This is what I get compared to IE rendering at a similar size: damned

I think I use all possible rendering hints to ensure smooth rendering, so is it an issue coming from svgSalamander?

    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);
        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.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
        g.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
        g.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
        g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
        try {
            synchronized (getSvgUniverse()) {
                svg.render(g);
            }
        } catch (SVGException ex) {
            Logging.log(Logging.LEVEL_ERROR, "Unable to load svg:", ex);
            return null;
        }
        return img;
    }

    private static synchronized SVGUniverse getSvgUniverse() {
        if (svgUniverse == null) {
            svgUniverse = new SVGUniverse();
            // CVE-2017-5617: Allow only data scheme (see #14319)
            svgUniverse.setImageDataInlineOnly(true);
        }
        return svgUniverse;
    }
blackears commented 4 years ago

I loaded your SVG file using the viewer in the SVGSalamander package. It displays antialiased to me, except for the tip of the pencil. Not sure what is different about your environment, but you might want to try looking at it in the viewer too.

Edit: actually, it does appear anti-aliased in a few other places too. But most of the picture appears to have anti-aliasing. I'm afraid I don't have a good idea as to why this is or what you could try differently.

don-vip commented 4 years ago

I just tried the SVG Player and it is aliased, unlike IE:

aliasing

I'm running Windows 10 / AdoptOpenJDK 1.8.0_212-b03, what's your environment?

ghost commented 4 years ago

Try using the following rendering hints:

public final static Map<Object, Object> RENDERING_HINTS = Map.of(
      KEY_ANTIALIASING,
      VALUE_ANTIALIAS_ON,
      KEY_ALPHA_INTERPOLATION,
      VALUE_ALPHA_INTERPOLATION_QUALITY,
      KEY_COLOR_RENDERING,
      VALUE_COLOR_RENDER_QUALITY,
      KEY_DITHERING,
      VALUE_DITHER_DISABLE,
      KEY_FRACTIONALMETRICS,
      VALUE_FRACTIONALMETRICS_ON,
      KEY_INTERPOLATION,
      VALUE_INTERPOLATION_BICUBIC,
      KEY_RENDERING,
      VALUE_RENDER_QUALITY,
      KEY_STROKE_CONTROL,
      VALUE_STROKE_PURE,
      KEY_TEXT_ANTIALIASING,
      VALUE_TEXT_ANTIALIAS_ON
  );

// ... later on ...

graphics.setRenderingHints( RENDERING_HINTS );

See: https://github.com/DaveJarvis/kmcaster/blob/master/src/main/com/whitemagicsoftware/kmcaster/SvgRasterizer.java

blackears commented 4 years ago

Can you confirm that this is something you can fix by setting rendering hints in your code so that I may close this bug report?

matkoniecz commented 4 years ago

I pinged JOSM devs at https://josm.openstreetmap.de/ticket/18131#comment:5

simon04 commented 4 years ago

JOSM developer here. We've already been setting RenderingHints.KEY_ANTIALIASING to RenderingHints.VALUE_ANTIALIAS_ON, see https://github.com/openstreetmap/josm/blob/master/src/org/openstreetmap/josm/tools/ImageProvider.java#L1639-L1652

Adding the additional hints does not make a difference. Here is the result I obtain when adding all hints from https://github.com/blackears/svgSalamander/issues/44#issuecomment-662588421

2020-08-22-185443_900x358_scrot

Any ideas to debug this problem/behaviour?

ghost commented 4 years ago

The Info tab itself is showing heavy aliasing ("Follow us", "Homepage", "Translations", and "Report Bug"). Have you tried making the dialog (application) anti-aliased to see the affect on SVG Salamander?

simon04 commented 4 years ago

Wow, following https://stackoverflow.com/a/13236994/205629 (the paintComponent part) makes a positive difference to the text rendering, but not to the rendered icons: 2020-08-22-194808_900x358_scrot

diff --git a/src/org/openstreetmap/josm/actions/AboutAction.java b/src/org/openstreetmap/josm/actions/AboutAction.java
index 505db8eb7..6899cb60c 100644
--- a/src/org/openstreetmap/josm/actions/AboutAction.java
+++ b/src/org/openstreetmap/josm/actions/AboutAction.java
@@ -9,7 +9,11 @@
 import java.awt.Dimension;
 import java.awt.FlowLayout;
 import java.awt.Font;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
 import java.awt.GridBagLayout;
+import java.awt.LayoutManager;
+import java.awt.RenderingHints;
 import java.awt.event.ActionEvent;
 import java.awt.event.KeyEvent;
 import java.io.BufferedReader;
@@ -93,7 +97,7 @@ JPanel buildAboutPanel() {
         setTextFromResourceFile(license, "/LICENSE");
         license.setCaretPosition(0);

-        JPanel info = new JPanel(new GridBagLayout());
+        JPanel info = new AntiAliasingPanel(new GridBagLayout());
         final JMultilineLabel label = new JMultilineLabel("<html>" +
                 "<h1>" + "JOSM – " + tr("Java OpenStreetMap Editor") + "</h1>" +
                 "<p style='font-size:75%'></p>" +
@@ -110,7 +114,7 @@ JPanel buildAboutPanel() {
         info.add(new JLabel(tr("Translations")), GBC.std().insets(10, 0, 10, 0));
         info.add(new UrlLabel("https://translations.launchpad.net/josm", 2), GBC.eol());
         info.add(new JLabel(tr("Follow us on")), GBC.std().insets(10, 10, 10, 0));
-        JPanel logos = new JPanel(new FlowLayout());
+        JPanel logos = new AntiAliasingPanel(new FlowLayout());
         logos.add(createImageLink("OpenStreetMap", /* ICON(dialogs/about/) */ "openstreetmap",
                 "https://www.openstreetmap.org/user/josmeditor/diary"));
         logos.add(createImageLink("Twitter", /* ICON(dialogs/about/) */ "twitter", "https://twitter.com/josmeditor"));
@@ -119,7 +123,7 @@ JPanel buildAboutPanel() {
         info.add(logos, GBC.eol().insets(0, 10, 0, 0));
         info.add(GBC.glue(0, 5), GBC.eol());

-        JPanel inst = new JPanel(new GridBagLayout());
+        JPanel inst = new AntiAliasingPanel(new GridBagLayout());
         final String pathToPreferences = ShowStatusReportAction
                 .paramCleanup(Preferences.main().getPreferenceFile().getAbsolutePath());
         inst.add(new JLabel(tr("Preferences are stored in {0}", pathToPreferences)), GBC.eol().insets(0, 0, 0, 10));
@@ -144,7 +148,7 @@ JPanel buildAboutPanel() {
         }

         // Intermediate panel to allow proper optionPane resizing
-        JPanel panel = new JPanel(new GridBagLayout());
+        JPanel panel = new AntiAliasingPanel(new GridBagLayout());
         panel.setPreferredSize(new Dimension(890, 300));
         panel.add(new JLabel("", ImageProvider.get("logo.svg", ImageProvider.ImageSizes.ABOUT_LOGO),
                 JLabel.CENTER), GBC.std().insets(0, 5, 0, 0));
@@ -255,4 +259,16 @@ private static JScrollPane createScrollPane(JosmTextArea area) {
         sp.setOpaque(false);
         return sp;
     }
+
+    private static class AntiAliasingPanel extends JPanel {
+        AntiAliasingPanel(LayoutManager layout) {
+            super(layout);
+        }
+
+        @Override
+        protected void paintComponent(Graphics g) {
+            ((Graphics2D) g).setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+            super.paintComponent(g);
+        }
+    }
 }

Edit: The same results can be obtained when running java -Dawt.useSystemAAFontSettings=on -Dswing.aatext=true -jar josm.jar

DevCharly commented 4 years ago

The "stepping" at the outside of the map and the pen are caused by clipping used in the SVG.

The map and the pen are larger than shown and the resulting shape is made by clip paths.

If you edit logo.svg and remove nested <path> from first two <clipPath> then the shape of the pen changes, but then is anti-aliased (see red arrows in right image):

image (left is unmodified; right is without first two clip paths)

So the question is whether Java supports anti-aliasing for clipping?

A workaround would be avoiding clip paths in SVG files.

simon04 commented 4 years ago

Dropping the clipping from RenderableElement results in an anti-aliased (but otherwise ugly) logo:

2020-08-22-202308_900x358_scrot

diff --git a/src/com/kitfox/svg/RenderableElement.java b/src/com/kitfox/svg/RenderableElement.java
index 8d0a2e8fd..46566e551 100644
--- a/src/com/kitfox/svg/RenderableElement.java
+++ b/src/com/kitfox/svg/RenderableElement.java
@@ -122,47 +122,6 @@ protected void beginLayer(Graphics2D g) throws SVGException
             cachedXform = g.getTransform();
             g.transform(xform);
         }
-
-        StyleAttribute styleAttrib = new StyleAttribute();
-
-        //Get clipping path
-//        StyleAttribute styleAttrib = getStyle("clip-path", false);
-        Shape clipPath = null;
-        int clipPathUnits = ClipPath.CP_USER_SPACE_ON_USE;
-        if (getStyle(styleAttrib.setName("clip-path"), false)
-             && !"none".equals(styleAttrib.getStringValue()))
-        {
-            URI uri = styleAttrib.getURIValue(getXMLBase());
-            if (uri != null)
-            {
-                ClipPath ele = (ClipPath) diagram.getUniverse().getElement(uri);
-                clipPath = ele.getClipPathShape();
-                clipPathUnits = ele.getClipPathUnits();
-            }
-        }
-
-        //Return if we're out of clipping range
-        if (clipPath != null)
-        {
-            if (clipPathUnits == ClipPath.CP_OBJECT_BOUNDING_BOX && (this instanceof ShapeElement))
-            {
-                Rectangle2D rect = ((ShapeElement) this).getBoundingBox();
-                AffineTransform at = new AffineTransform();
-                at.scale(rect.getWidth(), rect.getHeight());
-                clipPath = at.createTransformedShape(clipPath);
-            }
-
-            cachedClip = g.getClip();
-            if (cachedClip == null)
-            {
-                g.setClip(clipPath);
-            } else
-            {
-                Area newClip = new Area(cachedClip);
-                newClip.intersect(new Area(clipPath));
-                g.setClip(newClip);
-            }
-        }
     }

     /**
DevCharly commented 4 years ago

@simon04 anti-aliasing for text should be enabled by the look and feel.

Metal (at least on Windows) and other LaFs (e.g. FlatLaf) do this by adding some special values to the UI defaults and use SwingUtilities2.drawString() or drawStringUnderlineCharAt() to paint all text. This methods grab the anti-aliasing settings from the UI defaults and set them in the graphics object before painting text.

So using sun.swing.SwingUtilities2.drawString() instead of Graphics.drawString() in UI controls is usually a good idea 😉

Unfortunately those methods are no longer available since Java 9 (package sun.swing is module private), but a new methods are available: javax.swing.plaf.basic.BasicGraphicsUtils.drawString(JComponent c, Graphics ....)

If you target Java 9+, use BasicGraphicsUtils.drawString(JComponent c, ...). If you also target Java 8, here is a helper method that uses both methods: https://github.com/JFormDesigner/FlatLaf/blob/fd37339e2fce70040c6b56f4abee18e280ddeea1/flatlaf-core/src/main/java/com/formdev/flatlaf/util/JavaCompatibility.java#L39-L73

simon04 commented 4 years ago

I found an interesting blog post from 2006 about clipping in Java: Java 2D Trickery: Soft Clipping

For @JOSM, we currently target Java 8+ (there are outstanding issues before we can drop Java 8 support, see https://josm.openstreetmap.de/ticket/17858)

DevCharly commented 4 years ago

Found https://bugs.openjdk.java.net/browse/JDK-4212743 with some interesting comments by Jim Graham

This is by design. Clip shapes are not antialiased...

and a workaround...