// 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 . //! ofxColour(M11 §4):宿主 OCIO 能力宣告、实例协商属性、输入 clip //! 工作空间(ACEScg)、GetOutputColourspace 往返(偏好采纳 + //! 交叉引用解析 + 输出写回)。 //! //! 单库化后经 Rust API 直连(`Host::global().create_instance`),不再 //! 经已删除的 `oakplugin_host_scan`/`oakplugin_instance_create` C ABI。 mod common; use std::sync::Arc; use oakplugin::handle::RefBox; use oakplugin::host::Host; use oakplugin::instance::Instance; use oakplugin::property::Value; const TEST_PLUGIN_ID: &str = "org.oak.test-plugin"; /// 扫描并创建实例;不可用返回 None(skip)。 fn scan_and_create(id: &str) -> Option>> { if common::test_plugin_scan_dir().is_none() { common::skip("最小测试插件未构建"); return None; } let dir = common::test_plugin_scan_dir().unwrap(); let host = Host::global(); host.cache.scan_path(&dir).ok()?; host.create_instance(id, None).ok() } fn prop_str(props: &oakplugin::property::PropertySet, name: &str) -> Option { match props.get(name, 0)? { Value::String(s) => Some(s.to_string_lossy().into_owned()), _ => None, } } /// 宿主能力宣告:OCIO 模式 + native 配置列表 + GL 支持。 #[test] fn host_declares_ocio_capability() { let host = Host::global(); assert_eq!( prop_str(&host.props, "OfxImageEffectPropColourManagementStyle").as_deref(), Some("OfxImageEffectColourManagementOCIO") ); assert_eq!( host.props .dimension("OfxImageEffectPropColourManagementAvailableConfigs"), 1, "可用配置至少含 ofx-native-v1.5_aces-v1.3_ocio-v2.3" ); assert_eq!( prop_str(&host.props, "OfxImageEffectPropOpenGLRenderSupported").as_deref(), Some("true") ); } /// 实例期协商属性:style/config/OCIOConfig 已写;输入 clip 色彩空间 /// = 工作空间 ACEScg;输出 clip 未设(待 GetOutputColourspace)。 #[test] fn instance_and_clip_colourspace_props() { common::with_host(|| { let Some(inst) = scan_and_create(TEST_PLUGIN_ID) else { return; }; assert_eq!( prop_str(&inst.value.props, "OfxImageEffectPropColourManagementStyle").as_deref(), Some("OfxImageEffectColourManagementOCIO") ); assert_eq!( prop_str( &inst.value.props, "OfxImageEffectPropColourManagementConfig" ) .as_deref(), Some("ofx-native-v1.5_aces-v1.3_ocio-v2.3") ); assert_eq!( prop_str(&inst.value.props, "OfxImageEffectPropOCIOConfig").as_deref(), Some("ocio://default") ); let source = inst .value .clips .iter() .find(|c| c.name == "Source") .unwrap(); assert_eq!( prop_str(&source.props, "OfxImageClipPropColourspace").as_deref(), Some("ACEScg"), "输入 clip 色彩空间 = 工作空间" ); let output = inst .value .clips .iter() .find(|c| c.name == "Output") .unwrap(); assert!( prop_str(&output.props, "OfxImageClipPropColourspace") .map(|s| s.is_empty()) .unwrap_or(true), "输出 clip 色彩空间在 GetOutputColourspace 前未设" ); }); } /// GetOutputColourspace 往返:无偏好 → 插件回 "OfxColourspace_Source" /// 交叉引用 → 宿主解析为 Source 的实际色彩空间(ACEScg)并写回输出。 #[test] fn get_output_colourspace_cross_reference() { common::with_host(|| { let Some(inst) = scan_and_create(TEST_PLUGIN_ID) else { return; }; let cs = inst .value .get_output_colourspace(&[]) .expect("GetOutputColourspace 应成功"); assert_eq!(cs, "ACEScg", "交叉引用 OfxColourspace_Source → ACEScg"); inst.value.set_output_colourspace(&cs); let output = inst .value .clips .iter() .find(|c| c.name == "Output") .unwrap(); assert_eq!( prop_str(&output.props, "OfxImageClipPropColourspace").as_deref(), Some("ACEScg"), "输出 clip 已写回解析后的色彩空间" ); }); } /// GetOutputColourspace 偏好采纳:插件优先取宿主偏好的第一个色彩 /// 空间。 #[test] fn get_output_colourspace_preferred() { common::with_host(|| { let Some(inst) = scan_and_create(TEST_PLUGIN_ID) else { return; }; let cs = inst .value .get_output_colourspace(&["ACEScg".to_string(), "linear".to_string()]) .expect("GetOutputColourspace 应成功"); assert_eq!(cs, "ACEScg", "偏好列表第一个被采纳"); }); } /// 交叉引用解析的边界:未知 clip 引用原样返回;非交叉引用原样返回。 #[test] fn resolve_colourspace_edge_cases() { common::with_host(|| { let Some(inst) = scan_and_create(TEST_PLUGIN_ID) else { return; }; assert_eq!( inst.value .resolve_colourspace("OfxColourspace_NoSuchClip".to_string()), "OfxColourspace_NoSuchClip", "未知 clip 引用保持原样" ); assert_eq!( inst.value.resolve_colourspace("ACEScg".to_string()), "ACEScg", "非交叉引用原样返回" ); }); }