Color32 使用手册(修订版)
一、简介
Color32 是 egui/ecolor 中最常用的颜色类型,采用32位存储的预乘Alpha sRGBA格式。它是面向显示设备的颜色格式,所有操作都在伽马空间进行——这意味着它的数值变化与人眼感知一致,同时完美匹配显示器的物理特性。
什么是伽马空间?
// 伽马空间是连接物理世界和人眼感知的桥梁
物理光强(线性) ←(γ≈2.2)→ 伽马值(存储) ←(γ≈0.45)→ 人眼感知
// 简单理解:
// – 在伽马空间,数值增加一倍,人眼感觉亮度增加一倍
// – 在物理世界,这对应着约4.6倍的光强变化
// – 这种设计让存储值既符合直觉,又匹配显示器硬件
二、快速开始
2.1 创建颜色
use ecolor::Color32;
// 使用预定义常量
let red = Color32::RED;
let transparent = Color32::TRANSPARENT;
let black = Color32::BLACK;
let white = Color32::WHITE;
// 从RGB创建(不透明)
let custom = Color32::from_rgb(180, 200, 220);
// 从灰度创建
let gray = Color32::from_gray(128); // 视觉上50%的灰
// 从带透明度的RGBA创建(推荐方式)
let semi_red = Color32::from_rgba_unmultiplied(255, 0, 0, 128);
2.2 常用预定义颜色
| 透明 | TRANSPARENT | (0,0,0,0) |
| 黑 | BLACK | (0,0,0) |
| 深灰 | DARK_GRAY | (96,96,96) |
| 灰 | GRAY | (160,160,160) |
| 浅灰 | LIGHT_GRAY | (220,220,220) |
| 白 | WHITE | (255,255,255) |
| 红 | RED | (255,0,0) |
| 绿 | GREEN | (0,255,0) |
| 蓝 | BLUE | (0,0,255) |
| 黄 | YELLOW | (255,255,0) |
| 青 | CYAN | (0,255,255) |
| 紫 | MAGENTA | (255,0,255) |
| 橙 | ORANGE | (255,165,0) |
| 棕 | BROWN | (165,42,42) |
| 紫罗兰 | PURPLE | (128,0,128) |
| 金 | GOLD | (255,215,0) |
三、核心概念理解
3.1 预乘Alpha (Premultiplied Alpha)
Color32内部使用预乘Alpha格式,即RGB值已经乘以了Alpha通道:
// 普通RGBA(非预乘):(r, g, b, a)
// 预乘RGBA:(r*a/255, g*a/255, b*a/255, a)
// 示例:50%透明的红色
let normal = (255, 0, 0, 128); // 普通RGBA
let premultiplied = (128, 0, 0, 128); // 预乘RGBA(Color32内部格式)
预乘Alpha的优势:
- ✅ 统一处理普通颜色和加法颜色:alpha=0时自然表示光源
- ✅ 更高效的混合计算:减少一次乘法
- ✅ GPU纹理格式的标准:纹理过滤直接正确
3.2 伽马空间 (Gamma Space)
Color32的所有操作都在伽马空间进行,这是经过深思熟虑的设计:
// 为什么用伽马空间?
// 1. 对人眼:数值变化 = 感知变化
let dark = Color32::from_gray(64); // 视觉上25%灰
let mid = Color32::from_gray(128); // 视觉上50%灰
let light = Color32::from_gray(192); // 视觉上75%灰
// 2. 对显示器:数值直接对应电压
// 显示器亮度 = (存储值/255)^2.2
// 存储值128 → 亮度 ≈ 22% (刚好是物理上的50%?不,是感知上的50%)
// 3. 对整个系统:完美闭环
// 物理光强 → 伽马编码 → 存储 → 显示器 → 人眼感知
// ↑ ↓
// └────────── 互相补偿 ────────────────┘
3.3 加法颜色 (Additive Color)——统一框架中的特例
加法颜色是预乘Alpha格式的自然延伸,不是另类,而是同一数学框架下的优雅特例:
// 在预乘Alpha的统一框架中:
// – 普通颜色:alpha > 0,RGB = 颜色 × alpha
// – 加法颜色:alpha = 0,RGB = 光源强度
// 创建加法颜色
let additive_red = Color32::from_rgb_additive(255, 0, 0);
// 内部存储:[255, 0, 0, 0]
// 加法颜色的物理意义:光源
// – 红色光,强度255
// – 不遮挡背景,只会让背景变亮
// 检查是否为加法颜色
if color.is_additive() {
println!("这是光源(alpha=0)");
}
加法颜色不是特例,而是统一公式的自然结果:
// 统一的混合公式:结果 = 前景 + 背景 * (1-α_前景)
// 当前景是加法颜色(α=0)时:
结果 = 前景 + 背景 * 1 = 前景 + 背景 // 直接相加!
// 当背景是加法颜色(α_bg=0)时:
结果 = 前景 + 背景_add * (1–α_前景) // 背景被前景衰减
// 当两者都是加法(α=0)时:
结果 = 前景 + 背景_add // 光源叠加
// 所以加法颜色完全融入统一公式,不需要特殊处理!
3.4 伽马空间 vs 线性空间
// Color32(伽马空间)- 用于显示和感知均匀的操作
let darker = color.gamma_multiply(0.5); // 感知上变暗一半
// Rgba(线性空间)- 用于物理正确的计算
let rgba = Rgba::from(color); // 转换为线性空间
let linear_result = rgba * 0.5; // 物理上减半光强
let back = Color32::from(linear_result); // 转回伽马空间
// 选择指南:
// – 调色、UI、简单亮度调整 → 用Color32(伽马空间)
// – 光照、物理模拟、精确混合 → 转Rgba(线性空间)
四、构造方法详解
4.1 基础构造函数
| from_rgb(r, g, b) | 不透明RGB | Color32::from_rgb(255, 128, 0) |
| from_rgba_premultiplied(r, g, b, a) | 预乘RGBA(直接内部格式) | Color32::from_rgba_premultiplied(128, 0, 0, 128) |
| from_rgba_unmultiplied(r, g, b, a) | 普通RGBA(推荐) | Color32::from_rgba_unmultiplied(255, 0, 0, 128) |
| from_gray(l) | 灰度不透明 | Color32::from_gray(128) |
| from_black_alpha(a) | 黑色带透明度 | Color32::from_black_alpha(64) |
| from_white_alpha(a) | 白色带透明度 | Color32::from_white_alpha(64) |
| from_additive_luminance(l) | 加法亮度 | Color32::from_additive_luminance(128) |
4.2 特殊构造函数
// 从RGB创建加法颜色(光源)
let additive = Color32::from_rgb_additive(255, 200, 100);
// 从已有颜色获取加法版本(转为光源)
let original = Color32::RED;
let additive_version = original.additive(); // alpha设为0,RGB保持不变
4.3 性能优化说明
from_rgba_unmultiplied 包含特殊优化:
// alpha=0 直接返回透明
// alpha=255 直接返回不透明RGB
// 其他情况使用查找表加速计算
let color = Color32::from_rgba_unmultiplied(255, 0, 0, 128);
五、颜色访问方法
5.1 获取通道值
let color = Color32::from_rgba_unmultiplied(100, 150, 200, 180);
// 获取预乘后的各通道
let r = color.r(); // 预乘后的红色值
let g = color.g(); // 预乘后的绿色值
let b = color.b(); // 预乘后的蓝色值
let a = color.a(); // Alpha值(未预乘)
// 检查不透明度
if color.is_opaque() {
println!("完全不透明 (alpha=255)");
}
// 检查是否为加法颜色(光源)
if color.is_additive() {
println!("这是光源 (alpha=0)");
}
5.2 转换为数组/元组
// 转数组(预乘格式)
let array = color.to_array(); // [r, g, b, a]
// 转元组(预乘格式)
let tuple = color.to_tuple(); // (r, g, b, a)
5.3 转换为非预乘RGBA
// 获取普通RGBA值(用于颜色选择器显示等)
let [r, g, b, a] = color.to_srgba_unmultiplied();
// 注意:透明颜色可能有微小误差(±3以内)
5.4 转换为浮点值
// 直接转换为0-1浮点(无伽马转换,谨慎使用!)
let [r, g, b, a] = color.to_normalized_gamma_f32();
// 通常应该用 Rgba::from(color) 代替
六、颜色运算
6.1 颜色混合——统一的公式
let background = Color32::from_gray(200);
let foreground = Color32::from_rgba_unmultiplied(255, 0, 0, 128);
// 统一的混合公式:结果 = 前景 + 背景 * (1-α_前景)
let result = foreground.blend(background);
// 这个公式统一处理所有情况:
// – 普通前景 + 普通背景
// – 加法前景 (α=0) + 普通背景 → 直接相加
// – 普通前景 + 加法背景 → 背景被前景衰减
// – 双加法 → 光源叠加
// 等价于:
let result = background.gamma_multiply_u8(255 – foreground.a()) + foreground;
6.2 亮度调整
let color = Color32::RED;
// 伽马空间变暗(感知均匀,速度快)
let darker = color.gamma_multiply(0.5); // 感觉上暗了一半
let darker_u8 = color.gamma_multiply_u8(127); // u8版本(127≈0.5)
// 线性空间变暗(物理正确,但速度慢)
let linear_darker = color.linear_multiply(0.5); // 光强减半
6.3 颜色插值
let start = Color32::RED;
let end = Color32::BLUE;
// 在伽马空间插值(感知均匀)
let t = 0.3; // 0.0=start, 1.0=end
let mid = start.lerp_to_gamma(end, t);
6.4 获取不透明版本
let transparent = Color32::from_rgba_unmultiplied(100, 150, 200, 100);
let opaque = transparent.to_opaque(); // 转为不透明版本
6.5 亮度计算
let color = Color32::from_rgb(100, 150, 200);
let intensity = color.intensity(); // 返回0-1之间的感知亮度
// 使用标准灰度公式:0.299R + 0.587G + 0.114B
七、运算符重载
7.1 加法(饱和相加)
// 通道值饱和相加(不会溢出)
let c1 = Color32::from_rgb(200, 100, 50);
let c2 = Color32::from_rgb(100, 200, 220);
let sum = c1 + c2; // 每个通道饱和相加
// 加法对普通颜色和加法颜色一视同仁
let light = Color32::from_rgb_additive(100, 50, 20);
let background = Color32::from_gray(100);
let lit = background + light; // 光源叠加到背景
7.2 乘法(伽马空间)
// 伽马空间通道相乘(除以255归一化)
let c1 = Color32::from_rgb(200, 100, 50);
let c2 = Color32::from_rgb(100, 200, 220);
let product = c1 * c2; // 用于滤镜效果等
7.3 索引访问
let color = Color32::from_rgb(100, 150, 200);
let r = color[0]; // 等价于 color.r()
let g = color[1]; // 等价于 color.g()
let b = color[2]; // 等价于 color.b()
let a = color[3]; // 等价于 color.a()
// 也可以修改(需要mut)
let mut color = color;
color[0] = 255; // 修改红色通道
八、与Rgba的互操作
8.1 Color32 → Rgba
let color32 = Color32::RED;
// 转换为线性空间Rgba(自动伽马校正)
let rgba = Rgba::from(color32);
// 现在可以在线性空间进行复杂计算
let multiplied = rgba * 0.5;
let blended = rgba.lerp(&Rgba::WHITE, 0.3);
8.2 Rgba → Color32
let rgba = Rgba::from_rgb(0.5, 0.3, 0.8);
// 转回伽马空间Color32(自动伽马校正)
let color32 = Color32::from(rgba);
8.3 何时转换
| 设置UI颜色 | Color32 | 直接显示,感知均匀 |
| 颜色动画 | Rgba → 计算 → Color32 | 线性插值物理正确 |
| 光照计算 | Rgba | 物理正确性要求 |
| 复杂颜色混合 | Rgba | 线性空间混合正确 |
| 存储纹理 | Color32 | 节省内存,匹配硬件 |
九、实用技巧
9.1 调试输出
let color = Color32::from_rgba_unmultiplied(100, 150, 200, 128);
println!("{:?}", color); // 输出: #64_96_C8_80
// 格式:十六进制 RR_GG_BB_AA(预乘值!)
9.2 特殊颜色标记
// DEBUG_COLOR:用于调试,醒目但不刺眼
let debug = Color32::DEBUG_COLOR; // (0, 200, 0, 128)
// PLACEHOLDER:占位符,表示"无颜色"
let placeholder = Color32::PLACEHOLDER; // (64, 254, 0, 128)
9.3 性能最佳实践
// ✅ 好:使用预定义常量
let red = Color32::RED;
// ✅ 好:使用from_rgba_unmultiplied(有优化)
let semi = Color32::from_rgba_unmultiplied(255, 0, 0, 128);
// ✅ 好:批量操作在Rgba空间进行
let colors: Vec<Color32> = …;
let rgbs: Vec<Rgba> = colors.iter().map(|&c| Rgba::from(c)).collect();
// … 批量计算 …
let results: Vec<Color32> = rgbs.into_iter().map(Color32::from).collect();
// ❌ 不好:频繁在Color32和Rgba间转换
for color in colors {
let result = Color32::from(Rgba::from(color) * 0.5); // 循环内转换
}
十、完整示例
10.1 颜色选择器值处理
use ecolor::{Color32, Rgba};
struct ColorPicker {
color: Color32,
}
impl ColorPicker {
fn new() -> Self {
Self {
color: Color32::from_rgb(128, 128, 255),
}
}
// 从UI控件接收非预乘RGBA值
fn set_from_unmultiplied(&mut self, r: u8, g: u8, b: u8, a: u8) {
self.color = Color32::from_rgba_unmultiplied(r, g, b, a);
}
// 获取用于显示的非预乘RGBA值
fn get_for_display(&self) -> [u8; 4] {
self.color.to_srgba_unmultiplied()
}
// 调整透明度(感知均匀)
fn set_opacity(&mut self, opacity: u8) {
// 保持颜色不变,只改透明度
let [r, g, b, _] = self.color.to_srgba_unmultiplied();
self.color = Color32::from_rgba_unmultiplied(r, g, b, opacity);
}
// 变暗(感知均匀)
fn darken(&mut self, factor: f32) {
self.color = self.color.gamma_multiply(factor);
}
// 混合两个颜色
fn blend_with(&self, other: Color32) -> Color32 {
other.blend(self.color) // other在上,self在下
}
}
10.2 渐变动画
use ecolor::{Color32, Rgba};
struct GradientAnimation {
start: Color32,
end: Color32,
progress: f32, // 0.0 到 1.0
}
impl GradientAnimation {
fn new(start: Color32, end: Color32) -> Self {
Self {
start,
end,
progress: 0.0,
}
}
// 获取当前颜色(在伽马空间插值)- 感知均匀
fn current_gamma(&self) -> Color32 {
self.start.lerp_to_gamma(self.end, self.progress)
}
// 获取当前颜色(在线性空间插值)- 物理正确
fn current_linear(&self) -> Color32 {
let start_rgba = Rgba::from(self.start);
let end_rgba = Rgba::from(self.end);
let current_rgba = start_rgba.lerp(&end_rgba, self.progress);
Color32::from(current_rgba)
}
// 动画更新
fn update(&mut self, delta: f32) {
self.progress = (self.progress + delta).clamp(0.0, 1.0);
}
}
10.3 主题颜色系统
use ecolor::Color32;
struct Theme {
primary: Color32,
secondary: Color32,
background: Color32,
text: Color32,
error: Color32,
disabled: Color32,
}
impl Theme {
fn light() -> Self {
Self {
primary: Color32::from_rgb(0x42, 0xA5, 0xF5), // 亮蓝
secondary: Color32::from_rgb(0x66, 0x66, 0x66), // 中灰
background: Color32::WHITE,
text: Color32::BLACK,
error: Color32::RED,
disabled: Color32::from_rgb(0xCC, 0xCC, 0xCC),
}
}
fn dark() -> Self {
Self {
primary: Color32::from_rgb(0x90, 0xCA, 0xF9), // 浅蓝
secondary: Color32::from_rgb(0xB0, 0xB0, 0xB0), // 浅灰
background: Color32::from_rgb(0x33, 0x33, 0x33),
text: Color32::WHITE,
error: Color32::from_rgb(0xFF, 0x6B, 0x6B), // 浅红
disabled: Color32::from_rgb(0x66, 0x66, 0x66),
}
}
// 创建带透明度的版本
fn with_opacity(&self, color: Color32, opacity: u8) -> Color32 {
let [r, g, b, _] = color.to_srgba_unmultiplied();
Color32::from_rgba_unmultiplied(r, g, b, opacity)
}
// 悬停效果(感知均匀的亮化)
fn hover(&self, color: Color32) -> Color32 {
color.gamma_multiply(1.2) // 感觉上亮20%
}
// 点击效果(感知均匀的暗化)
fn active(&self, color: Color32) -> Color32 {
color.gamma_multiply(0.8) // 感觉上暗20%
}
}
10.4 光源系统示例——加法颜色的实际应用
use ecolor::Color32;
struct LightSource {
position: (f32, f32),
color: Color32, // 加法颜色
intensity: f32,
}
impl LightSource {
fn new(x: f32, y: f32, r: u8, g: u8, b: u8, intensity: f32) -> Self {
Self {
position: (x, y),
color: Color32::from_rgb_additive(
(r as f32 * intensity) as u8,
(g as f32 * intensity) as u8,
(b as f32 * intensity) as u8,
),
intensity,
}
}
// 光照计算:光源叠加到场景
fn illuminate(&self, scene_color: Color32, distance: f32) -> Color32 {
// 距离衰减
let attenuation = 1.0 / (1.0 + distance * 0.1);
let attenuated = self.color.gamma_multiply(attenuation);
// 光源叠加(加法混合)- 统一公式的自然结果
scene_color + attenuated
}
}
// 多个光源叠加
let mut scene = Color32::from_gray(50); // 深灰色背景
let lights = vec![
LightSource::new(10.0, 10.0, 255, 100, 50, 1.0), // 暖色光
LightSource::new(50.0, 30.0, 100, 200, 255, 0.8), // 冷色光
LightSource::new(80.0, 80.0, 255, 255, 100, 0.5), // 淡黄光
];
// 所有光源叠加 – 每个都是加法颜色,自然地相加
for light in &lights {
scene = light.illuminate(scene, 20.0);
}
// 最终 scene 是多个光源叠加的效果
十一、常见问题
Q1: 为什么颜色混合结果不符合预期?
可能原因:Color32在伽马空间混合,这是为了匹配人眼感知。如果需要物理正确的混合,转Rgba:
// 伽马空间混合(默认)- 感知均匀
let gamma_blend = fg.blend(bg);
// 线性空间混合(物理正确)
let fg_rgba = Rgba::from(fg);
let bg_rgba = Rgba::from(bg);
let linear_blend = Color32::from(fg_rgba * fg_alpha + bg_rgba * (1 – fg_alpha));
Q2: 加法颜色需要特殊处理吗?
不需要! 加法颜色完全融入统一的预乘框架:
// 加法颜色和普通颜色使用完全相同的API
let light = Color32::from_rgb_additive(255, 200, 100);
let normal = Color32::from_rgb(100, 150, 200);
// 混合时自动处理
let result = light.blend(normal); // 根据alpha自动选择行为
let sum = light + normal; // 加法运算符同样适用
Q3: from_rgba_unmultiplied 和 from_rgba_premultiplied 区别?
- from_rgba_unmultiplied:传入普通RGBA(如颜色选择器的值),内部自动转换为预乘格式
- from_rgba_premultiplied:直接传入预乘格式(通常不需要直接调用)
Q4: 如何精确控制透明度?
// 创建指定透明度的颜色
let color = Color32::from_rgba_unmultiplied(255, 0, 0, 128);
// 调整已有颜色的透明度
let [r, g, b, _] = color.to_srgba_unmultiplied();
let new_opacity = Color32::from_rgba_unmultiplied(r, g, b, 64);
Q5: 为什么 to_srgba_unmultiplied 返回的值有小误差?
因为预乘和反预乘过程中有取整运算,透明颜色的RGB值在往返转换时会有±3以内的误差,这是正常现象。
Q6: 伽马空间和线性空间,什么时候用哪个?
// 用Color32(伽马空间):
// – UI颜色设置
// – 主题定义
// – 感知均匀的亮度调整
// – 简单的颜色混合(如按钮悬停效果)
// 转Rgba(线性空间):
// – 物理光照计算
// – 精确的颜色混合
// – HDR颜色处理
// – 需要数学正确性的场景
十二、性能提示
十三、API速查表
| 构造 | from_rgb | 不透明RGB |
| from_rgba_unmultiplied | 普通RGBA(推荐) | |
| from_rgb_additive | 加法颜色(光源) | |
| from_gray | 灰度 | |
| 转换 | to_srgba_unmultiplied | 转普通RGBA |
| to_array/to_tuple | 转数组/元组 | |
| to_opaque | 转不透明 | |
| additive | 转加法颜色 | |
| 查询 | r/g/b/a | 获取通道 |
| is_opaque | 是否不透明 | |
| is_additive | 是否加法颜色 | |
| intensity | 计算亮度 | |
| 运算 | blend | 颜色混合(统一公式) |
| gamma_multiply | 伽马空间乘法 | |
| linear_multiply | 线性空间乘法 | |
| lerp_to_gamma | 伽马空间插值 | |
| 运算符 | + | 饱和相加(统一处理) |
| * | 伽马空间相乘 | |
| [] | 索引访问 |
十四、结语
Color32 是egui UI框架的核心颜色类型,它的设计基于两个深刻洞察:
理解这两点,就能明白为什么Color32可以如此简洁而强大。记住核心原则:显示和感知用Color32,物理计算转Rgba,就能充分发挥两种格式的优势。
加法颜色不是另类,而是这个统一框架的自然延伸——当alpha=0时,RGB自然成为光源强度,完美融入所有运算而无需特判。
网硕互联帮助中心

评论前必须登录!
注册