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

Filter on the root svg element doesn't get applied #61

Closed
stanio opened this issue Feb 24, 2024 · 9 comments
Closed

Filter on the root svg element doesn't get applied #61

stanio opened this issue Feb 24, 2024 · 9 comments
Labels
bug Something isn't working

Comments

@stanio
Copy link

stanio commented Feb 24, 2024

root-filter.svg:

<svg xmlns="http://www.w3.org/2000/svg"
    width="100" height="100" viewBox="0 0 100 100"
    filter="url(#drop-shadow)">
  <circle cx="50" cy="50" r="40" fill="cornflowerBlue" />
  <defs>
    <filter id="drop-shadow">
      <feGaussianBlur in="SourceAlpha" stdDeviation="2" />
      <feOffset dx="4" dy="4" result="offsetblur" />
      <feFlood flood-color="black" flood-opacity="0.5" />
      <feComposite in2="offsetblur" operator="in" />
      <feMerge>
        <feMergeNode />
        <feMergeNode in="SourceGraphic" />
      </feMerge>
    </filter>
  </defs>
</svg>
import java.io.File;

import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.geom.Dimension2D;
import java.awt.image.BufferedImage;

import javax.imageio.ImageIO;

import com.github.weisj.jsvg.SVGDocument;
import com.github.weisj.jsvg.parser.SVGLoader;

public class RootFilterTest {

    public static void main(String[] args) throws Exception {
        String inputName = "root-filter";
        SVGDocument svg = new SVGLoader()
                .load(new File(inputName + ".svg").toURI().toURL());
        Dimension2D size = svg.size();
        BufferedImage image = new BufferedImage((int) size.getWidth(),
                                                (int) size.getHeight(),
                                                BufferedImage.TYPE_INT_RGB);
        Graphics2D g = image.createGraphics();
        g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                           RenderingHints.VALUE_ANTIALIAS_ON);
        g.setColor(Color.WHITE);
        g.fillRect(0, 0, image.getWidth(), image.getHeight());
        svg.render(null, g);
        g.dispose();
        ImageIO.write(image, "png", new File(inputName + ".png"));
        System.out.println("Done.");
    }

}
Actual Expected
no-shadow with-shadow

Applying the filter on an all-encompassing g container works as expected:

<svg xmlns="http://www.w3.org/2000/svg"
    width="100" height="100" viewBox="0 0 100 100">
<g filter="url(#drop-shadow)">
  <circle cx="50" cy="50" r="40" fill="cornflowerBlue" />
</g>
  <defs>
    <filter id="drop-shadow">
      <feGaussianBlur in="SourceAlpha" stdDeviation="2" />
      <feOffset dx="4" dy="4" result="offsetblur" />
      <feFlood flood-color="black" flood-opacity="0.5" />
      <feComposite in2="offsetblur" operator="in" />
      <feMerge>
        <feMergeNode />
        <feMergeNode in="SourceGraphic" />
      </feMerge>
    </filter>
  </defs>
</svg>
@weisJ
Copy link
Owner

weisJ commented Feb 25, 2024

Resolved in 132bf20

weisJ added a commit that referenced this issue Feb 25, 2024
@stanio
Copy link
Author

stanio commented Feb 26, 2024

Tested with current 1.5.0-SNAPSHOT (1.5.0-20240226.002658) – now works as expected. Many thanks.

@stanio
Copy link
Author

stanio commented Feb 26, 2024

As a fun fact, I've just noticed Firefox (compared to Chrome, for example) doesn't appear to get this right, also.

@weisJ weisJ added the bug Something isn't working label Feb 26, 2024
stanio added a commit to stanio/stanio-misc that referenced this issue Feb 26, 2024
Employ JSVG 1.5.0-SNAPSHOT and clean up source now that both
weisJ/jsvg#61 and weisJ/jsvg#62 have been fixed.
@stanio
Copy link
Author

stanio commented Feb 29, 2024

This could be more tricky than it seems. I'm not sure which one is correct, but just want to save it for future reference. If I reduce the output size from the full view-box size:

<svg xmlns="http://www.w3.org/2000/svg"
    width="50" height="50" viewBox="0 0 100 100"
    filter="url(#drop-shadow)">

Chrome appears to apply different transformations to the filter itself compared to JSVG (zoom in the shadow blur and offset):

Chrome JSVG
Full size chrome-full-size jsvg-full-size
Half size chrome-half-size image

Chrome's result surprised me, though it could be by specification. It could happen to be some unspecified behavior, also.

If the filter is on an immediate child, Chrome and JSVG render the same:

<svg xmlns="http://www.w3.org/2000/svg"
    width="50" height="50" viewBox="0 0 100 100">
  <g filter="url(#drop-shadow)">
    ...
  </g>

@weisJ
Copy link
Owner

weisJ commented Feb 29, 2024

On first glance it looks like Chrome's behavior is wrong here. The specification for filters defines

primitiveUnits = "userSpaceOnUse | objectBoundingBox"

Specifies the coordinate system for the various length values within the filter primitives and for the attributes that define the filter primitive subregion.

If primitiveUnits is equal to userSpaceOnUse, ...

If primitiveUnits ...

The initial value for primitiveUnits is userSpaceOnUse.

filterUnits = "userSpaceOnUse | objectBoundingBox"

Defines the coordinate system for attributes x, y, width, height.

If filterUnits is equal to userSpaceOnUse, ....

If filterUnits is equal to objectBoundingBox, ...

The initial value for filterUnits is objectBoundingBox.

See https://drafts.fxtf.org/filter-effects/#FilterElement

Either the coordinate system when the filter is applied (which is the same for top level <svg> and an intermediate <g>).

Or the coordinate system used for filters is given by the bounding box of the object it is applied to.
The bounding box of an <svg> element is computed the same ways that for e.g <g>. See https://svgwg.org/svg2-draft/coords.html#TermObjectBoundingBox (specifically the algorithm to compute the bounding box. <svg> falls under a container element.)

To me this indicates the result should look the same as if the filter were defined on an intermediate <g> node.

@weisJ
Copy link
Owner

weisJ commented Feb 29, 2024

Just tested myself on Chrome 122.0.6261.94 and it appears that it behaves as jsvg. Which version of Chrome are you using?

@stanio
Copy link
Author

stanio commented Feb 29, 2024

I'm using Edge 122.0.2365.59 to preview the files – there I observe the problem, and then the bitmaps I've shown are generated by a build using Puppeteer (21.6.1, if I'm not mistaken) with a Chrome (could be Chromium) backend, the version of which I'm not aware of.

@weisJ weisJ closed this as completed Mar 11, 2024
@weisJ
Copy link
Owner

weisJ commented Mar 11, 2024

I decided that that the behaviour of Jsvg is the correct one in this scenario. If browser implementations don't even agree on how to handle it its probably better to steer away from making use of it anyway if one is concerned with cross-implementation compatibility.

@stanio
Copy link
Author

stanio commented Mar 11, 2024

Yep. I've settled on applying the filter on a <svg> child for the time being.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

2 participants