云计算百科
云计算领域专业知识百科平台

【egui】Color32 使用手册

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 常用预定义颜色

颜色常量RGB值
透明 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颜色处理
// – 需要数学正确性的场景


十二、性能提示

  • 优先使用预定义常量:Color32::RED 比 Color32::from_rgb(255,0,0) 更快
  • 批量操作转Rgba:大量颜色计算时,先转Rgba批量处理
  • 避免频繁转换:Color32 ↔ Rgba 转换有开销
  • 使用gamma_multiply_u8:u8版本比f32版本更快
  • 利用查找表:from_rgba_unmultiplied 内部已优化

  • 十三、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框架的核心颜色类型,它的设计基于两个深刻洞察:

  • 伽马空间:让存储值同时匹配人眼感知和显示器硬件,形成完美闭环
  • 预乘Alpha:用统一的数学框架同时表达普通颜色和加法颜色
  • 理解这两点,就能明白为什么Color32可以如此简洁而强大。记住核心原则:显示和感知用Color32,物理计算转Rgba,就能充分发挥两种格式的优势。

    加法颜色不是另类,而是这个统一框架的自然延伸——当alpha=0时,RGB自然成为光源强度,完美融入所有运算而无需特判。

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » 【egui】Color32 使用手册
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!