feat(plugin): OfxParametricParameterSuite v1
Parametric (curve/LUT) parameters: ParamValue::Parametric holds one ordered control-point curve per dimension (identity default over the declared range), evaluated as piecewise cubic Hermite with auto (centered-difference) slopes; the full suite — evaluate / count / get / set / add / delete / delete-all — with the spec's error codes, descriptor defaults copied to instances, and instanceChanged notifications on edits. paramDefine accepts OfxParamTypeParametric; the dimension/range and UI-colour properties round-trip. 148/148 real plugins discovered, 135 registered (one more than before: the parametric-suite consumer).
This commit is contained in:
@@ -66,6 +66,7 @@ pub mod instance;
|
||||
pub mod node;
|
||||
pub mod node_factory;
|
||||
pub mod param;
|
||||
pub mod param_curve;
|
||||
pub mod progress;
|
||||
pub mod property;
|
||||
pub mod render;
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! 参数体系:12 种参数实例 + param ↔ oaknode 桥。
|
||||
//! 参数体系:13 种参数实例 + param ↔ oaknode 桥。
|
||||
//!
|
||||
//! 对应 C++ 的 `ParamInstance`/`OliveParamInstance`。桥的语义
|
||||
//! (M9 已定):节点输入值变化 → 写回 OFX 参数;OFX 参数被插件
|
||||
@@ -69,7 +69,9 @@ pub const TYPE_PUSHBUTTON: &str = "OfxParamTypePushButton";
|
||||
pub const TYPE_GROUP: &str = "OfxParamTypeGroup";
|
||||
/// kOfxParamTypePage。
|
||||
pub const TYPE_PAGE: &str = "OfxParamTypePage";
|
||||
/// kOfxParamTypeParametric(第 1 期不支持,paramDefine 拒绝)。
|
||||
/// kOfxParamTypeParametric(值 = 曲线列表,经
|
||||
/// OfxParametricParameterSuite 读写,见
|
||||
/// [`crate::suites::parametric`] 与 [`crate::param_curve`])。
|
||||
pub const TYPE_PARAMETRIC: &str = "OfxParamTypeParametric";
|
||||
|
||||
// ---- 属性名(ofxParam.h / ofxCore.h)----
|
||||
@@ -150,6 +152,14 @@ pub(crate) const PAGE_SKIP_ROW: &str = "OfxParamPageSkipRow";
|
||||
pub(crate) const PAGE_SKIP_COLUMN: &str = "OfxParamPageSkipColumn";
|
||||
/// kOfxParamPropCustomInterpCallbackV1。
|
||||
pub(crate) const P_CUSTOM_INTERP: &str = "OfxParamPropCustomCallbackV1";
|
||||
/// kOfxParamPropParametricDimension(int×1,默认 1)。
|
||||
pub(crate) const P_PARAMETRIC_DIMENSION: &str = "OfxParamPropParametricDimension";
|
||||
/// kOfxParamPropParametricRange(double×2,默认 (0,1))。
|
||||
pub(crate) const P_PARAMETRIC_RANGE: &str = "OfxParamPropParametricRange";
|
||||
/// kOfxParamPropParametricUIColour(double×3N,默认未设)。
|
||||
pub(crate) const P_PARAMETRIC_UI_COLOUR: &str = "OfxParamPropParametricUIColour";
|
||||
/// kOfxParamPropParametricInteractBackground(pointer×1,默认 NULL)。
|
||||
pub(crate) const P_PARAMETRIC_INTERACT_BG: &str = "OfxParamPropParametricInteractBackground";
|
||||
/// kOfxParamPropPageChild。
|
||||
pub(crate) const P_PAGE_CHILD: &str = "OfxParamPropPageChild";
|
||||
/// kOfxParamPropGroupOpen。
|
||||
@@ -216,7 +226,8 @@ pub enum ParamKind {
|
||||
}
|
||||
|
||||
/// OFX 类型字符串 → 值类别 + 维度(HS: ofxhParam.cpp `findType`)。
|
||||
/// 无值类(PushButton/Group/Page/Unknown)返回 None。
|
||||
/// 无值类(PushButton/Group/Page)与 parametric(曲线值走
|
||||
/// OfxParametricParameterSuite,无标量变长入口)返回 None。
|
||||
pub(crate) fn kind_of_type(ofx_type: &str) -> Option<(ParamKind, usize)> {
|
||||
match ofx_type {
|
||||
TYPE_INTEGER => Some((ParamKind::Int, 1)),
|
||||
@@ -271,6 +282,10 @@ fn type_default(ofx_type: &str) -> ParamValue {
|
||||
TYPE_BYTES | TYPE_CUSTOM => ParamValue::Bytes(Vec::new()),
|
||||
TYPE_PUSHBUTTON => ParamValue::PushButton,
|
||||
TYPE_GROUP | TYPE_PAGE => ParamValue::Container,
|
||||
// parametric 默认 = 1 条恒等曲线(range 默认 (0,1))。
|
||||
TYPE_PARAMETRIC => ParamValue::Parametric(vec![crate::param_curve::Curve::identity(
|
||||
0.0, 1.0,
|
||||
)]),
|
||||
_ => ParamValue::Container, // 未知类型占位(paramDefine 已拒绝)
|
||||
},
|
||||
}
|
||||
@@ -319,6 +334,11 @@ pub enum ParamValue {
|
||||
PushButton,
|
||||
/// kOfxParamTypeGroup / Page(容器,无值)。
|
||||
Container,
|
||||
/// kOfxParamTypeParametric:每维一条曲线(曲线模型见
|
||||
/// [`crate::param_curve`];默认 = 1 条恒等曲线,维度由属性
|
||||
/// `OfxParamPropParametricDimension` 决定,见
|
||||
/// [`ParamDef::new`])。
|
||||
Parametric(Vec<crate::param_curve::Curve>),
|
||||
}
|
||||
|
||||
/// 参数定义(describe 产物,见 [`crate::descriptor::EffectDescriptor`])。
|
||||
@@ -434,6 +454,33 @@ impl ParamDef {
|
||||
TYPE_GROUP => {
|
||||
props.set_one(P_GROUP_OPEN, Value::Int(1));
|
||||
}
|
||||
TYPE_PARAMETRIC => {
|
||||
// parametric(HS addValueParamProps(eDouble, 0) 的
|
||||
// invariantProps + allParametric,ofxhParam.cpp:285-330):
|
||||
// 值类 invariant 属性照旧;Animates=1(HS 的 animates 计算
|
||||
// 对 parametric 为真);另加 4 个 parametric 专属属性。
|
||||
// 数值属性表(Min/Max/Display)HS 也加(变量维 double),
|
||||
// 但插件侧从不读 parametric 的数值属性(ofxsPropertyValidation
|
||||
// 只校验本块 4 个 + 通用属性),本 crate 遵循
|
||||
// is_numeric_type 的门控,不为 parametric 预置。
|
||||
props.set_one(P_IS_ANIMATING, Value::Int(0));
|
||||
props.set_one(P_IS_AUTO_KEYING, Value::Int(0));
|
||||
props.set_one(P_PERSISTANT, Value::Int(1));
|
||||
props.set_one(P_EVALUATE_ON_CHANGE, Value::Int(1));
|
||||
props.set_one(P_CAN_UNDO, Value::Int(1));
|
||||
props.set_one(
|
||||
P_CACHE_INVALIDATION,
|
||||
Value::String(cs(V_INVALIDATE_VALUE_CHANGE)),
|
||||
);
|
||||
props.set_one(P_ANIMATES, Value::Int(1));
|
||||
props.set_one(P_PARAMETRIC_DIMENSION, Value::Int(1));
|
||||
props.define(P_PARAMETRIC_UI_COLOUR, vec![]);
|
||||
props.set_one(P_PARAMETRIC_INTERACT_BG, Value::Pointer(std::ptr::null_mut()));
|
||||
props.define(
|
||||
P_PARAMETRIC_RANGE,
|
||||
vec![Value::Double(0.0), Value::Double(1.0)],
|
||||
);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
@@ -825,6 +872,39 @@ mod tests {
|
||||
));
|
||||
}
|
||||
|
||||
/// parametric 的默认:1 条恒等曲线;4 个专属属性就位
|
||||
/// (dimension=1、range=(0,1)、UIColour 未设、interact 背景 NULL)。
|
||||
#[test]
|
||||
fn parametric_type_default() {
|
||||
assert!(matches!(
|
||||
type_default(TYPE_PARAMETRIC),
|
||||
ParamValue::Parametric(ref c) if c.len() == 1 && c[0].len() == 2
|
||||
));
|
||||
let d = ParamDef::new("curve", TYPE_PARAMETRIC);
|
||||
assert!(matches!(d.default, ParamValue::Parametric(ref c) if c.len() == 1));
|
||||
assert!(matches!(
|
||||
d.props.get(P_PARAMETRIC_DIMENSION, 0),
|
||||
Some(Value::Int(1))
|
||||
));
|
||||
assert!(matches!(
|
||||
d.props.get(P_PARAMETRIC_RANGE, 0),
|
||||
Some(Value::Double(0.0))
|
||||
));
|
||||
assert!(matches!(
|
||||
d.props.get(P_PARAMETRIC_RANGE, 1),
|
||||
Some(Value::Double(1.0))
|
||||
));
|
||||
assert_eq!(d.props.dimension(P_PARAMETRIC_UI_COLOUR), 0);
|
||||
assert!(matches!(
|
||||
d.props.get(P_PARAMETRIC_INTERACT_BG, 0),
|
||||
Some(Value::Pointer(p)) if p.is_null()
|
||||
));
|
||||
// 默认曲线求值为恒等。
|
||||
if let ParamValue::Parametric(curves) = &d.default {
|
||||
assert_eq!(curves[0].evaluate(0.5), 0.5);
|
||||
}
|
||||
}
|
||||
|
||||
/// set_from_node 的维度截断/补零与字符串静默路径。
|
||||
#[test]
|
||||
fn set_from_node_dimension_rules() {
|
||||
|
||||
@@ -0,0 +1,393 @@
|
||||
// Oak Video Editor - Non-Linear Video Editor
|
||||
// Copyright (C) 2026 Oak Team
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! 参数化曲线模型(OfxParametricParameterSuite 的宿主侧数据与求值)。
|
||||
//!
|
||||
//! 曲线 = 按 key 升序排列的控制点列表,每个控制点携带 Hermite 切线
|
||||
//! (slope)。suite([`crate::suites::parametric`])只暴露 key/value
|
||||
//! 编辑入口,slope 一律由 [`Curve::recompute_slopes`] 自动计算——
|
||||
//! 内部点用两侧差分(centered finite difference),端点用单侧差分;
|
||||
//! 字段保持 `pub`,便于测试/宿主显式编辑斜率。
|
||||
//!
|
||||
//! 求值 = 分段三次 Hermite(见 [`Curve::evaluate`])。这是 OFX 规范
|
||||
//! 建议宿主采用的"曲线编辑器式"表示(ofxParametricParam.h 的
|
||||
//! parametric 参数文档),与贝塞尔形式等价(Hermite 切线 × 段长即
|
||||
//! 贝塞尔控制臂)。
|
||||
|
||||
/// 曲线控制点:`key`(parametric 位置,定义域由
|
||||
/// `OfxParamPropParametricRange` 限定)、`value`(求值结果)、
|
||||
/// `slope`(该点处的一阶导数,三次 Hermite 的切线)。
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub struct ControlPoint {
|
||||
/// parametric 位置(x)。
|
||||
pub key: f64,
|
||||
/// 求值结果(y)。
|
||||
pub value: f64,
|
||||
/// 一阶导数(自动差分 / 显式编辑)。
|
||||
pub slope: f64,
|
||||
}
|
||||
|
||||
impl ControlPoint {
|
||||
/// 按 key/value 构造(slope 由 [`Curve::recompute_slopes`] 填入)。
|
||||
pub fn new(key: f64, value: f64) -> Self {
|
||||
Self {
|
||||
key,
|
||||
value,
|
||||
slope: 0.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 一条参数曲线:控制点按 key 升序(不变量;由本模块的修改接口
|
||||
/// 维护)。
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct Curve {
|
||||
/// 控制点序列(key 升序,无重复 key)。
|
||||
pub points: Vec<ControlPoint>,
|
||||
}
|
||||
|
||||
impl Curve {
|
||||
/// 空曲线(`DeleteAllControlPoints` 后的状态;求值退化为恒等,
|
||||
/// 见 [`Curve::evaluate`])。
|
||||
pub fn empty() -> Self {
|
||||
Self {
|
||||
points: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// 恒等曲线:`{(lo, lo), (hi, hi)}` + 自动 slope —— parametric
|
||||
/// 参数的默认 default(ofxParametricParam.h:"The default default
|
||||
/// value of a parametric curve is to be an identity lookup")。
|
||||
/// `lo`/`hi` 来自 `OfxParamPropParametricRange`(默认 (0,1))。
|
||||
pub fn identity(lo: f64, hi: f64) -> Self {
|
||||
let mut c = Self {
|
||||
points: vec![
|
||||
ControlPoint::new(lo, lo),
|
||||
ControlPoint::new(hi, hi),
|
||||
],
|
||||
};
|
||||
c.recompute_slopes();
|
||||
c
|
||||
}
|
||||
|
||||
/// 由 key/value 对构造(slope 自动差分;按传入顺序——调用方
|
||||
/// 保证升序,或经 [`Curve::upsert`] 逐点插入)。
|
||||
pub fn from_pairs(pairs: &[(f64, f64)]) -> Self {
|
||||
let mut c = Self {
|
||||
points: pairs
|
||||
.iter()
|
||||
.map(|&(k, v)| ControlPoint::new(k, v))
|
||||
.collect(),
|
||||
};
|
||||
c.recompute_slopes();
|
||||
c
|
||||
}
|
||||
|
||||
/// 控制点数。
|
||||
pub fn len(&self) -> usize {
|
||||
self.points.len()
|
||||
}
|
||||
|
||||
/// 是否为空(`DeleteAllControlPoints` 后)。
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.points.is_empty()
|
||||
}
|
||||
|
||||
/// 第 `i` 个控制点(越界 → None)。
|
||||
pub fn nth(&self, i: usize) -> Option<&ControlPoint> {
|
||||
self.points.get(i)
|
||||
}
|
||||
|
||||
/// 重算全部 slope:内部点中心差分(两侧跨距),端点单侧差分;
|
||||
/// 单点/空曲线无差分 → slope 0。修改 key/value 后必须调用,
|
||||
/// 否则 Hermite 切线滞后于控制点几何。
|
||||
pub fn recompute_slopes(&mut self) {
|
||||
let n = self.points.len();
|
||||
for i in 0..n {
|
||||
self.points[i].slope = slope_at(&self.points, i);
|
||||
}
|
||||
}
|
||||
|
||||
/// 三次 Hermite 求值。
|
||||
///
|
||||
/// - 空曲线:恒等(f(x) = x)——与"默认 default 是恒等查找"
|
||||
/// 一致,`DeleteAllControlPoints` 后曲线退化为中性恒等,无端点
|
||||
/// 可钳制,直接返回 x;
|
||||
/// - 单点曲线:常数(该点 value);
|
||||
/// - x 在首/末 key 之外:钳制到端点值(任务/规范:越界 key
|
||||
/// 钳制到端点值)。
|
||||
///
|
||||
/// 数学:段 [x0, x1] 上令 t = (x - x0)/(x1 - x0),h = x1 - x0,
|
||||
/// 三次 Hermite 基函数
|
||||
/// h00 = 2t³ - 3t² + 1, h01 = -2t³ + 3t²
|
||||
/// h10 = t³ - 2t² + t, h11 = t³ - t²
|
||||
/// f(x) = h00·y0 + h01·y1 + h·(h10·m0 + h11·m1)。
|
||||
/// slope 为 1 的恒等曲线恰退化为 f(x) = x(端点钳制外)。
|
||||
pub fn evaluate(&self, x: f64) -> f64 {
|
||||
let n = self.points.len();
|
||||
if n == 0 {
|
||||
return x;
|
||||
}
|
||||
if n == 1 {
|
||||
return self.points[0].value;
|
||||
}
|
||||
if x <= self.points[0].key {
|
||||
return self.points[0].value;
|
||||
}
|
||||
if x >= self.points[n - 1].key {
|
||||
return self.points[n - 1].value;
|
||||
}
|
||||
// 定位所在段:points[i].key <= x < points[i+1].key。
|
||||
let i = self.points.partition_point(|p| p.key <= x) - 1;
|
||||
let a = self.points[i];
|
||||
let b = self.points[i + 1];
|
||||
let h = b.key - a.key;
|
||||
if h <= 0.0 {
|
||||
return a.value; // 防御:重复 key(不变量保证不出现)
|
||||
}
|
||||
let t = (x - a.key) / h;
|
||||
let t2 = t * t;
|
||||
let t3 = t2 * t;
|
||||
let h00 = 2.0 * t3 - 3.0 * t2 + 1.0;
|
||||
let h01 = -2.0 * t3 + 3.0 * t2;
|
||||
let h10 = t3 - 2.0 * t2 + t;
|
||||
let h11 = t3 - t2;
|
||||
h00 * a.value + h01 * b.value + h * (h10 * a.slope + h11 * b.slope)
|
||||
}
|
||||
|
||||
/// 放入 (key, value):同 key 已存在 → 覆盖其值(返回 false);
|
||||
/// 否则按 key 升序插入(返回 true)。随后重算 slope。
|
||||
/// 这是 Add 与 SetNth 的公共落点(SetNth 先移除再落点)。
|
||||
pub fn upsert(&mut self, key: f64, value: f64) -> bool {
|
||||
if let Some(p) = self.points.iter_mut().find(|p| p.key == key) {
|
||||
p.value = value;
|
||||
self.recompute_slopes();
|
||||
return false;
|
||||
}
|
||||
let idx = self.points.partition_point(|p| p.key < key);
|
||||
self.points.insert(idx, ControlPoint::new(key, value));
|
||||
self.recompute_slopes();
|
||||
true
|
||||
}
|
||||
|
||||
/// 改第 `nth` 个控制点为 (key, value)。key 变化可能破坏有序性
|
||||
/// (ofxParametricParam.h:SetNthControlPoint 的 key 可前移/后移
|
||||
/// 到其他点之前/之后)——先移除再按 key 插入;新 key 撞上其他
|
||||
/// 点的 key 时按覆盖语义处理。nth 越界 → Err(())。
|
||||
pub fn set_nth(&mut self, nth: usize, key: f64, value: f64) -> Result<(), ()> {
|
||||
if nth >= self.points.len() {
|
||||
return Err(());
|
||||
}
|
||||
self.points.remove(nth);
|
||||
self.upsert(key, value);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 删第 `nth` 个控制点(nth 越界 → Err(()));随后重算 slope。
|
||||
pub fn delete_nth(&mut self, nth: usize) -> Result<(), ()> {
|
||||
if nth >= self.points.len() {
|
||||
return Err(());
|
||||
}
|
||||
self.points.remove(nth);
|
||||
self.recompute_slopes();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 删除全部控制点(`DeleteAllControlPoints`)。
|
||||
pub fn clear(&mut self) {
|
||||
self.points.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/// 第 `i` 点处的差分斜率(不重算,供 [`Curve::recompute_slopes`])。
|
||||
/// 内部点:中心差分 (y_{i+1} - y_{i-1}) / (x_{i+1} - x_{i-1});
|
||||
/// 端点:单侧差分;单点/空曲线:0。防御重复 key 时除零 → 0。
|
||||
fn slope_at(points: &[ControlPoint], i: usize) -> f64 {
|
||||
let n = points.len();
|
||||
if n < 2 {
|
||||
return 0.0;
|
||||
}
|
||||
let (a, b) = if i == 0 {
|
||||
(&points[0], &points[1])
|
||||
} else if i == n - 1 {
|
||||
(&points[n - 2], &points[n - 1])
|
||||
} else {
|
||||
(&points[i - 1], &points[i + 1])
|
||||
};
|
||||
let dx = b.key - a.key;
|
||||
if dx == 0.0 {
|
||||
return 0.0;
|
||||
}
|
||||
(b.value - a.value) / dx
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn close(a: f64, b: f64, eps: f64) -> bool {
|
||||
(a - b).abs() <= eps
|
||||
}
|
||||
|
||||
/// 恒等曲线:{(0,0),(1,1)} + 自动 slope(1,1)→ Hermite 恰为
|
||||
/// f(x) = x(数学上精确,浮点逐位不保证——近似比较);非默认
|
||||
/// range 同样恒等。
|
||||
#[test]
|
||||
fn identity_curve_evaluates_to_x() {
|
||||
let c = Curve::identity(0.0, 1.0);
|
||||
assert_eq!(c.len(), 2);
|
||||
assert_eq!(
|
||||
c.points[0],
|
||||
ControlPoint {
|
||||
key: 0.0,
|
||||
value: 0.0,
|
||||
slope: 1.0
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
c.points[1],
|
||||
ControlPoint {
|
||||
key: 1.0,
|
||||
value: 1.0,
|
||||
slope: 1.0
|
||||
}
|
||||
);
|
||||
for x in [0.0, 0.1, 0.25, 0.5, 0.75, 0.9, 1.0] {
|
||||
assert!(close(c.evaluate(x), x, 1e-12), "identity at {x}");
|
||||
}
|
||||
// 非默认 range(插件改 OfxParamPropParametricRange 后,
|
||||
// 新曲线的默认 default 相应平移)。
|
||||
let c = Curve::identity(0.0, 255.0);
|
||||
assert!(close(c.evaluate(128.0), 128.0, 1e-12));
|
||||
}
|
||||
|
||||
/// 单段抛物线形状:{(0,0),(0.5,0.25),(1,1)}(y = x² 采样)。
|
||||
/// 自动 slope = (0.5, 1, 1.5)(端点单侧、中点中心差分);
|
||||
/// 求值精确等于分段三次 Hermite 的解析值(过控制点、两侧
|
||||
/// 单调递增、中点手算值)。
|
||||
#[test]
|
||||
fn parabola_shape_honours_hermite() {
|
||||
let c = Curve::from_pairs(&[(0.0, 0.0), (0.5, 0.25), (1.0, 1.0)]);
|
||||
assert_eq!(
|
||||
c.points.iter().map(|p| p.slope).collect::<Vec<_>>(),
|
||||
vec![0.5, 1.0, 1.5]
|
||||
);
|
||||
// 插值性质:控制点处精确命中。
|
||||
assert_eq!(c.evaluate(0.0), 0.0);
|
||||
assert_eq!(c.evaluate(0.5), 0.25);
|
||||
assert_eq!(c.evaluate(1.0), 1.0);
|
||||
// 中点解析值(t = 1/2 段内,精确二进制分数)。
|
||||
assert_eq!(c.evaluate(0.125), 0.05078125);
|
||||
assert_eq!(c.evaluate(0.25), 0.09375);
|
||||
assert_eq!(c.evaluate(0.75), 0.59375);
|
||||
// 形状:单调递增;首段在抛物线 y = x² 之上(Hermite 端点
|
||||
// 用弦斜率、中点用中心差分 → 首段整体上凸)。
|
||||
let xs = [0.1, 0.2, 0.3, 0.4, 0.6, 0.7, 0.8, 0.9];
|
||||
for w in xs.windows(2) {
|
||||
assert!(c.evaluate(w[0]) < c.evaluate(w[1]));
|
||||
}
|
||||
assert!(c.evaluate(0.25) > 0.25 * 0.25); // 高于抛物线采样点
|
||||
}
|
||||
|
||||
/// 越界钳制:x 在首/末 key 之外 → 端点值。
|
||||
#[test]
|
||||
fn out_of_range_clamps_to_endpoints() {
|
||||
let c = Curve::from_pairs(&[(0.2, 0.3), (0.8, 0.9)]);
|
||||
assert_eq!(c.evaluate(-100.0), 0.3);
|
||||
assert_eq!(c.evaluate(0.19), 0.3);
|
||||
assert_eq!(c.evaluate(0.2), 0.3);
|
||||
assert_eq!(c.evaluate(0.81), 0.9);
|
||||
assert_eq!(c.evaluate(100.0), 0.9);
|
||||
}
|
||||
|
||||
/// 增删点后求值正确:upsert 新点 → 该点命中、重排有序;删除后
|
||||
/// 恢复;同 key 覆盖不增点。
|
||||
#[test]
|
||||
fn upsert_and_delete_keep_evaluation_consistent() {
|
||||
let mut c = Curve::from_pairs(&[(0.0, 0.0), (1.0, 1.0)]);
|
||||
// 插入中点(返回 true = 新增),求值在其 key 处精确命中。
|
||||
assert!(c.upsert(0.3, 0.7));
|
||||
assert_eq!(c.len(), 3);
|
||||
assert_eq!(c.evaluate(0.3), 0.7);
|
||||
// 有序性保持。
|
||||
let keys: Vec<f64> = c.points.iter().map(|p| p.key).collect();
|
||||
assert_eq!(keys, vec![0.0, 0.3, 1.0]);
|
||||
// 同 key 覆盖(返回 false = 覆盖),数量不变。
|
||||
assert!(!c.upsert(0.3, 0.5));
|
||||
assert_eq!(c.len(), 3);
|
||||
assert_eq!(c.evaluate(0.3), 0.5);
|
||||
// 删除 → 恢复双点(恒等;非 2 幂 key 处逐位有舍入)。
|
||||
c.delete_nth(1).unwrap();
|
||||
assert_eq!(c.len(), 2);
|
||||
assert!(close(c.evaluate(0.3), 0.3, 1e-12));
|
||||
// 越界删除 → Err。
|
||||
assert!(c.delete_nth(2).is_err());
|
||||
}
|
||||
|
||||
/// set_nth 改 key 后保持有序(点在序列中移动)。
|
||||
#[test]
|
||||
fn set_nth_reorders_on_key_change() {
|
||||
let mut c = Curve::from_pairs(&[(0.0, 0.0), (0.5, 0.5), (1.0, 1.0)]);
|
||||
// 把第 0 点挪到 0.75(原第 1、2 点之前……之后)。
|
||||
c.set_nth(0, 0.75, 0.75).unwrap();
|
||||
let pairs: Vec<(f64, f64)> = c
|
||||
.points
|
||||
.iter()
|
||||
.map(|p| (p.key, p.value))
|
||||
.collect();
|
||||
assert_eq!(pairs, vec![(0.5, 0.5), (0.75, 0.75), (1.0, 1.0)]);
|
||||
assert_eq!(c.evaluate(0.75), 0.75);
|
||||
// 越界 nth → Err。
|
||||
assert!(c.set_nth(3, 0.9, 0.9).is_err());
|
||||
}
|
||||
|
||||
/// slope 编辑生效:同为 {(0,0),(1,1)},自动 slope (1,1) 时
|
||||
/// 求值 = 恒等;把 slope 显式压平为 0 后曲线变 S 形缓起缓收
|
||||
/// (中点值从 0.25 变 0.15625)。
|
||||
#[test]
|
||||
fn slope_editing_changes_evaluation() {
|
||||
let mut c = Curve::from_pairs(&[(0.0, 0.0), (1.0, 1.0)]);
|
||||
assert_eq!(c.evaluate(0.25), 0.25);
|
||||
// 显式编辑 slope(宿主/测试路径;suite 无 slope 入口,
|
||||
// 但字段公开即契约的一部分)。
|
||||
c.points[0].slope = 0.0;
|
||||
c.points[1].slope = 0.0;
|
||||
assert_eq!(c.evaluate(0.25), 0.15625);
|
||||
assert_eq!(c.evaluate(0.5), 0.5);
|
||||
assert!(close(c.evaluate(0.25), 0.15625, 1e-12));
|
||||
}
|
||||
|
||||
/// 空曲线与单点曲线:恒等 / 常数退化。
|
||||
#[test]
|
||||
fn degenerate_curves() {
|
||||
let c = Curve::empty();
|
||||
assert!(c.is_empty());
|
||||
assert_eq!(c.evaluate(0.3), 0.3);
|
||||
assert_eq!(c.evaluate(5.0), 5.0);
|
||||
let c = Curve::from_pairs(&[(0.5, 0.25)]);
|
||||
assert_eq!(c.evaluate(0.0), 0.25);
|
||||
assert_eq!(c.evaluate(100.0), 0.25);
|
||||
}
|
||||
|
||||
/// clear:DeleteAllControlPoints 的模型侧语义。
|
||||
#[test]
|
||||
fn clear_empties_curve() {
|
||||
let mut c = Curve::from_pairs(&[(0.0, 0.0), (1.0, 1.0)]);
|
||||
c.clear();
|
||||
assert!(c.is_empty());
|
||||
assert_eq!(c.evaluate(0.5), 0.5);
|
||||
}
|
||||
}
|
||||
@@ -34,6 +34,7 @@ pub mod memory;
|
||||
pub mod message;
|
||||
pub mod multithread;
|
||||
pub mod param;
|
||||
pub mod parametric;
|
||||
pub mod progress;
|
||||
pub mod property;
|
||||
pub mod timeline;
|
||||
@@ -224,6 +225,8 @@ pub(crate) const OFX_API_VERSION: i32 = 105;
|
||||
/// 第 2 期追加:OfxImageEffectOpenGLRenderSuite v1(GL 路径);
|
||||
/// ofxColour 无 suite 表(纯属性 + GetOutputColourspace action)。
|
||||
/// 第 3 期追加:OfxInteractSuite v1、OfxDrawSuite v1(interact 宿主)。
|
||||
/// 第 4 期追加:OfxParametricParameterSuite v1(曲线参数,
|
||||
/// suites/parametric.rs)。
|
||||
pub fn fetch_suite(name: &str, version: i32) -> Option<*const std::ffi::c_void> {
|
||||
let suite: *const std::ffi::c_void = match (name, version) {
|
||||
("OfxPropertySuite", 1) => ptr(property::suite_v1()),
|
||||
@@ -239,6 +242,7 @@ pub fn fetch_suite(name: &str, version: i32) -> Option<*const std::ffi::c_void>
|
||||
("OfxImageEffectOpenGLRenderSuite", 1) => ptr(gl_render::suite_v1()),
|
||||
("OfxInteractSuite", 1) => ptr(interact::suite_v1()),
|
||||
("OfxDrawSuite", 1) => ptr(draw::suite_v1()),
|
||||
("OfxParametricParameterSuite", 1) => ptr(parametric::suite_v1()),
|
||||
_ => return None,
|
||||
};
|
||||
Some(suite)
|
||||
@@ -253,7 +257,7 @@ fn ptr<T>(p: &'static T) -> *const std::ffi::c_void {
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
/// 十张 suite 的分发表:版本精确匹配、未知版本/名字 → None。
|
||||
/// suite 分发表:版本精确匹配、未知版本/名字 → None。
|
||||
#[test]
|
||||
fn fetch_suite_dispatch() {
|
||||
assert!(fetch_suite("OfxPropertySuite", 1).is_some());
|
||||
@@ -269,12 +273,14 @@ mod tests {
|
||||
assert!(fetch_suite("OfxImageEffectOpenGLRenderSuite", 1).is_some());
|
||||
assert!(fetch_suite("OfxInteractSuite", 1).is_some());
|
||||
assert!(fetch_suite("OfxDrawSuite", 1).is_some());
|
||||
assert!(fetch_suite("OfxParametricParameterSuite", 1).is_some());
|
||||
|
||||
assert!(fetch_suite("OfxPropertySuite", 2).is_none());
|
||||
assert!(fetch_suite("OfxMessageSuite", 3).is_none());
|
||||
assert!(fetch_suite("OfxImageEffectOpenGLRenderSuite", 2).is_none());
|
||||
assert!(fetch_suite("OfxInteractSuite", 2).is_none());
|
||||
assert!(fetch_suite("OfxDrawSuite", 2).is_none());
|
||||
assert!(fetch_suite("OfxParametricParameterSuite", 2).is_none());
|
||||
assert!(fetch_suite("OfxBogusSuite", 1).is_none());
|
||||
assert!(fetch_suite("", 1).is_none());
|
||||
}
|
||||
|
||||
@@ -18,7 +18,8 @@
|
||||
//!
|
||||
//! 语义对照 HS: ofxhParam.cpp:
|
||||
//! - paramDefine:describe 期把参数定义挂到效果描述符(未定义类型 →
|
||||
//! kOfxStatErrUnsupported,HS:1665-1710;parametric 第 1 期不支持);
|
||||
//! kOfxStatErrUnsupported,HS:1665-1710;parametric 类型放行,其
|
||||
//! 曲线读写走 OfxParametricParameterSuite,见 suites/parametric.rs);
|
||||
//! - paramGetHandle:按名查(未找到 → kOfxStatErrUnknown,HS:1758);
|
||||
//! - paramGetValue/paramSetValue 等变长入口在 C shim
|
||||
//! (cbits/ofx_param_shim.c):按 [`crate::param::ParamKind`] 解析
|
||||
@@ -330,8 +331,9 @@ pub(crate) fn unregister_params_of(instance_props: usize) {
|
||||
|
||||
/// paramSetValue 成功后的 instanceChanged 触发(HS:
|
||||
/// ofxhParam.cpp:1991-1994 `paramChangedByPlugin`)。未登记实例时
|
||||
/// no-op(未绑定节点的场景本就 no-op)。
|
||||
fn notify_changed(param_props: usize, name: &str) {
|
||||
/// no-op(未绑定节点的场景本就 no-op)。parametric suite 的
|
||||
/// Set/Add/Delete 复用此路径(pub(crate):suites/parametric.rs)。
|
||||
pub(crate) fn notify_changed(param_props: usize, name: &str) {
|
||||
let owner = PARAM_OWNER
|
||||
.lock()
|
||||
.unwrap_or_else(|e| e.into_inner())
|
||||
@@ -463,9 +465,9 @@ pub unsafe extern "C" fn ofx_param_missing_feature_impl(param: *mut c_void) -> c
|
||||
|
||||
// ---- 非变长入口 ----------------------------------------------------------
|
||||
|
||||
/// paramDefine:describe 期定义参数(未定义类型/parametric →
|
||||
/// Unsupported,HS:1704;重复名 → HS 允许,原样再建一条——宿主以
|
||||
/// 首个为准,与 HS 的 map 覆盖语义一致)。
|
||||
/// paramDefine:describe 期定义参数(未定义类型 → Unsupported,
|
||||
/// HS:1704;重复名 → HS 允许,原样再建一条——宿主以首个为准,与
|
||||
/// HS 的 map 覆盖语义一致)。
|
||||
unsafe extern "C" fn param_define(
|
||||
param_set: *mut c_void,
|
||||
param_type: *const c_char,
|
||||
@@ -485,7 +487,8 @@ unsafe extern "C" fn param_define(
|
||||
ParamSetRef::Descriptor(d) => d,
|
||||
ParamSetRef::Instance(_) => return Err(status::ERR_BAD_HANDLE),
|
||||
};
|
||||
// 类型合法性(kind_of_type 之外还有无值类与 parametric)。
|
||||
// 类型合法性(kind_of_type 之外还有无值类与 parametric;
|
||||
// parametric 的曲线值走 OfxParametricParameterSuite)。
|
||||
let valid = crate::param::kind_of_type(t).is_some()
|
||||
|| matches!(
|
||||
t,
|
||||
@@ -494,8 +497,9 @@ unsafe extern "C" fn param_define(
|
||||
| crate::param::TYPE_PUSHBUTTON
|
||||
| crate::param::TYPE_GROUP
|
||||
| crate::param::TYPE_PAGE
|
||||
| crate::param::TYPE_PARAMETRIC
|
||||
);
|
||||
if !valid || t == crate::param::TYPE_PARAMETRIC {
|
||||
if !valid {
|
||||
return Err(status::ERR_UNSUPPORTED);
|
||||
}
|
||||
let def = ParamDef::new(n, t);
|
||||
@@ -873,7 +877,8 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
// 未定义类型 / parametric → ErrUnsupported(HS:1704)。
|
||||
// 未定义类型 → ErrUnsupported(HS:1704);parametric → 放行
|
||||
// (曲线读写走 OfxParametricParameterSuite)。
|
||||
let bad = cs("OfxParamTypeBogus");
|
||||
let par = cs("OfxParamTypeParametric");
|
||||
unsafe {
|
||||
@@ -881,11 +886,9 @@ mod tests {
|
||||
(s.param_define)(handle, bad.as_ptr(), n.as_ptr(), &mut ph2),
|
||||
status::ERR_UNSUPPORTED
|
||||
);
|
||||
assert_eq!(
|
||||
(s.param_define)(handle, par.as_ptr(), n.as_ptr(), &mut ph2),
|
||||
status::ERR_UNSUPPORTED
|
||||
);
|
||||
assert_eq!((s.param_define)(handle, par.as_ptr(), n.as_ptr(), &mut ph2), 0);
|
||||
}
|
||||
assert_eq!(tag::kind(ph2), tag::PARAM_DEF);
|
||||
|
||||
// 实例期 paramDefine → BadHandle。
|
||||
let (_inst, ih) = make_instance();
|
||||
|
||||
@@ -0,0 +1,900 @@
|
||||
// Oak Video Editor - Non-Linear Video Editor
|
||||
// Copyright (C) 2026 Oak Team
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! OfxParametricParameterSuite v1:参数化曲线参数(调色插件的
|
||||
//! 曲线/LUT 类参数)。
|
||||
//!
|
||||
//! 语义对照 ofxParametricParam.h(属性表另见 HS ofxhParam.cpp:285-330):
|
||||
//! - 曲线 = 每维一条 [`crate::param_curve::Curve`],存于参数值
|
||||
//! [`crate::param::ParamValue::Parametric`](def 默认 / instance 当前
|
||||
//! 值,`ParamInstance::from_def` 复制默认);
|
||||
//! - 维度与定义域取参数属性 [`crate::param::P_PARAMETRIC_DIMENSION`]
|
||||
//! (int,默认 1)与 [`crate::param::P_PARAMETRIC_RANGE`]
|
||||
//! (double×2,默认 (0,1))——paramDefine 预置,插件 describe 期可改;
|
||||
//! - 求值 = 三次 Hermite(slope 自动差分),越界 parametricPosition
|
||||
//! 钳制到端点值,见 [`crate::param_curve::Curve::evaluate`];
|
||||
//! - describe 期(Def)的 Set/Add/Delete 改**默认值**(HS:"If a
|
||||
//! plugin wishes to set a different default value for a curve, it can
|
||||
//! use the suite to set key/value pairs on the descriptor. When a new
|
||||
//! instance is made, it will have these curve values as a default");
|
||||
//! - 第 1 期无参数动画(宿主
|
||||
//! `kOfxParamHostPropSupportsParametricAnimation = 0`):`time` 恒
|
||||
//! 忽略(曲线是实例级静态的),`addAnimationKey` 同理忽略;
|
||||
//! - 未配置的曲线(维度内但从未写入)按恒等默认求值/读数——与
|
||||
//! "default default 是恒等查找" 一致;任何写入路径先把缺失曲线
|
||||
//! 补成恒等默认再落库;
|
||||
//! - 错误码:坏句柄 → BadHandle,非 parametric 参数 → BadHandle,
|
||||
//! curveIndex / nth 越界 → BadIndex(头文件 GetNth/Set/Add 文档
|
||||
//! 里写的 Unknown 是类型未知的泛指,本实现按任务约定统一
|
||||
//! BadHandle/BadIndex)。
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::ffi::{c_char, c_double, c_int, c_void};
|
||||
|
||||
use crate::descriptor::EffectDescriptor;
|
||||
use crate::instance::Instance;
|
||||
use crate::param::{ParamDef, ParamInstance, ParamValue};
|
||||
use crate::param_curve::Curve;
|
||||
use crate::property::{PropertySet, Value};
|
||||
use crate::suites::{status, tag};
|
||||
|
||||
/// 函数表布局(与 SDK `OfxParametricParameterSuiteV1` 一致;字段序
|
||||
/// 以 ofxParametricParam.h 为准)。
|
||||
#[repr(C)]
|
||||
pub struct ParametricParameterSuiteV1 {
|
||||
/// parametricParamGetValue:求值曲线(越界位置钳制到端点值)。
|
||||
pub parametric_param_get_value: unsafe extern "C" fn(
|
||||
*mut c_void,
|
||||
c_int,
|
||||
c_double,
|
||||
c_double,
|
||||
*mut c_double,
|
||||
) -> c_int,
|
||||
/// parametricParamGetNControlPoints:控制点数。
|
||||
pub parametric_param_get_n_control_points:
|
||||
unsafe extern "C" fn(*mut c_void, c_int, c_double, *mut c_int) -> c_int,
|
||||
/// parametricParamGetNthControlPoint:第 nth 个控制点的 key/value。
|
||||
pub parametric_param_get_nth_control_point: unsafe extern "C" fn(
|
||||
*mut c_void,
|
||||
c_int,
|
||||
c_double,
|
||||
c_int,
|
||||
*mut c_double,
|
||||
*mut c_double,
|
||||
) -> c_int,
|
||||
/// parametricParamSetNthControlPoint:改第 nth 个控制点
|
||||
/// (key 变化后保持有序,slope 重算)。
|
||||
pub parametric_param_set_nth_control_point: unsafe extern "C" fn(
|
||||
*mut c_void,
|
||||
c_int,
|
||||
c_double,
|
||||
c_int,
|
||||
c_double,
|
||||
c_double,
|
||||
c_int,
|
||||
) -> c_int,
|
||||
/// parametricParamAddControlPoint:加入/覆盖控制点(保持有序)。
|
||||
pub parametric_param_add_control_point: unsafe extern "C" fn(
|
||||
*mut c_void,
|
||||
c_int,
|
||||
c_double,
|
||||
c_double,
|
||||
c_double,
|
||||
c_int,
|
||||
) -> c_int,
|
||||
/// parametricParamDeleteControlPoint:删第 nth 个控制点。
|
||||
pub parametric_param_delete_control_point:
|
||||
unsafe extern "C" fn(*mut c_void, c_int, c_int) -> c_int,
|
||||
/// parametricParamDeleteAllControlPoints:清空某维曲线。
|
||||
pub parametric_param_delete_all_control_points:
|
||||
unsafe extern "C" fn(*mut c_void, c_int) -> c_int,
|
||||
}
|
||||
|
||||
// ---- 句柄解析 -----------------------------------------------------------
|
||||
|
||||
/// param 句柄:describe 期是定义(值=默认值),实例期是实例。
|
||||
/// Def 持 `&mut`(Set/Add/Delete 直改默认值);Instance 持 `&`
|
||||
/// (经 [`ParamInstance::set_ofx`] 的内部 Mutex 改当前值)。
|
||||
enum ParamRef<'a> {
|
||||
Def(&'a mut ParamDef),
|
||||
Instance(&'a ParamInstance),
|
||||
}
|
||||
|
||||
/// 解析 param 句柄。空指针/标签不符 → BadHandle。
|
||||
fn resolve_param(handle: *mut c_void) -> Result<ParamRef<'static>, c_int> {
|
||||
if handle.is_null() {
|
||||
return Err(status::ERR_BAD_HANDLE);
|
||||
}
|
||||
unsafe {
|
||||
match tag::kind(handle) {
|
||||
tag::PARAM_DEF => Ok(ParamRef::Def(&mut *(tag::strip(handle) as *mut ParamDef))),
|
||||
tag::PARAM_INSTANCE => Ok(ParamRef::Instance(&*(
|
||||
tag::strip(handle) as *const ParamInstance
|
||||
))),
|
||||
_ => Err(status::ERR_BAD_HANDLE),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 公共入口模板:panic 兜底。
|
||||
#[track_caller]
|
||||
fn caught(f: impl FnOnce() -> Result<(), c_int>) -> c_int {
|
||||
let caller = std::panic::Location::caller();
|
||||
let code = std::panic::catch_unwind(std::panic::AssertUnwindSafe(f)).map_or_else(
|
||||
|_| status::FAILED,
|
||||
|r| r.map_or_else(|c| c, |()| status::OK),
|
||||
);
|
||||
if code != status::OK && std::env::var_os("OAK_OFX_TRACE").is_some() {
|
||||
eprintln!("[ofx] parametric suite error {code} at {caller}");
|
||||
}
|
||||
code
|
||||
}
|
||||
|
||||
// ---- 模型存取 -----------------------------------------------------------
|
||||
|
||||
/// 取参数曲线(Def=默认值,Instance=当前值)。值不是 parametric 曲线
|
||||
/// → BadHandle(非 parametric 参数的 suite 语义;先于索引检查,保证
|
||||
/// 非 parametric 恒为 BadHandle)。
|
||||
fn curves_of(r: &ParamRef) -> Result<Vec<Curve>, c_int> {
|
||||
match r {
|
||||
ParamRef::Def(d) => match &d.default {
|
||||
ParamValue::Parametric(c) => Ok(c.clone()),
|
||||
_ => Err(status::ERR_BAD_HANDLE),
|
||||
},
|
||||
ParamRef::Instance(p) => match p.get() {
|
||||
ParamValue::Parametric(c) => Ok(c),
|
||||
_ => Err(status::ERR_BAD_HANDLE),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// 写回曲线:Def → 默认值;Instance → 当前值 + instanceChanged 通知
|
||||
/// (复用 param suite 的 [`crate::suites::param::notify_changed`]——
|
||||
/// 曲线无标量节点值,节点回写是 no-op,但走既有通知路径保持宿主侧
|
||||
/// 变更感知的纪律一致)。
|
||||
fn write_curves(r: &mut ParamRef, curves: Vec<Curve>) {
|
||||
match r {
|
||||
ParamRef::Def(d) => d.default = ParamValue::Parametric(curves),
|
||||
ParamRef::Instance(p) => {
|
||||
let name = p.def.name.clone();
|
||||
let addr = &p.props as *const _ as usize;
|
||||
p.set_ofx(ParamValue::Parametric(curves));
|
||||
crate::suites::param::notify_changed(addr, &name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// param 的属性集(句柄即 props 地址,偏移 0 句柄约定)。
|
||||
fn props_of(handle: *mut c_void) -> &'static PropertySet {
|
||||
unsafe { &*tag::strip(handle) }
|
||||
}
|
||||
|
||||
/// 维度:`OfxParamPropParametricDimension`(int,默认 1;≤0 防御性
|
||||
/// 按 1 处理——头文件要求"greater than 0")。
|
||||
fn dimension_of(handle: *mut c_void) -> usize {
|
||||
match props_of(handle).get(crate::param::P_PARAMETRIC_DIMENSION, 0) {
|
||||
Some(Value::Int(d)) => d.max(1) as usize,
|
||||
_ => 1,
|
||||
}
|
||||
}
|
||||
|
||||
/// 定义域:`OfxParamPropParametricRange`(double×2,默认 (0,1))——
|
||||
/// 未配置曲线补恒等默认时的端点。
|
||||
fn range_of(handle: *mut c_void) -> (f64, f64) {
|
||||
let props = props_of(handle);
|
||||
let lo = match props.get(crate::param::P_PARAMETRIC_RANGE, 0) {
|
||||
Some(Value::Double(d)) => d,
|
||||
_ => 0.0,
|
||||
};
|
||||
let hi = match props.get(crate::param::P_PARAMETRIC_RANGE, 1) {
|
||||
Some(Value::Double(d)) => d,
|
||||
_ => 1.0,
|
||||
};
|
||||
(lo, hi)
|
||||
}
|
||||
|
||||
/// curveIndex 越界(相对 dimension 属性)→ BadIndex。
|
||||
fn check_curve_index(handle: *mut c_void, curve_index: c_int) -> Result<usize, c_int> {
|
||||
let ci = usize::try_from(curve_index).map_err(|_| status::ERR_BAD_INDEX)?;
|
||||
if ci >= dimension_of(handle) {
|
||||
return Err(status::ERR_BAD_INDEX);
|
||||
}
|
||||
Ok(ci)
|
||||
}
|
||||
|
||||
/// 取第 `ci` 条曲线;缺失(维内但从未写入)→ 恒等默认(只读路径
|
||||
/// 不落库:未配置曲线 = 恒等查找)。
|
||||
fn curve_at<'a>(curves: &'a [Curve], ci: usize, range: (f64, f64)) -> Cow<'a, Curve> {
|
||||
match curves.get(ci) {
|
||||
Some(c) => Cow::Borrowed(c),
|
||||
None => Cow::Owned(Curve::identity(range.0, range.1)),
|
||||
}
|
||||
}
|
||||
|
||||
/// 确保第 `ci` 条曲线存在(写入路径:缺失时补恒等默认再落库)。
|
||||
fn ensure_curve(curves: &mut Vec<Curve>, ci: usize, range: (f64, f64)) -> &mut Curve {
|
||||
while curves.len() <= ci {
|
||||
curves.push(Curve::identity(range.0, range.1));
|
||||
}
|
||||
&mut curves[ci]
|
||||
}
|
||||
|
||||
// ---- suite 函数 ---------------------------------------------------------
|
||||
|
||||
/// parametricParamGetValue:求值曲线。`time` 忽略(无参数动画);
|
||||
/// 越界 parametricPosition 钳制到端点值([`Curve::evaluate`])。
|
||||
unsafe extern "C" fn parametric_param_get_value(
|
||||
param: *mut c_void,
|
||||
curve_index: c_int,
|
||||
_time: c_double,
|
||||
parametric_position: c_double,
|
||||
out: *mut c_double,
|
||||
) -> c_int {
|
||||
caught(|| {
|
||||
if out.is_null() {
|
||||
return Err(status::ERR_VALUE);
|
||||
}
|
||||
unsafe { *out = 0.0 };
|
||||
let r = resolve_param(param)?;
|
||||
let curves = curves_of(&r)?;
|
||||
let ci = check_curve_index(param, curve_index)?;
|
||||
let range = range_of(param);
|
||||
let v = curve_at(&curves, ci, range).evaluate(parametric_position);
|
||||
unsafe { *out = v };
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
/// parametricParamGetNControlPoints:控制点数(未配置曲线 = 恒等默认
|
||||
/// 的 2 点)。
|
||||
unsafe extern "C" fn parametric_param_get_n_control_points(
|
||||
param: *mut c_void,
|
||||
curve_index: c_int,
|
||||
_time: c_double,
|
||||
out: *mut c_int,
|
||||
) -> c_int {
|
||||
caught(|| {
|
||||
if out.is_null() {
|
||||
return Err(status::ERR_VALUE);
|
||||
}
|
||||
unsafe { *out = 0 };
|
||||
let r = resolve_param(param)?;
|
||||
let curves = curves_of(&r)?;
|
||||
let ci = check_curve_index(param, curve_index)?;
|
||||
let n = curve_at(&curves, ci, range_of(param)).len();
|
||||
unsafe { *out = n as c_int };
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
/// parametricParamGetNthControlPoint:第 nth 个控制点的 key/value
|
||||
/// (nth 越界 → BadIndex;未配置曲线按恒等默认读数)。
|
||||
unsafe extern "C" fn parametric_param_get_nth_control_point(
|
||||
param: *mut c_void,
|
||||
curve_index: c_int,
|
||||
_time: c_double,
|
||||
nth: c_int,
|
||||
key: *mut c_double,
|
||||
value: *mut c_double,
|
||||
) -> c_int {
|
||||
caught(|| {
|
||||
if key.is_null() || value.is_null() {
|
||||
return Err(status::ERR_VALUE);
|
||||
}
|
||||
unsafe {
|
||||
*key = 0.0;
|
||||
*value = 0.0;
|
||||
};
|
||||
let r = resolve_param(param)?;
|
||||
let curves = curves_of(&r)?;
|
||||
let ci = check_curve_index(param, curve_index)?;
|
||||
let nth = usize::try_from(nth).map_err(|_| status::ERR_BAD_INDEX)?;
|
||||
let c = curve_at(&curves, ci, range_of(param));
|
||||
let p = c.nth(nth).ok_or(status::ERR_BAD_INDEX)?;
|
||||
unsafe {
|
||||
*key = p.key;
|
||||
*value = p.value;
|
||||
};
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
/// parametricParamSetNthControlPoint:改第 nth 个控制点为 (key,
|
||||
/// value)。key 变化可能破坏有序性(头文件明确提醒)——模型侧先移除
|
||||
/// 再按 key 插入、撞 key 按覆盖处理、slope 重算([`Curve::set_nth`])。
|
||||
/// `addAnimationKey` 忽略(宿主不支持参数动画)。
|
||||
unsafe extern "C" fn parametric_param_set_nth_control_point(
|
||||
param: *mut c_void,
|
||||
curve_index: c_int,
|
||||
_time: c_double,
|
||||
nth: c_int,
|
||||
key: c_double,
|
||||
value: c_double,
|
||||
_add_animation_key: c_int,
|
||||
) -> c_int {
|
||||
caught(|| {
|
||||
let mut r = resolve_param(param)?;
|
||||
let mut curves = curves_of(&r)?;
|
||||
let ci = check_curve_index(param, curve_index)?;
|
||||
let nth = usize::try_from(nth).map_err(|_| status::ERR_BAD_INDEX)?;
|
||||
ensure_curve(&mut curves, ci, range_of(param))
|
||||
.set_nth(nth, key, value)
|
||||
.map_err(|()| status::ERR_BAD_INDEX)?;
|
||||
write_curves(&mut r, curves);
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
/// parametricParamAddControlPoint:加入控制点;同 key 已存在 → 覆盖
|
||||
/// (头文件 "If a key exists sufficiently close to 'key', then it will
|
||||
/// be set to the indicated control point";本实现取精确同 key)。
|
||||
/// `addAnimationKey` 忽略(无参数动画)。
|
||||
unsafe extern "C" fn parametric_param_add_control_point(
|
||||
param: *mut c_void,
|
||||
curve_index: c_int,
|
||||
_time: c_double,
|
||||
key: c_double,
|
||||
value: c_double,
|
||||
_add_animation_key: c_int,
|
||||
) -> c_int {
|
||||
caught(|| {
|
||||
let mut r = resolve_param(param)?;
|
||||
let mut curves = curves_of(&r)?;
|
||||
let ci = check_curve_index(param, curve_index)?;
|
||||
ensure_curve(&mut curves, ci, range_of(param)).upsert(key, value);
|
||||
write_curves(&mut r, curves);
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
/// parametricParamDeleteControlPoint:删第 nth 个控制点(越界 →
|
||||
/// BadIndex)。
|
||||
unsafe extern "C" fn parametric_param_delete_control_point(
|
||||
param: *mut c_void,
|
||||
curve_index: c_int,
|
||||
nth: c_int,
|
||||
) -> c_int {
|
||||
caught(|| {
|
||||
let mut r = resolve_param(param)?;
|
||||
let mut curves = curves_of(&r)?;
|
||||
let ci = check_curve_index(param, curve_index)?;
|
||||
let nth = usize::try_from(nth).map_err(|_| status::ERR_BAD_INDEX)?;
|
||||
ensure_curve(&mut curves, ci, range_of(param))
|
||||
.delete_nth(nth)
|
||||
.map_err(|()| status::ERR_BAD_INDEX)?;
|
||||
write_curves(&mut r, curves);
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
/// parametricParamDeleteAllControlPoints:清空某维曲线(清空后求值
|
||||
/// 退化为恒等,[`Curve::evaluate`] 的空曲线分支)。
|
||||
unsafe extern "C" fn parametric_param_delete_all_control_points(
|
||||
param: *mut c_void,
|
||||
curve_index: c_int,
|
||||
) -> c_int {
|
||||
caught(|| {
|
||||
let mut r = resolve_param(param)?;
|
||||
let mut curves = curves_of(&r)?;
|
||||
let ci = check_curve_index(param, curve_index)?;
|
||||
ensure_curve(&mut curves, ci, range_of(param)).clear();
|
||||
write_curves(&mut r, curves);
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
/// 静态函数表实例。
|
||||
pub fn suite_v1() -> &'static ParametricParameterSuiteV1 {
|
||||
static SUITE: std::sync::OnceLock<ParametricParameterSuiteV1> = std::sync::OnceLock::new();
|
||||
SUITE.get_or_init(|| ParametricParameterSuiteV1 {
|
||||
parametric_param_get_value: parametric_param_get_value,
|
||||
parametric_param_get_n_control_points: parametric_param_get_n_control_points,
|
||||
parametric_param_get_nth_control_point: parametric_param_get_nth_control_point,
|
||||
parametric_param_set_nth_control_point: parametric_param_set_nth_control_point,
|
||||
parametric_param_add_control_point: parametric_param_add_control_point,
|
||||
parametric_param_delete_control_point: parametric_param_delete_control_point,
|
||||
parametric_param_delete_all_control_points: parametric_param_delete_all_control_points,
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::ffi::CString;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::host::Plugin;
|
||||
use crate::param::{ParamInstance, ParamSetInstance};
|
||||
use crate::property::PropertySet;
|
||||
|
||||
fn cs(s: &str) -> CString {
|
||||
CString::new(s).unwrap()
|
||||
}
|
||||
|
||||
fn descriptor_handle(d: &EffectDescriptor) -> *mut c_void {
|
||||
tag::make(&d.props as *const PropertySet, tag::DESCRIPTOR)
|
||||
}
|
||||
|
||||
/// 假插件(host::Plugin 的构造只为拿 Arc 喂给 Instance;describe
|
||||
/// 之外的字段不被本测试触碰)。
|
||||
fn dummy_plugin(descriptor: EffectDescriptor) -> Arc<Plugin> {
|
||||
unsafe extern "C" fn dummy_entry(
|
||||
_: *const c_char,
|
||||
_: *const c_void,
|
||||
_: *mut c_void,
|
||||
_: *mut c_void,
|
||||
) -> c_int {
|
||||
status::OK
|
||||
}
|
||||
Arc::new(Plugin {
|
||||
identifier: "test.plugin".into(),
|
||||
version: (1, 0),
|
||||
bundle_path: std::path::PathBuf::new(),
|
||||
contexts: vec![],
|
||||
descriptor,
|
||||
lib: std::ptr::null_mut(),
|
||||
entry: dummy_entry,
|
||||
ofx_plugin: std::ptr::null_mut(),
|
||||
})
|
||||
}
|
||||
|
||||
fn instance_handle(i: &Instance) -> *mut c_void {
|
||||
tag::make(&i.props as *const PropertySet, tag::INSTANCE)
|
||||
}
|
||||
|
||||
/// describe 期建一个 parametric 参数 "curve"(默认维度 1),返回
|
||||
/// 描述符与其 param 句柄。
|
||||
fn define_parametric() -> (EffectDescriptor, *mut c_void) {
|
||||
let desc = EffectDescriptor::new();
|
||||
let s = crate::suites::param::suite_v1();
|
||||
let dhandle = descriptor_handle(&desc);
|
||||
let t = cs(crate::param::TYPE_PARAMETRIC);
|
||||
let n = cs("curve");
|
||||
let mut ph: *mut c_void = std::ptr::null_mut();
|
||||
unsafe {
|
||||
assert_eq!((s.param_define)(dhandle, t.as_ptr(), n.as_ptr(), &mut ph), 0);
|
||||
}
|
||||
assert_eq!(tag::kind(ph), tag::PARAM_DEF);
|
||||
(desc, ph)
|
||||
}
|
||||
|
||||
/// 把 describe 产物实例化(default → 实例值复制),返回
|
||||
/// (实例 Arc, 实例 handle, 实例 param 句柄)。
|
||||
fn instantiate(desc: EffectDescriptor) -> (Arc<Instance>, *mut c_void, *mut c_void) {
|
||||
let params = ParamSetInstance {
|
||||
params: desc
|
||||
.params
|
||||
.iter()
|
||||
.map(|d| Box::new(ParamInstance::from_def((**d).clone())))
|
||||
.collect(),
|
||||
};
|
||||
let plugin = dummy_plugin(desc);
|
||||
let inst = Arc::new(Instance {
|
||||
props: PropertySet::new(),
|
||||
plugin,
|
||||
context: "OfxImageEffectContextFilter".into(),
|
||||
params,
|
||||
clips: vec![],
|
||||
node_identity: std::sync::atomic::AtomicUsize::new(0),
|
||||
destroyed: std::sync::atomic::AtomicBool::new(false),
|
||||
sequence_range: std::sync::Mutex::new(None),
|
||||
progress_cb: std::sync::Mutex::new(None),
|
||||
cancel: std::sync::atomic::AtomicBool::new(false),
|
||||
edit: std::sync::Mutex::new(crate::instance::EditTransaction::new()),
|
||||
render_lock: std::sync::Mutex::new(()),
|
||||
interact: std::sync::Mutex::new(None),
|
||||
});
|
||||
let ih = instance_handle(&inst);
|
||||
let s = crate::suites::param::suite_v1();
|
||||
let n = cs("curve");
|
||||
let mut ph: *mut c_void = std::ptr::null_mut();
|
||||
unsafe {
|
||||
assert_eq!((s.param_get_handle)(ih, n.as_ptr(), &mut ph, std::ptr::null_mut()), 0);
|
||||
}
|
||||
assert_eq!(tag::kind(ph), tag::PARAM_INSTANCE);
|
||||
(inst, ih, ph)
|
||||
}
|
||||
|
||||
/// describe 定义 + 实例化一步到位(默认维度 1)。
|
||||
fn make_instance() -> (Arc<Instance>, *mut c_void, *mut c_void) {
|
||||
let (desc, _dph) = define_parametric();
|
||||
instantiate(desc)
|
||||
}
|
||||
|
||||
fn ps() -> &'static ParametricParameterSuiteV1 {
|
||||
suite_v1()
|
||||
}
|
||||
|
||||
/// describe 期默认值:2 个恒等控制点 (0,0)/(1,1),求值 = 恒等。
|
||||
#[test]
|
||||
fn describe_defaults_are_identity() {
|
||||
let (_desc, ph) = define_parametric();
|
||||
let s = ps();
|
||||
let mut n = -1;
|
||||
unsafe {
|
||||
assert_eq!((s.parametric_param_get_n_control_points)(ph, 0, 0.0, &mut n), 0);
|
||||
}
|
||||
assert_eq!(n, 2);
|
||||
let (mut k, mut v) = (-1.0, -1.0);
|
||||
unsafe {
|
||||
assert_eq!((s.parametric_param_get_nth_control_point)(ph, 0, 0.0, 0, &mut k, &mut v), 0);
|
||||
}
|
||||
assert_eq!((k, v), (0.0, 0.0));
|
||||
unsafe {
|
||||
assert_eq!((s.parametric_param_get_nth_control_point)(ph, 0, 0.0, 1, &mut k, &mut v), 0);
|
||||
}
|
||||
assert_eq!((k, v), (1.0, 1.0));
|
||||
// 恒等求值(越界钳制端点值)。
|
||||
let mut out = 0.0;
|
||||
unsafe {
|
||||
assert_eq!((s.parametric_param_get_value)(ph, 0, 0.0, 0.5, &mut out), 0);
|
||||
}
|
||||
assert_eq!(out, 0.5);
|
||||
unsafe {
|
||||
assert_eq!((s.parametric_param_get_value)(ph, 0, 0.0, -3.0, &mut out), 0);
|
||||
assert_eq!(out, 0.0);
|
||||
assert_eq!((s.parametric_param_get_value)(ph, 0, 0.0, 7.0, &mut out), 0);
|
||||
assert_eq!(out, 1.0);
|
||||
}
|
||||
}
|
||||
|
||||
/// describe 期 Set/Add/Delete 改**默认值**,实例化时复制到实例。
|
||||
#[test]
|
||||
fn descriptor_edits_become_instance_defaults() {
|
||||
let (desc, ph) = define_parametric();
|
||||
let s = ps();
|
||||
// describe 期改默认:加两个点,改第 1 个点的值。
|
||||
unsafe {
|
||||
assert_eq!((s.parametric_param_add_control_point)(ph, 0, 0.0, 0.25, 0.5, 0), 0);
|
||||
assert_eq!((s.parametric_param_add_control_point)(ph, 0, 0.0, 0.75, 0.9, 0), 0);
|
||||
assert_eq!((s.parametric_param_set_nth_control_point)(ph, 0, 0.0, 1, 0.25, 0.6, 0), 0);
|
||||
}
|
||||
// 默认值本身生效。
|
||||
let mut out = 0.0;
|
||||
unsafe {
|
||||
assert_eq!((s.parametric_param_get_value)(ph, 0, 0.0, 0.25, &mut out), 0);
|
||||
}
|
||||
assert_eq!(out, 0.6);
|
||||
// 实例化:默认曲线随 from_def 复制。
|
||||
let (_inst, _ih, iph) = instantiate(desc);
|
||||
unsafe {
|
||||
assert_eq!((s.parametric_param_get_value)(iph, 0, 0.0, 0.25, &mut out), 0);
|
||||
}
|
||||
assert_eq!(out, 0.6);
|
||||
let mut n = 0;
|
||||
unsafe {
|
||||
assert_eq!((s.parametric_param_get_n_control_points)(iph, 0, 0.0, &mut n), 0);
|
||||
}
|
||||
assert_eq!(n, 4); // (0,0) (0.25,0.6) (0.75,0.9) (1,1)
|
||||
}
|
||||
|
||||
/// 实例期全链路:Add → Set → Delete → DeleteAll,求值随模型变化。
|
||||
#[test]
|
||||
fn instance_add_set_delete_eval_chain() {
|
||||
let (_inst, _ih, ph) = make_instance();
|
||||
let s = ps();
|
||||
|
||||
// Add 中点 (0.3, 0.7):插值性质 + 有序。
|
||||
unsafe {
|
||||
assert_eq!((s.parametric_param_add_control_point)(ph, 0, 0.0, 0.3, 0.7, 0), 0);
|
||||
}
|
||||
let mut out = 0.0;
|
||||
unsafe {
|
||||
assert_eq!((s.parametric_param_get_value)(ph, 0, 0.0, 0.3, &mut out), 0);
|
||||
}
|
||||
assert_eq!(out, 0.7);
|
||||
let mut n = 0;
|
||||
unsafe {
|
||||
assert_eq!((s.parametric_param_get_n_control_points)(ph, 0, 0.0, &mut n), 0);
|
||||
}
|
||||
assert_eq!(n, 3);
|
||||
|
||||
// SetNth 改 (0.3,0.7) → (0.4,0.6)。
|
||||
unsafe {
|
||||
assert_eq!((s.parametric_param_set_nth_control_point)(ph, 0, 0.0, 1, 0.4, 0.6, 0), 0);
|
||||
}
|
||||
let (mut k, mut v) = (-1.0, -1.0);
|
||||
unsafe {
|
||||
assert_eq!((s.parametric_param_get_nth_control_point)(ph, 0, 0.0, 1, &mut k, &mut v), 0);
|
||||
}
|
||||
assert_eq!((k, v), (0.4, 0.6));
|
||||
unsafe {
|
||||
assert_eq!((s.parametric_param_get_value)(ph, 0, 0.0, 0.4, &mut out), 0);
|
||||
}
|
||||
assert_eq!(out, 0.6);
|
||||
|
||||
// Delete 第 1 点 → 回到双点。
|
||||
unsafe {
|
||||
assert_eq!((s.parametric_param_delete_control_point)(ph, 0, 1), 0);
|
||||
}
|
||||
unsafe {
|
||||
assert_eq!((s.parametric_param_get_n_control_points)(ph, 0, 0.0, &mut n), 0);
|
||||
}
|
||||
assert_eq!(n, 2);
|
||||
|
||||
// DeleteAll → 0 点,求值退化为恒等。
|
||||
unsafe {
|
||||
assert_eq!((s.parametric_param_delete_all_control_points)(ph, 0), 0);
|
||||
assert_eq!((s.parametric_param_get_n_control_points)(ph, 0, 0.0, &mut n), 0);
|
||||
assert_eq!((s.parametric_param_get_value)(ph, 0, 0.0, 0.5, &mut out), 0);
|
||||
}
|
||||
assert_eq!(n, 0);
|
||||
assert_eq!(out, 0.5);
|
||||
}
|
||||
|
||||
/// SetNth 改 key 破坏有序性 → 自动重排(头文件提醒的语义)。
|
||||
#[test]
|
||||
fn set_nth_reorders_on_key_change() {
|
||||
let (_inst, _ih, ph) = make_instance();
|
||||
let s = ps();
|
||||
unsafe {
|
||||
assert_eq!((s.parametric_param_add_control_point)(ph, 0, 0.0, 0.5, 0.5, 0), 0);
|
||||
// 把第 0 点 (0,0) 挪到 0.75 → 序列变 (0.5) (0.75) (1)。
|
||||
assert_eq!((s.parametric_param_set_nth_control_point)(ph, 0, 0.0, 0, 0.75, 0.75, 0), 0);
|
||||
}
|
||||
let (mut k, mut v) = (-1.0, -1.0);
|
||||
for (i, (ek, ev)) in [(0.5, 0.5), (0.75, 0.75), (1.0, 1.0)].iter().enumerate() {
|
||||
unsafe {
|
||||
assert_eq!((s.parametric_param_get_nth_control_point)(ph, 0, 0.0, i as c_int, &mut k, &mut v), 0);
|
||||
}
|
||||
assert_eq!((k, v), (*ek, *ev), "nth {i}");
|
||||
}
|
||||
}
|
||||
|
||||
/// 同 key Add → 覆盖(不增点)。
|
||||
#[test]
|
||||
fn add_overwrites_same_key() {
|
||||
let (_inst, _ih, ph) = make_instance();
|
||||
let s = ps();
|
||||
unsafe {
|
||||
assert_eq!((s.parametric_param_add_control_point)(ph, 0, 0.0, 0.5, 0.4, 0), 0);
|
||||
assert_eq!((s.parametric_param_add_control_point)(ph, 0, 0.0, 0.5, 0.9, 0), 0);
|
||||
}
|
||||
let mut n = 0;
|
||||
unsafe {
|
||||
assert_eq!((s.parametric_param_get_n_control_points)(ph, 0, 0.0, &mut n), 0);
|
||||
}
|
||||
assert_eq!(n, 3); // (0,0) (0.5,0.9) (1,1)
|
||||
let (mut k, mut v) = (-1.0, -1.0);
|
||||
unsafe {
|
||||
assert_eq!((s.parametric_param_get_nth_control_point)(ph, 0, 0.0, 1, &mut k, &mut v), 0);
|
||||
}
|
||||
assert_eq!((k, v), (0.5, 0.9));
|
||||
}
|
||||
|
||||
/// 错误码:坏句柄 / 非 parametric / curveIndex 越界 / nth 越界 /
|
||||
/// 空 out。
|
||||
#[test]
|
||||
fn error_codes() {
|
||||
let s = ps();
|
||||
let mut out = 0.0;
|
||||
// 空句柄 → BadHandle。
|
||||
unsafe {
|
||||
assert_eq!(
|
||||
(s.parametric_param_get_value)(std::ptr::null_mut(), 0, 0.0, 0.5, &mut out),
|
||||
status::ERR_BAD_HANDLE
|
||||
);
|
||||
}
|
||||
// 空 out → ErrValue。
|
||||
let (_inst, _ih, ph) = make_instance();
|
||||
unsafe {
|
||||
assert_eq!(
|
||||
(s.parametric_param_get_value)(ph, 0, 0.0, 0.5, std::ptr::null_mut()),
|
||||
status::ERR_VALUE
|
||||
);
|
||||
}
|
||||
// curveIndex 越界(维度 1)→ BadIndex。
|
||||
unsafe {
|
||||
assert_eq!(
|
||||
(s.parametric_param_get_value)(ph, 1, 0.0, 0.5, &mut out),
|
||||
status::ERR_BAD_INDEX
|
||||
);
|
||||
}
|
||||
// nth 越界(2 点曲线)→ BadIndex。
|
||||
let mut k = 0.0;
|
||||
unsafe {
|
||||
assert_eq!(
|
||||
(s.parametric_param_get_nth_control_point)(ph, 0, 0.0, 5, &mut k, &mut out),
|
||||
status::ERR_BAD_INDEX
|
||||
);
|
||||
}
|
||||
// 非 parametric 参数 → BadHandle(Integer 参数的句柄)。
|
||||
let desc = EffectDescriptor::new();
|
||||
let psuite = crate::suites::param::suite_v1();
|
||||
let dhandle = descriptor_handle(&desc);
|
||||
let t = cs("OfxParamTypeInteger");
|
||||
let n = cs("gain");
|
||||
let mut gph: *mut c_void = std::ptr::null_mut();
|
||||
unsafe {
|
||||
assert_eq!((psuite.param_define)(dhandle, t.as_ptr(), n.as_ptr(), &mut gph), 0);
|
||||
// parametric 也定义一个,便于实例化后拿两个 handle。
|
||||
let pt = cs(crate::param::TYPE_PARAMETRIC);
|
||||
let pn = cs("curve");
|
||||
let mut pph: *mut c_void = std::ptr::null_mut();
|
||||
assert_eq!((psuite.param_define)(dhandle, pt.as_ptr(), pn.as_ptr(), &mut pph), 0);
|
||||
let params = ParamSetInstance {
|
||||
params: desc
|
||||
.params
|
||||
.iter()
|
||||
.map(|d| Box::new(ParamInstance::from_def((**d).clone())))
|
||||
.collect(),
|
||||
};
|
||||
let plugin = dummy_plugin(desc);
|
||||
let inst = Arc::new(Instance {
|
||||
props: PropertySet::new(),
|
||||
plugin,
|
||||
context: "OfxImageEffectContextFilter".into(),
|
||||
params,
|
||||
clips: vec![],
|
||||
node_identity: std::sync::atomic::AtomicUsize::new(0),
|
||||
destroyed: std::sync::atomic::AtomicBool::new(false),
|
||||
sequence_range: std::sync::Mutex::new(None),
|
||||
progress_cb: std::sync::Mutex::new(None),
|
||||
cancel: std::sync::atomic::AtomicBool::new(false),
|
||||
edit: std::sync::Mutex::new(crate::instance::EditTransaction::new()),
|
||||
render_lock: std::sync::Mutex::new(()),
|
||||
interact: std::sync::Mutex::new(None),
|
||||
});
|
||||
let ih = instance_handle(&inst);
|
||||
let mut g: *mut c_void = std::ptr::null_mut();
|
||||
let gn = cs("gain");
|
||||
assert_eq!((psuite.param_get_handle)(ih, gn.as_ptr(), &mut g, std::ptr::null_mut()), 0);
|
||||
assert_eq!(
|
||||
(s.parametric_param_get_value)(g, 0, 0.0, 0.5, &mut out),
|
||||
status::ERR_BAD_HANDLE
|
||||
);
|
||||
assert_eq!(
|
||||
(s.parametric_param_add_control_point)(g, 0, 0.0, 0.5, 0.5, 0),
|
||||
status::ERR_BAD_HANDLE
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 维度属性驱动:插件设 dimension=2 后第二维可用、第三维 BadIndex;
|
||||
/// 未配置曲线按恒等默认读数。
|
||||
#[test]
|
||||
fn dimension_prop_drives_curves() {
|
||||
let (desc, ph) = define_parametric();
|
||||
// 插件经属性 suite 把维度改成 2。
|
||||
let psuite = crate::suites::property::suite_v1();
|
||||
let dim = cs(crate::param::P_PARAMETRIC_DIMENSION);
|
||||
unsafe {
|
||||
assert_eq!((psuite.set_int)(ph, dim.as_ptr(), 0, 2), 0);
|
||||
}
|
||||
let s = ps();
|
||||
// 第二维:Add 后求值可用。
|
||||
unsafe {
|
||||
assert_eq!((s.parametric_param_add_control_point)(ph, 1, 0.0, 0.25, 0.5, 0), 0);
|
||||
}
|
||||
let mut out = 0.0;
|
||||
unsafe {
|
||||
assert_eq!((s.parametric_param_get_value)(ph, 1, 0.0, 0.25, &mut out), 0);
|
||||
}
|
||||
assert_eq!(out, 0.5);
|
||||
// 第一维未配置 → 恒等默认。
|
||||
unsafe {
|
||||
assert_eq!((s.parametric_param_get_value)(ph, 0, 0.0, 0.25, &mut out), 0);
|
||||
}
|
||||
assert_eq!(out, 0.25);
|
||||
let mut n = 0;
|
||||
unsafe {
|
||||
assert_eq!((s.parametric_param_get_n_control_points)(ph, 0, 0.0, &mut n), 0);
|
||||
}
|
||||
assert_eq!(n, 2);
|
||||
// 第三维 → BadIndex。
|
||||
unsafe {
|
||||
assert_eq!(
|
||||
(s.parametric_param_get_value)(ph, 2, 0.0, 0.5, &mut out),
|
||||
status::ERR_BAD_INDEX
|
||||
);
|
||||
assert_eq!(
|
||||
(s.parametric_param_add_control_point)(ph, 2, 0.0, 0.5, 0.5, 0),
|
||||
status::ERR_BAD_INDEX
|
||||
);
|
||||
}
|
||||
// 维度属性随 from_def 复制到实例(实例句柄上同样生效)。
|
||||
let (_inst, _ih, iph) = instantiate(desc);
|
||||
let mut n = -1;
|
||||
unsafe {
|
||||
assert_eq!((s.parametric_param_get_n_control_points)(iph, 1, 0.0, &mut n), 0);
|
||||
}
|
||||
assert_eq!(n, 3); // (0,0) (0.25,0.5) (1,1)
|
||||
}
|
||||
|
||||
/// range 属性参与未配置曲线的恒等默认(端点 = range 值)。
|
||||
#[test]
|
||||
fn range_prop_shapes_implicit_identity() {
|
||||
let (_desc, ph) = define_parametric();
|
||||
let psuite = crate::suites::property::suite_v1();
|
||||
let dim = cs(crate::param::P_PARAMETRIC_DIMENSION);
|
||||
let range = cs(crate::param::P_PARAMETRIC_RANGE);
|
||||
unsafe {
|
||||
assert_eq!((psuite.set_int)(ph, dim.as_ptr(), 0, 2), 0);
|
||||
// range = (0, 255)。
|
||||
assert_eq!((psuite.set_double)(ph, range.as_ptr(), 0, 0.0), 0);
|
||||
assert_eq!((psuite.set_double)(ph, range.as_ptr(), 1, 255.0), 0);
|
||||
}
|
||||
let s = ps();
|
||||
// 未配置的第二维:恒等默认端点 = range。
|
||||
let (mut k, mut v) = (-1.0, -1.0);
|
||||
unsafe {
|
||||
assert_eq!((s.parametric_param_get_nth_control_point)(ph, 1, 0.0, 1, &mut k, &mut v), 0);
|
||||
}
|
||||
assert_eq!((k, v), (255.0, 255.0));
|
||||
}
|
||||
|
||||
/// UI 属性(UIColour double×3N / InteractBackground 指针)经通用
|
||||
/// 属性 suite set/get,并随 from_def 复制到实例。
|
||||
#[test]
|
||||
fn ui_props_set_get_and_copy() {
|
||||
let (desc, ph) = define_parametric();
|
||||
let psuite = crate::suites::property::suite_v1();
|
||||
// UIColour:3 个 double(维度 1 → 1 个 RGB 三元组)。
|
||||
let colour = cs(crate::param::P_PARAMETRIC_UI_COLOUR);
|
||||
let rgb: [f64; 3] = [1.0, 0.0, 0.5];
|
||||
unsafe {
|
||||
assert_eq!((psuite.set_double_n)(ph, colour.as_ptr(), 3, rgb.as_ptr()), 0);
|
||||
}
|
||||
// InteractBackground:任意指针。
|
||||
let bg = cs(crate::param::P_PARAMETRIC_INTERACT_BG);
|
||||
let sentinel = 0x1234usize as *mut c_void;
|
||||
unsafe {
|
||||
assert_eq!((psuite.set_pointer)(ph, bg.as_ptr(), 0, sentinel), 0);
|
||||
}
|
||||
// 读回。
|
||||
let mut got = [0.0; 3];
|
||||
unsafe {
|
||||
assert_eq!((psuite.get_double_n)(ph, colour.as_ptr(), 3, got.as_mut_ptr()), 0);
|
||||
}
|
||||
assert_eq!(got, rgb);
|
||||
let mut gotp: *mut c_void = std::ptr::null_mut();
|
||||
unsafe {
|
||||
assert_eq!((psuite.get_pointer)(ph, bg.as_ptr(), 0, &mut gotp), 0);
|
||||
}
|
||||
assert_eq!(gotp, sentinel);
|
||||
// 实例 props 深拷贝。
|
||||
let (_inst, _ih, iph) = instantiate(desc);
|
||||
let mut got2 = [0.0; 3];
|
||||
unsafe {
|
||||
assert_eq!((psuite.get_double_n)(iph, colour.as_ptr(), 3, got2.as_mut_ptr()), 0);
|
||||
}
|
||||
assert_eq!(got2, rgb);
|
||||
let mut gotp2: *mut c_void = std::ptr::null_mut();
|
||||
unsafe {
|
||||
assert_eq!((psuite.get_pointer)(iph, bg.as_ptr(), 0, &mut gotp2), 0);
|
||||
}
|
||||
assert_eq!(gotp2, sentinel);
|
||||
}
|
||||
|
||||
/// 实例编辑走 instance-changed 通知路径(未绑定节点 → no-op,
|
||||
/// 不 panic 即通过;登记路径的节流语义由 param suite 测试覆盖)。
|
||||
#[test]
|
||||
fn instance_edits_go_through_notify_path() {
|
||||
let (_inst, _ih, ph) = make_instance();
|
||||
let s = ps();
|
||||
unsafe {
|
||||
assert_eq!((s.parametric_param_add_control_point)(ph, 0, 0.0, 0.2, 0.8, 0), 0);
|
||||
assert_eq!((s.parametric_param_set_nth_control_point)(ph, 0, 0.0, 1, 0.2, 0.9, 0), 0);
|
||||
assert_eq!((s.parametric_param_delete_control_point)(ph, 0, 1), 0);
|
||||
assert_eq!((s.parametric_param_delete_all_control_points)(ph, 0), 0);
|
||||
}
|
||||
let mut out = 0.0;
|
||||
unsafe {
|
||||
assert_eq!((s.parametric_param_get_value)(ph, 0, 0.0, 0.2, &mut out), 0);
|
||||
}
|
||||
assert_eq!(out, 0.2);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user