Repository navigation
[vector_graphics_compiler] Support SVG alpha masks (mask-type="alpha") - #13025
Conversation
There was a problem hiding this comment.
Code Review
This pull request adds support for SVG alpha masks (mask-type="alpha") in the vector graphics compiler by parsing and propagating the maskType attribute through the optimizer and visitor passes, and rendering it using a save layer with BlendMode.dstIn. The review feedback suggests restricting the single-path mask optimization for luminance masks to only solid white paths to prevent visual regressions, and trimming and lowercasing the parsed mask-type attribute to ensure robust, case-insensitive matching.
| final bool canOptimizeSingleMaskPath = | ||
| singleMaskPathNode != null && | ||
| (maskNode.maskType != 'alpha' || | ||
| (singleMaskPathNode.paint.fill?.shader == null && | ||
| singleMaskPathNode.paint.fill?.color.a == 255 && | ||
| singleMaskPathNode.paint.stroke == null)); |
There was a problem hiding this comment.
For luminance masks (when maskType != 'alpha'), optimizing a single-path mask to a clip path is only correct if the mask is solid white (i.e., its color is 0xffffffff and it has no shader or stroke). If the luminance mask has a gradient or a non-white color, its luminance is not binary 1.0/0.0, and optimizing it to a clip path will discard the luminance/gradient information, causing visual regressions. We should restrict the optimization for luminance masks to only solid white paths.
final bool canOptimizeSingleMaskPath = singleMaskPathNode != null &&
(maskNode.maskType == 'alpha'
? (singleMaskPathNode.paint.fill?.shader == null &&
singleMaskPathNode.paint.fill?.color.a == 255 &&
singleMaskPathNode.paint.stroke == null)
: (singleMaskPathNode.paint.fill?.shader == null &&
singleMaskPathNode.paint.fill?.color == const Color(0xffffffff) &&
singleMaskPathNode.paint.stroke == null));There was a problem hiding this comment.
Please respond, point seems valid.
There was a problem hiding this comment.
Good question! We looked into this carefully: in the existing compiler on main, MaskingOptimizer intentionally simplifies single-path luminance masks even when they have gradients or non-white colors (e.g. existing test Handles masks with blends and gradients correctly explicitly tests that single-path luminance masks with gradients are optimized to path intersections). Restricting luminance masks to solid white 0xffffffff without shaders would break existing optimization behavior for standard luminance masks.
For alpha masks (which this PR introduces), checking singleMaskPathNode.paint.fill?.color.a == 255 ensures we only optimize when the alpha mask is fully opaque, preserving partial opacity, transparent gradients (e.g. stop-opacity < 1.0), and group opacity. Retaining maskNode.maskType != MaskType.alpha || ... keeps existing luminance behavior intact while correctly protecting semi-transparent alpha masks.
There was a problem hiding this comment.
I would suggest creating a separate issue to deal with doing the right thing for luminance masks. It may be existing behavior, but it is not the right behavior, is it?
Also, an optimization that asks the shader if it is "all opaque" might expand the cases where we can use the optimization. We might have to pessimistically respond "no" for custom shaders, but the existing shaders could simply check their colors and tile modes to know if they fully opacify the path.
There was a problem hiding this comment.
Agreed on both points. I can file a follow up issue to track the luminance mask behavior and shader opacity check.
gaaclarke
left a comment
There was a problem hiding this comment.
- Can we add golden tests to flutter_svg? I'm unfamiliar with them but it looks like we have the ability to execute them. (follow up to this pr, see https://github.com/flutter/packages/tree/main/third_party/packages/flutter_svg/test/golden_widget)
- Does
<mask mask-type="alpha"><g opacity="0.5"><rect fill="black"/></g></mask>handle the opacity correctly? The agent seemed to think it will get lost
| final bool canOptimizeSingleMaskPath = | ||
| singleMaskPathNode != null && | ||
| (maskNode.maskType != 'alpha' || | ||
| (singleMaskPathNode.paint.fill?.shader == null && | ||
| singleMaskPathNode.paint.fill?.color.a == 255 && | ||
| singleMaskPathNode.paint.stroke == null)); |
There was a problem hiding this comment.
Please respond, point seems valid.
466b9cb to
b0c037a
Compare
|
Thanks for the review @gaaclarke! I've addressed all the feedback and pushed an update:
|
|
@edwin-hollen friendly reminder please hit "rerequest review"when you are ready. (i did it for you this time) |
BTW, who is "we"? |
gaaclarke
left a comment
There was a problem hiding this comment.
Okay, looking good.
Golden tests in flutter_svg: Yes, definitely! I will follow up with golden tests in flutter_svg once this compiler PR lands.
Let's do that as part of this PR.
Whoops, 'we' is what happens when I try to use a LLM to write my replies to varying levels of success 😆 I'm working on preparing the goldens this morning! |
|
Done! I've added golden widget tests for SVG alpha masks in
I also added a |
b0c037a to
7ceab9e
Compare
| # FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE. | ||
| # See https://github.com/flutter/flutter/blob/master/docs/ecosystem/contributing/README.md#changing-federated-plugins | ||
| dependency_overrides: | ||
| vector_graphics_compiler: {path: ../../../packages/vector_graphics_compiler} |
There was a problem hiding this comment.
Thanks for making this and proving correctness. I think we may have to land this as a separate PR though. We can't land it like this and we have to list a version number which can't happen until the other PR lands. Github has stacked PRs we might be able to use.
There was a problem hiding this comment.
Thanks! I've removed the flutter_svg changes from this PR so vector_graphics_compiler (1.3.1) can land first. Since PRs are opened from a fork and the repository currently has a 2-open-PR limit per author, I've saved the flutter_svg golden tests on a separate branch (upstream-svg-alpha-mask-goldens) and will open that follow-up PR as soon as this lands and vector_graphics_compiler 1.3.1 is published.
|
@flar can you be the second reviewer on this please? |
breaking out my Dart/Flutter decoder ring... |
flar
left a comment
There was a problem hiding this comment.
LGTM with some questions. I'll approve pending the answers...
| if (node is ResolvedPathNode) { | ||
| return node; | ||
| } else if (node is ParentNode && node.children.length == 1) { | ||
| } else if (node is ParentNode && node is! SaveLayerNode && node.children.length == 1) { |
There was a problem hiding this comment.
This appears unrelated to resolving masks, should it be separated into a different PR?
There was a problem hiding this comment.
Hi, thanks for reviewing! This is needed for alpha masks with group opacity (e.g. <mask mask-type="alpha"><g opacity="0.5"><path .../></g></mask>). The <g opacity="0.5"> node resolves to a SaveLayerNode. Without node is! SaveLayerNode, getSingleChild walks through the SaveLayerNode and extracts the inner opaque ResolvedPathNode, causing MaskingOptimizer to turn the mask into a path intersection and drop the group opacity.
| final bool canOptimizeSingleMaskPath = | ||
| singleMaskPathNode != null && | ||
| (maskNode.maskType != 'alpha' || | ||
| (singleMaskPathNode.paint.fill?.shader == null && | ||
| singleMaskPathNode.paint.fill?.color.a == 255 && | ||
| singleMaskPathNode.paint.stroke == null)); |
There was a problem hiding this comment.
I would suggest creating a separate issue to deal with doing the right thing for luminance masks. It may be existing behavior, but it is not the right behavior, is it?
Also, an optimization that asks the shader if it is "all opaque" might expand the cases where we can use the optimization. We might have to pessimistically respond "no" for custom shaders, but the existing shaders could simply check their colors and tile modes to know if they fully opacify the path.
| case 'luminance': | ||
| return MaskType.luminance; | ||
| } | ||
| return null; |
There was a problem hiding this comment.
The SVG mask attribute mentioned a default of luminance, is this enforced somewhere else?
There was a problem hiding this comment.
Yes! parseMaskType returns null when omitted so SvgAttributes.applyParent can inherit parent.maskType when unspecified. Downstream in visitor.dart (visitResolvedMaskNode) and masking_optimizer.dart, any maskType other than MaskType.alpha (null or MaskType.luminance) defaults to the luminance branch (_builder.addMask()).
flutter/packages@d8d9e97...a1ed23c 2026-10-13 [email protected] [material_ui] Clarify Slider labels for continuous sliders (flutter/packages#13121) 2026-10-12 [email protected] [in_app_purchase_storekit] Remove availability checks below the package minimum iOS 13 and macOS 10.15 (flutter/packages#13217) 2026-10-12 [email protected] [google_sign_in] Convert unit tests to Kotlin (flutter/packages#13000) 2026-10-12 [email protected] [vector_graphics_compiler] Support SVG alpha masks (mask-type="alpha") (flutter/packages#13025) 2026-10-11 [email protected] [camera_platform_interface] Add zero-shutter-lag API (flutter/packages#12996) 2026-10-10 [email protected] [material_ui] Ensure textScaler is taken into account for SnackBar action overflow (flutter/packages#12737) 2026-10-10 [email protected] [material_ui] Add more 0x0 size tests part 14 (flutter/packages#13107) 2026-10-10 [email protected] [cupertino_ui] Fix CupertinoPicker throwing when disposed during the tap-to-select animation (flutter/packages#13012) 2026-10-09 [email protected] Roll Flutter (stable) from 5fc3468 to abaf9c5 (3 revisions) (flutter/packages#13186) If this roll has caused a breakage, revert this CL and set the roller to dry run mode using the controls here: https://autoroll.skia.org/r/flutter-packages-flutter-autoroll Please CC [email protected] on the revert to ensure that a human is aware of the problem. To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md
Adds golden widget tests in `flutter_svg` for SVG alpha masks (`mask-type="alpha"` and `style="mask-type:alpha"`) and bumps the `vector_graphics_compiler` dependency constraint to `^1.3.1`. Follow-up to #13025 Part of flutter/flutter#158734 ### Added Golden Tests * `alpha_mask.opaque_style.png`: Opaque `#000000` mask using inline CSS `style="mask-type:alpha"`. * `alpha_mask.fill_opacity.png`: Semi-transparent mask using `mask-type="alpha"` and `fill-opacity="0.5"`. * `alpha_mask.group_opacity.png`: Group opacity inside an alpha mask (`<mask mask-type="alpha"><g opacity="0.5">...</g></mask>`). * `alpha_mask.gradient.png`: Linear gradient alpha mask fading `stop-opacity` from `1` to `0`. ## Pre-Review Checklist
flutter/packages@d8d9e97...a1ed23c 2026-10-13 [email protected] [material_ui] Clarify Slider labels for continuous sliders (flutter/packages#13121) 2026-10-12 [email protected] [in_app_purchase_storekit] Remove availability checks below the package minimum iOS 13 and macOS 10.15 (flutter/packages#13217) 2026-10-12 [email protected] [google_sign_in] Convert unit tests to Kotlin (flutter/packages#13000) 2026-10-12 [email protected] [vector_graphics_compiler] Support SVG alpha masks (mask-type="alpha") (flutter/packages#13025) 2026-10-11 [email protected] [camera_platform_interface] Add zero-shutter-lag API (flutter/packages#12996) 2026-10-10 [email protected] [material_ui] Ensure textScaler is taken into account for SnackBar action overflow (flutter/packages#12737) 2026-10-10 [email protected] [material_ui] Add more 0x0 size tests part 14 (flutter/packages#13107) 2026-10-10 [email protected] [cupertino_ui] Fix CupertinoPicker throwing when disposed during the tap-to-select animation (flutter/packages#13012) 2026-10-09 [email protected] Roll Flutter (stable) from 5fc3468 to abaf9c5 (3 revisions) (flutter/packages#13186) If this roll has caused a breakage, revert this CL and set the roller to dry run mode using the controls here: https://autoroll.skia.org/r/flutter-packages-flutter-autoroll Please CC [email protected] on the revert to ensure that a human is aware of the problem. To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md
[vector_graphics_compiler] Support SVG alpha masks (
mask-type="alpha")Fixes flutter/flutter#158734
This PR adds support for SVG alpha masks (
mask-type="alpha"andstyle="mask-type: alpha") invector_graphics_compiler, split out from #11909 per review feedback.Summary of Changes
vector_graphics_compiler):mask-typeattribute (and inlinestyle="mask-type: ..."property) on<mask>elements inSvgParser.isAlphaMaskthroughResolvedMaskNodeandMaskNode.BlendMode.dstInfor alpha masks instead ofBlendMode.srcInwith a luminance-to-alpha color filter matrix (BlendMode.modulate).MaskingOptimizerso single-path masks using gradients with translucent stops (stop-opacity < 1.0) are preserved rather than replaced with solid geometry intersection (which previously discarded the gradient's alpha channel).Pre-Review Checklist
[shared_preferences]///).If you need help, consider asking for advice on the #hackers-new channel on Discord.
Note: The Flutter team is currently trialing the use of Gemini Code Assist for GitHub. Comments from the
gemini-code-assistbot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed.TAG=agy
CONV=1bad96cc-81aa-438f-98f6-cb909289144a
Footnotes
Regular contributors who have demonstrated familiarity with the repository guidelines only need to comment if the PR is not auto-exempted by repo tooling. ↩ ↩2