Skip to content

Conversation

@tvolkert
Copy link
Contributor

Description

This avoids an unnecessary paint operation if you create a ColoredBox with a fully transparent color.

Use case

If apps want to toggle a color on and off, they might want to conditionally include a ColoredBox in their widget tree. But doing so will cause a rebuild() and a layout() every time they toggle the color. By including the ColoredBox unconditionally in their widget tree and only changing the color, they will only trigger a repaint() every time they toggle the color. However, in this case, they'll be frequently using a fully transparent color and would like to avoid the associated dart:ui operation.

Tests

I added the following tests:

  • A test that verifies that we do not issue a dart:ui paint operation in this case.

Checklist

  • I read the [Contributor Guide] and followed the process outlined there for submitting PRs.
  • I signed the [CLA].
  • I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement].
  • I read the [Tree Hygiene] wiki page, which explains my responsibilities.
  • I updated/added relevant documentation (doc comments with ///).
  • All existing and new tests are passing.
  • The analyzer (flutter analyze --flutter-repo) does not report any problems on my PR.
  • I am willing to follow-up on review comments in a timely manner.

Breaking Change

  • No, no existing tests failed, so this is not a breaking change.

@flutter-dashboard flutter-dashboard bot added the framework flutter/packages/flutter repository. See also f: labels. label Dec 17, 2020
@google-cla google-cla bot added the cla: yes label Dec 17, 2020
@skia-gold
Copy link

Gold has detected about 1 untriaged digest(s) on patchset 1.
View them at https://flutter-gold.skia.org/cl/github/72526

dnfield
dnfield previously approved these changes Dec 17, 2020
Copy link
Contributor

@dnfield dnfield left a comment

Choose a reason for hiding this comment

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

LGTM

This makes me wonder if we could/should optimize dart:ui Canvas operations that are given a paint that results in nothing being drawn - or, if not optimize, warn about it.

@dnfield
Copy link
Contributor

dnfield commented Dec 17, 2020

Hmm. That golden diff is making me wonder if this is somehow breaking non-0 alpha painting...

@flutter-dashboard
Copy link

Golden file changes have been found for this pull request. Click here to view and triage (e.g. because this is an intentional change).

If you are still iterating on this change and are not ready to resolve the images on the Flutter Gold dashboard, consider marking this PR as a draft pull request above. You will still be able to view image results on the dashboard, commenting will be silenced, and the check will not try to resolve itself until marked ready for review.

For more guidance, visit Writing a golden file test for package:flutter.

Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing.

Changes reported for pull request #72526 at sha 66c5f7e3ba2286230d394c8337eefe40e553e6af

@flutter-dashboard flutter-dashboard bot added the will affect goldens Changes to golden files label Dec 17, 2020
@tvolkert
Copy link
Contributor Author

tvolkert commented Dec 17, 2020

Hmm. That golden diff is making me wonder if this is somehow breaking non-0 alpha painting...

The golden failure is happening because when we use StretchMode.blurBackground, it triggers the use of a BackdropFilter, which pushes a BackdropFilterLayer . Then that layer causes paints of fully transparent colors to affect other layers.

Is the current behavior working as intended? 🤔 It seems counterintuitive that painting a transparency could do anything...

@cbracken
Copy link
Member

/cc @flar who would be able to provide details on the BackdropFilter side of things.

@dnfield
Copy link
Contributor

dnfield commented Dec 17, 2020

I think this is probably working as intended, although it's a bit odd.

The patch that added this was 11e1c24 (@Piinks). If I'm reading it right (and making other correct assumptions), we could use a SizedBox there because then the BDF wouldn't actually apply a blur.

e.g.

import 'dart:ui';
import 'package:flutter/material.dart';

final Color darkBlue = Color.fromARGB(255, 18, 32, 47);

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData.dark().copyWith(scaffoldBackgroundColor: darkBlue),
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        body: Center(
          child: MyWidget(),
        ),
      ),
    );
  }
}

class MyWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: <Widget>[
        SizedBox(
          height: 200,
          width: 200,
          child: ClipRect(
            child: Stack(
              children: <Widget>[
                Positioned.fill(child: Text('Hello 1')),
                Positioned.fill(
                  child: BackdropFilter(
                    filter: ImageFilter.blur(sigmaX: 2.0, sigmaY: 2.0),
                    child: Container(color: Colors.transparent),
                  ),
                ),
              ],
            ),
          ),
        ),
        Divider(),
        SizedBox(
          height: 200,
          width: 200,
          child: ClipRect(
            child: Stack(
              children: <Widget>[
                Positioned.fill(child: Text('Hello 2')),
                Positioned.fill(
                  child: BackdropFilter(
                    filter: ImageFilter.blur(sigmaX: 2.0, sigmaY: 2.0),
                    child: Container(),
                  ),
                ),
              ],
            ),
          ),
        ),
      ],
    );
  }
}

Unfortunately, this program behaves differently on web (DOM) and VM. I assume CanvasKit would behave like VM but haven't checked.

@dnfield dnfield dismissed their stale review December 17, 2020 20:54

Should resolve interaction with BDF first

@dnfield
Copy link
Contributor

dnfield commented Dec 17, 2020

@flar probably will know more, but I wonder if this has anything to do with premul/unpremul colors.

Colors.transparent is actually transparent black. I know for gradients this can cause confusion, since unlike web Flutter does not premultiply alpha when calculating the gradient (see #48534). Perhaps a similar effect is going on here.

@flar
Copy link
Contributor

flar commented Dec 21, 2020

There are a number of issues that make ignoring transparency problematic.

One is that it is only a NOP on some blend modes. Most notably it is a NOP with the default blend mode of SrcOver, but it would affect rendering with modes like Src, Dst, or Xor modes. So, if the ColoredBox was being used as one of the parties in a blending operation with a non-default mode, then it would have an impact if you used a transparent color. The main example I can think of is that if this widget was a child of a ColoredFilter that used one of the above modes then the transparent fill might affect the opacity of the result in a non-standard way.

Another case where it might affect output is under an operation like a blur where the blur effect needs to read samples from outside the image to blend with the pixels that are near the edge of the source image. The default edge sampling mode for ImageFilter.blur is the clamp mode which tells the blur to pretend that all of the surrounding pixels outside of the image/widget being blurred are copies of the pixels at its edge. Since the rendering bounds of our widgets auto-size themselves to the content painted in them, if the transparent fill extended past the bounds of the rest of the content of the widget in some direction then transparent pixels would be read for the "outside the bounds" pixels in that direction and that edge of the widget would "blur to transparency". If you then removed that fill of a transparent color, one of the other pixels in the widget might become the "edge" of its rendered output and that other value might be the one chosen to blur with all of the edge pixels. If an edge becomes fully opaque, there would be no drop-off in opacity at the edge of such a widget (out to a distance determined by the blur mode).

Consider if they animate the background from a solid color to transparency inside a blur. The edges would slowly fade and the content drawn on top of the background would animate from a blur into the initial color, slowly towards a blur into transparency as the animation progressed. If you suddenly omit the rendering of the opaque color at the end of the animation then the last frame would be a blur of just the bounds of the content and you would end up with that content smearing to the edge of the blurred result.

Knowing whether you can omit the transparency would involve knowledge of a lot of factors about the parent widgets in the tree (and potentially knowledge of a BDF on top of the widget since a BDF might be clipped to the bounds of its child which can change if you render or do not render that transparent rectangle).

@tvolkert
Copy link
Contributor Author

@flar thanks for the great explanation! I updated this to simply add a comment to ward off well-meaning future-me 🙂

Copy link
Contributor

@dnfield dnfield left a comment

Choose a reason for hiding this comment

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

LGTM

@tvolkert tvolkert merged commit d61d775 into flutter:master Dec 25, 2020
@tvolkert tvolkert deleted the alpha branch December 25, 2020 05:42
filiph added a commit to filiph/flutter-folio that referenced this pull request Aug 19, 2021
All this does is avoid adding an animated container when the opacity is 0 (which it is most of the time).

The new line avoids an unintuitive behavior of the Flutter Framework, in which a fully transparent overlay cannot be ignored by the rasterizer. More info here: flutter/flutter#72526 (comment).

I wasn’t able to test this, but I’m pretty sure this will have the effect of skipping the overlay animation when the opacity goes from 0 to non-zero (because AnimatedContainer only animates itself if it’s there for both the before and after — and I’m removing the before). If this is too jarring, we can think about other creative ways to still get that performance boost.
secretdev718 added a commit to secretdev718/folio that referenced this pull request May 20, 2024
All this does is avoid adding an animated container when the opacity is 0 (which it is most of the time).

The new line avoids an unintuitive behavior of the Flutter Framework, in which a fully transparent overlay cannot be ignored by the rasterizer. More info here: flutter/flutter#72526 (comment).

I wasn’t able to test this, but I’m pretty sure this will have the effect of skipping the overlay animation when the opacity goes from 0 to non-zero (because AnimatedContainer only animates itself if it’s there for both the before and after — and I’m removing the before). If this is too jarring, we can think about other creative ways to still get that performance boost.
Ihor-Suma added a commit to Ihor-Suma/Flutter-Folio that referenced this pull request Jul 4, 2025
All this does is avoid adding an animated container when the opacity is 0 (which it is most of the time).

The new line avoids an unintuitive behavior of the Flutter Framework, in which a fully transparent overlay cannot be ignored by the rasterizer. More info here: flutter/flutter#72526 (comment).

I wasn’t able to test this, but I’m pretty sure this will have the effect of skipping the overlay animation when the opacity goes from 0 to non-zero (because AnimatedContainer only animates itself if it’s there for both the before and after — and I’m removing the before). If this is too jarring, we can think about other creative ways to still get that performance boost.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

framework flutter/packages/flutter repository. See also f: labels. will affect goldens Changes to golden files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants