Flutter混合模式
1.概述
在flutter
开发中,BlendMode
是一个枚举类,用于控制两个图层之间的混合效果。涉及到两个图像,源图像src和目标图像dst。通过BlendMode
我们可以实现不同的混合效果,比如叠加、透明度混合、颜色混合等等。下面和大家一起探讨Flutter中混合模式的基本概念,以及开发过程中哪里地方可以实际应用~
2.BlendMode枚举值举例
BlendMode
定义了29种不同的混合模式,每一种模式代表一种特定的混合效果,以下列举了相关的枚举列表:
序号 | BlendMode名称 | 描述 |
---|---|---|
1 | clear | 完全透明,不绘制任何内容 |
2 | src | 使用源颜色,忽略目标图像 |
3 | dst | 使用目标颜色,忽略源图像 |
4 | src_over | 源图像绘制在目标图像上方 |
5 | dst_over | 目标图像绘制在源图像上方 |
6 | src_in | 仅显示源图像与目标图像重叠的部分,以源图像的透明度为准 |
7 | dst_in | 仅显示源图像和目标图像重叠部分,以目标图像透明度为准 |
8 | src_out | 源图像的非重叠部分 |
9 | dst_out | 目标图像非重叠部分 |
10 | src_atop | 源图像绘制在目标图像上方,但只有二者重叠的部分显示目标图像 |
11 | dst_atop | 目标图像绘制在源图像上方,但只有重叠部分显示源图像 |
12 | xor | 显示源图像和目标图像非重叠部分 |
13 | plus | 源颜色和目标颜色相加,结果超过1的部分被截断 |
14 | modulate | 源颜色和目标颜色相乘。 |
15 | screen | 源颜色和目标颜色的补色相乘,然后从1中减去结果 |
16 | overlay | 根据目标颜色的亮度选择multiply或screen |
17 | darken | 使用源和目标中较暗的颜色。 |
18 | lighten | 使用源和目标中较亮的颜色。 |
19 | color_dodge | 颜色减淡,基于源颜色增加目标颜色的亮度 |
20 | color_burn | 颜色加深,基于源颜色减少目标颜色的亮度 |
21 | hard_light | 根据源颜色的亮度选择multiply或screen |
22 | soft_light | 柔和的hard_light效果,混合前先减少源颜色的对比度 |
23 | difference | 源颜色和目标颜色的绝对差值 |
24 | exclusion | 类似于difference,但效果更柔和 |
25 | multiply | 源颜色和目标颜色相乘 |
26 | hue | 保留目标颜色的亮度和饱和度,使用源颜色的色调 |
27 | saturation | 保留目标颜色的亮度和色调,使用源颜色的饱和度 |
28 | color | 保留目标颜色的亮度,使用源颜色的色调和饱和度 |
29 | luminosity | 保留源颜色的亮度,使用目标颜色的色调和饱和度 |
3.效果展示
import 'package:flutter/material.dart';
/// 29种混合模式
var colorBlendMode = [
BlendMode.clear,
BlendMode.src,
BlendMode.dst,
BlendMode.srcOver,
BlendMode.dstOver,
BlendMode.srcIn,
BlendMode.dstIn,
BlendMode.srcOut,
BlendMode.dstOut,
BlendMode.srcATop,
BlendMode.dstATop,
BlendMode.xor,
BlendMode.plus,
BlendMode.modulate,
BlendMode.screen,
BlendMode.overlay,
BlendMode.darken,
BlendMode.lighten,
BlendMode.colorDodge,
BlendMode.colorBurn,
BlendMode.hardLight,
BlendMode.softLight,
BlendMode.difference,
BlendMode.exclusion,
BlendMode.multiply,
BlendMode.hue,
BlendMode.saturation,
BlendMode.color,
BlendMode.luminosity,
];
class MyMode extends StatelessWidget {
const MyMode({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('blend_mode'),
leading: IconButton(
icon: const Icon(Icons.arrow_back),
onPressed: () {
Navigator.pop(context);
},
),
),
body: GridView.count(
crossAxisCount: 4,
mainAxisSpacing: 10,
crossAxisSpacing: 10,
children: <Widget>[
for (var mode in colorBlendMode)
GridTile(
child: Stack(
children: <Widget>[
Image.network(
'https://raw.githubusercontent.com/flutter/flutter/master/dev/docs/favicon.ico',
color: Colors.yellow,
/// 指定要混合的颜色
colorBlendMode: mode,
),
Positioned(
bottom: -10,
left: 0,
right: 0,
child: Container(
color: Colors.transparent,
padding: const EdgeInsets.all(8.0),
child: Text(
mode.toString().split('.').last,
style: const TextStyle(
color: Colors.black, fontSize: 12.0),
),
),
),
],
),
),
],
),
),
);
}
}
OpenGL
、Porter-Duff
算法与BlendMode
:
在图形编程中,每种枚举的BlendMode代表一种特定的颜色混合行为。这些模式通常是基于
Porter-Duff
组合算法来定义的,该算法提供了一套用于处理图像和颜色混合的规则。OpenGL
作为一个广泛使用的图形库,通过其混合功能实现了这些BlendMode
。在OpenGL
中,可以通过设置不同的混合因子和使用内部函数来模拟Porter-Duff
算法描述的各种混合效果。OpenGL
提供了一种机制来实现Porter-Duff
定义的混合模式。开发者可以通过配置OpenGL的混合状态来选择不同的BlendMode
,从而在渲染过程中应用相应的Porter-Duff
混合规则。
简化理解:
OpenGL
:想象成一个调酒师,它有很多工具和技巧来混合饮料(在这里,饮料就是颜色或图像)。Porter-Duff算法
:就像是调酒的配方,告诉你如何按照特定的比例和方法混合不同的饮料成分,以达到预期的味道(视觉效果)。BlendMode
:相当于不同类型的鸡尾酒,每种都有其独特的混合方式。选择一种BlendMode,就像是告诉调酒师你要哪种风格的鸡尾酒
4.screen、multiply举例
4.1 multiply
1.amber琥珀色 RGB(255, 179,0) & blue蓝色 RGB(33,150,243)=> BlendMode.multipy混合后 RGB (33,105,0)
2.如何混合?
Porter-Duff: www.w3.org/TR/composit…
使用BlendMode.multiply的公式: 𝐵(𝐶𝑏,𝐶𝑠)=𝐶𝑏×𝐶𝑠B(Cb,Cs)=Cb×Cs
将蓝色和琥珀色的RGB值代入公式,计算混合后的颜色分量:
- 红色分量:33×255=8415
- 绿色分量:150×179=26850
- 蓝色分量:243×0=0
计算结果并转换为0到255之间的值(如果计算结果大于255,我们需要将结果除以255并向下取整):
- 红色分量:8415 % 255
- 绿色分量:26580 % 255
- 蓝色分量:0
混合后的颜色将是 (33,105,0)
效果:
画板:
4.2 screen
1.red红色 RGB(244,67,54)& blue蓝色 RGB(33,150,243)=> sreen混合,混合后RGB (244,176,245)
在BlendMode.screen混合模式的公式中,我们首先计算每个颜色分量的补色,然后计算两个颜色的补色的乘积,最后从1中减去这个乘积来得到混合后的颜色分量
在BlendMode.screen混合模式的公式中,"1" 代表的是颜色分量的最大值,即在RGB颜色模型中每个颜色通道的最大值。因为RGB颜色模型中每个通道(红色、绿色、蓝色)的值范围是0到255,所以这里的"1"实际上是255除以255
首先计算每个颜色分量的补色:
- 红色分量的补色:1−244/255
- 绿色分量的补色:1−67/255
- 蓝色分量的补色:1−54/255
- 蓝色分量的补色:1−33/255
- 绿色分量的补色:1−150/255
- 红色分量的补色:1−243/255
- R: 0.625 * 255 = 244
- G: 0.697 * 255 = 176
- B: 0.963 * 255 = 245
效果图:
看板:
5.应用
5.1蒙层
根据srcOut混合模式,讲遮罩部分设置成透明
import 'package:flutter/material.dart';
class BlendOverlay extends StatefulWidget {
const BlendOverlay({super.key});
@override
State<BlendOverlay> createState() => BlendOverlayState();
}
class BlendOverlayState extends State<BlendOverlay> {
int counter = 0;
GlobalKey containerKey = GlobalKey();
Offset overlayPosition = Offset.zero;
Size overlaySize = Size.zero;
OverlayEntry? overlayEntry;
@override
void initState() {
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
Future.delayed(const Duration(seconds: 1), () {
calculateOverlay();
showGuide();
});
});
super.initState();
}
void calculateOverlay() {
RenderBox? renderBox =
containerKey.currentContext?.findRenderObject() as RenderBox?;
if (renderBox != null) {
Offset position = renderBox.localToGlobal(Offset.zero);
overlayPosition = position;
overlaySize = renderBox.size;
debugPrint('Overlay Position: $overlayPosition Size: $overlaySize');
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.blue,
title: const Text(
'Blend Overlay',
style: TextStyle(
fontSize: 18,
color: Colors.black,
),
),
centerTitle: true,
leading: IconButton(
icon: const Icon(Icons.arrow_back),
onPressed: () {
Navigator.pop(context);
},
)),
body: bodyContent(),
);
}
Widget bodyContent() {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
color: Colors.blueGrey,
alignment: Alignment.center,
width: 100,
height: 100,
child: Text(
'$counter',
style: const TextStyle(
fontSize: 24,
),
),
),
const SizedBox(height: 20),
GestureDetector(
onTap: () {
setState(() {
counter++;
});
},
child: Container(
key: containerKey,
width: 80,
height: 80,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
color: Colors.amber,
),
),
)
],
),
);
}
void showGuide() {
overlayEntry = OverlayEntry(
builder: (context) {
return Material(
color: Colors.transparent,
child: Stack(
alignment: Alignment.topCenter,
children: [
ColorFiltered(
colorFilter: ColorFilter.mode(
Colors.black.withOpacity(0.6),
BlendMode.srcOut,
),
child: Stack(
alignment: Alignment.topCenter,
children: [
Container(
decoration: const BoxDecoration(
// 任何颜色均可
color: Colors.white,
backgroundBlendMode: BlendMode.dstOut,
),
),
Positioned(
top: overlayPosition.dy,
child: Container(
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.all(Radius.circular(20)),
),
width: overlaySize.width,
height: overlaySize.height,
),
),
],
),
),
Positioned(
top: overlayPosition.dy + 100,
child: GestureDetector(
onTap: () {
overlayEntry?.remove();
overlayEntry = null;
},
child: Container(
padding: const EdgeInsets.all(8),
color: Colors.white,
child: const Text(
"知道了",
style: TextStyle(fontSize: 20, color: Colors.black),
),
),
),
),
],
),
);
},
);
Overlay.of(context).insert(overlayEntry!);
}
}
5.2圆环
同时可以搭配动画使用,实现光圈等一系列效果
6.性能
- 简单混合模式 vs 复杂混合模式:一些简单的混合模式,如 src、dst、srcOver 等,通常对性能影响较小,因为它们的计算量较低。而一些复杂的混合模式,如 multiply、screen、overlay 等,可能涉及更复杂的像素计算和颜色混合算法,会增加 GPU 的计算负担,导致性能下降。
- 建议:
- 尽量避免在大量图层或动画中同时使用复杂的混合模式。
- 在需要使用混合模式的地方,尽量选择简单的混合模式,以减少 GPU 计算负担。
- 在性能要求较高的场景下,可以通过减少混合模式的使用或优化混合模式的算法来提升性能。
转载自:https://juejin.cn/post/7381426879816957971