JINMUSOFTWARE

Flutterで画像にカラーフィルタをかける方法

Flutter

Flutterでダークモードに対応するために、画像を少し暗くする方法を調査しました。

ダークモードに対応する画像

ダークモード設定時、テキスト、Buton、Iconなどは自動でダークテーマに対応したカラーに変わります。

しかし、自前で用意した画像は自動では切り替わりません。

少し暗めの画像を別途用意して切り替えてもいいのですが、ざっくりと画像を暗くして対応する方法も考えられます。

Flutterには、画像やWidgetにカラーフィルターを掛ける方法がいくつかあるようです。

バグもありましたので最後のほうに載せておきます。

Flutterで画像にカラーフィルタをかける方法

ここでは画像を少し暗くする方法として記載しています。

ImageでcolorBlendを使用する

Image.asset(
  'assets/docs/pic1.png',
  color: Colors.black.withOpacity(0.2),
  colorBlendMode: BlendMode.darken,
),

ImageとContainerをStackで重ねる

Containerにサイズを指定するのがちょっと手間

画像のサイズに合わせます。

Stack(
  children: <Widget>[
    Image.asset(
      'images/pic1.png',
      height: 100.0,
    ),
    Container(
      height: 100.0,
      width: 200.0,
      color: Colors.black.withOpacity(0.4),
    ),
  ],
),

ColorFilteredのColorFilter.modeを使用する

SingleChildScrollViewの中で使用すると、そのページを表示するとき、スクロール開始時にフィルタが全体に回ってしまうバグがある。ListViewの中ではそんなに発生しないようだ。

ColorFiltered(
  colorFilter: ColorFilter.mode(
    Colors.black.withOpacity(0.4),
    BlendMode.darken,
  ),
  child: Image.asset('images/300x300.png'),
),

ColorFilteredのColorFilter.matrixを使用する

final ColorFilter darken50 = const ColorFilter.matrix(<double>[
  0.5, 0, 0, 0, 0, // Red
  0, 0.5, 0, 0, 0, // Green
  0, 0, 0.5, 0, 0, // Blue
  0, 0, 0, 1, 0, // Alpha
]);

ColorFiltered(
  colorFilter: darken80,
  child: Image.asset('images/pic1.png'),
),

Containerの背景画像にColorFilter.modeを使用する

他の手法より手間がかかる。

ページの背景画像でよくみるコードかも。

Container(
  width: 200.0,
  height: 100.0,
  decoration: BoxDecoration(
    image: DecorationImage(
      colorFilter: ColorFilter.mode(
        // 暗くする
        Colors.black.withOpacity(0.2),
        BlendMode.darken,
      ),
      image: const AssetImage('images/pic1.png'),
      fit: BoxFit.cover,
    ),
  ),
  // なにか手前に表示する
  child: const Center(
    child: Text(
      '手前',
      style: TextStyle(fontSize: 24, color: Colors.white),
    ),
  ),
),

ShaderMask

Shadermaskで、LinearGradientを使ってもできますよと、Chat-GPTが教えてくれた。

ShaderMask(
  shaderCallback: (Rect bounds) {
    return LinearGradient(
      colors: [
        Colors.black.withOpacity(0.5),
        Colors.black.withOpacity(0.5),
      ],
      begin: Alignment.topCenter,
      end: Alignment.bottomCenter,
    ).createShader(bounds);
  },
  blendMode: BlendMode.darken,
  child: Image.asset('images/pic1.png', height: pictureHeight),
),

Opacityで透過させる

ダークテーマ時は背景も暗いだろうから、画像を透過させれば暗くなるはずという作戦。

これは暗いフィルターをかける処理ではないが同じような結果を生む。

Opacity(
  opacity: 0.5,
  child: Image.asset('images/pic1.png', height: 100.0),
),

Imageで透過させる

Imageでも透過させて表示させることは可能。

BlendModeは、dstAtopを使ってみた。

Image.asset(
  'images/pic1.png',
  height: pictureHeight,
  color: Colors.transparent.withOpacity(0.5),
  colorBlendMode: BlendMode.dstATop, // modeによる
),

ColorFilteredのバグ

SingleChildScrollViewの中で使用するとフィルタが全体に回ってしまう様な現象が発生することがあります。

発生タイミングは、ページ表示、画面をスクロールしない方向に引っ張ってからスクロールするときの開始時です。

ListViewでも発生しますがSingleChildScrollViewほど発生しませんでした。

ダークモード時の対応

ちなみに、ダークモード時の対応コード。私はこんな感じ。

// contextが必要なのでbuild内に記述しています

// モードによって透過率が変わります
final isDark = Theme.of(context).brightness == Brightness.dark;
final opacity = isDark ? 0.2 : 0.0;

// ダークモードの時は少し暗くなります
Image.asset(
  'assets/docs/pic1.png',
  color: Colors.black.withOpacity(opacity),
  colorBlendMode: BlendMode.darken,
),

調査環境

  • Windows11
  • Flutter version 3.24.2
  • Pixel 8 API 34 emulator
  • Pixel 6a 実機

関連記事

FlutterでThemeを設定する方法

参照

Image class – Flutter

ColorFiltered class – Flutter

ShaderMask class – Flutter

Opacity class – Flutter