56

I am attempting to replicate a login screen design my designer did for an app.

The background image uses a blendMode of softLight, the catch is that the colour it blends with is a gradient colour. Secondly there is actually two layers of different gradients (one purple gradient, one blue gradient)

Original Image:

Original Image

Final Gradient Image

Final Gradient Image

Now I have tried using colorBlendMode, e.g.

Image.asset(
      'assets/pioneer-party.jpg',
      fit: BoxFit.cover,
      color: Color(0xff0d69ff).withOpacity(1.0),
      colorBlendMode: BlendMode.softLight,
    ),

The problem is that the color property only takes a single colour.

I then tried BoxDecoration, e.g.

DecoratedBox(
      decoration: new BoxDecoration(
        color: const Color(0xff7c94b6),
        image: new DecorationImage(
          fit: BoxFit.cover,
          colorFilter: new ColorFilter.mode(Colors.purple.withOpacity(1.0), BlendMode.softLight),
          image: new NetworkImage(
            'http://www.allwhitebackground.com/images/2/2582-190x190.jpg',
          ),
        ),
      ),
    ),

Which still leaves me with the same problem. I then tried stacking each layer individually and then playing around with gradients to make it appear close to the design, e.g.

Image.asset(
      'assets/pioneer-party.jpg',
      fit: BoxFit.cover,
      color: Color(0xff0d69ff).withOpacity(1.0),
      colorBlendMode: BlendMode.softLight,
    ),
    DecoratedBox(
      decoration: BoxDecoration(
        gradient: LinearGradient(
          begin: FractionalOffset.topCenter,
          end: FractionalOffset.bottomCenter,
          colors: [
            Color(0xff0d69ff).withOpacity(0.0),
            Color(0xff0069ff).withOpacity(0.8),
          ],
        ),
      ),
    ),
    DecoratedBox(
      decoration: BoxDecoration(
        gradient: LinearGradient(
          begin: FractionalOffset.topLeft,
          end: FractionalOffset.bottomRight,
          colors: [
            Color(0xff692eff).withOpacity(0.8),
            Color(0xff642cf4).withOpacity(0.8),
            Color(0xff602ae9).withOpacity(0.8),
            Color(0xff5224c8).withOpacity(0.8),
            Color(0xff5e29e5).withOpacity(0.8),
          ],
        stops: [0.0,0.25,0.5,0.75,1.0]
        ),
      ),
    ),

Which gives me somewhat close to what I want, but not entirely what I need.

Does anyone know of a way to achieve this?

EDIT:

I was also thinking about blending the two images together, but haven't found a way of doing that except using opacity or something. Ideally would like it to be rendered natively rather than using "hacks" to achieve it.

Robert Stevens
  • 7,817
  • 3
  • 13
  • 15
  • I'm not able to write a full answer at the moment but I'd advise you to look into using [`CustomPaint`](https://docs.flutter.io/flutter/widgets/CustomPaint-class.html) and the canvas. Using the canvas you can draw gradients & images to your heart's content and then apply filters while merging the layers. It'll be a bit of an expensive draw but should be alright if everything is static. If you have things moving around then maybe I can reevaluate at some point. But then again - if the image is static, why wouldn't you just merge it in photoshop first and use the final image =D. – rmtmckenzie Jul 05 '18 at 12:05
  • Hi @rmtmckenzie thanks for the input. The image will be static, but we want to use this effect throughout the app, so at various places the same effect will apply but a different background image will be used. otherwise yeah i would definitely rather use a photoshop image and get done with it haha. – Robert Stevens Jul 05 '18 at 12:17
  • Okay then you should be able to do what you want with CustomPaint. Once you figure out the semantics of the boilderplate needed for it, the Canvas itself acts very much like a Canvas in other languages, so if you're familiar with them you should be able to figure it out. If not, that's what we're here for! – rmtmckenzie Jul 05 '18 at 12:20
  • Here the answer: https://stackoverflow.com/questions/55102880/flutter-image-fade-out-at-bottom-gradient – Sangam Nov 29 '21 at 16:34

8 Answers8

99

Use Stack to Get this Effect Its Very easy.

   Stack(children: <Widget>[
        Container(
          decoration: BoxDecoration(
            color: Colors.transparent,
            image: DecorationImage(
              fit: BoxFit.fill,
              image: AssetImage(
                'images/bg.jpg',
              ),
            ),
          ),
          height: 350.0,
        ),
        Container(
          height: 350.0,
          decoration: BoxDecoration(
              color: Colors.white,
              gradient: LinearGradient(
                  begin: FractionalOffset.topCenter,
                  end: FractionalOffset.bottomCenter,
                  colors: [
                    Colors.grey.withOpacity(0.0),
                    Colors.black,
                  ],
                  stops: [
                    0.0,
                    1.0
                  ])),
        )
      ]),  

Cheers

Pablo Cegarra
  • 20,955
  • 12
  • 92
  • 110
Abhishek Razy
  • 1,151
  • 7
  • 8
  • Excellent. I've tried to use ShaderMask to get the same result, but it seems that ShaderMask still has some unexpected behavior (see for example https://stackoverflow.com/questions/61396798/apply-borderradius-to-widget-with-shadermask) . This solution seems is the best one i've seen so far to apply a gradient filter over an image. – Uj Corb Apr 24 '20 at 06:30
  • I've been also searching for this solution and it works for "image + gradient color" composition. – sphinxlike Dec 26 '20 at 09:46
  • what to do if I don't want to fix the height of the container :( – Aditya Prasad Dhal Feb 21 '23 at 11:39
21

You can try this too:

ColorFiltered(
  colorFilter: ColorFilter.mode(Colors.red.withOpacity(0.4), BlendMode.srcOver),
  child: YourWidget(),
) 
CopsOnRoad
  • 237,138
  • 77
  • 654
  • 440
16

my solution was wrapping my image with shaderMask and this is an example that worked for me

ShaderMask(
  shaderCallback: (bounds) {
    return LinearGradient(
      colors: node.badge!.colorsArray,
    ).createShader(bounds);
  },
  child: Image.asset(
    Assets.main_logo,
    height: 27.0,
    fit: BoxFit.cover,
  ),
  blendMode: BlendMode.srcATop,
),

If you refer to the BlendMode documentation https://docs.flutter.io/flutter/dart-ui/BlendMode-class.html, you can actually find what you need a busy cat

Iheb Briki
  • 236
  • 4
  • 6
3

ShaderMask Widget Class will be helpful

ShaderMask(

      // gradient layer  ----------------------------
      shaderCallback: (bound) {
        return  LinearGradient(
      end: FractionalOffset.topCenter,
      begin: FractionalOffset.bottomCenter,
      colors: [
        Colors.black.withOpacity(0.99),
        Colors.black.withOpacity(0.7),
        Colors.transparent,
      ],
      stops: [
        0.0,
        0.3,
        0.45
      ]) 
      .createShader(bound);
      },

      blendMode: BlendMode.srcOver,


      // your widget  ------------------------
      child: Container(
        height: double.infinity,
        width: double.infinity,
        child:
        Image.asset("image.png")
      ),
    );
Omar Essam El-Din
  • 1,415
  • 14
  • 17
2

Edit

The const double scale = 2.04015; ontly works when running Flutter in browser. For mobile, use const double scale = 0;

=================================================

Three and a half years late to the party, but I noticed that none of the answers grasped to even understood what the desired effect is. So let's first analyse the wanted outcome and then code it.

Gradient Maps

This effect is known as 'Gradient Map', as individual pixel values are being mapped to a gradient based on a predefined set of colours.

Let's take a look. If an input is a grayscale image, Color color1 = Colors.blue, and Color color2 = Colors.purple, the individual pixel colors should be transformed in the following way -

Visual representation of gradient maps

Where

  • Black pixel color - transformed into blue color
  • White pixel color - transformed into purple color
  • Color in between - transformed into an appropriate color between blue and purple

Code

My initial thoughts were to take an image, decode it into Uint8List using getBytes(), process the list and finally encode it back into a png. While this approach worked, it was nowhere near performant for a realtime usecase.

Therefore, the second viable approach was to create an effect using a color matrices -

class GradientMap extends StatelessWidget {

  final Color color1;
  final Color color2;
  final double contrast;
  final ImageProvider imageProvider;

  const GradientMap({
    this.color1 = Colors.white,
    this.color2 = Colors.black,
    this.contrast = 0,
    required this.imageProvider,
    Key? key
  }) : super(key: key);


  ColorFilter grayscaleMatrix() => ColorFilter.matrix([
    0.2126,       0.7152,       0.0722,       0, 0,
    0.2126,       0.7152,       0.0722,       0, 0,
    0.2126,       0.7152,       0.0722,       0, 0,
    contrast-0.4, contrast-0.4, contrast-0.4, 1, 0,
  ]);


  ColorFilter invertMatrix() => const ColorFilter.matrix([
    -1,  0,  0, 0, 255,
     0, -1,  0, 0, 255,
     0,  0, -1, 0, 255,
     0,  0,  0, 1, 0,
  ]);


  ColorFilter tintMatrix () {
    final int r = color2.red;
    final int g = color2.green;
    final int b = color2.blue;

    final double rTint = r / 255;
    final double gTint = g / 255;
    final double bTint = b / 255;

    const double scale = 2.04015; // Use 0 on mobile instead
    const double translate = 1 - scale * 0.5;

    return ColorFilter.matrix(<double>[
      (rTint * scale), (rTint * scale), (rTint * scale), (0), (r * translate),
      (gTint * scale), (gTint * scale), (gTint * scale), (0), (g * translate),
      (bTint * scale), (bTint * scale), (bTint * scale), (0), (b * translate),
      (0            ), (0            ), (0            ), (1), (0            ),
    ]);
  }


  @override
  Widget build(BuildContext context) {
    return Container(
      color: color1,
      child: ColorFiltered(
        colorFilter: tintMatrix(),
        child: ColorFiltered(
          colorFilter: invertMatrix(),
          child: ColorFiltered(
            colorFilter: grayscaleMatrix(),
            child: Image(image: imageProvider)),
        ),
      ),
    );
  }
}

Alternatively, you can combine grayscaleMatrix() and invertMatrix() into a single one to eliminate a need for a third ColorFiltered() widget.

The widget takes as an input

  • color1 - the lighter Color
  • color2 - the darker Color
  • contrast - double blending between color1 and color2
  • imageProvider - ImageProvider such as AssetImage() or NetworkImage()

and displays the transformed image. enter image description here

Usage

Just wrap your ImageProvider with this widget in the following way -

GradientMap(
  color1: Color.fromRGBO(158, 80, 254, 1),
  color2: Color.fromRGBO(15, 4, 192, 1),
  contrast: 0,
  imageProvider: NetworkImage("https://i.imgur.com/C5xknx8.png"),
)

Drawbacks

While this approach is performant, the user does not have a control over the blending algorithm between the two input colors. Furthermore, the widget only takes two colors as an input. This eliminates a possibility to use multicolor gradients.

vwk
  • 45
  • 7
1
Container(
                  height: 35.h,
                  decoration: BoxDecoration(
                      borderRadius: BorderRadius.all(Radius.circular(6)),
                      image: DecorationImage(
                        image: AssetImage('assets/images/event_detail.png'),
                        fit: BoxFit.fill,

                      )),
                  child: Column(...

enter image description here

Blockquote If you wrap Column with Container it will see like that

Container(
                  height: 35.h,
                  decoration: BoxDecoration(
                      borderRadius: BorderRadius.all(Radius.circular(6)),
                      image: DecorationImage(
                        image: AssetImage('assets/images/event_detail.png'),
                        fit: BoxFit.fill,

                      )),
                  child: Container( //This is our new Container
                    decoration: BoxDecoration(
                      gradient: LinearGradient(
                        begin: Alignment.bottomLeft,
                        end: Alignment.topRight,
                        colors: [
                          Colors.red.withOpacity(0.8),
                          Colors.red.withOpacity(0.1),
                        ],
                      )
                    ),
                    child: Column(...

enter image description here

0
   return Scaffold(
      body: Container(
        width: double.infinity,
        decoration: BoxDecoration(
            image: DecorationImage(
                fit: BoxFit.cover,
                image: AssetImage('assets/images/example.png')
            )
        ),
        child: OverflowBox(
          child: Container(
            decoration: BoxDecoration(
              gradient: LinearGradient(
                  begin: begin,
                  end: end,
                  colors: [
                    Color(0xFF000000),
                    Color(0x2d2d2d00)
                  ]
              ),
            ),
          ),
        ),
      ),
    );

0

you can wrap container inside container. This code working for me:

Container(
      decoration: const BoxDecoration(
        image: DecorationImage(
            fit: BoxFit.cover,
            image: AssetImage('assets/images/background.jpg')),
      ),
      child: Container(
        padding: const EdgeInsets.fromLTRB(12, 40, 12, 0),
        width: double.infinity,
        decoration: BoxDecoration(
          gradient: LinearGradient(
              begin: Alignment.topCenter,
              end: Alignment.bottomCenter,
              colors: [Colors.black.withOpacity(0), Colors.black]),
        ),
        child: <YOUR WIDGET>
THANGSTAR
  • 21
  • 2