docs(transform): 中文注释图像变换逻辑
This commit is contained in:
@@ -1,3 +1,33 @@
|
|||||||
|
//! 图像几何变换模块(纯坐标运算,不涉及像素缓冲区)。
|
||||||
|
//!
|
||||||
|
//! 对应 Wayland `wl_output::Transform` 的 8 种旋转变体(旋转 + 翻转),
|
||||||
|
//! 为屏幕捕获提供 ROI(Region of Interest)裁剪与坐标系换算。
|
||||||
|
//!
|
||||||
|
//! # 与 Go 的对照
|
||||||
|
//!
|
||||||
|
//! - Go 标准库 `image/geom.go` 的 `Rectangle` 仅支持轴对齐矩形;本模块额外处理
|
||||||
|
//! 90°/180°/270° 旋转与水平/垂直翻转下的矩形映射。
|
||||||
|
//! - Go 用 `int` 表示坐标;本模块用 `i32`(与 `wl_output` 协议一致)。
|
||||||
|
//! - Wayland 协议要求捕获 ROI 在变换后的"帧坐标"中给出,本模块负责
|
||||||
|
//! "屏坐标 → 帧坐标"的换算(见 [`screen_to_frame`])。
|
||||||
|
//!
|
||||||
|
//! 注意:本模块**不操作像素缓冲区**(无 `&[u8]` / `Vec::with_capacity`),
|
||||||
|
//! 只做整数算术;真正的像素拷贝在 `state.rs` / `cap_portal.rs` 中通过
|
||||||
|
//! DMA-BUF 或 shm 完成。计划文档中提到的 `&[u8]` slice / `Vec` 预分配
|
||||||
|
//! 等模式不属于本模块,本模块的"重量级"Rust 模式聚焦在 `match` 穷尽匹配、
|
||||||
|
//! 元组解构、if 表达式、or-pattern 与整数 helper 方法(`.abs()`/`.clamp()`)。
|
||||||
|
|
||||||
|
// Wayland `wl_output::Transform` 的 8 种变体:4 种纯旋转(Normal*)+ 4 种
|
||||||
|
// "先水平翻转再旋转"(Flipped*)。单元 enum(无关联数据),`Copy + Eq` 派生
|
||||||
|
// 使其可在 `match` / `==` 中零开销使用。
|
||||||
|
//
|
||||||
|
// Go 没有内置 enum,等价于 `type Transform int` + `const ( Normal = iota; ... )`;
|
||||||
|
// Rust 的 enum 是真代数类型,编译期保证 `match` 穷尽性(漏写一个 variant
|
||||||
|
// 会直接编译失败,而 Go 的 switch 不强制 default)。
|
||||||
|
//
|
||||||
|
// `#[derive(...)]` 宏说明:`Debug`→允许 `{:?}` 调试输出;`Clone, Copy`→
|
||||||
|
// 单元 enum 按位复制即可(等价于 Go 整数值语义);`PartialEq, Eq`→自动生成
|
||||||
|
// `==`/`!=`,基于 variant tag 比较。
|
||||||
/// Coordinate transformation module for Wayland output transforms.
|
/// Coordinate transformation module for Wayland output transforms.
|
||||||
///
|
///
|
||||||
/// Handles the 8 `wl_output` transform variants (rotation + reflection)
|
/// Handles the 8 `wl_output` transform variants (rotation + reflection)
|
||||||
@@ -16,6 +46,11 @@ pub enum Transform {
|
|||||||
Flipped270,
|
Flipped270,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 轴对齐矩形(Axis-Aligned Bounding Box,AABB)。
|
||||||
|
//
|
||||||
|
// 所有字段 `i32`(与 Wayland 协议一致);Go 类比 `image.Rectangle` 但
|
||||||
|
// 用 `(x, y, w, h)` 而非 `(Min, Max)`,便于直接喂给 FFmpeg VAAPI 的 ROI 参数。
|
||||||
|
// `Copy + Eq`:值语义,函数传参/返回零开销(无 `&Rect` 借用开销)。
|
||||||
/// Axis-aligned rectangle in integer coordinates.
|
/// Axis-aligned rectangle in integer coordinates.
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub struct Rect {
|
pub struct Rect {
|
||||||
@@ -25,6 +60,11 @@ pub struct Rect {
|
|||||||
pub h: i32,
|
pub h: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 返回变换对应的 2×2 基础矩阵 `(a, b, c, d)`。
|
||||||
|
//
|
||||||
|
// 这是纯算术查表,无副作用,编译器很容易内联到调用点。返回值用 4-tuple 而非
|
||||||
|
// `[i32; 4]` 数组:Rust 元组每字段可有不同类型(此处都是 i32 但语义不同),
|
||||||
|
// 模式匹配解构时更显式(`let (a, b, c, d) = ...`)。
|
||||||
/// Returns the 2×2 basis matrix (a, b, c, d) for the given transform.
|
/// Returns the 2×2 basis matrix (a, b, c, d) for the given transform.
|
||||||
///
|
///
|
||||||
/// The matrix represents the affine mapping from screen coordinates to
|
/// The matrix represents the affine mapping from screen coordinates to
|
||||||
@@ -35,18 +75,37 @@ pub struct Rect {
|
|||||||
/// [new_y] = [c d] [y]
|
/// [new_y] = [c d] [y]
|
||||||
/// ```
|
/// ```
|
||||||
pub fn transform_basis(transform: Transform) -> (i32, i32, i32, i32) {
|
pub fn transform_basis(transform: Transform) -> (i32, i32, i32, i32) {
|
||||||
|
// `match` 是 Rust 的模式匹配控制流,对 enum 必须**穷尽**(exhaustive):
|
||||||
|
// 漏写任意 variant 会直接编译失败。Go 的 `switch` 不强制 default,
|
||||||
|
// 此处 8 个 variant 必须全部列出,编译器即充当完整性检查器。
|
||||||
|
//
|
||||||
|
// 每个 arm 形如 `Pattern => expr,`,返回的 4-tuple 编码矩阵系数。
|
||||||
|
// 这些数值来自 Wayland `wl_output::Transform` 协议规范,不可随意修改。
|
||||||
match transform {
|
match transform {
|
||||||
|
// 单位矩阵:屏幕坐标 = 帧坐标。
|
||||||
Transform::Normal => (1, 0, 0, 1),
|
Transform::Normal => (1, 0, 0, 1),
|
||||||
|
// 顺时针 90°:x/y 互换并取反。
|
||||||
Transform::Normal90 => (0, 1, -1, 0),
|
Transform::Normal90 => (0, 1, -1, 0),
|
||||||
|
// 180°:两轴都取反。
|
||||||
Transform::Normal180 => (-1, 0, 0, -1),
|
Transform::Normal180 => (-1, 0, 0, -1),
|
||||||
|
// 顺时针 270°(= 逆时针 90°)。
|
||||||
Transform::Normal270 => (0, -1, 1, 0),
|
Transform::Normal270 => (0, -1, 1, 0),
|
||||||
|
// 水平翻转(沿 Y 轴镜像):x 取反。
|
||||||
Transform::Flipped => (-1, 0, 0, 1),
|
Transform::Flipped => (-1, 0, 0, 1),
|
||||||
|
// 翻转 + 90°。
|
||||||
Transform::Flipped90 => (0, 1, 1, 0),
|
Transform::Flipped90 => (0, 1, 1, 0),
|
||||||
|
// 翻转 + 180°(等价于垂直翻转)。
|
||||||
Transform::Flipped180 => (1, 0, 0, -1),
|
Transform::Flipped180 => (1, 0, 0, -1),
|
||||||
|
// 翻转 + 270°。
|
||||||
Transform::Flipped270 => (0, -1, -1, 0),
|
Transform::Flipped270 => (0, -1, -1, 0),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 将矩形从"屏幕坐标"映射到"帧坐标",并平移到第一象限([0, frame_w) × [0, frame_h))。
|
||||||
|
//
|
||||||
|
// 这是 ROI(捕获区域)参数换算的核心:用户在屏幕上选了一块 `(x, y, w, h)`,
|
||||||
|
// 但 Wayland 帧已应用了 output transform(例如 90° 旋转),编码器看到的帧
|
||||||
|
// 坐标与屏幕坐标不同,必须先变换再喂给 VAAPI。
|
||||||
/// Transform a rectangle from screen space to frame space.
|
/// Transform a rectangle from screen space to frame space.
|
||||||
///
|
///
|
||||||
/// Applies the 2×2 basis matrix and computes offsets so the result
|
/// Applies the 2×2 basis matrix and computes offsets so the result
|
||||||
@@ -57,11 +116,17 @@ pub fn transform_basis(transform: Transform) -> (i32, i32, i32, i32) {
|
|||||||
/// new_y = c * x + d * y + offset_y
|
/// new_y = c * x + d * y + offset_y
|
||||||
/// ```
|
/// ```
|
||||||
pub fn screen_to_frame(transform: Transform, rect: Rect, frame_w: i32, frame_h: i32) -> Rect {
|
pub fn screen_to_frame(transform: Transform, rect: Rect, frame_w: i32, frame_h: i32) -> Rect {
|
||||||
|
// 元组解构(tuple destructuring):4-tuple 一次性拆成 4 个 `i32` 变量。
|
||||||
|
// 类比 Go 的 `a, b, c, d := transformBasis(transform)`,但 Rust 的元组
|
||||||
|
// 是真类型(可作为参数/返回值),Go 只能用多返回值模拟。
|
||||||
let (a, b, c, d) = transform_basis(transform);
|
let (a, b, c, d) = transform_basis(transform);
|
||||||
|
|
||||||
// Compute the offset so that the transformed origin maps correctly.
|
// Compute the offset so that the transformed origin maps correctly.
|
||||||
// For transforms with negative components, we need to shift by the
|
// For transforms with negative components, we need to shift by the
|
||||||
// frame dimension to keep coordinates in [0, frame_w) × [0, frame_h).
|
// frame dimension to keep coordinates in [0, frame_w) × [0, frame_h).
|
||||||
|
// `if ... { ... } else { ... }` 在 Rust 中是**表达式**(而非语句),
|
||||||
|
// 直接产出值赋给 `offset_x`。Go 没有 ternary,必须 `var offset_x int;
|
||||||
|
// if ... { offset_x = frame_w }`,Rust 这种写法更紧凑。
|
||||||
let offset_x = if a + b < 0 { frame_w } else { 0 };
|
let offset_x = if a + b < 0 { frame_w } else { 0 };
|
||||||
let offset_y = if c + d < 0 { frame_h } else { 0 };
|
let offset_y = if c + d < 0 { frame_h } else { 0 };
|
||||||
|
|
||||||
@@ -70,6 +135,13 @@ pub fn screen_to_frame(transform: Transform, rect: Rect, frame_w: i32, frame_h:
|
|||||||
let new_w = a * rect.w + b * rect.h;
|
let new_w = a * rect.w + b * rect.h;
|
||||||
let new_h = c * rect.w + d * rect.h;
|
let new_h = c * rect.w + d * rect.h;
|
||||||
|
|
||||||
|
// 结构体字面量(struct literal):`Rect { x: ..., y: ..., ... }`。
|
||||||
|
// 类比 Go 的 `image.Rectangle{Min: ..., Max: ...}`;Rust 允许字段简写
|
||||||
|
//(变量名与字段名相同时只写一个,例如 `x` 而非 `x: x`)。
|
||||||
|
//
|
||||||
|
// `.abs()` 是 `i32` 的内置方法(取绝对值):
|
||||||
|
// 旋转后 `new_w`/`new_h` 可能为负(例如 90° 下宽变成原高取反),
|
||||||
|
// 矩形尺寸必须非负,故取绝对值。
|
||||||
Rect {
|
Rect {
|
||||||
x: new_x,
|
x: new_x,
|
||||||
y: new_y,
|
y: new_y,
|
||||||
@@ -78,32 +150,61 @@ pub fn screen_to_frame(transform: Transform, rect: Rect, frame_w: i32, frame_h:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 90°/270° 旋转变换下,输出画布的宽高需要交换(横向屏幕旋转后变纵向)。
|
||||||
|
//
|
||||||
|
// 辅助函数:是则返回 `(h, w)`,否则原样返回 `(w, h)`。Go 类比:
|
||||||
|
// ```go
|
||||||
|
// func transposeIf(t Transform, w, h int) (int, int) {
|
||||||
|
// switch t { case Normal90, Normal270, Flipped90, Flipped270: return h, w }
|
||||||
|
// return w, h
|
||||||
|
// }
|
||||||
|
// ```
|
||||||
/// Swap width and height for 90° or 270° rotations.
|
/// Swap width and height for 90° or 270° rotations.
|
||||||
///
|
///
|
||||||
/// After a quarter-turn rotation the output dimensions are transposed
|
/// After a quarter-turn rotation the output dimensions are transposed
|
||||||
/// relative to the input. This helper returns `(h, w)` for those cases
|
/// relative to the input. This helper returns `(h, w)` for those cases
|
||||||
/// and `(w, h)` unchanged otherwise.
|
/// and `(w, h)` unchanged otherwise.
|
||||||
pub fn transpose_if_transform_transposed(transform: Transform, w: i32, h: i32) -> (i32, i32) {
|
pub fn transpose_if_transform_transposed(transform: Transform, w: i32, h: i32) -> (i32, i32) {
|
||||||
|
// `match` 配合 **or-pattern**:用 `|` 把多个 variant 合并为一个 arm,
|
||||||
|
// 共享同一个表达式分支。Go 的 `switch` 用 `case A, B, C:` fallthrough 等价。
|
||||||
|
// 注意 Rust 的 match 不存在隐式 fallthrough,每个 arm 必须 `=>` 显式给出表达式。
|
||||||
match transform {
|
match transform {
|
||||||
|
// 四种"四分之一圈"旋转:宽高必须互换。
|
||||||
Transform::Normal90
|
Transform::Normal90
|
||||||
| Transform::Normal270
|
| Transform::Normal270
|
||||||
| Transform::Flipped90
|
| Transform::Flipped90
|
||||||
| Transform::Flipped270 => (h, w),
|
| Transform::Flipped270 => (h, w),
|
||||||
|
// `_` 是通配符(wildcard),匹配所有未列出的 variant。
|
||||||
|
// Rust 要求 match 穷尽,最后用 `_ =>` 兜底等价于 Go `default:` 分支。
|
||||||
|
// 此处涵盖 `Normal` / `Normal180` / `Flipped` / `Flipped180`。
|
||||||
_ => (w, h),
|
_ => (w, h),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 将矩形裁剪到 `(0, 0) .. (bounds_w, bounds_h)` 范围内。
|
||||||
|
//
|
||||||
|
// 用于 ROI 校验:用户给的坐标可能为负或越界,编码器不接受这样的区域,
|
||||||
|
// 必须先 clamp 到合法范围。Go 标准库没有 `clamp` 内置函数(Go 1.21 才加入
|
||||||
|
// `min`/`max` 内置),通常要手写 `if x < lo { x = lo } else if x > hi { x = hi }`;
|
||||||
|
// Rust 的 `i32::clamp(lo, hi)` 是方法调用,语义更直观。
|
||||||
/// Clip a rectangle so it stays inside `(0, 0) .. (bounds_w, bounds_h)`.
|
/// Clip a rectangle so it stays inside `(0, 0) .. (bounds_w, bounds_h)`.
|
||||||
///
|
///
|
||||||
/// The resulting rectangle has non-negative origin and its extent does
|
/// The resulting rectangle has non-negative origin and its extent does
|
||||||
/// not exceed the bounds.
|
/// not exceed the bounds.
|
||||||
pub fn fit_inside_bounds(rect: Rect, bounds_w: i32, bounds_h: i32) -> Rect {
|
pub fn fit_inside_bounds(rect: Rect, bounds_w: i32, bounds_h: i32) -> Rect {
|
||||||
|
// `.clamp(lo, hi)`:将值限制在 `[lo, hi]` 闭区间内(小于 lo 返回 lo,
|
||||||
|
// 大于 hi 返回 hi,否则原值)。返回 `i32`(self by value)。
|
||||||
let x = rect.x.clamp(0, bounds_w);
|
let x = rect.x.clamp(0, bounds_w);
|
||||||
let y = rect.y.clamp(0, bounds_h);
|
let y = rect.y.clamp(0, bounds_h);
|
||||||
|
// `.min(other)`:返回 `self` 与 `other` 的较小值(等价 Go 的 `if a < b` 三元)。
|
||||||
|
// 此处把矩形的右边界限制到 `bounds_w`,避免越界。
|
||||||
let right = (rect.x + rect.w).min(bounds_w);
|
let right = (rect.x + rect.w).min(bounds_w);
|
||||||
let bottom = (rect.y + rect.h).min(bounds_h);
|
let bottom = (rect.y + rect.h).min(bounds_h);
|
||||||
|
// `.max(other)`:返回较大值。此处保证宽高非负(`right - x` 在
|
||||||
|
// 完全越界的退化情形下可能为负,取 max(0) 兜底)。
|
||||||
let w = (right - x).max(0);
|
let w = (right - x).max(0);
|
||||||
let h = (bottom - y).max(0);
|
let h = (bottom - y).max(0);
|
||||||
|
// 字段简写:`x`/`y`/`w`/`h` 变量名与 `Rect` 字段名相同,可省略 `field: value`。
|
||||||
Rect { x, y, w, h }
|
Rect { x, y, w, h }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user