fluttermix-blend-modeflutter-image

How to use mix blend mode in flutter?


I have tried the blend mode that seems to be working for only color i.e colorblendmode. Is there any way to achieve the mix-blend-mode as of CSS?

  Stack(
        children: <Widget>[
          Image.asset(
            "asset/text.PNG",
            height: double.maxFinite,
            width: double.maxFinite,
            fit: BoxFit.fitHeight,
            color: Colors.red,
            colorBlendMode: BlendMode.multiply,
          ),
          Image.asset("asset/face.jpg",
              width: double.maxFinite,
              fit: BoxFit.fitHeight,
              color: Colors.red,
              colorBlendMode: BlendMode.multiply),
        ],
      ),

This results in something like: The output of code above

What i want to get Output from CSS


Solution

  • Using RenderProxyBox and some painting, I was able to recreate the exact sample on the CSS website without asynchronously loading the image in your Flutter code.

    Image using CSS (left) vs image using Flutter(right).

    Blendmode CSS vs Flutter

    Read the article I wrote about this here

    To start, a BlendMask SingleChildRenderObject is created that creates a RenderProxyBox render object called RenderBlendMask is created. The child is painted with a BlendMode and an opacity.

    import 'package:flutter/rendering.dart';
    import 'package:flutter/widgets.dart';
    
    // Applies a BlendMode to its child.
    class BlendMask extends SingleChildRenderObjectWidget {
      final BlendMode blendMode;
      final double opacity;
    
      const BlendMask({
        required this.blendMode,
        this.opacity = 1.0,
        super.key,
        super.child,
      });
    
      @override
      RenderObject createRenderObject(context) {
        return RenderBlendMask(blendMode, opacity);
      }
    
      @override
      void updateRenderObject(BuildContext context, RenderBlendMask renderObject) {
        renderObject.blendMode = blendMode;
        renderObject.opacity = opacity;
      }
    }
    
    class RenderBlendMask extends RenderProxyBox {
      BlendMode blendMode;
      double opacity;
    
      RenderBlendMask(this.blendMode, this.opacity);
    
      @override
      void paint(context, offset) {
        // Create a new layer and specify the blend mode and opacity to composite it with:
        context.canvas.saveLayer(
          offset & size,
          Paint()
            ..blendMode = blendMode
            ..color = Color.fromARGB((opacity * 255).round(), 255, 255, 255),
        );
    
        super.paint(context, offset);
    
        // Composite the layer back into the canvas using the blendmode:
        context.canvas.restore();
      }
    }
    

    Now to blend two widgets (Not restricted to images), just add the widget you want to blend above another using a stack, and wrap it in your BlendMode.

    class ImageMixer extends StatelessWidget {
      const ImageMixer({super.key});
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          body: SizedBox.expand(
            child: Stack(
              children: [
                SizedBox.expand(
                  child: Image.asset(
                    'images/sky.jpg',
                  ),
                ),
                BlendMask(
                  opacity: 1.0,
                  blendMode: BlendMode.softLight,
                  child: SizedBox.expand(
                    child: Image.asset(
                      'images/monkey.jpg',
                    ),
                  ),
                ),
              ],
            ),
          ),
        );
      }
    }
    

    That produces the image above, and it works exactly like the CSS example.