How can I mask a widget with a png UI image using flutter's custom paint

2,551

Unable to solve the Custom Painter rendering issue, I ended up using a different approach. I simply render the masked png on a separate canvas, export a ui.Image and draw it using the custom painter. Thins enables me to use BlendMode.srcIn and keep transparency. You can read more about it on this blog post

Share:
2,551
jony
Author by

jony

Updated on December 14, 2022

Comments

  • jony
    jony over 1 year

    I can't seem to mask a widget with a png ui.Image and keep transparency. The transparent (invisible) pixels are rendered black.

    I have tried using BlendMode srcIn when drawing the mask png image on my custom painter, the expected behavior is the colorful background masked by the jelly bean shape. Yet the transparent (should be deleted) pixels are rendered black (see image attached). Is this a bug? or am I missing something? I'm also aware of the ImageShader option, but it lacks flexibility I require in my app. When transitioning between screens and applying hero animation, there is a brief moment in which the canvas is rendered as expected.

    Any help will be much appreciated :)

    App screen shot app print screen

    Mask Image mask image

    BG Image bg image

    Expected expected

    import 'dart:typed_data';
    import 'dart:ui' as ui;
    
    import 'package:flutter/material.dart';
    import 'package:flutter/rendering.dart';
    import 'package:http/http.dart' as http;
    
    void main() => runApp(MyApp());
    
    class MyApp extends StatelessWidget {
    
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'Flutter Demo',
          theme: ThemeData(
            primarySwatch: Colors.blue,
          ),
          home: MyHomePage(title: 'Flutter Demo Home Page'),
        );
      }
    }
    
    class MyHomePage extends StatefulWidget {
      MyHomePage({Key key, this.title}) : super(key: key);
    
      final String title;
    
      @override
      _MyHomePageState createState() => _MyHomePageState();
    }
    
    class _MyHomePageState extends State<MyHomePage> {
      ui.Image mask;
    
      initState() {
        super.initState();
    
        load('https://img.pngio.com/shape-png-free-download-free-shapes-png-493_315.png')
            .then((image) {
          setState(() {
            mask = image;
          });
        });
      }
    
      Future<Uint8List> _loadFromUrl(String url) async {
        final response = await http.get(url);
    
        if (response.statusCode >= 200 && response.statusCode < 300) {
          return response.bodyBytes;
        } else {
          return null;
        }
      }
    
      Future<ui.Image> load(String asset) async {
        var a = await _loadFromUrl(asset);
        ui.Codec codec = await ui.instantiateImageCodec(a);
        ui.FrameInfo fi = await codec.getNextFrame();
        return fi.image;
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text(widget.title),
          ),
          body: Stack(
            children: <Widget>[
              Positioned(
                top: 10,
                left: 100,
                child: Hero(
                    tag: 'imageHero',
                    child: Container(
                      width: 200,
                      height: 200,
                      child: CustomPaint(
                        foregroundPainter: LayerPainter(mask: mask),
                        child: Image.network(
                            "https://www.thecommercialhotel.com/wp-content/uploads/2015/11/Live-Lounge-BG.jpg",
                            fit: BoxFit.fill),
                      ),
                    )),
              )
            ],
          ),
          floatingActionButton: FloatingActionButton(
            onPressed: () {
              Navigator.push(context,
                  MaterialPageRoute(builder: (BuildContext context) {
                return Page2(mask: mask,);
              }));
            },
            tooltip: 'Increment',
            child: Icon(Icons.add),
          ), // This trailing comma makes auto-formatting nicer for build methods.
        );
      }
    }
    
    class LayerPainter extends CustomPainter {
      ui.Image mask;
    
      LayerPainter({@required this.mask}) {}
    
      @override
      void paint(Canvas canvas, Size size) {
        if (mask != null) {
          canvas.drawImageRect(
              mask,
              Rect.fromLTWH(0, 0, mask.width.toDouble(), mask.height.toDouble()),
              Rect.fromLTWH(0, 0, size.width, size.height),
              new Paint()..blendMode = BlendMode.dstIn);
        }
      }
    
      @override
      bool shouldRepaint(LayerPainter oldDelegate) {
        return oldDelegate.mask != this.mask;
      }
    }
    
    class Page2 extends StatelessWidget {
    
      final ui.Image mask;
    
      Page2({ @required this.mask});
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
            body: Stack(
            children: <Widget>[
              Positioned(
                top: 400,
                left: 100,
                child: Hero(
                    tag: 'imageHero',
                    child: Container(
                      width: 200,
                      height: 200,
                      child: CustomPaint(
                        foregroundPainter: LayerPainter(mask: mask),
                        child: Image.network(
                            "https://www.thecommercialhotel.com/wp-content/uploads/2015/11/Live-Lounge-BG.jpg",
                            fit: BoxFit.fill),
                      ),
                    )),
              )
            ],
          ));
      }
    }