Flutter 框架跨平台鸿蒙开发——图片混合模式
ColorBlendMode用于控制图片与颜色的混合效果。

图片混合模式
一、ColorBlendMode概述与核心概念
ColorBlendMode是Flutter中用于控制图片与颜色混合效果的核心概念,源自计算机图形学领域的图像处理技术。BlendMode定义了源图像(Source)和目标图像(Destination)如何进行数学运算来生成最终的混合结果。Flutter中的BlendMode源自经典的Porter-Duff图像合成算法,该算法由Thomas Porter和Tom Duff在1984年提出,至今仍是图像处理领域的基础理论之一。
混合模式在图像处理、UI设计、游戏开发等领域有着广泛的应用。在移动应用开发中,图片混合模式被广泛应用于UI美化、图片编辑、主题切换等场景。比如深色模式下的图标着色、图片水印添加、滤镜效果实现、特殊视觉效果的创建等,都离不开混合模式的支持。通过混合模式,开发者可以实现复杂的视觉效果,而无需修改原始图像资源。
混合模式的工作原理基于像素级别的数学运算。对于每个像素点,系统会根据源像素的RGB值和Alpha通道值、目标像素的RGB值和Alpha通道值,以及选定的混合模式公式,计算出最终像素的RGB值和Alpha值。不同的混合模式使用不同的数学公式,从而产生不同的视觉效果。这种像素级别的操作使得混合模式可以实现非常精细和可控的效果。
混合模式主要分为以下几个大类:基础模式、变暗模式、变亮模式、对比模式、色彩模式等。每个类别下又包含多个具体的模式,每个模式都有其独特的数学公式和视觉效果。理解这些分类和每个模式的特点,有助于开发者快速选择适合自己需求的混合模式。
混合模式分类与特性
| 类别 | 模式 | 描述 | 数学特性 | 典型应用 |
|---|---|---|---|---|
| 基础混合 | 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。
二、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=S⋅T | 像素值相乘,颜色加深 | 0 ~ min(S,T) |
| screen | D = 1 − ( 1 − S ) ⋅ ( 1 − T ) D = 1 - (1-S) \cdot (1-T) D=1−(1−S)⋅(1−T) | 反转相乘,颜色变亮 | 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=HS⋅S+LT | 保留目标亮度,取源色彩 | 0 ~ 1 |
| luminosity | D = L S ⋅ T D = L_S \cdot T D=LS⋅T | 保留目标色彩,取源亮度 | 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表示目标的亮度。
混合模式的可视化效果
三、常用混合模式详解与最佳实践
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−(1−S)×(1−T),其中 S S S和 T T T都是归一化到0-1范围的RGB值。展开后得到: D = S + T − S × T D = S + T - S \times T D=S+T−S×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=1−2×(1−S)×(1−T)
其中 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
更多推荐

所有评论(0)