16

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

sid
  • 407
  • 5
  • 13
  • Don't know. But maybe you can play around with the different combinations of BlendMode.. I mean, not just multiply. Another way could be try OverlayPainter and BlendMode. – JRamos29 Jun 28 '20 at 09:42
  • I think I have a solution using RenderElements and BlendModes. Should be out later. – Wilson Wilson Nov 07 '20 at 16:53

2 Answers2

18

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';

class BlendMask extends SingleChildRenderObjectWidget {
  final BlendMode blendMode;
  final double opacity;

  BlendMask({
    @required this.blendMode,
    this.opacity = 1.0,
    Key key,
    Widget child,
  }) : super(key: key, child: 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) {
    context.canvas.saveLayer(
        offset & size,
        Paint()
          ..blendMode = blendMode
          ..color = Color.fromARGB((opacity * 255).round(), 255, 255, 255));

    super.paint(context, offset);

    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 {
  @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.

Wilson Wilson
  • 3,296
  • 18
  • 50
  • 2
    No problem @ReikoDev. I should probably link to the article I wrote about it in case you're interested. https://www.wilsonwilson.dev/articles/css-style-blending-using-flutter/ – Wilson Wilson May 29 '21 at 19:55
  • Thanks once again @Wilson! I'm playing with canvas and this helps a lot! One question, when you have a custom layout (e.g. made on Illustrator), something like this https://ibb.co/WV3JxZd/, which approach do you use to make it real on flutter? – Reiko Dev May 29 '21 at 21:12
  • 1
    @ReikoDev Oh wow, lots and lots of custom paint. I don't believe it's a realistic or good idea to build everything with flutter though. For things like the stars, the *curtains*, the banners, and probably the containers, you'd be better off with raw design files. For reactive components like the progress bar, you'd be safe in custom paint's hands :D – Wilson Wilson May 30 '21 at 22:06
  • Oh, i see! Once again, thank you Wilson!! – Reiko Dev May 30 '21 at 22:50
  • Great answer! Concise, flexible and ready to use. Thank you. – Ashkan Sarlak Mar 27 '22 at 09:56
  • @WilsonWilson I'm using this, but it is rather expensive so I tried wrapping it in a `RepaintBoundary` but that behaves weirdly, any tips? – Matthiee Mar 10 '23 at 08:17
10

Try ShaderMask.
BlendModes

Example of use:

import 'dart:async';
import 'dart:typed_data';
import 'dart:ui' as ui;

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

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: Scaffold(
        body: SafeArea(
          child: MyHomePage(),
        ),
      ),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  Future<ui.Image> loadUiImage(String imageAssetPath) async {
    final data = await rootBundle.load(imageAssetPath);
    final completer = Completer<ui.Image>();
    ui.decodeImageFromList(Uint8List.view(data.buffer), completer.complete);
    return completer.future;
  }

  @override
  Widget build(BuildContext context) {
    return FutureBuilder<ui.Image>(
        future: loadUiImage('assets/cara_img.png'),
        builder: (context, img) {
          return img.hasData
              ? ShaderMask(
                  blendMode: BlendMode.colorDodge,
                  shaderCallback: (bounds) => ImageShader(
                    img.data,
                    TileMode.clamp,
                    TileMode.clamp,
                    Matrix4.identity().storage,
                  ),
                  child: Image.asset('assets/code.png'),
                )
              : Container(
                  color: Colors.red,
                  height: 100,
                  width: 100,
                  child: Text('loading'),
                );
        });
  }
}

code.png:
code.png cara_img.png:
cara_img.png

Results:
enter image description here enter image description here enter image description here

Kherel
  • 14,882
  • 5
  • 50
  • 80
  • That's a fantastic solution, but it's a bit bulky and you have to load images asynchronously. Why not use RenderObjects? – Wilson Wilson Nov 08 '20 at 11:51