【学Rust写CAD】14线性插值函数(加入color.rs)

lerp 函数源码

rust 复制代码
/// 颜色线性插值
    /// 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。这是一种在两个值之间进行平滑过渡的技术,在这里用于颜色过渡。

工作原理
  1. 输入参数:
  • self: 起始颜色

  • end: 目标颜色

  • t: 插值系数,范围0-256(0=完全使用起始颜色,256=完全使用目标颜色)

  1. 颜色分量分离:
  • 将颜色值拆分为蓝色/红色分量和alpha/绿色分量

使用掩码0xff00ff来提取这些分量(因为颜色通常以ARGB格式存储)

  1. 差值计算:

计算目标颜色和起始颜色在各分量上的差值

  • 使用wrapping_sub处理可能的溢出
  1. 应用插值系数:
  • 将差值乘以插值系数t,然后右移8位(相当于除以256)

  • 这实现了按比例混合两个颜色

  1. 组合结果:
  • 将插值后的分量重新组合成最终颜色

  • 使用掩码确保各分量在正确的位置

为什么这样实现

这种实现方式有几个优点:

  • 使用位操作而不是浮点运算,效率更高

  • 同时处理两个颜色分量(蓝色/红色和alpha/绿色),减少操作次数

  • 使用包装算术(wrapping)避免溢出检查,提高性能

示例

如果起始颜色是红色(0xFF0000),目标颜色是蓝色(0x0000FF),当t=128时:

结果会是紫色(0x800080),正好在红色和蓝色中间

这种技术广泛应用于图形渲染、动画过渡和颜色渐变等场景。

测试代码

测试代码包括多种边界情况和典型场景的测试:

rust 复制代码
#[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 = 1
        assert_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);
    }
}
测试说明
  1. 基本测试:
  • 相同颜色插值结果应该不变

  • 从黑到白插值的边界和中间值

  • 从红到蓝插值的典型场景

  1. 透明度测试:
  • 测试alpha通道的插值是否正确

3.边界测试:

  • 确保t=0和t=256时返回正确的颜色

  • 测试t值超出范围时的行为(虽然参数是u32,但实现只使用低9位)

  1. 分量测试:
  • 测试所有颜色分量同时变化的情况

  • 测试颜色值溢出的处理

  1. 特殊场景:
  • 测试包装算术(wrapping)的正确性

这些测试覆盖了正常使用场景和边界情况,可以验证lerp方法的正确性。

相关推荐
pumpkin845145 小时前
学习笔记十五——rust柯里化,看不懂 `fn add(x) -> impl Fn(y)` 的同学点进来!
笔记·学习·rust
pumpkin845145 小时前
学习笔记二十——Rust trait
笔记·学习·rust
pumpkin8451413 小时前
学习笔记十九——Rust多态
笔记·学习·rust
疏狂难除1 天前
【Tauri2】026——Tauri+Webassembly
rust·wasm·tauri2
Hello.Reader1 天前
给你的 Rust 通用库“插上” WebAssembly 的翅膀
javascript·rust·wasm
苏近之1 天前
一文了解 Rust 中的 Cell 和内部可变性
rust
yezipi耶不耶3 天前
Rust学习之实现命令行小工具minigrep(二)
开发语言·学习·rust
hboot3 天前
rust 全栈应用框架dioxus
前端·rust·全栈
muyouking113 天前
0.深入探秘 Rust Web 框架 Axum
开发语言·前端·rust
勇敢牛牛_3 天前
【Rust基础】使用Rocket构建基于SSE的流式回复
开发语言·后端·rust