lerp 函数源码
/// 颜色线性插值/// t 取值范围 0..256,0 表示完全使用当前颜色(self),256 表示完全使用目标颜色(end)#[inline]pub fn lerp(self, end: Color, t: u32) -> Color {let mask = 0xff00ff;// 提取目标颜色的蓝色和红色分量let brb = end.0 & 0xff00ff;// 提取目标颜色的alpha和绿色分量let bag = (end.0 >> 8) & 0xff00ff;// 提取当前颜色的蓝色和红色分量let arb = self.0 & 0xff00ff;// 提取当前颜色的alpha和绿色分量let aag = (self.0 >> 8) & 0xff00ff;// 计算分量差值let drb = brb.wrapping_sub(arb);let dag = bag.wrapping_sub(aag);// 应用插值系数let drb = drb.wrapping_mul(t) >> 8;let dag = dag.wrapping_mul(t) >> 8;// 计算最终分量值let rb = arb + drb;let ag = aag + dag;// 组合并返回结果颜色Color((rb & mask) | ((ag << 8) & !mask))}
颜色线性插值(Lerp)解释
这段代码实现了两个颜色之间的线性插值(linear interpolation),通常称为lerp。这是一种在两个值之间进行平滑过渡的技术,在这里用于颜色过渡。
工作原理
- 输入参数:
-
self: 起始颜色
-
end: 目标颜色
-
t: 插值系数,范围0-256(0=完全使用起始颜色,256=完全使用目标颜色)
- 颜色分量分离:
- 将颜色值拆分为蓝色/红色分量和alpha/绿色分量
使用掩码0xff00ff来提取这些分量(因为颜色通常以ARGB格式存储)
- 差值计算:
计算目标颜色和起始颜色在各分量上的差值
- 使用wrapping_sub处理可能的溢出
- 应用插值系数:
-
将差值乘以插值系数t,然后右移8位(相当于除以256)
-
这实现了按比例混合两个颜色
- 组合结果:
-
将插值后的分量重新组合成最终颜色
-
使用掩码确保各分量在正确的位置
为什么这样实现
这种实现方式有几个优点:
-
使用位操作而不是浮点运算,效率更高
-
同时处理两个颜色分量(蓝色/红色和alpha/绿色),减少操作次数
-
使用包装算术(wrapping)避免溢出检查,提高性能
示例
如果起始颜色是红色(0xFF0000),目标颜色是蓝色(0x0000FF),当t=128时:
结果会是紫色(0x800080),正好在红色和蓝色中间
这种技术广泛应用于图形渲染、动画过渡和颜色渐变等场景。
测试代码
测试代码包括多种边界情况和典型场景的测试:
#[cfg(test)]
mod tests {use super::Color;#[test]fn test_lerp() {// 测试相同颜色插值let white = Color(0xFFFFFFFF);for i in 0..=256 {assert_eq!(white.lerp(white, i).0, 0xFFFFFFFF, "相同颜色插值失败,i={}", i);}// 测试从黑到白插值let black = Color(0xFF000000);let white = Color(0xFFFFFFFF);assert_eq!(black.lerp(white, 0).0, 0xFF000000, "t=0应返回起始颜色");assert_eq!(black.lerp(white, 256).0, 0xFFFFFFFF, "t=256应返回目标颜色");assert_eq!(black.lerp(white, 128).0, 0xFF7F7F7F, "中间值不正确");// 测试红色到蓝色插值let red = Color(0xFFFF0000);let blue = Color(0xFF0000FF);assert_eq!(red.lerp(blue, 0).0, 0xFFFF0000);assert_eq!(red.lerp(blue, 256).0, 0xFF0000FF);assert_eq!(red.lerp(blue, 128).0, 0xFF7F007F);// 测试透明度变化let opaque = Color(0xFF123456);let transparent = Color(0x00123456);assert_eq!(opaque.lerp(transparent, 128).0, 0x7F123456);// 测试边界情况let color1 = Color(0x12345678);let color2 = Color(0x87654321);assert_eq!(color1.lerp(color2, 0).0, color1.0);assert_eq!(color1.lerp(color2, 256).0, color2.0);// 测试所有分量同时变化let start = Color(0x00000000);let end = Color(0x01020304);assert_eq!(start.lerp(end, 64).0, 0x00404040); // 1/4 of end color}#[test]fn test_lerp_edge_cases() {// 测试t值超出范围的情况(虽然参数是u32,但实际只使用低9位)let c1 = Color(0x00000000);let c2 = Color(0xFFFFFFFF);assert_eq!(c1.lerp(c2, 257).0, c1.lerp(c2, 1).0); // 257 & 0xFF = 1assert_eq!(c1.lerp(c2, 512).0, c1.lerp(c2, 0).0); // 512 & 0xFF = 0// 测试颜色值溢出情况let low = Color(0x00000000);let high = Color(0xFFFFFFFF);assert_eq!(low.lerp(high, 255).0, 0xFFFEFEFE);}
}
测试说明
- 基本测试:
-
相同颜色插值结果应该不变
-
从黑到白插值的边界和中间值
-
从红到蓝插值的典型场景
- 透明度测试:
- 测试alpha通道的插值是否正确
3.边界测试:
-
确保t=0和t=256时返回正确的颜色
-
测试t值超出范围时的行为(虽然参数是u32,但实现只使用低9位)
- 分量测试:
-
测试所有颜色分量同时变化的情况
-
测试颜色值溢出的处理
- 特殊场景:
- 测试包装算术(wrapping)的正确性
这些测试覆盖了正常使用场景和边界情况,可以验证lerp方法的正确性。