🦀 Rust + WASM 实战系列 第 3 篇 阅读时间:约 6 分钟 | 实战可运行
📌 写在前面
上一篇讲了反色(new = 255 - old),简单到爆。
这一篇难度略升:亮度 + 对比度两个滑块联动。看似 PS 里的"傻瓜按钮",背后其实是 3 个数学公式:
亮度:new = old + delta
对比度:new = (old - 128) × factor + 128
安全范围:clamp(0, 255)
🚀 TL;DR
| 操作 | 公式 | 视觉效果 |
|---|---|---|
| 亮度 + | new = old + delta | 整张图变亮 / 变暗 |
| 对比度 + | new = (old - 128) × factor + 128 | 亮的更亮,暗的更暗 |
| 对比度 - | factor 趋近 0 | 整张图趋于中灰 |
关键点:
- 亮度是绝对偏移(加法)
- 对比度是相对缩放(乘法 + 中心点偏移)
- 两个操作先后顺序有讲究(先对比度再亮度)
📖 目录
一、为什么需要亮度 + 对比度?
相机拍出来的图,90% 都需要后期调色。最常用的两个操作:
| 操作 | 解决什么问题 |
|---|---|
| 调亮度 | 拍暗了 → 提亮;拍亮了 → 压暗 |
| 调对比度 | 灰蒙蒙 → 增强对比;刺眼 → 降低对比 |
它们完全正交:可以单独调,也可以组合调。
二、公式拆解
亮度(Brightness)—— 最简单
new = old + delta
| delta 取值 | 效果 |
|---|---|
| +50 | 整张图变亮 |
| 0 | 不变 |
| -50 | 整张图变暗 |
对比度(Contrast)—— 围绕 128 拉伸
new = (old - 128) × factor + 128
| factor 取值 | 效果 |
|---|---|
| 2.0(最强) | 亮(>128)的更亮,暗(<128)的更暗 |
| 1.0(默认) | 不变 |
| 0.0(最弱) | 全部变 128(中灰) |
为什么是 128? 因为 8 bit 灰度的中点就是 128,围绕它做对称拉伸。
factor 怎么算? 通常用一个 -100~+100 的滑块值映射:
factor = 1 + contrast / 100
| contrast 滑块值 | factor | 视觉效果 |
|---|---|---|
| +100 | 2.0 | 对比度拉满 |
| 0 | 1.0 | 不变 |
| -100 | 0.0 | 整张图变中灰(128) |
安全截断 clamp
运算后值可能超出 0~255,必须截断:
final = new.clamp(0, 255)
不截断会怎样? → 整数下溢/上溢 → 颜色错乱、可能出现条纹。
三、顺序问题:先对比度还是先亮度?
这是个实战中容易出错的点。
✅ 正确顺序:先对比度 → 再亮度
❌ 错误顺序:先亮度 → 再对比度
为什么?
假设你想"提亮 + 增强对比度":
✅ 先对比度再亮度(推荐)
原值 100 → 对比度 ×1.5 → 100 + (100-128)×0.5 = 86
→ 亮度 +30 → 86 + 30 = 116
效果:暗部提亮同时保持对比
❌ 先亮度再对比度(不推荐)
原值 100 → 亮度 +30 → 130
→ 对比度 ×1.5 → 128 + (130-128)×0.5 = 129
效果:对比度被抵消,几乎没变化
直觉解释:先调对比度是在"塑形",先调亮度是在"挪位置"。先塑形再挪位置才不会破坏形状。
四、关键代码
核心 3 个公式:
fn adjust_pixel(r: u8, g: u8, b: u8, brightness: f32, factor: f32) -> (u8, u8, u8) {
let adjust = |old: u8| -> u8 {
// 1) 对比度:以 128 为中心拉伸
let contrasted = (old as f32 - 128.0) * factor + 128.0;
// 2) 亮度:直接相加
let adjusted = contrasted + brightness;
// 3) 截断到 0~255
adjusted.clamp(0.0, 255.0) as u8
};
(adjust(r), adjust(g), adjust(b))
}
// 调用前预计算:
// factor = 1.0 + contrast as f32 / 100.0 // -100~100 → 0~2
JS 端调用:
import { brightness_contrast } from 'pixel-math-wasm'
const result = brightness_contrast(pixels, w, h, brightness, contrast)
// 像素 宽 高 亮度 对比度
注:完整实现(含 wasm_bindgen 绑定、RGBA 循环、Alpha 保留)在 src/image/brightness.rs 里,约 50 行。
五、踩坑提醒 ⚠️
1. 整数运算会溢出
// ❌ 错误:u8 直接相乘,超过 255 会溢出
let new = (old - 128) * 2 + 128;
// ✅ 正确:先转 f32 计算,再 clamp 截断回 u8
let new = ((old as f32 - 128.0) * factor + 128.0).clamp(0.0, 255.0) as u8;
2. 忘记截断会怎样?
let old: u8 = 200;
let new: f32 = (old as f32 - 128.0) * 2.0 + 128.0; // = 272
let bad: u8 = new as u8; // = 16(溢出回绕,不是 255)
结果:本来该变 255 的,变成 16 → 出现"鬼影条纹"。
3. clamp 是浮点方法
// f32 / f64 上有 clamp
let x: f32 = 300.0;
x.clamp(0.0, 255.0); // = 255.0
// u8 / i32 上没有 clamp,要用 clamp 自带函数
let n: u8 = 300; // 编译就过不了
4. 滑块拖动 vs 滑动结束
<!-- ✅ 拖动过程中就触发(实时预览) -->
<input @input="onSliderChange" />
<!-- ⚠️ 松手才触发(性能更好但延迟) -->
<input @change="onSliderChange" />
1080p 图片每次处理约 20~50ms,@input 够用;图片特别大才需要 @change。
七、应用场景 & 效果
| 场景 | 典型参数 |
|---|---|
| 拍暗了提亮 | brightness = +30, contrast = +10 |
| 阴天灰蒙蒙 | brightness = +10, contrast = +40 |
| 强光下过曝 | brightness = -20, contrast = -10 |
| 夜景增强 | brightness = -10, contrast = +60 |
| 复古褪色效果 | brightness = 0, contrast = -50 |
| 黑白对比艺术照 | contrast = +80 |
八、进阶玩法
玩法 1:HSL 空间调节
上面是直接在 RGB 空间调。更"专业"的做法是:
RGB → HSL → 调 L 通道 → 转回 RGB
优点:不会改变色相,只改亮度。 缺点:转换有性能开销,慢 3~5 倍。
普通场景 RGB 调就够了,专业调色才需要 HSL。 该内容讲在后续章节中介绍
玩法 2:自适应对比度(直方图均衡化)
固定对比度对所有图片用同一参数。更智能的做法:
- 先算整张图的灰度直方图
- 找到 1% 和 99% 百分位的灰度值
- 把这两个值映射到 0 和 255
效果:任何图片都能自动拉到最佳对比度。这就是 Photoshop 的"自动对比度"。
玩法 3:局部对比度
整张图用同一对比度不够用,局部调效果更好:
原图 → 高斯模糊 → 原图 - 模糊 = 高频细节
高频细节 × factor → 加回原图 = 局部对比度增强
这就是著名的 Unsharp Mask(USM)锐化算法。后面 Sobel 之后会详细讲。
九、参考资料
- Photoshop 帮助文档:「图像 → 调整 → 亮度/对比度」官方说明
- GIMP 文档:Colors → Brightness-Contrast
- Wikipedia - Contrast (vision):对比度的视觉感知原理
- Wikipedia - Image histogram:直方图均衡化原理(玩法 2)
🎁 写在最后
亮度 / 对比度看着简单,但它是图像处理的"万能调色板":
- 90% 的图片后期需求,用这两个滑块就能解决
- 理解了"先对比度再亮度"的顺序,你就掌握了 PS 调色的核心思路
- 进阶的 HSL、USM 都是在这两个基础操作上演化出来的
觉得有帮助的话,点赞 👍、收藏 ⭐、关注 三连支持一下!
下篇预告:《二值化黑白阈值分割:图片只剩纯黑纯白》—— 4 行代码搞定,Otsu 算法自动找最佳阈值,敬请期待。
📦 项目地址:pixel-math-wasm 🦀 Rust + WebAssembly 实战系列:图像处理 → 几何变换 → 分形 → 线代 → 概率 → 综合项目
🏷️ 标签:#Rust #WebAssembly #图像处理 #亮度 #对比度 #调色 #算法