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

Add gradient support to SVG integration #357

Merged
merged 7 commits into from
Sep 12, 2023

Conversation

simbleau
Copy link
Member

Adds radial and linear gradient support.

image

Co-authored-by: Sebastian J. Hamel <[email protected]>
})
.collect();
let center: vello::kurbo::Point = gr.transform.apply(gr.cx, gr.cy).into();
// TODO: SVG has a scaleX and scaleY - But peniko::Gradient::new_radial only takes a radius.
Copy link
Member Author

Choose a reason for hiding this comment

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

Special attention to this - Maybe worth filing an issue for Vello to introduce radiusX and radiusY for radial gradients? SVG has rx and ry.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Based on the SVG spec (https://developer.mozilla.org/en-US/docs/Web/SVG/Element/radialGradient#attributes), there are actually two centers, start and end, which are represented by (fx, fy) and (cx, cy) respectively. In peniko/vello, this type of gradient can be created with the Gradient::new_two_point_radial constructor which does take two radii. Unfortunately, it doesn't look like usvg exposes the fr attribute (the focal point, or start circle radius) so I'd just pass the r attribute for both.

This would look something like Gradient::new_two_point_radial((gr.fx, gr.fy), gr.r, (gr.cx, gr.cy), gr.r).

For the transform, we can't apply it directly to the gradient properties because non-uniform scales or skews will change the geometry of the gradient. The vello rendering functions take an optional brush_transform parameter to handle this. We probably want to change the paint_to_brush function to return Option<(Brush, Option<Affine>)> so that gradient transforms can be passed along.

@simbleau simbleau changed the title Add gradient support Add gradient support to SVG integration Aug 20, 2023
Copy link
Collaborator

@dfrg dfrg left a comment

Choose a reason for hiding this comment

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

Thanks for tackling this!

Our gradient API doesn't match SVG (maybe it should?) so mapping these properties is a bit tricky. I've added some comments inline that will hopefully clarify how this might be done.

usvg::Paint::LinearGradient(_) => None,
usvg::Paint::RadialGradient(_) => None,
usvg::Paint::LinearGradient(gr) => {
let stops: Vec<Color> = gr
Copy link
Collaborator

Choose a reason for hiding this comment

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

I'd suggest converting these to peniko::ColorStop so we can also capture the offset field. As is, this only works properly if the stops are evenly spaced.

Some(Brush::Gradient(gradient))
}
usvg::Paint::RadialGradient(gr) => {
let stops: Vec<Color> = gr
Copy link
Collaborator

Choose a reason for hiding this comment

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

Same as above :)

})
.collect();
let center: vello::kurbo::Point = gr.transform.apply(gr.cx, gr.cy).into();
// TODO: SVG has a scaleX and scaleY - But peniko::Gradient::new_radial only takes a radius.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Based on the SVG spec (https://developer.mozilla.org/en-US/docs/Web/SVG/Element/radialGradient#attributes), there are actually two centers, start and end, which are represented by (fx, fy) and (cx, cy) respectively. In peniko/vello, this type of gradient can be created with the Gradient::new_two_point_radial constructor which does take two radii. Unfortunately, it doesn't look like usvg exposes the fr attribute (the focal point, or start circle radius) so I'd just pass the r attribute for both.

This would look something like Gradient::new_two_point_radial((gr.fx, gr.fy), gr.r, (gr.cx, gr.cy), gr.r).

For the transform, we can't apply it directly to the gradient properties because non-uniform scales or skews will change the geometry of the gradient. The vello rendering functions take an optional brush_transform parameter to handle this. We probably want to change the paint_to_brush function to return Option<(Brush, Option<Affine>)> so that gradient transforms can be passed along.

@simbleau
Copy link
Member Author

simbleau commented Sep 2, 2023

We should be good to go! Thanks for the assist.

One note: I don't know where you found fr (focal radius)? Is that customary or only in some rare SVGs?

The SVG I have with a radial gradient looks like this (notice how it only has r="1"):

<radialGradient id="paint0_radial_134_116" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(46.38 14.8357) rotate(88.0869) scale(6.82519 8.87416)">
<stop stop-color="#64B5CE"/>
<stop offset="1" stop-color="#A4DBED"/>
</radialGradient>

And actually, it looks even better! The radial gradient is correct now.
image

If you want the full fountain.svg:

<svg width="93" height="82" viewBox="0 0 93 82" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M91.9808 54.1354C87.8167 74.3638 67.001 81.872 44.9785 81.872C24.1054 81.872 0.878906 70.2195 0.878906 51.6722C0.879149 29.261 20.5751 20.2507 46.2329 20.2507C71.8907 20.2507 96.1449 33.907 91.9808 54.1354Z" fill="#A9A8A8"/>
<path d="M91.43 46.0117C91.43 61.757 66.7709 73.8117 46.9013 73.8117C26.2102 73.8117 1.59497 61.757 1.59497 46.0117C1.59497 30.2665 25.2756 17.5024 45.9667 17.5024C66.6577 17.5024 91.43 30.2665 91.43 46.0117Z" fill="#BFBEBE"/>
<path d="M84.9454 43.9409C84.9454 56.4318 71.8627 67.1684 45.5368 67.1684C28.5098 67.1684 7.69491 56.4318 7.69491 43.9409C7.69491 31.45 29.5423 20.2507 46.5694 20.2507C63.5964 20.2507 84.9454 31.45 84.9454 43.9409Z" fill="#ACA9A9"/>
<path d="M84.509 45.223C84.509 57.7139 71.5534 68.4505 45.4834 68.4505C28.6218 68.4505 8.0093 57.7139 8.0093 45.223C8.0093 32.732 29.6444 21.5327 46.5059 21.5327C63.3675 21.5327 84.509 32.732 84.509 45.223Z" fill="#64B5CE"/>
<path d="M84.5089 45.2229C84.5089 57.7139 71.5533 68.4505 45.4833 68.4505C28.6217 68.4505 8.00918 57.7139 8.00918 45.2229C8.00918 32.732 29.6442 21.5327 46.5058 21.5327C63.3674 21.5327 84.5089 32.732 84.5089 45.2229Z" fill="#64B5CE"/>
<path d="M46.3361 48.8465C44.1574 48.8465 43.3089 49.0639 43.133 47.7982V21.5327L50.071 22.016V47.5275C50.071 48.0096 49.9177 48.5202 49.4826 48.7277C48.6453 49.1268 47.5563 48.8465 46.3361 48.8465Z" fill="#A9A8A8"/>
<path d="M60.211 18.1626C58.9662 23.9797 52.7436 26.1388 46.1602 26.1388C39.9204 26.1388 32.9772 22.7879 32.9772 17.4542C32.9772 11.0094 38.8651 8.41832 46.5352 8.41832C54.2053 8.41832 61.4558 12.3455 60.211 18.1626Z" fill="#9B9696"/>
<path d="M60.211 17.7415C58.9662 23.5586 52.7436 25.7177 46.1602 25.7177C39.9204 25.7177 32.9772 22.3668 32.9772 17.0332C32.9772 10.5883 38.8651 7.99723 46.5352 7.99723C54.2053 7.99723 61.4558 11.9244 60.211 17.7415Z" fill="#A9A8A8"/>
<path d="M59.8057 14.9007C59.8057 18.992 51.9519 22.1243 46.7163 22.1243C40.634 22.1243 33.3983 18.992 33.3983 14.9007C33.3983 10.8094 40.3593 7.49277 46.4416 7.49277C52.5238 7.49277 59.8057 10.8094 59.8057 14.9007Z" fill="#BFBEBE"/>
<path d="M36.5459 17.1594C33.5432 13.8839 36.9937 11.1944 38.0094 10.4367C39.0252 9.67907 38.7318 10.2488 38.4271 9.52616C38.2604 9.13085 39.9293 8.32143 41.9425 7.89755C42.4925 7.70297 42.6876 8.47467 43.5767 8.51455C44.4657 8.55444 46.4743 7.71209 45.5006 8.51455C46.4625 8.08923 49.2086 8.08748 50.1986 8.5148C51.1885 8.94211 50.5475 7.22738 54.9323 9.3531C55.5248 9.64039 54.7174 10.2968 55.3354 10.4367C57.7754 10.9891 58.9837 15.3597 57.1747 16.9097C56.6943 17.3213 58.3376 17.8867 59.2515 20.7533C58.9665 21.0788 58.3376 24.2524 53.4215 25.0402C52.287 20.9857 51.1083 20.2204 51.1083 20.2204C51.1083 20.2204 48.8318 21.0699 46.9146 21.0699C44.9973 21.0699 43.4515 20.6628 42.6678 20.7533C41.498 21.4489 40.7961 23.0086 40.4451 25.0402C40.108 25.8291 35.2598 22.9307 34.4791 21.6828C34.245 18.6412 36.9176 17.565 36.5459 17.1594Z" fill="url(#paint0_radial_134_116)"/>
<path d="M46.3982 14.8987C45.5426 14.8987 45.2094 14.9793 45.1403 14.5103V4.77841L47.865 4.95747V13.9064C47.865 14.4084 47.6531 14.9391 47.1512 14.9341C46.9222 14.9318 46.6684 14.8987 46.3982 14.8987Z" fill="#A9A8A8"/>
<path d="M51.9818 4.00475C51.493 6.16011 49.0492 6.96011 46.4638 6.96011C44.0134 6.96011 41.2866 5.71853 41.2866 3.74229C41.2866 1.35435 43.5989 0.394287 46.6111 0.394287C49.6233 0.394287 52.4707 1.84939 51.9818 4.00475Z" fill="#9B9696"/>
<path d="M51.9819 3.7716C51.4931 5.92696 49.0494 6.72696 46.464 6.72696C44.0135 6.72696 41.2867 5.48538 41.2867 3.50914C41.2868 1.1212 43.599 0.161137 46.6112 0.161137C49.6234 0.161137 52.4708 1.61624 51.9819 3.7716Z" fill="#A9A8A8"/>
<path d="M51.8227 2.79615C51.8227 4.31206 48.7383 5.47265 46.6822 5.47265C44.2936 5.47265 41.452 4.31206 41.452 2.79615C41.452 1.28024 44.1857 0.0513535 46.5743 0.0513535C48.9629 0.0513535 51.8227 1.28024 51.8227 2.79615Z" fill="#BFBEBE"/>
<path d="M56.4985 48.492C56.4985 44.8686 54.4566 25.6827 52.1929 21.4477L57.1089 17.0358C60.4901 18.8437 65.8972 34.0125 65.8972 44.079C64.3745 46.8706 61.9213 48.492 56.4985 48.492Z" fill="url(#paint1_linear_134_116)"/>
<path d="M37.3097 48.492C37.3097 44.8686 39.3516 25.6827 41.6154 21.4477L36.6993 17.0358C33.3181 18.8437 27.911 34.0125 27.911 44.079C29.4337 46.8706 31.8869 48.492 37.3097 48.492Z" fill="url(#paint2_linear_134_116)"/>
<rect x="33.2376" y="25.0652" width="1" height="1" rx="0.5" transform="rotate(6.31699 33.2376 25.0652)" fill="#C2EAF6"/>
<rect width="1" height="1" rx="0.5" transform="matrix(-0.993928 0.110029 0.110029 0.993928 61.1137 25.0652)" fill="#C2EAF6"/>
<rect x="38.4273" y="23.6275" width="1" height="1" rx="0.5" transform="rotate(6.31699 38.4273 23.6275)" fill="#C2EAF6"/>
<rect width="1" height="1" rx="0.5" transform="matrix(-0.993928 0.110029 0.110029 0.993928 54.8655 22.7397)" fill="#C2EAF6"/>
<rect x="36.2194" y="25.3953" width="1" height="1" rx="0.5" transform="rotate(6.31699 36.2194 25.3953)" fill="#C2EAF6"/>
<rect width="1" height="1" rx="0.5" transform="matrix(-0.993928 0.110029 0.110029 0.993928 58.1319 25.3953)" fill="#C2EAF6"/>
<rect x="35.4492" y="32.3528" width="1" height="1" rx="0.5" transform="rotate(6.31699 35.4492 32.3528)" fill="#C2EAF6"/>
<rect width="1" height="1" rx="0.5" transform="matrix(-0.993928 0.110029 0.110029 0.993928 58.9021 32.3528)" fill="#C2EAF6"/>
<rect x="30.9233" y="36.8823" width="1" height="1" rx="0.5" transform="rotate(6.31699 30.9233 36.8823)" fill="#C2EAF6"/>
<rect width="1" height="1" rx="0.5" transform="matrix(-0.993928 0.110029 0.110029 0.993928 63.428 36.8823)" fill="#C2EAF6"/>
<rect x="32.2474" y="34.0106" width="1" height="1" rx="0.5" transform="rotate(6.31699 32.2474 34.0106)" fill="#C2EAF6"/>
<rect width="1" height="1" rx="0.5" transform="matrix(-0.993928 0.110029 0.110029 0.993928 62.1039 34.0106)" fill="#C2EAF6"/>
<rect x="35.6729" y="39.4203" width="1" height="1" rx="0.5" transform="rotate(6.31699 35.6729 39.4203)" fill="#C2EAF6"/>
<rect width="1" height="1" rx="0.5" transform="matrix(-0.993928 0.110029 0.110029 0.993928 58.6784 39.4203)" fill="#C2EAF6"/>
<rect x="32.3611" y="42.072" width="1" height="1" rx="0.5" transform="rotate(6.31699 32.3611 42.072)" fill="#C2EAF6"/>
<rect width="1" height="1" rx="0.5" transform="matrix(-0.993928 0.110029 0.110029 0.993928 61.9902 42.072)" fill="#C2EAF6"/>
<defs>
<radialGradient id="paint0_radial_134_116" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(46.38 14.8357) rotate(88.0869) scale(6.82519 8.87416)">
<stop stop-color="#64B5CE"/>
<stop offset="1" stop-color="#A4DBED"/>
</radialGradient>
<linearGradient id="paint1_linear_134_116" x1="59.1599" y1="18.7006" x2="59.1599" y2="48.492" gradientUnits="userSpaceOnUse">
<stop stop-color="#A4DBED"/>
<stop offset="1" stop-color="#A4DBED" stop-opacity="0"/>
</linearGradient>
<linearGradient id="paint2_linear_134_116" x1="34.6484" y1="18.7006" x2="34.6484" y2="48.492" gradientUnits="userSpaceOnUse">
<stop stop-color="#A4DBED"/>
<stop offset="1" stop-color="#A4DBED" stop-opacity="0"/>
</linearGradient>
</defs>
</svg>

Copy link
Collaborator

@dfrg dfrg left a comment

Choose a reason for hiding this comment

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

Apologies for the delay. I’m currently out of the country on a work trip. Nice job catching the opacity issue. I noticed it but haven’t had time to review.

This PR looks good to me now. Thanks!

@dfrg
Copy link
Collaborator

dfrg commented Sep 12, 2023

One note: I don't know where you found fr (focal radius)? Is that customary or only in some rare SVGs?

It’s part of the SVG spec and is mentioned in the Mozilla docs linked above. I’m not sure how common it is in practice but we implemented it to handle COLRv1 gradients which also require it. I suspect it’s not exposed in usvg because tinyskia isn’t capable of rendering full two point conical gradients.

@simbleau simbleau merged commit 0d5a926 into linebender:main Sep 12, 2023
4 checks passed
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.

2 participants