#

图片混合模式

一、ColorBlendMode概述与核心概念

ColorBlendMode是Flutter中用于控制图片与颜色混合效果的核心概念,源自计算机图形学领域的图像处理技术。BlendMode定义了源图像(Source)和目标图像(Destination)如何进行数学运算来生成最终的混合结果。Flutter中的BlendMode源自经典的Porter-Duff图像合成算法,该算法由Thomas Porter和Tom Duff在1984年提出,至今仍是图像处理领域的基础理论之一。

混合模式在图像处理、UI设计、游戏开发等领域有着广泛的应用。在移动应用开发中,图片混合模式被广泛应用于UI美化、图片编辑、主题切换等场景。比如深色模式下的图标着色、图片水印添加、滤镜效果实现、特殊视觉效果的创建等,都离不开混合模式的支持。通过混合模式,开发者可以实现复杂的视觉效果,而无需修改原始图像资源。

混合模式的工作原理基于像素级别的数学运算。对于每个像素点,系统会根据源像素的RGB值和Alpha通道值、目标像素的RGB值和Alpha通道值,以及选定的混合模式公式,计算出最终像素的RGB值和Alpha值。不同的混合模式使用不同的数学公式,从而产生不同的视觉效果。这种像素级别的操作使得混合模式可以实现非常精细和可控的效果。

混合模式主要分为以下几个大类:基础模式、变暗模式、变亮模式、对比模式、色彩模式等。每个类别下又包含多个具体的模式,每个模式都有其独特的数学公式和视觉效果。理解这些分类和每个模式的特点,有助于开发者快速选择适合自己需求的混合模式。

BlendMode混合模式体系

基础模式
Basic Modes

变暗模式
Darken Modes

变亮模式
Lighten Modes

对比模式
Contrast Modes

色彩模式
Component Modes

srcIn

srcOut

srcATop

dstIn

dstOut

dstATop

multiply

darken

colorBurn

screen

lighten

colorDodge

overlay

hardLight

softLight

difference

exclusion

color

luminosity

saturation

hue

混合模式分类与特性

类别 模式 描述 数学特性 典型应用
基础混合 srcIn 源在目标内部显示 只保留目标的Alpha 图标着色
srcOut 源在目标外部显示 反转目标的Alpha 遮罩效果
srcATop 源叠加在目标上 结合srcIn和dstIn 叠加效果
变暗混合 multiply 正片叠底 像素值相乘 阴影、水印
darken 变暗 取两者较暗像素 叠加深色
colorBurn 颜色加深 增强对比度 强烈暗化
变亮混合 screen 滤色 反转后相乘 高光、提亮
lighten 变亮 取两者较亮像素 叠加亮色
colorDodge 颜色减淡 增加亮度 强烈提亮
对比混合 overlay 叠加 multiply+screen 增强对比
hardLight 强光 增强版的overlay 强光照
softLight 柔光 柔和版的overlay 柔和光照
色彩混合 color 颜色 保留目标亮度 颜色调整
luminosity 亮度 保留目标色相 亮度调整

Porter-Duff合成算法原理

Porter-Duff合成算法的核心概念是Alpha混合。每个像素都有RGBA四个通道,R(Red)表示红色分量、G(Green)表示绿色分量、B(Blue)表示蓝色分量、A(Alpha)表示透明度。Alpha通道的值范围是0(完全透明)到1(完全不透明)。合成时,系统会根据源像素和目标像素的Alpha值,计算最终像素的Alpha值和颜色值。

合成的基本公式为:

  • 最终Alpha = 源Alpha × 目标Alpha
  • 最终颜色 = 源颜色 × 源Alpha + 目标颜色 × 目标Alpha × (1 - 源Alpha)

Porter-Duff算法提供了多种合成模式,每种模式定义了不同的Alpha计算方式。最常用的是srcIn模式,它只在目标像素不透明的地方显示源像素。这个公式可以简化为:最终Alpha = 源Alpha × 目标Alpha。

混合模式

计算

源图像
Source

数学运算

目标图像
Destination

结果图像
Result

像素颜色

Alpha通道

像素颜色

Alpha通道

颜色公式

Alpha公式

最终颜色

最终Alpha

二、ColorFiltered使用详解

ColorFiltered是Flutter中应用颜色混合模式的主要组件。它是一个widget,包裹在需要添加滤镜效果的widget外层。ColorFiltered的colorFilter属性接受一个ColorFilter对象,ColorFilter有两个构造函数:ColorFilter.mode和ColorFilter.matrix。ColorFilter.mode用于简单的混合模式,接受一个颜色和一个BlendMode。ColorFilter.matrix用于更复杂的颜色变换,接受一个4x5的颜色矩阵。

ColorFiltered的影响范围是其子树。这意味着包裹的widget及其所有子元素都会受到混合模式的影响。这一点非常重要,因为如果不小心包裹了过大的widget树,可能会导致意外的效果或性能问题。最佳实践是只包裹需要应用混合模式的具体widget,比如单个Image或Icon。

ColorFiltered的性能开销取决于混合模式的复杂度和子树的大小。简单的混合模式(如srcIn)性能开销很小,而复杂的矩阵运算则性能开销较大。此外,ColorFiltered会影响子树的渲染性能,因为需要额外的混合运算。因此,应该避免在动画或频繁更新的widget中使用ColorFiltered,或者考虑使用预渲染的图片。

// 基础用法:简单着色
ColorFiltered(
  colorFilter: ColorFilter.mode(
    Colors.blue,
    BlendMode.srcIn,
  ),
  child: Image.asset('assets/icon.png'),
)

// 基础用法:图片变暗
ColorFiltered(
  colorFilter: ColorFilter.mode(
    Colors.black.withOpacity(0.3),
    BlendMode.multiply,
  ),
  child: Image.asset('assets/photo.jpg'),
)

// 基础用法:图片变亮
ColorFiltered(
  colorFilter: ColorFilter.mode(
    Colors.white.withOpacity(0.5),
    BlendMode.screen,
  ),
  child: Image.asset('assets/photo.jpg'),
)

// 高级用法:颜色矩阵
ColorFiltered(
  colorFilter: ColorFilter.matrix(<double>[
    1, 0, 0, 0, 0,  // R通道:保持不变
    0, 1, 0, 0, 0,  // G通道:保持不变
    0, 0, 1, 0, 0,  // B通道:保持不变
    0, 0, 0, 1, 0,  // A通道:保持不变
  ]),
  child: Image.asset('assets/image.png'),
)

混合模式数学公式详解

每种混合模式都有其对应的数学运算方式,理解这些公式可以帮助开发者预测混合效果,选择最合适的模式。下表展示了常用模式的计算原理:

混合模式 数学公式 效果特点 结果范围
srcIn D = S ⋅ α T D = S \cdot \alpha_T D=SαT 源只显示在目标透明区域 0 ~ S
srcOut D = S ⋅ ( 1 − α T ) D = S \cdot (1 - \alpha_T) D=S(1αT) 源在目标外部显示 0 ~ S
multiply D = S ⋅ T D = S \cdot T D=ST 像素值相乘,颜色加深 0 ~ min(S,T)
screen D = 1 − ( 1 − S ) ⋅ ( 1 − T ) D = 1 - (1-S) \cdot (1-T) D=1(1S)(1T) 反转相乘,颜色变亮 max(S,T) ~ 1
overlay T < 0.5 时 multiply, 否则 screen T < 0.5 \text{时 multiply, 否则 screen} T<0.5 multiply, 否则 screen 结合multiply和screen 0 ~ 1
darken D = min ⁡ ( S , T ) D = \min(S, T) D=min(S,T) 取两者较暗像素 0 ~ min(S,T)
lighten D = max ⁡ ( S , T ) D = \max(S, T) D=max(S,T) 取两者较亮像素 max(S,T) ~ 1
color D = H S ⋅ S + L T D = H_S \cdot S + L_T D=HSS+LT 保留目标亮度,取源色彩 0 ~ 1
luminosity D = L S ⋅ T D = L_S \cdot T D=LST 保留目标色彩,取源亮度 0 ~ 1

其中: S S S表示源像素(Source), T T T表示目标像素(Destination), D D D表示结果像素(Destination), α T \alpha_T αT表示目标的Alpha通道值, H S H_S HS表示源的色相和饱和度, L T L_T LT表示目标的亮度。

混合模式的可视化效果

multiply

screen

overlay

srcIn

源颜色
蓝色

目标颜色
白色

结果
蓝色

目标颜色
黑色

结果
蓝色

目标颜色
灰色

结果
深蓝

目标形状
圆形

圆形蓝色

三、常用混合模式详解与最佳实践

srcIn - 源在内部

srcIn是最常用的着色混合模式,它只在目标图像的非透明区域显示源图像。具体来说,结果像素的Alpha值等于源像素的Alpha值乘以目标像素的Alpha值,颜色值等于源像素的颜色值。这个模式常用于给图标换色,因为图标通常有透明背景,使用srcIn可以只给图标本身着色,而不会影响透明区域。

srcIn的应用场景非常广泛。最常见的场景是动态主题图标,应用的主题颜色变化时,所有图标都会跟随主题颜色变化。使用srcIn模式,只需要一套单色图标资源,就能实现任意颜色的图标。另一个场景是按钮图标的状态变化,比如按下、禁用等状态,可以通过改变srcIn的颜色来实现,而不需要准备多套图标资源。

使用srcIn时需要注意,目标图像必须有透明区域。如果目标图像完全不透明,srcIn的效果可能不明显。因此,图标资源应该使用PNG格式,确保有正确的透明度信息。此外,srcIn的颜色应该是纯色或接近纯色,使用复杂的渐变色可能无法得到预期的效果。

// 场景1:动态主题图标
class ThemedIcon extends StatelessWidget {
  final IconData iconData;
  final Color themeColor;

  const ThemedIcon({
    super.key,
    required this.iconData,
    required this.themeColor,
  });

  
  Widget build(BuildContext context) {
    return ColorFiltered(
      colorFilter: ColorFilter.mode(themeColor, BlendMode.srcIn),
      child: Icon(iconData, size: 24),
    );
  }
}

// 场景2:按钮状态变化
class StatefulButton extends StatefulWidget {
  
  _StatefulButtonState createState() => _StatefulButtonState();
}

class _StatefulButtonState extends State<StatefulButton> {
  bool _isPressed = false;
  bool _isDisabled = false;

  
  Widget build(BuildContext context) {
    Color iconColor;
    if (_isDisabled) {
      iconColor = Colors.grey.shade400;
    } else if (_isPressed) {
      iconColor = Colors.blue.shade700;
    } else {
      iconColor = Colors.blue;
    }

    return GestureDetector(
      onTapDown: (_) => setState(() => _isPressed = true),
      onTapUp: (_) => setState(() => _isPressed = false),
      onTapCancel: () => setState(() => _isPressed = false),
      child: ColorFiltered(
        colorFilter: ColorFilter.mode(iconColor, BlendMode.srcIn),
        child: Icon(Icons.favorite),
      ),
    );
  }
}

// 场景3:深色模式适配
class AdaptiveIcon extends StatelessWidget {
  
  Widget build(BuildContext context) {
    final brightness = Theme.of(context).brightness;
    final iconColor = brightness == Brightness.dark
        ? Colors.white
        : Colors.black;

    return ColorFiltered(
      colorFilter: ColorFilter.mode(iconColor, BlendMode.srcIn),
      child: Icon(Icons.settings),
    );
  }
}

multiply - 正片叠底

multiply模式将源和目标的颜色值相乘,结果总是比原图更暗。这个模式类似于在两张幻灯片上绘制,最终的颜色是两者的叠加效果。multiply适合实现阴影效果、深色水印、降低图片亮度等场景。由于颜色值相乘,结果永远不会比两者中较暗的更亮,这使得multiply是一个纯粹的变暗模式。

multiply的一个特点是对于白色背景的图像,multiply会保留原始颜色,因为白色(值为1)乘以任何颜色都等于该颜色本身。这使得multiply非常适合给白色背景的图标或图片添加深色滤镜。另一个特点是黑色(值为0)会使结果完全变黑,因为任何值乘以0都等于0。

multiply的数学公式为: D = S × T D = S \times T D=S×T,其中 S S S T T T都是归一化到0-1范围的RGB值。如果使用传统的0-255范围,公式需要调整为: D = ( S × T ) / 255 D = (S \times T) / 255 D=(S×T)/255。由于乘法运算的特性,multiply的结果总是小于或等于两个输入中的较小值。

// 场景1:图片水印
class WatermarkedImage extends StatelessWidget {
  final Widget child;

  const WatermarkedImage({super.key, required this.child});

  
  Widget build(BuildContext context) {
    return Stack(
      children: [
        child,
        Positioned(
          bottom: 16,
          right: 16,
          child: ColorFiltered(
            colorFilter: ColorFilter.mode(
              Colors.black.withOpacity(0.5),
              BlendMode.multiply,
            ),
            child: Container(
              padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
              child: Text(
                '@watermark',
                style: TextStyle(
                  color: Colors.white,
                  fontSize: 12,
                ),
              ),
            ),
          ),
        ),
      ],
    );
  }
}

// 场景2:阴影效果
class ShadowedCard extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return Container(
      decoration: BoxDecoration(
        color: Colors.white,
        boxShadow: [
          BoxShadow(
            color: Colors.black.withOpacity(0.2),
            blurRadius: 10,
            offset: Offset(0, 4),
          ),
        ],
      ),
      child: ColorFiltered(
        colorFilter: ColorFilter.mode(
          Colors.black.withOpacity(0.05),
          BlendMode.multiply,
        ),
        child: Padding(
          padding: EdgeInsets.all(16),
          child: Text('卡片内容'),
        ),
      ),
    );
  }
}

// 场景3:降低亮度滤镜
class DarkenedImage extends StatelessWidget {
  final String imagePath;
  final double darkness;

  const DarkenedImage({
    super.key,
    required this.imagePath,
    required this.darkness,
  });

  
  Widget build(BuildContext context) {
    return ColorFiltered(
      colorFilter: ColorFilter.mode(
        Colors.black.withOpacity(darkness.clamp(0.0, 1.0)),
        BlendMode.multiply,
      ),
      child: Image.asset(imagePath),
    );
  }
}

screen - 滤色

screen与multiply相反,将颜色值反转后相乘,结果总是比原图更亮。screen模拟了两个投影仪同时投射到同一屏幕上的效果,光线叠加使得最终图像更亮。screen适合实现高光效果、提亮图片、添加亮色水印等场景。由于反转相乘的特性,screen是一个纯粹的变亮模式。

screen的一个特点是对于黑色背景的图像,screen会保留原始颜色,因为黑色(值为0)反转后为1(白色),1乘以任何颜色都等于该颜色本身。这使得screen非常适合给黑色背景的图标或图片添加亮色滤镜。另一个特点是白色(值为1)会使结果完全变白,因为任何值乘以1都等于该值本身,而screen反转后为0,任何值加上0都等于0(反转回白色)。

screen的数学公式为: D = 1 − ( 1 − S ) × ( 1 − T ) D = 1 - (1 - S) \times (1 - T) D=1(1S)×(1T),其中 S S S T T T都是归一化到0-1范围的RGB值。展开后得到: D = S + T − S × T D = S + T - S \times T D=S+TS×T。这个公式保证了结果永远不会小于两个输入中的较大值。

// 场景1:高光效果
class HighlightedText extends StatelessWidget {
  final String text;

  const HighlightedText({super.key, required this.text});

  
  Widget build(BuildContext context) {
    return Container(
      decoration: BoxDecoration(
        gradient: LinearGradient(
          colors: [Colors.blue, Colors.purple],
        ),
      ),
      child: Center(
        child: ColorFiltered(
          colorFilter: ColorFilter.mode(
            Colors.white.withOpacity(0.3),
            BlendMode.screen,
          ),
          child: Text(
            text,
            style: TextStyle(
              fontSize: 32,
              fontWeight: FontWeight.bold,
              color: Colors.black,
            ),
          ),
        ),
      ),
    );
  }
}

// 场景2:提亮图片
class LightenedImage extends StatelessWidget {
  final String imagePath;
  final double brightness;

  const LightenedImage({
    super.key,
    required this.imagePath,
    required this.brightness,
  });

  
  Widget build(BuildContext context) {
    return ColorFiltered(
      colorFilter: ColorFilter.mode(
        Colors.white.withOpacity(brightness.clamp(0.0, 1.0)),
        BlendMode.screen,
      ),
      child: Image.asset(imagePath),
    );
  }
}

overlay - 叠加

overlay是一个对比混合模式,根据目标像素的亮度,在multiply和screen之间切换。对于亮度小于0.5的像素,使用multiply(变暗);对于亮度大于等于0.5的像素,使用screen(变亮)。这种机制使得overlay能够同时增强明暗对比,产生更有冲击力的视觉效果。

overlay非常适合增强图片的对比度、实现复古胶片效果、艺术性色彩处理等场景。与单纯的multiply或screen不同,overlay不会让图片整体变暗或变亮,而是增强原有的明暗对比,使暗的地方更暗,亮的地方更亮。

overlay的数学公式为:

  • 如果 L ( T ) < 0.5 L(T) < 0.5 L(T)<0.5 D = 2 × S × T D = 2 \times S \times T D=2×S×T
  • 如果 L ( T ) ≥ 0.5 L(T) \geq 0.5 L(T)0.5 D = 1 − 2 × ( 1 − S ) × ( 1 − T ) D = 1 - 2 \times (1 - S) \times (1 - T) D=12×(1S)×(1T)

其中 L ( T ) L(T) L(T)表示目标像素的亮度值,计算公式为: L = 0.299 × R + 0.587 × G + 0.114 × B L = 0.299 \times R + 0.587 \times G + 0.114 \times B L=0.299×R+0.587×G+0.114×B

// 场景1:增强对比度
class EnhancedImage extends StatelessWidget {
  final String imagePath;
  final double intensity;

  const EnhancedImage({
    super.key,
    required this.imagePath,
    required this.intensity,
  });

  
  Widget build(BuildContext context) {
    return ColorFiltered(
      colorFilter: ColorFilter.mode(
        Colors.grey.withOpacity(intensity.clamp(0.0, 1.0)),
        BlendMode.overlay,
      ),
      child: Image.asset(imagePath),
    );
  }
}

// 场景2:复古滤镜
class RetroFilter extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return ColorFiltered(
      colorFilter: ColorFilter.mode(
        Color.fromRGBO(255, 200, 100, 0.3),
        BlendMode.overlay,
      ),
      child: Image.asset('assets/photo.jpg'),
    );
  }
}

其他常用混合模式

模式 效果 适用场景 颜色建议
darken 变暗 叠加深色、阴影 深色
lighten 变亮 叠加亮色、高光 浅色
colorBurn 颜色加深 强烈暗化效果 深色、高饱和度
colorDodge 颜色减淡 强烈提亮效果 浅色、高亮度
softLight 柔光 柔和的光照效果 中等亮度
hardLight 强光 强烈的光照效果 高亮度、高对比度
difference 差异 色彩差异、反色效果 任意颜色
exclusion 排除 类似difference但更柔和 任意颜色
hue 色相 改变颜色的色相 目标色相
saturation 饱和度 调整颜色的饱和度 中等饱和度

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐