0

This sketch shows what the final result should look like:

enter image description here

  1. A CustomPainter fills its Canvas (yellow area) with a slightly translucent background color. (Amber with an opacity of 0.8 in the sketch).
  2. The CustomPainter draws a rectangle onto the canvas. And here it's getting interesting: The rectangle should only change the alpha value of the background color drawn at the previous step. The idea is to highlight some points of interest, by fading some "holes" in and out (visualized by the smaller, darker rectangle inside the yellow rectangle in the sketch above).

In code it looks simple:

class Highlighter extends CustomPainter {
  ValueListenable<double> valueListenable;
  Color backgroundColor;

  Highlighter({required this.valueListenable, this.backgroundColor = Colors.amber}) : super(repaint: valueListenable);

  @override
  void paint(Canvas canvas, Size size) {
    Color colorHole = backgroundColor.withOpacity(0.40);
    Paint holePainter = Paint();
    holePainter.color = colorHole;
    holePainter.blendMode = BlendMode.dstOut;

    canvas.saveLayer(null, holePainter);
    // Step 1: Draw the background:
    canvas.drawColor(backgroundColor.withOpacity(0.80), BlendMode.srcOver);

    // Step 2: Highlight a rectangle:
    canvas.drawRect(const Rect.fromLTWH(100, 100, 100, 100), holePainter);
    canvas.restore();
  }

  @override
  bool shouldRepaint(CustomPainter oldDelegate) {
    return true;
  }
}

Problem is, the colors ain't right:

  • canvas.DrawColor() draws some shade of gray instead of Amber, although the holes appear to be ok.
  • Removing the saveLayer()/restore() calls draws the background with the right color, but then the holes ain't transparent.

Question now is: After filling the canvas with a color, how can you set parts of it to translucent?

If there's a more efficient/performant way to do it, please let me now as well - getting rid of the saveLayer() call would be great...

Any advise is welcome. Thank you.

SePröbläm
  • 5,142
  • 6
  • 31
  • 45
  • You the middle part empty for big rect and then place another one here? – Md. Yeasin Sheikh Aug 17 '22 at 12:19
  • 1
    @YeasinSheikh Thanks for sharing the idea. Drawing the canvas with the hole left empty and then just filling the hole with a second draw call would for sure work. But I'm afraid it will most likely cause artifacts where the anti aliased borders (of the hole and the filled hole) overlap. The way it looks, this should be doable using `BlendMode` as well, which would be my preferred solution. – SePröbläm Aug 17 '22 at 12:39
  • Since it's a simple design why not use a normal colum row combo to get this result? – Kaushik Chandru Aug 20 '22 at 04:14
  • Looking at what you're trying to achieve why not use Stack widget and position other widgets on top of each other with Positioned. Any particular reason why it needs to be CustomPainter & Canvas? – Piotr Aug 20 '22 at 13:16

2 Answers2

0

Try my version:

class Highlighter extends CustomPainter {
  ValueListenable<double> valueListenable;
  Color backgroundColor;
  Rect target;

  Highlighter({this.valueListenable, this.backgroundColor = Colors.amber, this.target = const Rect.fromLTWH(145, 320, 100, 100)})
      : super(repaint: valueListenable);

  @override
  void paint(Canvas canvas, Size size) {
    bool withRestore = false;
    if (withRestore) {
      //with canvas.restore
      canvas.saveLayer(Rect.largest, Paint());
      canvas.drawRect(Rect.fromLTWH(0, 0, size.width, size.height), Paint()..color = backgroundColor.withOpacity(0.4));
      canvas.drawRect(target, Paint()..blendMode = BlendMode.clear);
      canvas.restore();
    } else {
      //without canvas.restore
      Paint backgroundPaint = Paint();
      backgroundPaint.blendMode = BlendMode.src;
      backgroundPaint.color = backgroundColor.withOpacity(0.4);
      // // Step 1: Draw the background:
      canvas.drawRect(Rect.fromLTWH(0, 0, size.width, target.top), backgroundPaint);
      canvas.drawRect(Rect.fromLTWH(0, 0, target.left, size.height), backgroundPaint);
      canvas.drawRect(Rect.fromLTWH(target.left + target.width, 0, target.left, size.height), backgroundPaint);
      canvas.drawRect(Rect.fromLTWH(0, target.top + target.height, size.width, target.top), backgroundPaint);
    }
  }

  @override
  bool shouldRepaint(CustomPainter oldDelegate) {
    return true;
  }
}

enter image description here

cloudpham93
  • 524
  • 1
  • 4
  • Thanks for sharing this! It didn't solve the problem, but anyway, please take the bounty as a token of gratitude. – SePröbläm Aug 22 '22 at 13:54
0

The difficulty was to fade in/out the holes from the background (yellow) barrier. The approaches mentioned by @cloudpham93 and @YeasinSheikh do work, as long as you just want to cut a hole into the barrier. If the holes should fade in/out however, the following steps are needed:

  1. Call setLayer() and pass a Paint() with blendMode set to srcOver.
  2. Draw the background barrier with the desired opacity.
  3. Call setLayer() again, but pass a Paint() with blendMode set to dstOut.
  4. Draw draw the holes with the desired opacity.
  5. Call restore() twice. The first call of restore() will overwrite the opacity of the pixels on the background layer. The second call will draw the background layer onto whatever is underneath it.

Thanks for all the advise!

SePröbläm
  • 5,142
  • 6
  • 31
  • 45
  • Can you also post a code snippet of the final result? It's not particularly trivial to write the instructions in code and I am getting a hard time in recreating it! – AshishB May 14 '23 at 12:06