Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Can't get a proper antialiased SVG rendering #44

Open
don-vip opened this issue Sep 14, 2019 · 13 comments
Open

Can't get a proper antialiased SVG rendering #44

don-vip opened this issue Sep 14, 2019 · 13 comments

Comments

@don-vip
Copy link
Contributor

don-vip commented Sep 14, 2019

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
Copy link
Owner

blackears commented Sep 26, 2019

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
Copy link
Contributor Author

don-vip commented Sep 27, 2019

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
Copy link

ghost commented Jul 22, 2020

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
Copy link
Owner

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
Copy link

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

@simon04
Copy link
Contributor

simon04 commented Aug 22, 2020

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 #44 (comment)

2020-08-22-185443_900x358_scrot

Any ideas to debug this problem/behaviour?

@ghost
Copy link

ghost commented Aug 22, 2020

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
Copy link
Contributor

simon04 commented Aug 22, 2020

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
Copy link
Contributor

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
Copy link
Contributor

simon04 commented Aug 22, 2020

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
Copy link
Contributor

@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
Copy link
Contributor

simon04 commented Aug 22, 2020

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
Copy link
Contributor

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...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants