Flutter: Add a gradient background to your image container

Quentin Klein
La Mobilery
Published in
7 min readJul 4, 2024

Been a while since I wrote here… Time flies.

Today I want to share with you something I found fascinating and useful when working on my latest POC.

I wanted to display images of video games in my Flutter app, but I do not control the size of the images.
Therefore, to keep consistency in the user interface, I put them into a square container that has a fixed size.

This container should take the max size possible and an image centered inside it.

Thing is, I was not satisfied by the rendered results because… Well, let me show you.

Centering the image in our SquareImageContainer

The first step of our journey is to create a basic container and center any image size within it.

To do so, we need two things, a square container, and images.

The Square Image Container

Let us create a basic Widget, nothing fancy here.

To create a square container, all you have to do is define a Container and compute which size it will have by checking the maximum width or height that can fit inside.

import 'dart:math';

import 'package:flutter/material.dart';

class SquareImageContainer extends StatelessWidget {

final ImageProvider imageProvider;

const SquareImageContainer({
super.key,
required this.imageProvider,
});

@override
Widget build(BuildContext context) {
final size = MediaQuery.of(context).size;
final containerSize = min(size.width, size.height);
return Container(
decoration: BoxDecoration(
color: Colors.blue,
),
width: containerSize,
height: containerSize,
child: Center(child: Image(imageProvider)),
);
}

}

The blue Color is used to make it easy to see that the widget size is on the screen.

And then, for our image, we will keep it easy using…

body: SquareImageContainer(
imageProvider: Image.asset('assets/images/cs.png').image,
),

Here we go

Our image is centered in our container

It looks awful.

Add a beautiful background to our container

Let’s do some fancy stuff here and let me introduce you to a marvellous package called palette_generator.

palette_generator is a package that is maintained by the flutter team and, at the time of writing this post, has not been updated for 10 months!

Anyway, why change something that works perfectly? 😨

Here is the short description of what it does

A Flutter package to extract prominent colors from an Image, typically used to find colors for a user interface.

In other words, you feed this package an image. It poops you the main colors used in it.

Lets give it a try with your image.

Adding palette_generator

Adding palette_generator is simple, you add it in thepubspec.yaml and then you can use it.

It works fine with a method called fromImageProvider that takes, well, an ImageProvider.

PaletteGenerator.fromImageProvider(ImageProvider)

and returns a… Future<PaletteGenerator>

Let’s do a basic call here

final ip = Image.asset('assets/images/cs.png').image;
final pg = await PaletteGenerator.fromImageProvider(ip);
debugPrint(pg.toString());

and see the results :

PaletteGenerator#f4751(
paletteColors: [
PaletteColor#c6edc(
color: Color(0xff25222a),
titleTextColor: Color(0x56ffffff),
bodyTextColor: Color(0x77ffffff),
population: 15140
),
PaletteColor#6915a(
color: Color(0xff80aaca),
titleTextColor: Color(0x7a000000),
bodyTextColor: Color(0xa3000000),
population: 14943
),
PaletteColor#06ae0(
color: Color(0xff3e3d42),
titleTextColor: Color(0x62ffffff),
bodyTextColor: Color(0x8dffffff),
population: 14936
),
PaletteColor#335bf(
color: Color(0xff3f4d65),
titleTextColor: Color(0x6fffffff),
bodyTextColor: Color(0xa1ffffff),
population: 14884
),
PaletteColor#255a7(
color: Color(0xff4d6680),
titleTextColor: Color(0x8cffffff),
bodyTextColor: Color(0xcdffffff),
population: 14749
),
PaletteColor#f4df2(
color: Color(0xffa4cde2),
titleTextColor: Color(0x72000000),
bodyTextColor: Color(0x93000000),
population: 7640
),
PaletteColor#b9b7d(
color: Color(0xff759197),
titleTextColor: Color(0x86000000),
bodyTextColor: Color(0xb9000000),
population: 7526
),
PaletteColor#7c455(
color: Color(0xff607d9b),
titleTextColor: Color(0x95000000),
bodyTextColor: Color(0xe1000000),
population: 7460
),
PaletteColor#74e42(
color: Color(0xffcaddd6),
titleTextColor: Color(0x6e000000),
bodyTextColor: Color(0x8f000000),
population: 7172
),
PaletteColor#86eea(
color: Color(0xff9ea995),
titleTextColor: Color(0x7b000000),
bodyTextColor: Color(0xa2000000),
population: 3676
),
PaletteColor#044e0(color: Color(0xffd3c49a), titleTextColor: Color(0x72000000), bodyTextColor: Color(0x94000000), population: 3649),
PaletteColor#7cd42(color: Color(0xff5f7581), titleTextColor: Color(0xa5ffffff), bodyTextColor: Color(0xf1ffffff), population: 3476),
PaletteColor#70b42(color: Color(0xff5f7176), titleTextColor: Color(0x9dffffff), bodyTextColor: Color(0xe6ffffff), population: 1910),
PaletteColor#f9904(color: Color(0xff666a6a), titleTextColor: Color(0x95ffffff), bodyTextColor: Color(0xdaffffff), population: 1022),
PaletteColor#5eede(color: Color(0xff6f6a58), titleTextColor: Color(0x95ffffff), bodyTextColor: Color(0xdcffffff), population: 489)
],
targets: []
)

What is a PaletteColor?

Well, according to the documentation.

/// A color palette color generated by the [PaletteGenerator].
///
/// This palette color represents a dominant [color] in an image, and has a
/// [population] of how many pixels in the source image it represents. It picks
/// a [titleTextColor] and a [bodyTextColor] that contrast sufficiently with the
/// source [color] for comfortable reading.
///
/// See also:
///
/// * [PaletteGenerator], a class for selecting color palettes from images.

So, two important things here are population and color.
For our image, let’s do a basic pie chart to see what is going on.

The original image
The PaletteColor

And now, we will use this palette as an input to generate a gradient of colors that will be used in place of the background color when rendering the image container.

To do so, we will create a method over PaletteGenerator that returns a gradient based on a certain number of colors, let’s say five by default.

The toGradientColors() extension.

extension PaletteGeneratorToGradient on PaletteGenerator {
List<Color> toGradientColors({int maxColor = 5}) {
paletteColors.sort((PaletteColor a, PaletteColor b) {
return b.population.compareTo(a.population);
});
switch (paletteColors.length) {
case 0:
return [Colors.transparent, Colors.transparent];
case 1:
return [paletteColors[0].color, paletteColors[0].color];
default:
return [
...paletteColors.map((e) => e.color).take(5),
];
}
}
}

First of all, we sort our colors by population and then take the first N elements from that sorted list using map to only keep the color property.

A gradient needs at least 2 colors in order for it work properly so the switch statement is used to ensure there are enough colors available before returning them.

In case there is no color, we return a transparent color array.
In case there is only one color, we return two times this color (like a plain background color).
In other cases, we do our magic.

What will happen if we call it on our palette?

Color(0xff25222a),
Color(0xff80aaca),
Color(0xff3e3d42),
Color(0xff3f4d65),
Color(0xff4d6680)

Perfect.

Use it as a gradient in our SquareImageContainer

To use our colors in a gradient background we will need to change two things.

As it is asynchronous, we have to change the build to return a FutureBuilder and then we need to create the gradient in the decoration.

No more talking, just code.


class SquareImageContainer extends StatelessWidget {

final ImageProvider imageProvider;

const SquareImageContainer({
super.key,
required this.imageProvider,
});

@override
Widget build(BuildContext context) {
final size = MediaQuery.of(context).size;
final containerSize = min(size.width, size.height);
final imageChild = Center(child: Image(image: imageProvider));
return FutureBuilder(
future: PaletteGenerator.fromImageProvider(
imageProvider,
maximumColorCount: 5,
),
builder: (context, paletteValueSnapshot) {
List<Color> colors = [];
if (paletteValueSnapshot.hasData) {
final data = paletteValueSnapshot.data;
colors.addAll(data?.toGradientColors() ?? []);
}

return Container(
decoration: colors.isNotEmpty ? BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: colors,
),
) : null,
width: containerSize,
height: containerSize,
child: imageChild
);
}
);
}

}

And voilà

Our gradient background

And for the fun here, are the results with more images

Showcase

All of the images are generated using dall-e

Some notes

I used Zapp for this project, it is an amazing tool that make possible to sandbox a flutter application in your browser. Try it out if you want

palette_generator only works with RGBA 8-bit images. Like PNG. Feeding it a JPEG won’t work as expected because of compression.

And as usual, here is a TL;DR; of what is said here in picture

If you read this far then congratulations (and thank you), my name is Quentin and I also make indie Flutter Apps that you can try, the last one keeps you from forgetting important dates on your calendar, try it out :)

--

--