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

Use the view bounds as the intrinsic size of an SVG in SvgDecoder. #688

Merged
merged 3 commits into from
Mar 4, 2021

Conversation

colinrtwhite
Copy link
Member

Currently we use an SVG's width/height to determine its intrinsic size and its aspect ratio. However, we should actually be using its viewBox since that's the actual size of the rendered content. e.g. we could have an SVG that's 500x500, but a view box that's only the center 100x200 pixels. In that case the aspect ratio of the rendered content is 1x2 and not 1x1.

Also adds a useViewBoundsAsIntrinsicSize constructor param to opt out of the changes if it causes issues.

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" width="200px"
height="200px" viewBox="332.5 200 140 140" xml:space="preserve">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" width="1000px"
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changing this so the test cases fail if it uses the width/height for the aspect ratio.

@colinrtwhite colinrtwhite requested a review from Jawnnypoo March 4, 2021 07:32
class SvgDecoder(private val context: Context) : Decoder {
class SvgDecoder @JvmOverloads constructor(
private val context: Context,
private val useViewBoundsAsIntrinsicSize: Boolean = true
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it okay that this changes behavior for those who are using this currently? Maybe it should default to false until a 1.2 or a 2.0 so that folks don't update their library and end up with unexpected behavior? Otherwise, I'd just make sure its communicated clearly in the CHANGELOG.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Jawnnypoo Good point. Will call this out at the top of the release notes.

I'm definitely for only enabling this in 1.2 (which will be the next release). 2.0 is still a ways away so I'd want to introduce the behaviour change before then. Also I'm hoping this change will be invisible for most users as for most SVGs width/height already has the same aspect ratio as viewBox. This change mostly improves the handling for weird SVGs that Coil wouldn't render properly.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gotcha! Thanks for clarifying 👍

@colinrtwhite colinrtwhite merged commit 24bdab7 into master Mar 4, 2021
@colinrtwhite colinrtwhite deleted the colin/svg_view_bounds branch March 4, 2021 19:20
@zhanghai
Copy link
Contributor

zhanghai commented Apr 13, 2021

Could you clarify why should the intrinsic size of an SVG be its view box size (if present)? You mentioned in changelog that this way it would "correctly follow the SVG specification", but I couldn't find relevant specification in W3C SVG Specification.

Meanwhile, I tried to create an SVG with width 24, height 24 and a view box of 0 0 6 24, so that the size and view box have different aspect ratio. Both Nautilus (GNOME Files) file properties and EOG (GNOME Image Viewer) reported a image size of 24x24, and both EOG and Inkscape displayed the SVG in 1:1 aspect ratio instead of the 1:4 from view box.

<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 6 24" version="1.1">
    <path fill="#000000" d="M 10,5 V 19 H -4 V 5 H 10 M 10,3 H -4 c -1.1,0 -2,0.9 -2,2 v 14 c 0,1.1 0.9,2 2,2 h 14 c 1.1,0 2,-0.9 2,-2 V 5 C 12,3.9 11.1,3 10,3 Z m -4.86,8.86 -3,3.87 L 0,13.14 -3,17 H 9 Z" />
</svg>

@colinrtwhite
Copy link
Member Author

@zhanghai Sorry, the title of this PR isn't accurate. This PR changes the logic so the view box is used to compute the aspect ratio to fit into the target size. For example, if we have an SVG that is 100x200, but the view box is only 100x100, the image actually only has a 1x1 aspect ratio (everything outside of the view box isn't rendered).

If the target size is OriginalSize, the width/height elements of the SVG are used as expected.

@zhanghai
Copy link
Contributor

zhanghai commented Apr 13, 2021

I see, thanks for the clarification about using view box for fitting the target size. However, it still seems to me the original behavior is correct.

For example, if we have an SVG that is 100x200, but the view box is only 100x100, the image actually only has a 1x1 aspect ratio (everything outside of the view box isn't rendered).

This doesn't sound correct according to my understanding of SVG.

  1. Although the view box may be smaller, it's still perfectly valid for content to be drawn outside the view box and be shown in the final SVG, because:

    1. the viewBox attribute is essentially just a shorthand for a transformation matrix:

      The effect of the ‘viewBox’ attribute is that the user agent automatically supplies the appropriate transformation matrix to map the specified rectangle in user space to the bounds of a designated region (often, the viewport).

      https://www.w3.org/TR/SVG11/coords.html#ViewBoxAttribute

    2. the default value for preserveAspectRatio (when viewBox is present) is xMidYMid instead of none, so that uniform scaling is performed and the view box is centered, leaving additional space for drawing.

      • xMidYMid (the default) - Force uniform scaling.
        Align the midpoint X value of the element's ‘viewBox’ with the midpoint X value of the viewport.
        Align the midpoint Y value of the element's ‘viewBox’ with the midpoint Y value of the viewport.

      https://www.w3.org/TR/SVG11/coords.html#PreserveAspectRatioAttribute

      (One potential source of confusion is that for Android VectorDrawable XML files, there is no preserveAspectRatio equivalent and android:viewportWidth & android:viewportHeight behaves as if preserveAspectRatio is none. But Android VectorDrawable XML isn't SVG.)

    3. The SVG file in my previous comment is an example of this (It's the Material Design icon for "image", and obviously draws outside its view box), and I've confirmed in EOG and Inkscape that they treat the SVG file as if the view box is just a transformation matrix, and nothing else (size, aspect ratio, zoom) is affected by the view box.

      <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 6 24" version="1.1">
          <path fill="#000000" d="M 10,5 V 19 H -4 V 5 H 10 M 10,3 H -4 c -1.1,0 -2,0.9 -2,2 v 14 c 0,1.1 0.9,2 2,2 h 14 c 1.1,0 2,-0.9 2,-2 V 5 C 12,3.9 11.1,3 10,3 Z m -4.86,8.86 -3,3.87 L 0,13.14 -3,17 H 9 Z" />
      </svg>
  2. Even if no content is drawn outside of the view box, it is still perfectly valid for an SVG file to utilize the view box for transformation, and it should be handled as if the transformation were applied at a root group instead of as the view box.

    (i.e. I didn't find anything in the specification that says the viewBox attribute should be the viewport of the user agent, and on the contrary, the specification seems to suggest the viewBox attribute is just another way to specify a transformation. The value for viewBox are the coordinates pre-tranform, but it's the post-tranform coordinates that should be rendered and respected.)

    Take this SVG file for an ellipse as an example:

        <svg xmlns="http://www.w3.org/2000/svg" width="48" height="24" viewBox="0 0 24 24" preserveAspectRatio="none" version="1.1">
            <circle cx="12" cy="12" fill="#000000" r="12" />
        </svg>

    It should be handled exactly like the following equivalent SVG file:

        <svg xmlns="http://www.w3.org/2000/svg" width="48" height="24" version="1.1">
            <g transform="scale(2, 1)">
                <circle cx="12" cy="12" fill="#000000" r="12" />
            </g>
        </svg>

    I've confirmed that both EOG and Inkscape treats the two SVG files in exactly the same way.

    This is actually also covered similarly in the specification's discussion of how the user agent may implement viewBox with transform:

    ... The effect is equivalent to having a viewport of size 300px by 200px and the following supplemental transformation in the document, as follows:

    <?xml version="1.0" standalone="no"?>
    <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" 
      "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
    <svg width="300px" height="200px" version="1.1"
         xmlns="http://www.w3.org/2000/svg">
      <g transform="scale(0.2)">
        <!-- Rest of document goes here -->
      </g>
    </svg>
    

    https://www.w3.org/TR/SVG11/coords.html#ViewBoxAttribute

  3. Even if some non-standard handling of viewBox for fitting the target size is desired for some reason, it should actually respect the value of preserveAspectRatio to be a correct implementation.

    The current implementation would return an incorrect result if preserveAspectRatio is set to none, in which case the aspect ratio from the width and height attributes should definitely be used instead of the one from viewBox.

    I still don't see why we need non-standard handling of viewBox for fitting the target size though.

colinrtwhite added a commit that referenced this pull request Oct 5, 2022
)

* Prefer view bounds as size in SvgDecoder.

* Adjust algorithm.

* Tweak test SVG.
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

Successfully merging this pull request may close these issues.

3 participants