比较提交
| 作者 | SHA1 | 提交日期 | |
|---|---|---|---|
|
|
58f09e5756 | ||
|
|
730d1755ca | ||
|
|
2f2c081980 | ||
|
|
2e79602761 | ||
|
|
13821366a5 | ||
|
|
189a5d7634 | ||
|
|
356683b41a | ||
|
|
bea9bdfc0a | ||
|
|
dffa547d92 | ||
|
|
acbe115c8a | ||
|
|
abaea2b1df | ||
|
|
788ba0d551 | ||
|
|
2f7702fa20 |
没有与搜索条件相匹配的文件
@@ -2,12 +2,16 @@
|
|||||||
|
|
||||||
基于本仓库的 FxPlug4 初始工程制作的人像美化滤镜,支持 Motion 和 Final Cut Pro。源码使用 Objective-C、Vision、Core Image 与 Metal。
|
基于本仓库的 FxPlug4 初始工程制作的人像美化滤镜,支持 Motion 和 Final Cut Pro。源码使用 Objective-C、Vision、Core Image 与 Metal。
|
||||||
|
|
||||||
|
代码由 OpenAI + Codex 全程编写,站长仅参与调试和测试。
|
||||||
|
|
||||||
功能包括磨皮、肤色、细节、牙齿美白、眼睛提亮、九类脸型调整、自动人脸跟随和多人处理。原版 MSE18V2 的静态分析依据、近似实现与尚未对齐的部分见 [分析说明](docs/analysis/REA-分析与复刻说明.md)。本版尚未完成原版逐像素与复杂运动跟踪对照。
|
功能包括磨皮、肤色、细节、牙齿美白、眼睛提亮、九类脸型调整、自动人脸跟随和多人处理。原版 MSE18V2 的静态分析依据、近似实现与尚未对齐的部分见 [分析说明](docs/analysis/REA-分析与复刻说明.md)。本版尚未完成原版逐像素与复杂运动跟踪对照。
|
||||||
|
|
||||||
## 使用
|
## 使用
|
||||||
|
|
||||||
正式安装路径为 `/Library/Plug-Ins/FxPlug/beatygril.app`。Motion 滤镜分类为「大鱼」,滤镜名「大鱼人像美化」。Final Cut Pro 在「效果 → 大鱼磨皮瘦脸 → 大鱼人像美化」中使用配套 Motion 效果,模板目录必须同时保留 `.moef` 与 `Drop Zone.tiff`。美化会随当前帧的人脸自动更新,不需要点击分析。界面以磨皮、美白、瘦脸三张卡片呈现,默认跟随主要人脸;多人模式对多脸变形。单项眼睛可暂时停用并保留数值,右侧箭头恢复默认;磨皮半径、饱和度、锐化和整体混合等收在「更多设置」。旧手动分析与调试参数仅保留隐藏兼容,实际输出统一使用实时自动跟随。
|
正式安装路径为 `/Library/Plug-Ins/FxPlug/beatygril.app`。Motion 滤镜分类为「大鱼」,滤镜名「大鱼人像美化」。Final Cut Pro 在「效果 → 大鱼磨皮瘦脸 → 大鱼人像美化」中使用配套 Motion 效果,模板目录必须同时保留 `.moef` 与 `Drop Zone.tiff`。美化会随当前帧的人脸自动更新,不需要点击分析。界面以磨皮、美白、瘦脸三张卡片呈现,默认跟随主要人脸;多人模式对多脸变形。单项眼睛可暂时停用并保留数值,右侧箭头恢复默认;磨皮半径、饱和度、锐化和整体混合等收在「更多设置」。旧手动分析与调试参数仅保留隐藏兼容,实际输出统一使用实时自动跟随。
|
||||||
|
|
||||||
|
1.3.0 的磨皮卡片依次提供「磨皮程度、微细锐化、五官保护、非肤色区保护」。两种保护默认 100%;降低五官保护让五官区域参与更多磨皮,降低非肤色区保护则扩大磨皮范围,均不扩大肤色提亮范围。微细锐化在局部支持区域恢复纹理;旧全局磨皮和全局锐化仍保留在更多设置,旧工程的参数与归档继续兼容。迁移证据、实现边界与验证见[新版磨皮参数迁移](docs/analysis/新版磨皮参数迁移.md)。1.3.0(15)APP 已完成签名、公证及正式安装,1636 项自动检查通过;原生宿主效果由用户实测。1.2.0 下载链接仍对应原发行包,本次未更新文章或安装包。
|
||||||
|
|
||||||
## 开发和恢复
|
## 开发和恢复
|
||||||
|
|
||||||
需要 Xcode 27.1、FxPlug SDK 4.3.4 与 PluginManager 1.7.4;最低运行系统 macOS 13。正式产物为 arm64/x86_64 通用版本。
|
需要 Xcode 27.1、FxPlug SDK 4.3.4 与 PluginManager 1.7.4;最低运行系统 macOS 13。正式产物为 arm64/x86_64 通用版本。
|
||||||
@@ -21,7 +25,9 @@ xcodebuild -project beatygril/beatygril.xcodeproj -scheme 'Wrapper Application'
|
|||||||
|
|
||||||
工程从 `/Library/Developer/SDKs/FxPlug.sdk` 读取头文件与链接信息,从 `/Library/Developer/Frameworks` 嵌入可运行的 Apple 框架。构建默认使用团队 B3W25FX5H5 的 Developer ID Application 身份;正式交付使用 `python3 scripts/deliver.py` 完成公证、粘附和安装;安装前先关闭 Motion 与 FCP,脚本在检测到宿主仍运行时保留原安装版并停止。原生验收完成后继续脚本的清理阶段,清除临时注册与 APP 副本。
|
工程从 `/Library/Developer/SDKs/FxPlug.sdk` 读取头文件与链接信息,从 `/Library/Developer/Frameworks` 嵌入可运行的 Apple 框架。构建默认使用团队 B3W25FX5H5 的 Developer ID Application 身份;正式交付使用 `python3 scripts/deliver.py` 完成公证、粘附和安装;安装前先关闭 Motion 与 FCP,脚本在检测到宿主仍运行时保留原安装版并停止。原生验收完成后继续脚本的清理阶段,清除临时注册与 APP 副本。
|
||||||
|
|
||||||
源码仓库:`https://dayu.media/git/dayu/paintfcp-fxplug.git`。恢复入口见 [RECOVERY.md](RECOVERY.md),当前验证记录见 [验证记录](docs/验证记录.md)。原始模板保存在 `baseline-20261021` 标签。
|
公开源码:[GitHub · FCP-BeatyGril](https://github.com/FAMEDaYu/FCP-BeatyGril)。大鱼站备份:`https://dayu.media/git/dayu/paintfcp-fxplug.git`。恢复入口见 [RECOVERY.md](RECOVERY.md),当前验证记录见 [验证记录](docs/验证记录.md)。原始模板保存在 `baseline-20261021` 标签。
|
||||||
|
|
||||||
|
继续开发或移交时先读[项目交接文档](docs/项目交接.md),包含当前发布状态、源码入口、坐标与交互约定、验收边界,以及签名打包和双远端保存流程。
|
||||||
|
|
||||||
## 1.0.5 瘦脸修复
|
## 1.0.5 瘦脸修复
|
||||||
|
|
||||||
@@ -57,4 +63,4 @@ xcodebuild -project beatygril/beatygril.xcodeproj -scheme 'Wrapper Application'
|
|||||||
|
|
||||||
`large.png`(640×360)和 `small.png`(192×108)与 `.moef` 同目录,安装器安装后重新锁定。手动复制或从 Git 恢复模板时,需对这两个 PNG 重新执行 `chflags uchg`,不能锁定整个模板目录。FCP 当前分类为「大鱼磨皮瘦脸」,Motion 原生滤镜分类仍为「大鱼」。
|
`large.png`(640×360)和 `small.png`(192×108)与 `.moef` 同目录,安装器安装后重新锁定。手动复制或从 Git 恢复模板时,需对这两个 PNG 重新执行 `chflags uchg`,不能锁定整个模板目录。FCP 当前分类为「大鱼磨皮瘦脸」,Motion 原生滤镜分类仍为「大鱼」。
|
||||||
|
|
||||||
仓库属性已设为公开;大鱼站统一 Git 网关目前仍要求登录并校验会员,公开属性并未绕过网关。发布文章另提供可公开下载的当前源码快照,不依赖 Git 登录;安装包始终免费。
|
GitHub 提供公开源码、提交历史和版本标签;[1.2.0 Release](https://github.com/FAMEDaYu/FCP-BeatyGril/releases/tag/v1.2.0) 提供正式签名公证安装包。大鱼站 Git 备份仍受站点登录与会员网关限制,发布文章另提供公开源码快照;安装包始终免费。
|
||||||
+19
-1
@@ -1,6 +1,7 @@
|
|||||||
# 恢复说明
|
# 恢复说明
|
||||||
|
|
||||||
- 源码远程:`https://dayu.media/git/dayu/paintfcp-fxplug.git`
|
- 大鱼备份远程(origin):`https://dayu.media/git/dayu/paintfcp-fxplug.git`
|
||||||
|
- GitHub 公开远程(github):`https://github.com/FAMEDaYu/FCP-BeatyGril.git`
|
||||||
- 本地项目根:`/Users/dayu/Documents/PaintFCP`
|
- 本地项目根:`/Users/dayu/Documents/PaintFCP`
|
||||||
- 工作分支:`main`
|
- 工作分支:`main`
|
||||||
- 修改前版本标签:`baseline-20261021`,完整 SHA:`1746f38398588e2367abb37a5c2b31cb64042429`
|
- 修改前版本标签:`baseline-20261021`,完整 SHA:`1746f38398588e2367abb37a5c2b31cb64042429`
|
||||||
@@ -96,3 +97,20 @@ Motion/FCP原生验收范围及未完成项见`docs/verification/v1.1.2/native-q
|
|||||||
模板恢复到 `~/Movies/Motion Templates.localized/Effects.localized/大鱼磨皮瘦脸/大鱼人像美化/`,需同时保留 `.moef`、`Drop Zone.tiff`、`large.png`、`small.png`。Git 不保存 BSD 文件标志,手工复制后对两张 PNG 执行 `chflags uchg` 并核对 `ls -lO`;不要锁目录和工程。自动安装 PKG 会恢复这两个锁。
|
模板恢复到 `~/Movies/Motion Templates.localized/Effects.localized/大鱼磨皮瘦脸/大鱼人像美化/`,需同时保留 `.moef`、`Drop Zone.tiff`、`large.png`、`small.png`。Git 不保存 BSD 文件标志,手工复制后对两张 PNG 执行 `chflags uchg` 并核对 `ls -lO`;不要锁目录和工程。自动安装 PKG 会恢复这两个锁。
|
||||||
|
|
||||||
仓库已为公开属性,但当前网站 Git 网关仍执行登录 / 会员校验。匿名 `ls-remote` 未通过,不宣称无需登录的 Git 恢复已通过;认证的远端克隆和文章的公开源码快照分别核验。服务端备份边界与前文相同。
|
仓库已为公开属性,但当前网站 Git 网关仍执行登录 / 会员校验。匿名 `ls-remote` 未通过,不宣称无需登录的 Git 恢复已通过;认证的远端克隆和文章的公开源码快照分别核验。服务端备份边界与前文相同。
|
||||||
|
|
||||||
|
1.2.0 的最终标签完整 SHA 为 `3c7261c3a861b9f4704cfb36055d7b500c239dab`,已从认证远端独立克隆恢复并比对183个受跟踪文件,strict/full fsck、Xcode工程读取、恢复出的PKG签名和票据验证通过。正式PKG SHA-256 为 `5bd77c28b3a7e54f3d4060be1f16bcb145d9bc4d6b9cfb9eb137c01130440243`。后续main修正发布助手和补充文档,不改变已签名产品源码696d5f3或正式包。
|
||||||
|
|
||||||
|
公开文章:https://dayu.media/15593/ ,包含免费安装包、免登录源码快照与仓库链接。源码快照对应 `788ba0d5514595cf349af33074055d70b0754185`;它包含完整构建源码,排除PKG二进制,不包含Git历史。完整历史可从仓库恢复,仍需满足当前站点Git网关登录条件。
|
||||||
|
|
||||||
|
## GitHub 公开恢复副本
|
||||||
|
|
||||||
|
GitHub 远端 `https://github.com/FAMEDaYu/FCP-BeatyGril.git` 已完成完整历史与七个标签的推送,`origin` 及其 main 上游保持大鱼仓库。独立匿名克隆恢复节点为 `356683b41af702d6c0143ad7fa5f540d752d6e14`,197 文件逐字节核验、strict/full fsck 和全部标签回读通过。后续 main 补充发布说明与验证文档,产品源码保持相同。GitHub Release 的公证安装包已匿名下载核对哈希、签名和票据。详见 `docs/GitHub同步说明.md` 和 `docs/verification/github/`。
|
||||||
|
|
||||||
|
|
||||||
|
## 1.3.0 新面板参数恢复
|
||||||
|
|
||||||
|
签名源码节点 `2f2c081980dc610ac810f8daf92da8966f310802` 已推送并读回核验 origin 与 github 的 `codex/new-beauty-controls` 分支。两个远端地址沿用本文既有配置。独立远端克隆与 strict/full fsck、205 个受跟踪文件逐字节比较通过,见 `docs/verification/v1.3.0/recovery-verification.json`。无 LFS、子模块;依赖与签名凭据仍需单独恢复。
|
||||||
|
|
||||||
|
本节点增加五官保护强度、非肤色区保护和局部微细锐化,PGBeautySettings v2 兼容旧 160 字节 v1 状态,面板归档仍为 schema 1。测试命令不变,1636 项通过。恢复签名源码时克隆并切换该分支,或 `git switch --detach 2f2c081980dc610ac810f8daf92da8966f310802`。
|
||||||
|
|
||||||
|
1.3.0(15)已正式安装到 `/Library/Plug-Ins/FxPlug/beatygril.app`。签名、通用架构、公证票据、Gatekeeper 和 PluginKit 正式路径均通过,已清理本次构建、旧 APP 备份和公证 ZIP。最终回执见 `docs/verification/v1.3.0/delivery-receipt.json`、`installed-verification.json`;`prepared-release.json` 保留安装前状态。`v1.3.0` 标签包含签名源码及后续交付文档。本次没有生成新 PKG 或更新文章,1.2.0 的公开发行资料继续有效;新滑杆的原生宿主视觉仍由用户测试。
|
||||||
+2
-2
@@ -1,10 +1,10 @@
|
|||||||
# 第三方资料与许可边界
|
# 第三方资料与许可边界
|
||||||
|
|
||||||
本项目自己的 Objective-C、Metal、脚本及原创文档按根目录 MIT License 开源。署名 Dayu 不表示第三方代码、样本与 SDK 的版权转移。
|
代码由 OpenAI + Codex 全程编写,站长仅参与调试和测试。项目代码、脚本及文档按根目录 MIT License 提供。署名 Dayu 不表示第三方代码、样本与 SDK 的版权转移。
|
||||||
|
|
||||||
- Apple FxPlug / PluginManager SDK 和随正式插件分发的运行框架属于 Apple;受 Apple 相应 SDK 与再分发条款约束,源码仓库不包含这些框架。编译需自行安装 Xcode 与 FxPlug SDK。macOS Vision、AppKit、CoreImage 属于系统组件。
|
- Apple FxPlug / PluginManager SDK 和随正式插件分发的运行框架属于 Apple;受 Apple 相应 SDK 与再分发条款约束,源码仓库不包含这些框架。编译需自行安装 Xcode 与 FxPlug SDK。macOS Vision、AppKit、CoreImage 属于系统组件。
|
||||||
- `Templates/大鱼人像美化/Drop Zone.tiff` 是 Motion 工程的标准占位资源,随模板保留;不将其声明为 Dayu 原创作品或另行授予 MIT 权利。
|
- `Templates/大鱼人像美化/Drop Zone.tiff` 是 Motion 工程的标准占位资源,随模板保留;不将其声明为 Dayu 原创作品或另行授予 MIT 权利。
|
||||||
- `tests/fixtures/astronaut.png` 来源于 NASA 的 Eileen Collins 图像,经 scikit-image 样本分发,详见同目录 ATTRIBUTION.md。相关分析预览由该公开样本生成。
|
- `tests/fixtures/astronaut.png` 来源于 NASA 的 Eileen Collins 图像,经 scikit-image 样本分发,详见同目录 ATTRIBUTION.txt。相关分析预览由该公开样本生成。
|
||||||
- `docs/analysis` 中的对照分析、符号和反汇编摘录是互操作研究材料,不包括目标商业 APP、模型或 metallib。MIT 不为其原权利人的内容重新授权。本项目不声称与目标商业插件的模型或画面逐像素一致。
|
- `docs/analysis` 中的对照分析、符号和反汇编摘录是互操作研究材料,不包括目标商业 APP、模型或 metallib。MIT 不为其原权利人的内容重新授权。本项目不声称与目标商业插件的模型或画面逐像素一致。
|
||||||
- `Assets/Artwork/portrait-cover.png`、APP 中 `LauncherInfo.png` 及两张模板缩略图由内置图像生成工具创作。人物为虚构成年古风人物,封面是宣传插画,不是真实滤镜前后效果对比;生成说明和提示词见 `docs/design`。未使用目标插件的宣传人像。
|
- `Assets/Artwork/portrait-cover.png`、APP 中 `LauncherInfo.png` 及两张模板缩略图由内置图像生成工具创作。人物为虚构成年古风人物,封面是宣传插画,不是真实滤镜前后效果对比;生成说明和提示词见 `docs/design`。未使用目标插件的宣传人像。
|
||||||
- 不包含用户测试素材、用户工程、真实人脸坐标数据、签名私钥、公证密码或账号令牌。签名证书与公证凭据不属于源码恢复范围。
|
- 不包含用户测试素材、用户工程、真实人脸坐标数据、签名私钥、公证密码或账号令牌。签名证书与公证凭据不属于源码恢复范围。
|
||||||
|
|||||||
@@ -17,9 +17,9 @@
|
|||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>XPC!</string>
|
<string>XPC!</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>1.2.0</string>
|
<string>1.3.0</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>14</string>
|
<string>15</string>
|
||||||
<key>NSHumanReadableCopyright</key>
|
<key>NSHumanReadableCopyright</key>
|
||||||
<string></string>
|
<string></string>
|
||||||
<key>PlugInKit</key>
|
<key>PlugInKit</key>
|
||||||
@@ -72,7 +72,7 @@
|
|||||||
<key>uuid</key>
|
<key>uuid</key>
|
||||||
<string>FB14B0E6-59B2-4836-916F-CD0C559E9241</string>
|
<string>FB14B0E6-59B2-4836-916F-CD0C559E9241</string>
|
||||||
<key>version</key>
|
<key>version</key>
|
||||||
<string>1.2.0</string>
|
<string>1.3.0</string>
|
||||||
</dict>
|
</dict>
|
||||||
<dict>
|
<dict>
|
||||||
<key>className</key>
|
<key>className</key>
|
||||||
@@ -84,7 +84,7 @@
|
|||||||
<key>uuid</key>
|
<key>uuid</key>
|
||||||
<string>E0E432C8-3D71-4ABA-B006-E7F519E06C87</string>
|
<string>E0E432C8-3D71-4ABA-B006-E7F519E06C87</string>
|
||||||
<key>version</key>
|
<key>version</key>
|
||||||
<string>1.2.0</string>
|
<string>1.3.0</string>
|
||||||
<key>protocolNames</key>
|
<key>protocolNames</key>
|
||||||
<array>
|
<array>
|
||||||
<string>FxOnScreenControl</string>
|
<string>FxOnScreenControl</string>
|
||||||
|
|||||||
@@ -82,6 +82,26 @@ static NSDictionary<NSString *, CIColorKernel *> *PGKernels(void) {
|
|||||||
"kernel vec4 pgMaskedMix(__sample source,__sample effect,__sample mask,float amount) {"
|
"kernel vec4 pgMaskedMix(__sample source,__sample effect,__sample mask,float amount) {"
|
||||||
"vec4 s=unpremultiply(source); vec4 e=unpremultiply(effect);"
|
"vec4 s=unpremultiply(source); vec4 e=unpremultiply(effect);"
|
||||||
"return premultiply(vec4(mix(s.rgb,e.rgb,clamp(mask.r*amount,0.0,1.0)),s.a)); }");
|
"return premultiply(vec4(mix(s.rgb,e.rgb,clamp(mask.r*amount,0.0,1.0)),s.a)); }");
|
||||||
|
// Keep smoothing support separate from the exposure/color mask. The
|
||||||
|
// two new controls independently release feature cutouts and background.
|
||||||
|
k[@"smoothingSupport"] = PGKernel(@
|
||||||
|
"kernel vec4 pgSmoothingSupport(__sample protectedMask,__sample baseMask,float featureProtection,float skinProtection) {"
|
||||||
|
"float base=clamp(baseMask.r,0.0,1.0);float protectedValue=clamp(protectedMask.r,0.0,1.0);"
|
||||||
|
"float value=mix(base,protectedValue,featureProtection);"
|
||||||
|
"if(skinProtection<1.0){float extended=1.0-skinProtection*(1.0-base);"
|
||||||
|
"float retention=base>0.00001?clamp(protectedValue/base,0.0,1.0):1.0;"
|
||||||
|
"value=extended*mix(1.0,retention,featureProtection);}"
|
||||||
|
"return vec4(value,value,value,1.0); }");
|
||||||
|
k[@"microDetail"] = PGKernel(@
|
||||||
|
"kernel vec4 pgMicroDetail(__sample processed,__sample original,__sample low,__sample coverage,float amount) {"
|
||||||
|
"vec4 outPixel=unpremultiply(processed);vec3 src=unpremultiply(original).rgb;"
|
||||||
|
"vec3 high=src-unpremultiply(low).rgb;vec3 weights=vec3(0.299,0.587,0.114);"
|
||||||
|
"float y=dot(src,weights);float detailY=dot(high,weights);float size=length(high);"
|
||||||
|
"float lightGate=smoothstep(0.07,0.22,y)*(1.0-smoothstep(0.86,0.99,y));"
|
||||||
|
"float textureGate=smoothstep(0.0015,0.010,size)*(1.0-smoothstep(0.16,0.36,size));"
|
||||||
|
"float gain=amount*clamp(coverage.r,0.0,1.0)*lightGate*textureGate*1.18;"
|
||||||
|
"vec3 detail=vec3(detailY*0.92)+(high-vec3(detailY))*0.28;"
|
||||||
|
"return premultiply(vec4(clamp(outPixel.rgb+detail*gain,0.0,1.0),outPixel.a)); }");
|
||||||
k[@"colorCoverage"] = PGKernel(@
|
k[@"colorCoverage"] = PGKernel(@
|
||||||
"kernel vec4 pgColorCoverage(__sample coverage,__sample image,float invert) {"
|
"kernel vec4 pgColorCoverage(__sample coverage,__sample image,float invert) {"
|
||||||
"vec3 c=unpremultiply(image).rgb; float y=dot(c,vec3(0.299,0.587,0.114));"
|
"vec3 c=unpremultiply(image).rgb; float y=dot(c,vec3(0.299,0.587,0.114));"
|
||||||
@@ -157,7 +177,7 @@ static NSDictionary<NSString *, CIColorKernel *> *PGKernels(void) {
|
|||||||
float mix = PGClamp(s.mix, 0, 1);
|
float mix = PGClamp(s.mix, 0, 1);
|
||||||
if (mix == 0 && !s.showMask) return image;
|
if (mix == 0 && !s.showMask) return image;
|
||||||
NSDictionary *k = PGKernels();
|
NSDictionary *k = PGKernels();
|
||||||
if (k.count != 11) {
|
if (k.count != 13) {
|
||||||
if (error) *error = [NSError errorWithDomain:@"PGBeautyRenderer" code:2
|
if (error) *error = [NSError errorWithDomain:@"PGBeautyRenderer" code:2
|
||||||
userInfo:@{NSLocalizedDescriptionKey:@"The beauty image kernels could not be compiled."}];
|
userInfo:@{NSLocalizedDescriptionKey:@"The beauty image kernels could not be compiled."}];
|
||||||
return nil;
|
return nil;
|
||||||
@@ -173,7 +193,10 @@ static NSDictionary<NSString *, CIColorKernel *> *PGKernels(void) {
|
|||||||
float sharpness = PGClamp(s.sharpness,0,2);
|
float sharpness = PGClamp(s.sharpness,0,2);
|
||||||
float teeth = PGClamp(s.teethWhitening,0,1);
|
float teeth = PGClamp(s.teethWhitening,0,1);
|
||||||
float eyes = PGClamp(s.eyeBrightening,0,1);
|
float eyes = PGClamp(s.eyeBrightening,0,1);
|
||||||
BOOL needsSkin = local*amount>0 || brightness>0 || hue!=0 || saturation!=0 || even>0 ||
|
float protection = s.featureProtection ? PGClamp(s.featureProtectionAmount,0,1) : 0;
|
||||||
|
float skinProtection = PGClamp(s.nonSkinProtection,0,1);
|
||||||
|
float micro = PGClamp(s.microDetail,0,1)*amount;
|
||||||
|
BOOL needsSkin = local*amount>0 || micro>0 || brightness>0 || hue!=0 || saturation!=0 || even>0 ||
|
||||||
(s.skinTonePreset>0 && s.skinTonePreset<=6) || (s.showMask && s.debugChannel==0);
|
(s.skinTonePreset>0 && s.skinTonePreset<=6) || (s.showMask && s.debugChannel==0);
|
||||||
CIImage *black = [[CIImage imageWithColor:[CIColor colorWithRed:0 green:0 blue:0 alpha:1]] imageByCroppingToRect:extent];
|
CIImage *black = [[CIImage imageWithColor:[CIColor colorWithRed:0 green:0 blue:0 alpha:1]] imageByCroppingToRect:extent];
|
||||||
CIImage *teethInput = teethMask ? [teethMask imageByCroppingToRect:extent] : black;
|
CIImage *teethInput = teethMask ? [teethMask imageByCroppingToRect:extent] : black;
|
||||||
@@ -200,12 +223,29 @@ static NSDictionary<NSString *, CIColorKernel *> *PGKernels(void) {
|
|||||||
float invert = s.debugChannel==1 || s.debugChannel==2 ? (s.invertMask ? 1 : 0) : 0;
|
float invert = s.debugChannel==1 || s.debugChannel==2 ? (s.invertMask ? 1 : 0) : 0;
|
||||||
return PGApply(k[@"debug"],extent,@[image,preview,@(invert)]);
|
return PGApply(k[@"debug"],extent,@[image,preview,@(invert)]);
|
||||||
}
|
}
|
||||||
|
CIImage *smoothingMask=mask;
|
||||||
|
if((local*amount>0 || micro>0) && (protection<1 || skinProtection<1)) {
|
||||||
|
CIImage *baseRaw=colorMask ? [colorMask imageByCroppingToRect:extent] : raw;
|
||||||
|
float soft=PGClamp(s.maskSoftness,0,1);
|
||||||
|
CIImage *baseMask=PGApply(k[@"mask"],extent,@[baseRaw,PGBlur(baseRaw,0.35+soft*radius*0.35,extent),
|
||||||
|
image,PGBlur(image,1.5,extent),eyeInput,teethInput,
|
||||||
|
@(PGClamp(s.maskThreshold,0,255)/255.f),@(soft),@(PGClamp(s.maskEdgeProtect,0,1)),@0,@(s.invertMask?1:0)]);
|
||||||
|
smoothingMask=PGApply(k[@"smoothingSupport"],extent,@[mask,baseMask,@(protection),@(skinProtection)]);
|
||||||
|
}
|
||||||
// Detail protection may contain small eye, brow, nose and lip cutouts.
|
// Detail protection may contain small eye, brow, nose and lip cutouts.
|
||||||
// Exposure and color must not reuse those holes or sharpen a face ellipse
|
// Exposure and color must not reuse those holes or sharpen a face ellipse
|
||||||
// with the detail threshold; that creates visible rings and islands.
|
// with the detail threshold; that creates visible rings and islands.
|
||||||
CIImage *toneMask=mask;
|
CIImage *toneMask=mask;
|
||||||
BOOL changesColor=brightness>0 || hue!=0 || saturation!=0 || even>0 ||
|
BOOL changesColor=brightness>0 || hue!=0 || saturation!=0 || even>0 ||
|
||||||
(s.skinTonePreset>0 && s.skinTonePreset<=6);
|
(s.skinTonePreset>0 && s.skinTonePreset<=6);
|
||||||
|
if(!colorMask && changesColor && s.featureProtection) {
|
||||||
|
// Standalone callers may not supply the separate uncut coverage.
|
||||||
|
// At least keep our eye/teeth cutouts out of their color adjustment.
|
||||||
|
float soft=PGClamp(s.maskSoftness,0,1);
|
||||||
|
toneMask=PGApply(k[@"mask"],extent,@[raw,PGBlur(raw,0.35+soft*radius*0.35,extent),
|
||||||
|
image,PGBlur(image,1.5,extent),eyeInput,teethInput,
|
||||||
|
@(PGClamp(s.maskThreshold,0,255)/255.f),@(soft),@(PGClamp(s.maskEdgeProtect,0,1)),@0,@(s.invertMask?1:0)]);
|
||||||
|
}
|
||||||
if(colorMask && changesColor) {
|
if(colorMask && changesColor) {
|
||||||
CGFloat feather=MAX(3,MIN(extent.size.width,extent.size.height)*0.03);
|
CGFloat feather=MAX(3,MIN(extent.size.width,extent.size.height)*0.03);
|
||||||
CIImage *support=[colorMask imageByCroppingToRect:extent];
|
CIImage *support=[colorMask imageByCroppingToRect:extent];
|
||||||
@@ -219,8 +259,12 @@ static NSDictionary<NSString *, CIColorKernel *> *PGKernels(void) {
|
|||||||
CGFloat broad = fmin(16,(2.55+0.45*local)*fine+1.1+1.3*local);
|
CGFloat broad = fmin(16,(2.55+0.45*local)*fine+1.1+1.3*local);
|
||||||
float compression = 0.12+2.4*powf(local,2.15)+1.25*local;
|
float compression = 0.12+2.4*powf(local,2.15)+1.25*local;
|
||||||
CIImage *detail = PGApply(k[@"detail"],extent,@[result,PGBlur(result,fine,extent),
|
CIImage *detail = PGApply(k[@"detail"],extent,@[result,PGBlur(result,fine,extent),
|
||||||
PGBlur(result,broad,extent),@(compression),@(s.featureProtection ? 1 : 0)]);
|
PGBlur(result,broad,extent),@(compression),@(protection)]);
|
||||||
result = PGApply(k[@"maskedMix"],extent,@[result,detail,mask,@(amount)]);
|
result = PGApply(k[@"maskedMix"],extent,@[result,detail,smoothingMask,@(amount)]);
|
||||||
|
}
|
||||||
|
if(micro>0) {
|
||||||
|
CGFloat fine=fmin(7.4,2.275+3.15*pow(local,0.72)+0.95*local);
|
||||||
|
result=PGApply(k[@"microDetail"],extent,@[result,image,PGBlur(image,fine,extent),smoothingMask,@(micro)]);
|
||||||
}
|
}
|
||||||
CIImage *color = result;
|
CIImage *color = result;
|
||||||
if (brightness>0) {
|
if (brightness>0) {
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <math.h>
|
||||||
|
|
||||||
// Persisted FxPlug render state. Append fields and bump version for migrations.
|
// Persisted FxPlug render state. Append fields and bump version for migrations.
|
||||||
typedef struct {
|
typedef struct {
|
||||||
@@ -12,11 +14,17 @@ typedef struct {
|
|||||||
float faceJumpGuardRange, selectionX, selectionY;
|
float faceJumpGuardRange, selectionX, selectionY;
|
||||||
uint32_t featureProtection, skinTonePreset, showMask, invertMask, debugChannel;
|
uint32_t featureProtection, skinTonePreset, showMask, invertMask, debugChannel;
|
||||||
uint32_t showFaceBoxes, showTrackBox, singleFace, fastMultiFace, trackingEnabled, targetIndex;
|
uint32_t showFaceBoxes, showTrackBox, singleFace, fastMultiFace, trackingEnabled, targetIndex;
|
||||||
|
// v2 keeps the entire 160-byte v1 prefix, including legacy bool/strength IDs.
|
||||||
|
float featureProtectionAmount, nonSkinProtection, microDetail;
|
||||||
} PGBeautySettings;
|
} PGBeautySettings;
|
||||||
|
|
||||||
|
enum { PGBeautySettingsV1Size = 160, PGBeautySettingsVersion = 2 };
|
||||||
|
_Static_assert(offsetof(PGBeautySettings,featureProtectionAmount)==PGBeautySettingsV1Size,
|
||||||
|
"The v1 render-state prefix must remain stable");
|
||||||
|
|
||||||
static inline PGBeautySettings PGBeautyDefaults(void) {
|
static inline PGBeautySettings PGBeautyDefaults(void) {
|
||||||
PGBeautySettings s = {0};
|
PGBeautySettings s = {0};
|
||||||
s.version = 1;
|
s.version = PGBeautySettingsVersion;
|
||||||
s.amount = 1.0f;
|
s.amount = 1.0f;
|
||||||
s.radius = 8.0f;
|
s.radius = 8.0f;
|
||||||
s.mix = 1.0f;
|
s.mix = 1.0f;
|
||||||
@@ -24,6 +32,8 @@ static inline PGBeautySettings PGBeautyDefaults(void) {
|
|||||||
s.maskSoftness = 0.6f;
|
s.maskSoftness = 0.6f;
|
||||||
s.maskEdgeProtect = 0.4f;
|
s.maskEdgeProtect = 0.4f;
|
||||||
s.featureProtection = 1;
|
s.featureProtection = 1;
|
||||||
|
s.featureProtectionAmount = 1;
|
||||||
|
s.nonSkinProtection = 1;
|
||||||
s.faceJumpGuardRange = 1.8f;
|
s.faceJumpGuardRange = 1.8f;
|
||||||
s.selectionX = s.selectionY = 0.5f;
|
s.selectionX = s.selectionY = 0.5f;
|
||||||
return s;
|
return s;
|
||||||
|
|||||||
@@ -11,9 +11,10 @@ NSArray<NSDictionary *> *PGInspectorControls(void) {
|
|||||||
#define ITEM(ID,NAME,FIELD,SECTION,KIND,FORMAT,MIN,MAX,BYPASS,STORAGE) \
|
#define ITEM(ID,NAME,FIELD,SECTION,KIND,FORMAT,MIN,MAX,BYPASS,STORAGE) \
|
||||||
[items addObject:@{@"id":@(ID),@"name":@NAME,@"section":@SECTION,@"kind":@KIND,@"format":@FORMAT,@"min":@(MIN),@"max":@(MAX),@"default":@(defaults.FIELD),@"bypass":@(BYPASS),@"offset":@(offsetof(PGBeautySettings,FIELD)),@"storage":@STORAGE}]
|
[items addObject:@{@"id":@(ID),@"name":@NAME,@"section":@SECTION,@"kind":@KIND,@"format":@FORMAT,@"min":@(MIN),@"max":@(MAX),@"default":@(defaults.FIELD),@"bypass":@(BYPASS),@"offset":@(offsetof(PGBeautySettings,FIELD)),@"storage":@STORAGE}]
|
||||||
ITEM(PGSmoothingEnabled,"磨皮",featureProtection,"smooth","master","integer",0,1,NO,"master");
|
ITEM(PGSmoothingEnabled,"磨皮",featureProtection,"smooth","master","integer",0,1,NO,"master");
|
||||||
ITEM(PGMidtone,"局部磨皮",midtoneDetail,"smooth","slider","percent",0,1,YES,"float");
|
ITEM(PGMidtone,"磨皮程度",midtoneDetail,"smooth","slider","percent",0,1,YES,"float");
|
||||||
ITEM(PGHighPass,"全局磨皮",highPassDetail,"smooth","slider","percent",0,1,YES,"float");
|
ITEM(PGMicroDetail,"微细锐化",microDetail,"smooth","slider","percent",0,1,YES,"float");
|
||||||
ITEM(PGFeatureProtect,"五官保护",featureProtection,"smooth","toggle","integer",0,1,NO,"uint");
|
ITEM(PGFeatureProtectionAmount,"五官保护",featureProtectionAmount,"smooth","slider","percent",0,1,YES,"float");
|
||||||
|
ITEM(PGNonSkinProtection,"非肤色区保护",nonSkinProtection,"smooth","slider","percent",0,1,YES,"float");
|
||||||
ITEM(PGColorEnabled,"美白",featureProtection,"color","master","integer",0,1,NO,"master");
|
ITEM(PGColorEnabled,"美白",featureProtection,"color","master","integer",0,1,NO,"master");
|
||||||
ITEM(PGTonePreset,"肤色",skinTonePreset,"color","preset","integer",0,6,NO,"uint");
|
ITEM(PGTonePreset,"肤色",skinTonePreset,"color","preset","integer",0,6,NO,"uint");
|
||||||
NSMutableDictionary *preset=[items.lastObject mutableCopy];
|
NSMutableDictionary *preset=[items.lastObject mutableCopy];
|
||||||
@@ -22,9 +23,10 @@ NSArray<NSDictionary *> *PGInspectorControls(void) {
|
|||||||
ITEM(PGTeeth,"白牙",teethWhitening,"color","slider","percent",0,1,YES,"float");
|
ITEM(PGTeeth,"白牙",teethWhitening,"color","slider","percent",0,1,YES,"float");
|
||||||
ITEM(PGHue,"皮肤色相",skinHue,"color","slider","degrees",-30,30,YES,"float");
|
ITEM(PGHue,"皮肤色相",skinHue,"color","slider","degrees",-30,30,YES,"float");
|
||||||
ITEM(PGShapeEnabled,"瘦脸",featureProtection,"shape","master","integer",0,1,NO,"master");
|
ITEM(PGShapeEnabled,"瘦脸",featureProtection,"shape","master","integer",0,1,NO,"master");
|
||||||
ITEM(PGTrackBoxes,"人脸框预览",showTrackBox,"shape","toggle","integer",0,1,NO,"uint");
|
ITEM(PGTrackBoxes,"检测框预览",showTrackBox,"shape","toggle","integer",0,1,NO,"uint");
|
||||||
ITEM(PGFastMulti,"多人模式",fastMultiFace,"shape","toggle","integer",0,1,NO,"uint");
|
ITEM(PGFastMulti,"多人模式",fastMultiFace,"shape","toggle","integer",0,1,NO,"uint");
|
||||||
ITEM(PGJumpGuard,"防跳脸范围",faceJumpGuardRange,"shape","slider","percent",1,4,NO,"float");
|
NSMutableDictionary *multi=[items.lastObject mutableCopy];
|
||||||
|
multi[@"tooltip"]=@"多人模式易抖动,请谨慎使用";items[items.count-1]=multi;
|
||||||
ITEM(PGThin,"瘦脸",faceThin,"shape","slider","signed",0,1,YES,"float");
|
ITEM(PGThin,"瘦脸",faceThin,"shape","slider","signed",0,1,YES,"float");
|
||||||
ITEM(PGBigEye,"大眼",faceBigEye,"shape","slider","signed",0,1,YES,"float");
|
ITEM(PGBigEye,"大眼",faceBigEye,"shape","slider","signed",0,1,YES,"float");
|
||||||
ITEM(PGShort,"短脸",faceShort,"shape","slider","signed",-1,1,YES,"float");
|
ITEM(PGShort,"短脸",faceShort,"shape","slider","signed",-1,1,YES,"float");
|
||||||
@@ -36,11 +38,13 @@ NSArray<NSDictionary *> *PGInspectorControls(void) {
|
|||||||
ITEM(PGSmallHead,"小头",faceSmallHead,"shape","slider","signed",0,1,YES,"float");
|
ITEM(PGSmallHead,"小头",faceSmallHead,"shape","slider","signed",0,1,YES,"float");
|
||||||
ITEM(PGSmallHeadRange,"小头范围",faceSmallHeadRange,"shape","slider","percent",0,1,NO,"float");
|
ITEM(PGSmallHeadRange,"小头范围",faceSmallHeadRange,"shape","slider","percent",0,1,NO,"float");
|
||||||
ITEM(PGAmount,"磨皮强度",amount,"more","slider","percent",0,1,YES,"float");
|
ITEM(PGAmount,"磨皮强度",amount,"more","slider","percent",0,1,YES,"float");
|
||||||
|
ITEM(PGHighPass,"全局磨皮",highPassDetail,"more","slider","percent",0,1,YES,"float");
|
||||||
ITEM(PGRadius,"磨皮半径",radius,"more","slider","radius",1,30,NO,"float");
|
ITEM(PGRadius,"磨皮半径",radius,"more","slider","radius",1,30,NO,"float");
|
||||||
ITEM(PGSaturation,"肤色饱和度",skinSaturation,"more","slider","percent",-100,100,YES,"float");
|
ITEM(PGSaturation,"肤色饱和度",skinSaturation,"more","slider","percent",-100,100,YES,"float");
|
||||||
ITEM(PGEvenTone,"均匀肤色",skinEvenTone,"more","slider","percent",0,1,YES,"float");
|
ITEM(PGEvenTone,"均匀肤色",skinEvenTone,"more","slider","percent",0,1,YES,"float");
|
||||||
ITEM(PGEyeBright,"眼睛提亮",eyeBrightening,"more","slider","percent",0,1,YES,"float");
|
ITEM(PGEyeBright,"眼睛提亮",eyeBrightening,"more","slider","percent",0,1,YES,"float");
|
||||||
ITEM(PGSharpness,"锐化",sharpness,"more","slider","percent",0,1,YES,"float");
|
ITEM(PGSharpness,"锐化",sharpness,"more","slider","percent",0,1,YES,"float");
|
||||||
|
ITEM(PGJumpGuard,"防跳脸范围",faceJumpGuardRange,"more","slider","percent",1,4,NO,"float");
|
||||||
ITEM(PGMix,"整体混合",mix,"more","slider","percent",0,1,NO,"float");
|
ITEM(PGMix,"整体混合",mix,"more","slider","percent",0,1,NO,"float");
|
||||||
#undef ITEM
|
#undef ITEM
|
||||||
controls=[items copy];
|
controls=[items copy];
|
||||||
@@ -62,7 +66,12 @@ static double PGControlValue(id raw,NSDictionary *d) {
|
|||||||
+ (instancetype)stateWithValues:(NSDictionary *)values disabledParameters:(NSSet *)disabled hasEdits:(BOOL)edited {
|
+ (instancetype)stateWithValues:(NSDictionary *)values disabledParameters:(NSSet *)disabled hasEdits:(BOOL)edited {
|
||||||
PGInspectorState *state=[self new]; NSMutableDictionary *safe=[NSMutableDictionary new];NSMutableSet *bypassed=[NSMutableSet new];
|
PGInspectorState *state=[self new]; NSMutableDictionary *safe=[NSMutableDictionary new];NSMutableSet *bypassed=[NSMutableSet new];
|
||||||
for(NSDictionary *d in PGInspectorControls()) {
|
for(NSDictionary *d in PGInspectorControls()) {
|
||||||
NSNumber *key=d[@"id"];safe[key]=@(PGControlValue(values[key],d));
|
NSNumber *key=d[@"id"];id raw=values[key];
|
||||||
|
// Schema 1 documents stored five-feature protection as the legacy bool.
|
||||||
|
// New numeric values win when present; old archives gain its exact 0/1.
|
||||||
|
if(key.unsignedIntValue==PGFeatureProtectionAmount && !raw && [values[@(PGFeatureProtect)] isKindOfClass:NSNumber.class])
|
||||||
|
raw=@([values[@(PGFeatureProtect)] boolValue]?1:0);
|
||||||
|
safe[key]=@(PGControlValue(raw,d));
|
||||||
if([d[@"bypass"] boolValue] && [disabled containsObject:key]) [bypassed addObject:key];
|
if([d[@"bypass"] boolValue] && [disabled containsObject:key]) [bypassed addObject:key];
|
||||||
}
|
}
|
||||||
state->_values=[safe copy];state->_disabledParameters=[bypassed copy];state->_hasEdits=edited;return state;
|
state->_values=[safe copy];state->_disabledParameters=[bypassed copy];state->_hasEdits=edited;return state;
|
||||||
@@ -115,7 +124,8 @@ PGBeautySettings PGSettingsWithInspectorState(PGBeautySettings s,PGInspectorStat
|
|||||||
if([storage isEqual:@"float"]) *(float *)((uint8_t *)&s+offset)=value;
|
if([storage isEqual:@"float"]) *(float *)((uint8_t *)&s+offset)=value;
|
||||||
else *(uint32_t *)((uint8_t *)&s+offset)=(uint32_t)value;
|
else *(uint32_t *)((uint8_t *)&s+offset)=(uint32_t)value;
|
||||||
}
|
}
|
||||||
if(![state valueForParameter:PGSmoothingEnabled])s.amount=s.midtoneDetail=s.highPassDetail=0;
|
s.featureProtection=s.featureProtectionAmount>0;
|
||||||
|
if(![state valueForParameter:PGSmoothingEnabled])s.amount=s.midtoneDetail=s.highPassDetail=s.microDetail=0;
|
||||||
if(![state valueForParameter:PGColorEnabled]) {s.skinBrightness=s.skinHue=s.skinSaturation=s.skinEvenTone=s.teethWhitening=s.eyeBrightening=0;s.skinTonePreset=0;}
|
if(![state valueForParameter:PGColorEnabled]) {s.skinBrightness=s.skinHue=s.skinSaturation=s.skinEvenTone=s.teethWhitening=s.eyeBrightening=0;s.skinTonePreset=0;}
|
||||||
if(![state valueForParameter:PGShapeEnabled]) {s.faceThin=s.faceBigEye=s.faceShort=s.faceNarrow=s.faceChin=s.faceV=s.faceJaw=s.faceCheekbone=s.faceSmallHead=0;s.showTrackBox=0;}
|
if(![state valueForParameter:PGShapeEnabled]) {s.faceThin=s.faceBigEye=s.faceShort=s.faceNarrow=s.faceChin=s.faceV=s.faceJaw=s.faceCheekbone=s.faceSmallHead=0;s.showTrackBox=0;}
|
||||||
return s;
|
return s;
|
||||||
@@ -189,6 +199,12 @@ PGBeautySettings PGSettingsWithInspectorState(PGBeautySettings s,PGInspectorStat
|
|||||||
else [api getFloatValue:&value fromParameter:key atTime:time];
|
else [api getFloatValue:&value fromParameter:key atTime:time];
|
||||||
values[@(key)]=@(value);
|
values[@(key)]=@(value);
|
||||||
}
|
}
|
||||||
|
// The old bool can still disable protection. A true value must preserve a
|
||||||
|
// numeric setting already read above; its registered default is one for
|
||||||
|
// old documents that have no independently stored numeric parameter.
|
||||||
|
BOOL legacyFeatureProtection=YES;
|
||||||
|
if([api getBoolValue:&legacyFeatureProtection fromParameter:PGFeatureProtect atTime:time] && !legacyFeatureProtection)
|
||||||
|
values[@(PGFeatureProtectionAmount)]=@0;
|
||||||
BOOL legacyDetail=YES;[api getBoolValue:&legacyDetail fromParameter:PGDetailEnabled atTime:time];
|
BOOL legacyDetail=YES;[api getBoolValue:&legacyDetail fromParameter:PGDetailEnabled atTime:time];
|
||||||
NSMutableSet *disabled=legacyDetail?[NSMutableSet set]:[NSMutableSet setWithObjects:@(PGHighPass),@(PGSharpness),nil];
|
NSMutableSet *disabled=legacyDetail?[NSMutableSet set]:[NSMutableSet setWithObjects:@(PGHighPass),@(PGSharpness),nil];
|
||||||
// Earlier versions put high-pass smoothing under a different group. Keep
|
// Earlier versions put high-pass smoothing under a different group. Keep
|
||||||
@@ -196,7 +212,7 @@ PGBeautySettings PGSettingsWithInspectorState(PGBeautySettings s,PGInspectorStat
|
|||||||
// off, rather than turning a still-active global effect off on first edit.
|
// off, rather than turning a still-active global effect off on first edit.
|
||||||
if(![values[@(PGSmoothingEnabled)] boolValue] && legacyDetail && [values[@(PGHighPass)] doubleValue]>0) {
|
if(![values[@(PGSmoothingEnabled)] boolValue] && legacyDetail && [values[@(PGHighPass)] doubleValue]>0) {
|
||||||
values[@(PGSmoothingEnabled)]=@YES;
|
values[@(PGSmoothingEnabled)]=@YES;
|
||||||
[disabled addObject:@(PGMidtone)];[disabled addObject:@(PGAmount)];
|
[disabled addObject:@(PGMidtone)];[disabled addObject:@(PGAmount)];[disabled addObject:@(PGMicroDetail)];
|
||||||
}
|
}
|
||||||
_state=[PGInspectorState stateWithValues:values disabledParameters:disabled hasEdits:NO];
|
_state=[PGInspectorState stateWithValues:values disabledParameters:disabled hasEdits:NO];
|
||||||
}
|
}
|
||||||
@@ -232,6 +248,11 @@ PGBeautySettings PGSettingsWithInspectorState(PGBeautySettings s,PGInspectorStat
|
|||||||
else if([d[@"kind"] isEqual:@"toggle"] || [d[@"kind"] isEqual:@"master"]) [api setBoolValue:value!=0 toParameter:key atTime:time];
|
else if([d[@"kind"] isEqual:@"toggle"] || [d[@"kind"] isEqual:@"master"]) [api setBoolValue:value!=0 toParameter:key atTime:time];
|
||||||
else [api setFloatValue:value toParameter:key atTime:time];
|
else [api setFloatValue:value toParameter:key atTime:time];
|
||||||
}
|
}
|
||||||
|
BOOL featureProtection=[next isParameterEnabled:PGFeatureProtectionAmount] && [next valueForParameter:PGFeatureProtectionAmount]>0;
|
||||||
|
id<FxParameterRetrievalAPI_v6> reader=[_manager apiForProtocol:@protocol(FxParameterRetrievalAPI_v6)];
|
||||||
|
BOOL previousProtection=!featureProtection;
|
||||||
|
if(![reader getBoolValue:&previousProtection fromParameter:PGFeatureProtect atTime:time] || previousProtection!=featureProtection)
|
||||||
|
[api setBoolValue:featureProtection toParameter:PGFeatureProtect atTime:time];
|
||||||
_state=next;_pendingUntil=NSDate.timeIntervalSinceReferenceDate+.35;_pendingTime=time;_statusMessage=@"";return YES;
|
_state=next;_pendingUntil=NSDate.timeIntervalSinceReferenceDate+.35;_pendingTime=time;_statusMessage=@"";return YES;
|
||||||
}
|
}
|
||||||
- (BOOL)setValue:(double)value forParameter:(UInt32)identifier {
|
- (BOOL)setValue:(double)value forParameter:(UInt32)identifier {
|
||||||
|
|||||||
@@ -202,6 +202,8 @@ static PGInspectorGlyphButton *PGUIButton(NSString *glyph, NSColor *accent, id t
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
NSString *key=[descriptor[@"id"]stringValue];
|
NSString *key=[descriptor[@"id"]stringValue];
|
||||||
|
NSString *tooltip=descriptor[@"tooltip"];
|
||||||
|
if(tooltip.length){self.toolTip=tooltip;_label.toolTip=tooltip;_toggle.toolTip=tooltip;_slider.toolTip=tooltip;_valueField.toolTip=tooltip;}
|
||||||
_slider.identifier=[@"pg.value."stringByAppendingString:key];_valueField.identifier=[@"pg.number."stringByAppendingString:key];
|
_slider.identifier=[@"pg.value."stringByAppendingString:key];_valueField.identifier=[@"pg.number."stringByAppendingString:key];
|
||||||
_toggle.identifier=[@"pg.toggle."stringByAppendingString:key];_eye.identifier=[@"pg.enable."stringByAppendingString:key];_reset.identifier=[@"pg.reset."stringByAppendingString:key];
|
_toggle.identifier=[@"pg.toggle."stringByAppendingString:key];_eye.identifier=[@"pg.enable."stringByAppendingString:key];_reset.identifier=[@"pg.reset."stringByAppendingString:key];
|
||||||
}return self;
|
}return self;
|
||||||
@@ -282,11 +284,11 @@ static PGInspectorGlyphButton *PGUIButton(NSString *glyph, NSColor *accent, id t
|
|||||||
@implementation PGInspectorCard
|
@implementation PGInspectorCard
|
||||||
- (BOOL)isFlipped { return YES; }
|
- (BOOL)isFlipped { return YES; }
|
||||||
- (CGFloat)heightForWidth:(CGFloat)width {
|
- (CGFloat)heightForWidth:(CGFloat)width {
|
||||||
if(!_expanded)return 44;CGFloat height=49;for(PGInspectorRow *row in _rows){height+=[row heightForWidth:MAX(0,width-24)];if([row.descriptor[@"id"]unsignedIntValue]==PGJumpGuard)height+=8;}return height+7;
|
if(!_expanded)return 44;CGFloat height=49;for(PGInspectorRow *row in _rows){height+=[row heightForWidth:MAX(0,width-24)];if([row.descriptor[@"id"]unsignedIntValue]==PGFastMulti)height+=8;}return height+7;
|
||||||
}
|
}
|
||||||
- (void)layout {
|
- (void)layout {
|
||||||
[super layout];CGFloat w=NSWidth(self.bounds);_titleLabel.frame=NSMakeRect(25,13,MAX(70,w-80),22);_master.frame=NSMakeRect(w-39,10,25,25);
|
[super layout];CGFloat w=NSWidth(self.bounds);_titleLabel.frame=NSMakeRect(25,13,MAX(70,w-80),22);_master.frame=NSMakeRect(w-39,10,25,25);
|
||||||
CGFloat y=49;for(PGInspectorRow *row in _rows){row.hidden=!_expanded;if(!_expanded)continue;CGFloat h=[row heightForWidth:w-24];row.frame=NSMakeRect(12,y,w-24,h);y+=h;if([row.descriptor[@"id"]unsignedIntValue]==PGJumpGuard)y+=8;}
|
CGFloat y=49;for(PGInspectorRow *row in _rows){row.hidden=!_expanded;if(!_expanded)continue;CGFloat h=[row heightForWidth:w-24];row.frame=NSMakeRect(12,y,w-24,h);y+=h;if([row.descriptor[@"id"]unsignedIntValue]==PGFastMulti)y+=8;}
|
||||||
}
|
}
|
||||||
- (void)drawRect:(NSRect)dirtyRect {
|
- (void)drawRect:(NSRect)dirtyRect {
|
||||||
(void)dirtyRect;NSBezierPath *shape=[NSBezierPath bezierPathWithRoundedRect:NSInsetRect(self.bounds,.5,.5) xRadius:13 yRadius:13];
|
(void)dirtyRect;NSBezierPath *shape=[NSBezierPath bezierPathWithRoundedRect:NSInsetRect(self.bounds,.5,.5) xRadius:13 yRadius:13];
|
||||||
@@ -294,7 +296,7 @@ static PGInspectorGlyphButton *PGUIButton(NSString *glyph, NSColor *accent, id t
|
|||||||
[[_accent colorWithAlphaComponent:.22]setStroke];shape.lineWidth=.8;[shape stroke];
|
[[_accent colorWithAlphaComponent:.22]setStroke];shape.lineWidth=.8;[shape stroke];
|
||||||
NSRect pill=NSMakeRect(12,10,MAX(53,[_heading length]*13+20),25);[[_accent colorWithAlphaComponent:.10]setFill];NSBezierPath *p=[NSBezierPath bezierPathWithRoundedRect:pill xRadius:8 yRadius:8];[p fill];[[_accent colorWithAlphaComponent:.32]setStroke];p.lineWidth=.7;[p stroke];
|
NSRect pill=NSMakeRect(12,10,MAX(53,[_heading length]*13+20),25);[[_accent colorWithAlphaComponent:.10]setFill];NSBezierPath *p=[NSBezierPath bezierPathWithRoundedRect:pill xRadius:8 yRadius:8];[p fill];[[_accent colorWithAlphaComponent:.32]setStroke];p.lineWidth=.7;[p stroke];
|
||||||
if(_expanded){[[PGUIMuted() colorWithAlphaComponent:.20]setStroke];NSBezierPath *line=[NSBezierPath bezierPath];[line moveToPoint:NSMakePoint(12,43)];[line lineToPoint:NSMakePoint(NSWidth(self.bounds)-12,43)];line.lineWidth=.5;[line stroke];
|
if(_expanded){[[PGUIMuted() colorWithAlphaComponent:.20]setStroke];NSBezierPath *line=[NSBezierPath bezierPath];[line moveToPoint:NSMakePoint(12,43)];[line lineToPoint:NSMakePoint(NSWidth(self.bounds)-12,43)];line.lineWidth=.5;[line stroke];
|
||||||
for(PGInspectorRow *row in _rows)if([row.descriptor[@"id"]unsignedIntValue]==PGJumpGuard){CGFloat y=NSMaxY(row.frame)+4;[line removeAllPoints];[line moveToPoint:NSMakePoint(12,y)];[line lineToPoint:NSMakePoint(NSWidth(self.bounds)-12,y)];[line stroke];}
|
for(PGInspectorRow *row in _rows)if([row.descriptor[@"id"]unsignedIntValue]==PGFastMulti){CGFloat y=NSMaxY(row.frame)+4;[line removeAllPoints];[line moveToPoint:NSMakePoint(12,y)];[line lineToPoint:NSMakePoint(NSWidth(self.bounds)-12,y)];[line stroke];}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -14,5 +14,6 @@ enum {
|
|||||||
PGTrackData1=60, PGTrackData2=61, PGTrackData3=62,
|
PGTrackData1=60, PGTrackData2=61, PGTrackData3=62,
|
||||||
PGSmoothingEnabled=70, PGColorEnabled=71, PGShapeEnabled=72,
|
PGSmoothingEnabled=70, PGColorEnabled=71, PGShapeEnabled=72,
|
||||||
PGDetailEnabled=73, PGHelp=74,
|
PGDetailEnabled=73, PGHelp=74,
|
||||||
|
PGFeatureProtectionAmount=75, PGNonSkinProtection=76, PGMicroDetail=77,
|
||||||
PGInspectorPanel=210
|
PGInspectorPanel=210
|
||||||
};
|
};
|
||||||
@@ -30,6 +30,9 @@ static const PGSlider PGSliders[] = {
|
|||||||
PGS(PGEyeBright,"眼睛提亮",eyeBrightening,0,1,.01,YES),
|
PGS(PGEyeBright,"眼睛提亮",eyeBrightening,0,1,.01,YES),
|
||||||
PGS(PGHighPass,"高频细节平滑",highPassDetail,0,1,.01,YES),
|
PGS(PGHighPass,"高频细节平滑",highPassDetail,0,1,.01,YES),
|
||||||
PGS(PGSharpness,"锐化",sharpness,0,1,.01,YES),
|
PGS(PGSharpness,"锐化",sharpness,0,1,.01,YES),
|
||||||
|
PGS(PGFeatureProtectionAmount,"五官保护强度",featureProtectionAmount,0,1,.01,YES),
|
||||||
|
PGS(PGNonSkinProtection,"非肤色区保护",nonSkinProtection,0,1,.01,YES),
|
||||||
|
PGS(PGMicroDetail,"微细锐化",microDetail,0,1,.01,YES),
|
||||||
PGS(PGThin,"瘦脸",faceThin,0,1,.01,YES),
|
PGS(PGThin,"瘦脸",faceThin,0,1,.01,YES),
|
||||||
PGS(PGBigEye,"大眼",faceBigEye,0,1,.01,YES),
|
PGS(PGBigEye,"大眼",faceBigEye,0,1,.01,YES),
|
||||||
PGS(PGShort,"短脸",faceShort,-1,1,.01,NO),
|
PGS(PGShort,"短脸",faceShort,-1,1,.01,NO),
|
||||||
@@ -141,6 +144,7 @@ static CIImage *PGImageFromTile(FxImageTile *tile) {
|
|||||||
void (^end)(void)=^{ok=[api endParameterSubGroup]&&ok;};
|
void (^end)(void)=^{ok=[api endParameterSubGroup]&&ok;};
|
||||||
group(@"磨皮",100); toggle(@"启用磨皮",PGSmoothingEnabled,YES);
|
group(@"磨皮",100); toggle(@"启用磨皮",PGSmoothingEnabled,YES);
|
||||||
slider(PGAmount); slider(PGMidtone); slider(PGRadius); toggle(@"保护五官",PGFeatureProtect,YES); end();
|
slider(PGAmount); slider(PGMidtone); slider(PGRadius); toggle(@"保护五官",PGFeatureProtect,YES); end();
|
||||||
|
group(@"磨皮保护与细节",106); slider(PGFeatureProtectionAmount); slider(PGNonSkinProtection); slider(PGMicroDetail); end();
|
||||||
group(@"肤色",101); toggle(@"启用肤色",PGColorEnabled,YES);
|
group(@"肤色",101); toggle(@"启用肤色",PGColorEnabled,YES);
|
||||||
ok=[api addPopupMenuWithName:@"肤色风格" parameterID:PGTonePreset defaultValue:0 menuEntries:@[@"原色",@"自然白",@"冷白",@"暖白",@"红润白",@"小麦",@"古铜"] parameterFlags:kFxParameterFlag_HIDDEN]&&ok;
|
ok=[api addPopupMenuWithName:@"肤色风格" parameterID:PGTonePreset defaultValue:0 menuEntries:@[@"原色",@"自然白",@"冷白",@"暖白",@"红润白",@"小麦",@"古铜"] parameterFlags:kFxParameterFlag_HIDDEN]&&ok;
|
||||||
slider(PGBrightness); slider(PGHue); slider(PGSaturation); slider(PGEvenTone); slider(PGTeeth); slider(PGEyeBright); end();
|
slider(PGBrightness); slider(PGHue); slider(PGSaturation); slider(PGEvenTone); slider(PGTeeth); slider(PGEyeBright); end();
|
||||||
@@ -207,7 +211,7 @@ static CIImage *PGImageFromTile(FxImageTile *tile) {
|
|||||||
double x=.5,y=.5; [api getXValue:&x YValue:&y fromParameter:PGSelection atTime:time];
|
double x=.5,y=.5; [api getXValue:&x YValue:&y fromParameter:PGSelection atTime:time];
|
||||||
s.selectionX=isfinite(x)?fmin(1,fmax(0,x)):.5; s.selectionY=isfinite(y)?fmin(1,fmax(0,y)):.5;
|
s.selectionX=isfinite(x)?fmin(1,fmax(0,x)):.5; s.selectionY=isfinite(y)?fmin(1,fmax(0,y)):.5;
|
||||||
BOOL enabled=YES;
|
BOOL enabled=YES;
|
||||||
[api getBoolValue:&enabled fromParameter:PGSmoothingEnabled atTime:time]; if(!enabled) s.amount=s.midtoneDetail=0;
|
[api getBoolValue:&enabled fromParameter:PGSmoothingEnabled atTime:time]; if(!enabled) s.amount=s.midtoneDetail=s.microDetail=0;
|
||||||
enabled=YES; [api getBoolValue:&enabled fromParameter:PGColorEnabled atTime:time];
|
enabled=YES; [api getBoolValue:&enabled fromParameter:PGColorEnabled atTime:time];
|
||||||
if(!enabled) {s.skinBrightness=s.skinHue=s.skinSaturation=s.skinEvenTone=s.teethWhitening=s.eyeBrightening=0;s.skinTonePreset=0;}
|
if(!enabled) {s.skinBrightness=s.skinHue=s.skinSaturation=s.skinEvenTone=s.teethWhitening=s.eyeBrightening=0;s.skinTonePreset=0;}
|
||||||
enabled=YES; [api getBoolValue:&enabled fromParameter:PGDetailEnabled atTime:time]; if(!enabled) s.highPassDetail=s.sharpness=0;
|
enabled=YES; [api getBoolValue:&enabled fromParameter:PGDetailEnabled atTime:time]; if(!enabled) s.highPassDetail=s.sharpness=0;
|
||||||
@@ -263,9 +267,16 @@ static CIImage *PGImageFromTile(FxImageTile *tile) {
|
|||||||
- (BOOL)renderDestinationImage:(FxImageTile *)destination sourceImages:(NSArray<FxImageTile *> *)sources pluginState:(NSData *)state atTime:(CMTime)time error:(NSError **)error {
|
- (BOOL)renderDestinationImage:(FxImageTile *)destination sourceImages:(NSArray<FxImageTile *> *)sources pluginState:(NSData *)state atTime:(CMTime)time error:(NSError **)error {
|
||||||
if(sources.count==0||!sources.firstObject.ioSurface||!destination.ioSurface||![state isKindOfClass:NSData.class]||state.length==0||state.length>320*1024*1024) return PGFail(error,@"宿主传入的画面或状态无效。");
|
if(sources.count==0||!sources.firstObject.ioSurface||!destination.ioSurface||![state isKindOfClass:NSData.class]||state.length==0||state.length>320*1024*1024) return PGFail(error,@"宿主传入的画面或状态无效。");
|
||||||
NSDictionary *plist=[NSPropertyListSerialization propertyListWithData:state options:NSPropertyListImmutable format:nil error:error];
|
NSDictionary *plist=[NSPropertyListSerialization propertyListWithData:state options:NSPropertyListImmutable format:nil error:error];
|
||||||
if(![plist isKindOfClass:NSDictionary.class]||![plist[@"settings"] isKindOfClass:NSData.class]||[plist[@"settings"] length]!=sizeof(PGBeautySettings)) return PGFail(error,@"渲染状态版本无效。");
|
if(![plist isKindOfClass:NSDictionary.class]||![plist[@"settings"] isKindOfClass:NSData.class]) return PGFail(error,@"渲染状态版本无效。");
|
||||||
PGBeautySettings s;[plist[@"settings"] getBytes:&s length:sizeof(s)];
|
NSData *encoded=plist[@"settings"]; uint32_t version=0;
|
||||||
if(s.version!=1) return PGFail(error,@"此版本不支持保存的渲染状态。");
|
if(encoded.length<sizeof(version)) return PGFail(error,@"渲染状态版本无效。");
|
||||||
|
[encoded getBytes:&version length:sizeof(version)];
|
||||||
|
PGBeautySettings s=PGBeautyDefaults();
|
||||||
|
if(version==1 && encoded.length==PGBeautySettingsV1Size) {
|
||||||
|
[encoded getBytes:&s length:PGBeautySettingsV1Size];s.version=PGBeautySettingsVersion;
|
||||||
|
} else if(version==PGBeautySettingsVersion && encoded.length==sizeof(s)) {
|
||||||
|
[encoded getBytes:&s length:sizeof(s)];
|
||||||
|
} else return PGFail(error,@"此版本不支持保存的渲染状态。");
|
||||||
s.trackingEnabled=0;s.singleFace=0;
|
s.trackingEnabled=0;s.singleFace=0;
|
||||||
CIImage *input=PGImageFromTile(sources.firstObject); if(!input) return PGFail(error,@"无法读取 IOSurface。");
|
CIImage *input=PGImageFromTile(sources.firstObject); if(!input) return PGFail(error,@"无法读取 IOSurface。");
|
||||||
@synchronized(self) {
|
@synchronized(self) {
|
||||||
|
|||||||
@@ -17,9 +17,9 @@
|
|||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>APPL</string>
|
<string>APPL</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>1.2.0</string>
|
<string>1.3.0</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>14</string>
|
<string>15</string>
|
||||||
<key>LSMinimumSystemVersion</key>
|
<key>LSMinimumSystemVersion</key>
|
||||||
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
|
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
|
||||||
<key>NSHumanReadableCopyright</key>
|
<key>NSHumanReadableCopyright</key>
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
# GitHub 同步与恢复
|
||||||
|
|
||||||
|
公开源码:https://github.com/FAMEDaYu/FCP-BeatyGril
|
||||||
|
|
||||||
|
正式安装包:https://github.com/FAMEDaYu/FCP-BeatyGril/releases/tag/v1.2.0
|
||||||
|
|
||||||
|
本地 `github` 远端指向上述 GitHub 仓库,`origin` 继续指向大鱼仓库,`main` 仍跟随 `origin/main`。当前项目的完整提交历史、main 和七个版本标签已推送,两端引用与本地一致;未改写历史或覆盖标签。后续更新需要分别推送到 origin 和 github,不会自动定时同步。
|
||||||
|
|
||||||
|
## 已验证的 GitHub 恢复节点
|
||||||
|
|
||||||
|
匿名恢复提交:`356683b41af702d6c0143ad7fa5f540d752d6e14`。在关闭凭据助手和交互登录的独立临时目录中成功克隆 GitHub,197 个受跟踪文件逐字节一致、七个标签引用一致,strict/full fsck 通过。正式 PKG 的源码内副本与 GitHub Release 匿名下载副本 SHA-256 相同,下载副本的 Installer 签名与公证票据通过。后续 main 提交补充文章和验证记录,产品源码与该恢复节点一致。
|
||||||
|
|
||||||
|
安装包:`Dayu-Portrait-1.2.0.pkg`,5,011,005 bytes,SHA-256 `5bd77c28b3a7e54f3d4060be1f16bcb145d9bc4d6b9cfb9eb137c01130440243`。Release 标签 `v1.2.0` 仍指向 `3c7261c3a861b9f4704cfb36055d7b500c239dab`,没有重新编译或改动正式安装包。
|
||||||
|
|
||||||
|
```sh
|
||||||
|
git clone https://github.com/FAMEDaYu/FCP-BeatyGril.git
|
||||||
|
cd FCP-BeatyGril
|
||||||
|
git switch --detach 356683b41af702d6c0143ad7fa5f540d752d6e14
|
||||||
|
```
|
||||||
|
|
||||||
|
本项目没有 LFS 或子模块。编译仍需自行安装 Xcode 和 Apple FxPlug SDK;签名私钥与公证凭据不在 Git 内。GitHub 公共克隆无需登录;大鱼备份仍受站点登录和会员网关限制。两边都只承诺已经提交、推送并核验的节点。
|
||||||
|
|
||||||
|
源代码编写署名:代码由 OpenAI + Codex 全程编写,站长仅参与调试和测试。
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
# 2.4.0 面板参数迁移
|
||||||
|
|
||||||
|
2026-10-22(Asia/Shanghai)。用户通过截图纠正原需求:迁移新版面板参数,并非新增身体瘦身。此前[瘦身目标核对](瘦身迁移目标核对.md)保留为历史调查;本次继续工作使用 `codex/new-beauty-controls`,不再等待其他目标包。
|
||||||
|
|
||||||
|
## 参考与证据
|
||||||
|
|
||||||
|
目标:`/Users/dayu/Downloads/MSE18V2.app`,2.4.0 / 20260724150000;插件通用二进制 SHA-256:`577303e0de72e07feb576c882e82382b3f056b023a7e987d990ffb37f6b16109`;arm64 切片:`afc71d0d21fd3c1ca95ca745ddafcd729e36ad95f17d8638009f2e45d9b7e8df`。分析使用 Ghidra 12.1.4 与只读 Mach-O 元数据/反汇编,原文件未改。
|
||||||
|
|
||||||
|
| 参考面板 | 目标字段/转换 | 本项目入口 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| 磨皮程度 | ID12 → midtoneDetail,直接 0–100% | 保留 PGMidtone 12,调整名称 |
|
||||||
|
| 微细锐化 | ID4 → sharpness,局部细节恢复 | 新 PGMicroDetail 77;原全局锐化 4 留在更多设置 |
|
||||||
|
| 五官保护 | ID30 → featureProtectionRelease = 1 − UI;旧 bool 随 release 接近 1 而关闭 | 新 PGFeatureProtectionAmount 75,直接保护量;旧 bool 30 保留类型与读取 |
|
||||||
|
| 非肤色区保护 | ID17 → highPassDetail = 1 − UI;新主路径用于肤色蒙版扩展 | 新 PGNonSkinProtection 76;原独立全局磨皮 17 留在更多设置 |
|
||||||
|
| 白牙、检测框预览、多人及脸型 | 截图中的这些项已经存在 | 保留;检测框名称调整、多人模式增加提示 |
|
||||||
|
|
||||||
|
两个新保护量默认为 100%,微细锐化默认为 0%。内部使用直接保护量,不直接复用参考包的反向 release 数值。
|
||||||
|
|
||||||
|
`setFloatValue:parameterID:minValue:maxValue:`(`0x1000116d8`)分别反转 17/30 的输入;Evidence:`ev_1fb7d2361dabf814edd6f22d217265b63efa9af24739741772ad5f579e0c67e3`。`valuesForControlPanelValue:` 返回相应原始字段;Evidence:`ev_20da8abf4eb5101b0902627255c643a783b7a15337aa4a757820ab71616f33e7`。显示格式 `strength` 是直接乘 100,只有 `protectionInverse` 格式自身反转;17/30 在绘制与交互同步时提前反转,不能把所有 strength 控制都解释为反向。格式函数汇编 Evidence:`ev_8e2a143f81d173c7643ecee2f1dac4a02145edbf82f113c8a48271428baf747c`。
|
||||||
|
|
||||||
|
目标原生处理入口 `applySkinSmoothingToImage:…`(`0x1000358d8`)把 params+0x50 用于非肤色参与,把 params+0x48 用于五官保护释放。Evidence:`ev_34eb4fe26dbaed9fc37d664d9363db903339c1447b6cd7fdb1980dcd61e99572`。可读 CI 内核字符串建立两个关键关系:肤色蒙版向白色线性扩展;保护蒙版与未保护蒙版按 release 线性混合。细节恢复使用原画与低频图的差,并受局部蒙版和亮度/纹理门控。字符串证据:`ev_d02bfe8046953e9287bd46c5403e6585354a5aa6df782a775d6a6fd0d96f1d62`。这里只记录公式和含义,不提交目标模型、metallib 或资源。
|
||||||
|
|
||||||
|
## 实施与兼容
|
||||||
|
|
||||||
|
本项目继续使用自研 Vision 几何蒙版与 Core Image 管线。新的平滑支持蒙版通过现有未保护/已保护覆盖率恢复五官衰减比例,再对非肤色区域扩展;两个控制独立于曝光/肤色蒙版。默认 100%/100% 直接沿用原覆盖路径。微细锐化在局部支持区域恢复亮度细节与少量色彩细节,可在磨皮量为零时独立使用。
|
||||||
|
|
||||||
|
原目标的语义蒙版、特征形状、纹理处理和复杂组合尚未全链对齐,因此本次是参数含义、连续控制和对应局部处理的迁移,不表示整个新版已逐像素复刻。旧版全局磨皮/全局锐化仍可通过更多设置使用,不与新保护项偷换含义;历史工程保留原强度。
|
||||||
|
|
||||||
|
PGBeautySettings v2 尾部增加三个 float,保留 v1 的 160 字节前缀。解码只接受 v1+160 字节或 v2+新完整长度;旧状态补中性默认值。PGInspectorState 仍使用 schema 1 数值字典,旧 bool 30 迁移到缺失的 75,新数值存在时优先采用新值。面板提交先保存权威对象 210,成功后再镜像旧 bool;每次拖动步进仍使用独立宿主 action。
|
||||||
|
|
||||||
|
模板继续发布原 210 对象与两项署名。新隐藏参数由插件注册和缺省值补齐,原 `.moef` 字节未改,无须覆盖用户模板。固定 Bundle ID、滤镜 UUID、安装路径及宣传资源不变。
|
||||||
|
|
||||||
|
## 验证
|
||||||
|
|
||||||
|
最终通过 1636 项:Beauty 57、Face 116、几何 796、宿主适配 ASan 341、OSC ASan 98、Inspector ASan 228(其中 1 项为可选离屏 PNG 输出)。新增检查包含保护量 0/50/100 的像素响应、非肤色保护只改变局部磨皮支持、微细锐化区域、颜色覆盖隔离、v1 状态迁移与错误长度拒绝、新滑杆归档/插值/输入/重置/旁路/主开关、松手前中间状态发布。离屏 AppKit 面板在多种宽度检查;原生宿主视觉验收需另记实际结果。
|
||||||
|
|
||||||
|
完整 REA 证据仅在忽略的 `build/body-analysis/new-controls-evidence.json`。本文保留目标身份、入口、公式与边界,恢复构建不依赖该本地调查目录。
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
# 瘦身迁移:目标版本核对
|
||||||
|
|
||||||
|
核对日期:2026-10-22(Asia/Shanghai)。开发分支:`codex/body-slimming`;源码基线:`13821366a5baee9beea40972118d7fe634b5c735`。
|
||||||
|
|
||||||
|
**后续已澄清**:用户通过截图确认记错了,实际需求是迁移新增面板参数。后续以[新版磨皮参数迁移](新版磨皮参数迁移.md)为准,下面保留纠正前的有界调查结论,不再表示当前等待其他插件包。
|
||||||
|
|
||||||
|
## 当前结果
|
||||||
|
|
||||||
|
用户指定的新目标为 `/Users/dayu/Downloads/MSE18V2.app`。已读取项目交接并检查目标包,**尚未定位到可迁移的新增“瘦身”板块**。当前只完成目标核对与接入点调查,未修改产品源码、模板或正式安装的 1.2.0 APP,未编译或执行目标程序。
|
||||||
|
|
||||||
|
接续时先请用户确认瘦身控制的具体名称、面板画面或包含该功能的目标包路径。不能把现有“瘦脸”误当作“瘦身”,也不能自行设计身体变形后称为已迁移原版算法。
|
||||||
|
|
||||||
|
## 目标身份
|
||||||
|
|
||||||
|
| 项目 | 新下载目标 | 旧安装参考 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| APP 路径 | `/Users/dayu/Downloads/MSE18V2.app` | `/Applications/MSE18V2.app` |
|
||||||
|
| 外层与插件版本 | `2.4.0` / `20260724150000` | `1.0.20260617-170517` / `20260617170517` |
|
||||||
|
| 插件 Bundle ID | `com.ms.plugins.mse18v2` | `com.ms.plugins.mse18v2` |
|
||||||
|
| 插件通用二进制 SHA-256 | `577303e0de72e07feb576c882e82382b3f056b023a7e987d990ffb37f6b16109` | `6c5d74bc844a5c5f745718b48ea10ec6635d08aa08a5b6dc6e62653980849f8a` |
|
||||||
|
|
||||||
|
新插件 arm64 切片 SHA-256:`afc71d0d21fd3c1ca95ca745ddafcd729e36ad95f17d8638009f2e45d9b7e8df`。插件二进制位于各 APP 的 `Contents/PlugIns/MSE18V2.pluginkit/Contents/MacOS/MSE18V2`。
|
||||||
|
|
||||||
|
## 已观察证据与范围
|
||||||
|
|
||||||
|
- 对新旧包的外层和插件、arm64 与 x86_64 切片,静态检查 `__ustring`、`__cstring`、`__objc_methname`、`__objc_classname`。在这些段内未发现“瘦身、瘦腰、瘦腿、身体”及对应 body/slim/waist/slender/stretch/shoushen 功能命名。可读板块仍为磨皮、美白、瘦脸;新包另有“预览设置、肤色范围、非肤色区保护”等文字。这是有界字符串检查,不能排除动态生成或其他未解释数据。
|
||||||
|
- 新插件 arm64 的 Ghidra 参数注册函数 `MSSuperSkinSmoothingPlugIn::addParametersWithError:`(`0x10001fc10`)注册面板 210、磨皮/肤色/现有脸型参数和三模块开关,所见注册列表没有身体参数。Evidence:`ev_4d3e7e7dc8aa21347adbd0f0847fab13e8b69462bd5e6adf3657cbc0c1998266`。
|
||||||
|
- 面板值类的方法包括 `faceThin`、`faceNarrow`、`faceChin`、`faceJaw`、`faceSmallHead` 等,以及 skin/tone/face 三个模块开关。过程名称搜索没有返回身体变形入口;不能由名称负结果断言所有匿名函数均不存在身体算法。Evidence:`ev_446e4a969ff46db0600fe15988d0f396f394641aaeac760d6372a0c0509bf177`、`ev_157df22ee7802216f3581c9842a9629c69ee66097db068b825695bdfd99385d1`。
|
||||||
|
- 所见几何 GPU 管线仍为 `faceThin*`;新旧 metallib 的 12 个命名函数集合相同。文件变大不能证明新增身体算法,也不能由相同函数名断言函数内容相同。
|
||||||
|
- REA 对提供者已定义字符串对象的搜索只返回无关的 `URLRequest.httpBody`。Ghidra 未在该搜索中解码中文 UTF-16 常量,因此中文检查另使用实际 Mach-O 字节,未把 REA 空结果当作整个包不含中文的证据。Evidence:`ev_015a585ce5565d874f5b55af0c363bed634f09a29088ef660d3ca1c547626c26`。
|
||||||
|
|
||||||
|
REA 使用 Ghidra 12.1.4。通用 Mach-O 不支持该提供者直接选架构,故只读提取 arm64 切片分析。会话已关闭;切片、完整证据和快照仅保存在忽略的 `build/body-analysis/`,不作为构建或远端恢复依赖,不上传原版 APP、模型或 metallib。上述地址与 Evidence ID 仅对应本次 SHA,目标改变后需重新核对。
|
||||||
|
|
||||||
|
## 已完成与待定事项
|
||||||
|
|
||||||
|
| 问题 | 状态 |
|
||||||
|
| --- | --- |
|
||||||
|
| 项目交接、Git 基线、现有正式安装身份 | 已核对,产品源码未改 |
|
||||||
|
| 新下载包身份及与旧包差异 | 已核对;确为不同内容 |
|
||||||
|
| 新增瘦身控制、默认值、范围、画布操作 | 未定位,需确认正确目标 |
|
||||||
|
| 身体算法、处理顺序、跟随与蒙版行为 | 未建立证据,未实施 |
|
||||||
|
| 产品编译、回归、签名、公证、安装 | 本次未执行 |
|
||||||
|
|
||||||
|
## 确认目标后的接入提示
|
||||||
|
|
||||||
|
现有工程只有人脸几何,新增身体效果应有独立状态与总开关,不能依赖人脸检测成功。具体控制数量、范围、算法和顺序以正确目标的证据为准。
|
||||||
|
|
||||||
|
- `PGInspectorState.m` 的描述表同时驱动面板、归档和插值。保留参数 210 及既有 ID;数值字典缺失项会补默认值。新增卡片时,`PGInspectorView.m` 需按 section 识别“更多设置”,不能继续依赖 `index==3`。
|
||||||
|
- `PGBeautySettings` 当前是 160 字节 v1 裸结构体。若追加字段,应保留旧前缀并明确接受旧版本/长度组合,补齐新默认值;同步修改宿主解码,拒绝截断、超长或未知版本。不要只更改 `sizeof` 检查。
|
||||||
|
- OSC 必须同步完整面板对象 210,单写隐藏参数会被已编辑的面板状态覆盖。OSC 回调使用宿主已建立的参数上下文,不能套用面板的 `startAction/endAction` 路径;保留原选脸 ID 和 UUID。
|
||||||
|
- 身体变形需核对与外观处理、四类蒙版及整体混合的先后关系;验证无人脸、非零 extent、原点标签、极端参数、旧归档、撤销、连续拖动与保存重开。
|
||||||
|
|
||||||
|
本次分支基线已在 `origin` 和 `github` 读回核验。两端的最新研究记录以当前分支引用为准;尚无瘦身功能交付标签。
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"article_id": 15593,
|
||||||
|
"url": "https://dayu.media/15593/",
|
||||||
|
"status": "publish",
|
||||||
|
"github_repository": "https://github.com/FAMEDaYu/FCP-BeatyGril",
|
||||||
|
"github_release": "https://github.com/FAMEDaYu/FCP-BeatyGril/releases/tag/v1.2.0",
|
||||||
|
"anonymous_frontend_http": 200,
|
||||||
|
"both_github_links_present_in_frontend": true,
|
||||||
|
"code_writing_credit_retained": "代码由 OpenAI + Codex 全程编写,站长仅参与调试和测试。",
|
||||||
|
"resource_price_download_cover_seo_and_author_preserved": true,
|
||||||
|
"only_content_requested": true,
|
||||||
|
"frontend_seo_unique_and_exact": true,
|
||||||
|
"idempotency_key": "55e60cc5-509a-4ae9-ba96-c0d25b2c575a",
|
||||||
|
"before_content_sha256": "2701991a543b80114c062cc7ee68b83afff505dd9456cfe82f0b8819ea81b119",
|
||||||
|
"after_content_sha256": "c66e1ddb374714a938442cd24cf5034482ea4e67d6f8dd79c07d43ba74ad1b1d",
|
||||||
|
"verified_at_utc": "2026-10-21T19:32:48.060004+00:00"
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"target": "https://github.com/FAMEDaYu/FCP-BeatyGril.git",
|
||||||
|
"source_commit": "dffa547d92478dd471fb4926579df91084581756",
|
||||||
|
"all_reachable_blobs_scanned": 406,
|
||||||
|
"secret_pattern_matches": [],
|
||||||
|
"package_sha256": "5bd77c28b3a7e54f3d4060be1f16bcb145d9bc4d6b9cfb9eb137c01130440243",
|
||||||
|
"connector_target_push_permission": false,
|
||||||
|
"local_https_credential_present": false,
|
||||||
|
"status": "prepared_waiting_for_GitHub_write_authentication",
|
||||||
|
"github_visibility": "public",
|
||||||
|
"github_repository_id": "1428817365",
|
||||||
|
"github_repository_initial_state": "empty",
|
||||||
|
"origin_retained": "https://dayu.media/git/dayu/paintfcp-fxplug.git",
|
||||||
|
"github_remote_added": true
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"repository": "https://github.com/FAMEDaYu/FCP-BeatyGril",
|
||||||
|
"repository_id": 1428817365,
|
||||||
|
"visibility": "public",
|
||||||
|
"source_commit": "356683b41af702d6c0143ad7fa5f540d752d6e14",
|
||||||
|
"local_refs": {
|
||||||
|
"refs/heads/main": "356683b41af702d6c0143ad7fa5f540d752d6e14",
|
||||||
|
"refs/tags/baseline-20261021": "4124c350ce288b5fa3cf843ec0e9751e9db19b4c",
|
||||||
|
"refs/tags/v1.0.4": "302dc20c0a90c0b8934c4341d65c2467d4ed3386",
|
||||||
|
"refs/tags/v1.0.5": "1008651f6e2111a26d1420021c49f40d6f728b3a",
|
||||||
|
"refs/tags/v1.0.6": "5793e3de499bfe320b5e9a9db16ef7bc79a5243d",
|
||||||
|
"refs/tags/v1.1.0": "3bb33818eaa8033b8b1d140eb64e31f38ec3fa75",
|
||||||
|
"refs/tags/v1.1.2": "e9fddda5e13042f761755762fa39dfe090853c6a",
|
||||||
|
"refs/tags/v1.2.0": "3c7261c3a861b9f4704cfb36055d7b500c239dab"
|
||||||
|
},
|
||||||
|
"github_refs_verified": true,
|
||||||
|
"main_upstream_retained": "origin",
|
||||||
|
"auth_storage": "macOS keyring through GitHub CLI; no token included"
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"remote": "https://github.com/FAMEDaYu/FCP-BeatyGril.git",
|
||||||
|
"anonymous_clone": true,
|
||||||
|
"credentials_disabled_for_clone": true,
|
||||||
|
"commit": "356683b41af702d6c0143ad7fa5f540d752d6e14",
|
||||||
|
"tracked_files_compared": 197,
|
||||||
|
"all_tag_refs_equal": true,
|
||||||
|
"tag_count": 7,
|
||||||
|
"git_fsck_full_strict": true,
|
||||||
|
"package_sha256": "5bd77c28b3a7e54f3d4060be1f16bcb145d9bc4d6b9cfb9eb137c01130440243",
|
||||||
|
"lfs": false,
|
||||||
|
"submodules": false,
|
||||||
|
"temporary_clone_removed": true
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"repository": "FAMEDaYu/FCP-BeatyGril",
|
||||||
|
"url": "https://github.com/FAMEDaYu/FCP-BeatyGril/releases/tag/v1.2.0",
|
||||||
|
"release_id": 419972270,
|
||||||
|
"tag": "v1.2.0",
|
||||||
|
"draft": false,
|
||||||
|
"prerelease": false,
|
||||||
|
"asset": {
|
||||||
|
"id": 663571667,
|
||||||
|
"name": "Dayu-Portrait-1.2.0.pkg",
|
||||||
|
"size": 5011005,
|
||||||
|
"digest": "sha256:5bd77c28b3a7e54f3d4060be1f16bcb145d9bc4d6b9cfb9eb137c01130440243",
|
||||||
|
"browser_download_url": "https://github.com/FAMEDaYu/FCP-BeatyGril/releases/download/v1.2.0/Dayu-Portrait-1.2.0.pkg"
|
||||||
|
},
|
||||||
|
"sha256": "5bd77c28b3a7e54f3d4060be1f16bcb145d9bc4d6b9cfb9eb137c01130440243",
|
||||||
|
"anonymous_asset_download_hash_matches": true,
|
||||||
|
"downloaded_pkg_signature_and_ticket_valid": true
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
{
|
||||||
|
"article_id": 15593,
|
||||||
|
"url": "https://dayu.media/15593/",
|
||||||
|
"status": "publish",
|
||||||
|
"updated_fields": [
|
||||||
|
"content"
|
||||||
|
],
|
||||||
|
"attribution": "代码由 OpenAI + Codex 全程编写,站长仅参与调试和测试。",
|
||||||
|
"removed_original_authorship_wording": true,
|
||||||
|
"anonymous_frontend_http": 200,
|
||||||
|
"frontend_attribution_verified": true,
|
||||||
|
"unrelated_settings_unchanged": true,
|
||||||
|
"preserved": [
|
||||||
|
"title",
|
||||||
|
"author account (article publisher)",
|
||||||
|
"excerpt",
|
||||||
|
"categories",
|
||||||
|
"tags",
|
||||||
|
"featured_media",
|
||||||
|
"resource including all free-download settings and URLs",
|
||||||
|
"seo",
|
||||||
|
"fcpx configuration",
|
||||||
|
"installation instructions and native code block"
|
||||||
|
],
|
||||||
|
"automatic_derived_change": {
|
||||||
|
"field": "fcpx.article_state.checked_at",
|
||||||
|
"before": "2026-10-22 01:52:41",
|
||||||
|
"after": "2026-10-22 02:01:36",
|
||||||
|
"meaning": "Content save hook refreshed check timestamp; sync remains disabled."
|
||||||
|
},
|
||||||
|
"frontend_seo_exact_and_unique": true,
|
||||||
|
"idempotency_key": "66b51f0d-24ed-4ed9-82fd-e61e626cec4b",
|
||||||
|
"before_content_sha256": "5968a343e8a9116a5adb7885eafd487acb23608be32d422144196454b1434290",
|
||||||
|
"after_content_sha256": "2701991a543b80114c062cc7ee68b83afff505dd9456cfe82f0b8819ea81b119",
|
||||||
|
"verification_time_utc": "2026-10-21T18:02:35.974219+00:00"
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"roots": [
|
||||||
|
"/Applications",
|
||||||
|
"/Library/Plug-Ins/FxPlug",
|
||||||
|
"/Users/dayu/Library/Plug-Ins/FxPlug",
|
||||||
|
"/Users/dayu/Documents/PaintFCP",
|
||||||
|
"/tmp/paintfcp-build-20261021"
|
||||||
|
],
|
||||||
|
"product_paths": [
|
||||||
|
"/Library/Plug-Ins/FxPlug/beatygril.app"
|
||||||
|
],
|
||||||
|
"errors": [],
|
||||||
|
"removed": [
|
||||||
|
"build/pkg-expanded"
|
||||||
|
],
|
||||||
|
"scope": "bounded scan; not all mounted volumes"
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"tag": "v1.2.0",
|
||||||
|
"commit": "3c7261c3a861b9f4704cfb36055d7b500c239dab",
|
||||||
|
"files_verified": 183,
|
||||||
|
"fsck_full_strict": true,
|
||||||
|
"package_sha256": "5bd77c28b3a7e54f3d4060be1f16bcb145d9bc4d6b9cfb9eb137c01130440243",
|
||||||
|
"recovered_package_signature_and_ticket": true,
|
||||||
|
"xcode_project_readable": true,
|
||||||
|
"lfs": false,
|
||||||
|
"submodules": false,
|
||||||
|
"authentication": "saved member Git credential; anonymous gateway access remains restricted",
|
||||||
|
"temporary_clone_removed": true
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"path": "/Users/dayu/Documents/PaintFCP/Templates/大鱼人像美化/large.png",
|
||||||
|
"uchg": true,
|
||||||
|
"sha256": "a02ddb255b1ae255c749bb837a759e2dee1c3b8ffb9bdc1167957761bd5a1b97"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "/Users/dayu/Documents/PaintFCP/Templates/大鱼人像美化/small.png",
|
||||||
|
"uchg": true,
|
||||||
|
"sha256": "1552b838dd88f60dee0795e20aaca652f9c558ecefc93500e895fbff77c2a4d5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "/Users/dayu/Movies/Motion Templates.localized/Effects.localized/大鱼磨皮瘦脸/大鱼人像美化/large.png",
|
||||||
|
"uchg": true,
|
||||||
|
"sha256": "a02ddb255b1ae255c749bb837a759e2dee1c3b8ffb9bdc1167957761bd5a1b97"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "/Users/dayu/Movies/Motion Templates.localized/Effects.localized/大鱼磨皮瘦脸/大鱼人像美化/small.png",
|
||||||
|
"uchg": true,
|
||||||
|
"sha256": "1552b838dd88f60dee0795e20aaca652f9c558ecefc93500e895fbff77c2a4d5"
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
{
|
||||||
|
"article_id": 15593,
|
||||||
|
"url": "https://dayu.media/15593/",
|
||||||
|
"status": "publish",
|
||||||
|
"anonymous_http": 200,
|
||||||
|
"category": 3,
|
||||||
|
"free_for_all": true,
|
||||||
|
"featured_media": 15595,
|
||||||
|
"native_code_block_present": true,
|
||||||
|
"seo_tags": {
|
||||||
|
"title": [
|
||||||
|
"大鱼人像美化 1.2.0|FCP / Motion 免费开源自动美颜插件"
|
||||||
|
],
|
||||||
|
"description": [
|
||||||
|
"大鱼人像美化是适用于 Final Cut Pro 和 Motion 的免费开源 FxPlug4 插件,支持磨皮、美白、自动人脸跟随及瘦脸调整。提供 Apple 签名公证 PKG 安装包和源码,安装自动注册,无需激活码,并附具体安装、使用及红色警告排查说明。"
|
||||||
|
],
|
||||||
|
"keywords": [
|
||||||
|
"大鱼人像美化,FCP美颜插件,Motion磨皮,自动瘦脸,自动跟随,FxPlug4,免费开源"
|
||||||
|
],
|
||||||
|
"unique_and_exact": true
|
||||||
|
},
|
||||||
|
"assets": [
|
||||||
|
{
|
||||||
|
"role": "package",
|
||||||
|
"url": "https://dayu.media/wp-content/uploads/2026/10/1792604749-1ea4dca3cc5a5c3.pkg",
|
||||||
|
"anonymous_http": 200,
|
||||||
|
"bytes": 5011005,
|
||||||
|
"sha256": "5bd77c28b3a7e54f3d4060be1f16bcb145d9bc4d6b9cfb9eb137c01130440243",
|
||||||
|
"matches_local": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"role": "source",
|
||||||
|
"url": "https://dayu.media/wp-content/uploads/2026/10/1792604741-f6e09da5b67c006.zip",
|
||||||
|
"anonymous_http": 200,
|
||||||
|
"bytes": 9607987,
|
||||||
|
"sha256": "a235fb3168c09a1e3a4fc5f347b3957cbd9ee2a1c3848e2af1dda2e64afbb870",
|
||||||
|
"matches_local": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"role": "cover",
|
||||||
|
"url": "https://dayu.media/wp-content/uploads/2026/10/1792604750-357ca780597c39f.png",
|
||||||
|
"anonymous_http": 200,
|
||||||
|
"bytes": 2776027,
|
||||||
|
"sha256": "a346f06e7793b4e5483ccd4b3b86f34c2036bbe6bbddb7ad9cc70c47e7d014ab",
|
||||||
|
"matches_local": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"role": "card",
|
||||||
|
"url": "https://dayu.media/wp-content/uploads/2026/10/1792604743-1c99c2ba53c7614.png",
|
||||||
|
"anonymous_http": 200,
|
||||||
|
"bytes": 3065968,
|
||||||
|
"sha256": "239552eeea738a32fafa70922d3cd1d4c115ff75d672574fa4c5acd941381fba",
|
||||||
|
"matches_local": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source_article_click_anonymous_verified": true,
|
||||||
|
"fcpx_app_sync_requested": false,
|
||||||
|
"git_visibility": "Gitea public attribute verified; anonymous gateway access still requires membership as disclosed in article"
|
||||||
|
}
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
{
|
||||||
|
"article": {
|
||||||
|
"id": 15593,
|
||||||
|
"url": "https://dayu.media/15593/",
|
||||||
|
"edit_url": "https://dayu.media/wp-admin/post.php?post=15593&action=edit",
|
||||||
|
"status": "publish",
|
||||||
|
"title": "大鱼人像美化 1.2.0:FCP / Motion 免费开源自动美颜插件",
|
||||||
|
"content": "<!-- wp:paragraph -->\n<p>大鱼人像美化是一款适用于 Final Cut Pro 与 Motion 的免费开源人像插件,把磨皮、美白和脸型调整放进同一块原生卡片面板。插件会识别当前画面中的人脸并自动跟随,无需先点击分析;你可以按素材需要调整强度,拖动滑杆时持续查看效果。</p>\n<!-- /wp:paragraph -->\n<!-- wp:paragraph -->\n<p><strong>代码由 OpenAI + Codex 全程编写,站长仅参与调试和测试。</strong></p>\n<!-- /wp:paragraph -->\n\n<!-- wp:image {\"sizeSlug\":\"full\"} -->\n<figure class=\"wp-block-image size-full\"><img src=\"https://dayu.media/wp-content/uploads/2026/10/1792604750-357ca780597c39f.png\" alt=\"大鱼人像美化古风宣传封面,展示自动瘦脸、自动跟随与自动美颜\" /><figcaption class=\"wp-element-caption\">古风宣传插画;面部线条用于示意瘦脸与跟随功能。</figcaption></figure>\n<!-- /wp:image -->\n\n<!-- wp:heading -->\n<h2 class=\"wp-block-heading\">可以做什么</h2>\n<!-- /wp:heading -->\n\n<!-- wp:list {\"className\":\"wp-block-list\"} -->\n<ul class=\"wp-block-list\"><!-- wp:list-item -->\n<li>磨皮:局部磨皮、全局磨皮与五官保护,强度分别控制。</li>\n<!-- /wp:list-item -->\n\n<!-- wp:list-item -->\n<li>美白:肤色预设、肤色亮度、皮肤色相和牙齿美白。</li>\n<!-- /wp:list-item -->\n\n<!-- wp:list-item -->\n<li>脸型:瘦脸、大眼、短脸、窄脸、下巴、V 脸、下颌骨、颧骨与小头调整。</li>\n<!-- /wp:list-item -->\n\n<!-- wp:list-item -->\n<li>自动跟随:默认跟随主要人脸,也提供多人模式、防跳脸范围与检测框预览。</li>\n<!-- /wp:list-item -->\n\n<!-- wp:list-item -->\n<li>实时预览:滑杆、数值、单项开关和重置集中在同一面板,便于反复对比。</li>\n<!-- /wp:list-item --></ul>\n<!-- /wp:list -->\n\n<!-- wp:paragraph -->\n<p>自动识别负责定位处理区域,效果强度由你控制。建议先从较小的数值开始;多人交叉、严重遮挡和极端侧脸的结果需要结合具体素材检查。</p>\n<!-- /wp:paragraph -->\n\n<!-- wp:heading -->\n<h2 class=\"wp-block-heading\">版本与下载</h2>\n<!-- /wp:heading -->\n\n<!-- wp:paragraph -->\n<p>当前版本 <strong>1.2.0(14)</strong>,支持 macOS 13 或更高版本,包含 Apple Silicon 与 Intel 两种架构。下载包包含 FxPlug4 程序和一个配套 FCP 效果模板。APP 与 PKG 均已完成 Developer ID 正式签名、Apple 公证和票据粘附;安装包为所有用户免费资源。</p>\n<!-- /wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>本次更新加入古风封面与锁定缩略图、免费开源说明页和主 APP 三秒自动退出。人脸处理与面板交互沿用 1.1.2 已验收实现,不需要重新运行手动分析。</p>\n<!-- /wp:paragraph -->\n\n<!-- wp:heading -->\n<h2 class=\"wp-block-heading\">安装</h2>\n<!-- /wp:heading -->\n\n<!-- wp:paragraph -->\n<p>安装前先保存工作并退出 Final Cut Pro 和 Motion,安装期间保持关闭。运行本页提供的 PKG,按安装器提示完成安装;它会把程序安装到 <code>/Library/Plug-Ins/FxPlug/beatygril.app</code>,把配套效果安装到当前登录用户的 Motion Templates 目录,并自动验证和修复系统插件注册。</p>\n<!-- /wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>本插件免费开源,<strong>无需激活码、付费注册或账号授权</strong>。主 APP 打开时显示说明图,窗口显示三秒后自动退出;宿主中的滤镜由独立服务运行。安装器还会重新锁定两张缩略图,避免被宿主自动替换。</p>\n<!-- /wp:paragraph -->\n\n<!-- wp:image {\"sizeSlug\":\"full\"} -->\n<figure class=\"wp-block-image size-full\"><img src=\"https://dayu.media/wp-content/uploads/2026/10/1792604743-1c99c2ba53c7614.png\" alt=\"主程序说明页:免费开源,自动注册,无需激活码,启动三秒后自动退出\" /></figure>\n<!-- /wp:image -->\n\n<!-- wp:heading -->\n<h2 class=\"wp-block-heading\">使用</h2>\n<!-- /wp:heading -->\n\n<!-- wp:paragraph -->\n<p>Final Cut Pro:打开<strong>效果</strong>浏览器,找到<strong>大鱼磨皮瘦脸</strong>分类,将<strong>大鱼人像美化</strong>添加到人像片段。Motion:在原生滤镜资源库的<strong>大鱼</strong>分类中选择<strong>大鱼人像美化</strong>。</p>\n<!-- /wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>在检查器中按需调节磨皮、美白和脸型。每项左侧的眼睛可暂时停用该项,数值会保留;右侧回转箭头用于重置。人脸会随画面自动更新,开启检测框预览可以检查跟随位置。多人模式建议先用短片段观察稳定性。</p>\n<!-- /wp:paragraph -->\n\n<!-- wp:heading -->\n<h2 class=\"wp-block-heading\">常见问题</h2>\n<!-- /wp:heading -->\n\n<!-- wp:paragraph -->\n<p>如果效果没有出现或显示红色警告,先退出 Final Cut Pro 和 Motion,Line truncated
|
||||||
|
"excerpt": "磨皮、美白、瘦脸与自动人脸跟随,原生卡片面板实时预览。免费开源,自动注册,无需激活码;提供签名公证安装包和源码。",
|
||||||
|
"author": 1,
|
||||||
|
"categories": [
|
||||||
|
3
|
||||||
|
],
|
||||||
|
"tags": [],
|
||||||
|
"featured_media": 15595,
|
||||||
|
"resource": {
|
||||||
|
"cao_status": "1",
|
||||||
|
"cao_close_novip_pay": "0",
|
||||||
|
"cao_is_boosvip": "",
|
||||||
|
"cao_video": "",
|
||||||
|
"cao_is_video_free": "",
|
||||||
|
"cao_price": "0",
|
||||||
|
"cao_vip_rate": "1",
|
||||||
|
"cao_ref_bonus_rate": "0.25",
|
||||||
|
"cao_expire_day": "0",
|
||||||
|
"cao_downurl": "https://dayu.media/wp-content/uploads/2026/10/1792604749-1ea4dca3cc5a5c3.pkg",
|
||||||
|
"cao_downurl_bak": "",
|
||||||
|
"cao_demourl": "",
|
||||||
|
"video_url": "",
|
||||||
|
"video_poster_url": "",
|
||||||
|
"cao_pwd": "",
|
||||||
|
"cao_original_filename": "1792604749-1ea4dca3cc5a5c3.pkg",
|
||||||
|
"post_style": "sidebar",
|
||||||
|
"cao_info": [
|
||||||
|
{
|
||||||
|
"title": "版本",
|
||||||
|
"desc": "1.2.0(14)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "宿主",
|
||||||
|
"desc": "Final Cut Pro / Motion"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "系统",
|
||||||
|
"desc": "macOS 13 或更高版本"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "架构",
|
||||||
|
"desc": "Apple Silicon / Intel"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "授权",
|
||||||
|
"desc": "免费开源,无需激活码"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"seo": {
|
||||||
|
"title": {
|
||||||
|
"enabled": true,
|
||||||
|
"value": "大鱼人像美化 1.2.0|FCP / Motion 免费开源自动美颜插件"
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"enabled": true,
|
||||||
|
"value": "大鱼人像美化是适用于 Final Cut Pro 和 Motion 的免费开源 FxPlug4 插件,支持磨皮、美白、自动人脸跟随及瘦脸调整。提供 Apple 签名公证 PKG 安装包和源码,安装自动注册,无需激活码,并附具体安装、使用及红色警告排查说明。"
|
||||||
|
},
|
||||||
|
"keywords": {
|
||||||
|
"enabled": true,
|
||||||
|
"value": "大鱼人像美化,FCP美颜插件,Motion磨皮,自动瘦脸,自动跟随,FxPlug4,免费开源"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"fcpx": {
|
||||||
|
"enabled": "0",
|
||||||
|
"kinds": [],
|
||||||
|
"version": "",
|
||||||
|
"entry": "",
|
||||||
|
"article_state": {
|
||||||
|
"code": "hidden",
|
||||||
|
"message": "未同步到 APP",
|
||||||
|
"checked_at": "2026-10-22 03:32:39"
|
||||||
|
},
|
||||||
|
"file_check": "",
|
||||||
|
"product_id": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"name": "Dayu-Portrait-1.2.0.pkg",
|
||||||
|
"size": 5011005,
|
||||||
|
"browser_download_url": "https://dayu.media/git/attachments/2d27be42-6380-421c-8475-7524574ec889",
|
||||||
|
"uuid": "2d27be42-6380-421c-8475-7524574ec889",
|
||||||
|
"created_at": "2026-10-22T01:48:05+08:00"
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"id": 128,
|
||||||
|
"tag_name": "v1.2.0",
|
||||||
|
"name": "大鱼人像美化 1.2.0",
|
||||||
|
"html_url": "https://dayu.media/git/dayu/paintfcp-fxplug/releases/tag/v1.2.0",
|
||||||
|
"url": "https://dayu.media/git/api/v1/repos/dayu/paintfcp-fxplug/releases/128",
|
||||||
|
"draft": false,
|
||||||
|
"prerelease": false,
|
||||||
|
"body": "大鱼人像美化 1.2.0(14)\n\n免费开源。自动跟随、人像磨皮、美白和脸型调整。新增古风宣传封面与缩略图,主 APP 显示免费开源说明并在窗口显示三秒后退出。\n\nAPP 和 PKG 均已正式签名、Apple 公证并粘附票据。\n\n免费安装包与免 Git 登录源码快照:https://dayu.media/15593/\n\n安装包仓库位置:releases/1.2.0/大鱼人像美化-1.2.0.pkg\nSHA-256:5bd77c28b3a7e54f3d4060be1f16bcb145d9bc4d6b9cfb9eb137c01130440243\n\n当前站点 Git 网关仍需登录;仓库公开属性已设为 public,站外读者可通过文章免费下载源码快照。"
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"source_click_url": "https://dayu.media?cao_direct_download=1&file_url=https%3A%2F%2Fdayu.media%2Fwp-content%2Fuploads%2F2026%2F10%2F1792604741-f6e09da5b67c006.zip",
|
||||||
|
"http": 200,
|
||||||
|
"content_type": "application/octet-stream",
|
||||||
|
"final_url": "https://dayu.media?cao_direct_download=1&file_url=https%3A%2F%2Fdayu.media%2Fwp-content%2Fuploads%2F2026%2F10%2F1792604741-f6e09da5b67c006.zip",
|
||||||
|
"bytes": 9607987,
|
||||||
|
"sha256": "a235fb3168c09a1e3a4fc5f347b3957cbd9ee2a1c3848e2af1dda2e64afbb870",
|
||||||
|
"click_download_matches_source": true
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"commit": "788ba0d5514595cf349af33074055d70b0754185",
|
||||||
|
"files": 182,
|
||||||
|
"package_excluded": true,
|
||||||
|
"filename": "Dayu-Portrait-1.2.0-source.zip",
|
||||||
|
"bytes": 9607987,
|
||||||
|
"sha256": "a235fb3168c09a1e3a4fc5f347b3957cbd9ee2a1c3848e2af1dda2e64afbb870"
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"date": "2026-10-22",
|
||||||
|
"timezone": "Asia/Shanghai",
|
||||||
|
"version": "1.3.0",
|
||||||
|
"build": "15",
|
||||||
|
"status": "passed",
|
||||||
|
"checks": {
|
||||||
|
"beauty": 57,
|
||||||
|
"face": 116,
|
||||||
|
"geometry": 796,
|
||||||
|
"host_adapter_asan": 341,
|
||||||
|
"osc_asan": 98,
|
||||||
|
"inspector_asan_including_optional_preview": 228
|
||||||
|
},
|
||||||
|
"total": 1636,
|
||||||
|
"commands": [
|
||||||
|
"./scripts/test.sh",
|
||||||
|
"PAINTFCP_HOST_ASAN=1 ./scripts/test-host.sh",
|
||||||
|
"PAINTFCP_OSC_ASAN=1 ./scripts/test-osc.sh",
|
||||||
|
"PAINTFCP_INSPECTOR_ASAN=1 PAINTFCP_INSPECTOR_PNG=<local-path> ./scripts/test-inspector.sh"
|
||||||
|
],
|
||||||
|
"notes": [
|
||||||
|
"OSC passed before the later standalone beauty fallback and legacy inspector numeric-read fixes; those fixes do not change OSC code.",
|
||||||
|
"Core, host adapter and inspector rerun after all source fixes.",
|
||||||
|
"Inspector includes one optional offscreen PNG write assertion.",
|
||||||
|
"No native Motion/FCP interaction test performed for this version; active user hosts are preserved.",
|
||||||
|
"Reference interpretation and implementation limitations are in docs/analysis/新版磨皮参数迁移.md."
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
{
|
||||||
|
"status": "installed_user_qa_pending",
|
||||||
|
"bundleIdentifier": "com.dayu.beatygril",
|
||||||
|
"installationPath": "/Library/Plug-Ins/FxPlug/beatygril.app",
|
||||||
|
"options": {
|
||||||
|
"waitForHosts": true,
|
||||||
|
"userTests": true
|
||||||
|
},
|
||||||
|
"nativeEffectQA": {
|
||||||
|
"status": "pending_user",
|
||||||
|
"performedByScript": false
|
||||||
|
},
|
||||||
|
"source": {
|
||||||
|
"commit": "2f2c081980dc610ac810f8daf92da8966f310802",
|
||||||
|
"trackedWorktreeChanges": []
|
||||||
|
},
|
||||||
|
"updatedAt": "2026-10-22T07:15:35.383726+00:00",
|
||||||
|
"version": "1.3.0",
|
||||||
|
"build": "15",
|
||||||
|
"stagingSignature": "Executable=/private/tmp/paintfcp-build-20261021/Build/Products/Release/beatygril.app/Contents/MacOS/beatygril\nIdentifier=com.dayu.beatygril\nFormat=app bundle with Mach-O universal (x86_64 arm64)\nCodeDirectory v=20500 size=478 flags=0x10000(runtime) hashes=4+7 location=embedded\nHash type=sha256 size=32\nCandidateCDHash sha256=93e3245cafc8ced5f2c01b8a72c8e0c3933eee83\nCandidateCDHashFull sha256=93e3245cafc8ced5f2c01b8a72c8e0c3933eee837831a2d779233e4364dded7c\nHash choices=sha256\nCMSDigest=93e3245cafc8ced5f2c01b8a72c8e0c3933eee837831a2d779233e4364dded7c\nCMSDigestType=2\nCDHash=93e3245cafc8ced5f2c01b8a72c8e0c3933eee83\nSignature size=8968\nAuthority=Developer ID Application: zhong luo (B3W25FX5H5)\nAuthority=Developer ID Certification Authority\nAuthority=Apple Root CA\nTimestamp=Oct 22, 2026 at 15:07:35\nInfo.plist entries=22\nTeamIdentifier=B3W25FX5H5\nRuntime Version=27.0.0\nSealed Resources version=2 rules=13 files=5\nInternal requirements count=1 size=180\nTotal signatures=1\nChosen signature=1\n",
|
||||||
|
"notarization": {
|
||||||
|
"id": "ca96837f-bf33-44fa-8d58-799026ce5c7d",
|
||||||
|
"message": "Processing complete",
|
||||||
|
"status": "Accepted"
|
||||||
|
},
|
||||||
|
"signingVerified": true,
|
||||||
|
"stapleValidated": true,
|
||||||
|
"stagingPluginUnregistered": true,
|
||||||
|
"runningHosts": [],
|
||||||
|
"installedSignature": "Executable=/Library/Plug-Ins/FxPlug/beatygril.app/Contents/MacOS/beatygril\nIdentifier=com.dayu.beatygril\nFormat=app bundle with Mach-O universal (x86_64 arm64)\nCodeDirectory v=20500 size=478 flags=0x10000(runtime) hashes=4+7 location=embedded\nHash type=sha256 size=32\nCandidateCDHash sha256=93e3245cafc8ced5f2c01b8a72c8e0c3933eee83\nCandidateCDHashFull sha256=93e3245cafc8ced5f2c01b8a72c8e0c3933eee837831a2d779233e4364dded7c\nHash choices=sha256\nCMSDigest=93e3245cafc8ced5f2c01b8a72c8e0c3933eee837831a2d779233e4364dded7c\nCMSDigestType=2\nCDHash=93e3245cafc8ced5f2c01b8a72c8e0c3933eee83\nSignature size=8968\nAuthority=Developer ID Application: zhong luo (B3W25FX5H5)\nAuthority=Developer ID Certification Authority\nAuthority=Apple Root CA\nTimestamp=Oct 22, 2026 at 15:07:35\nNotarization Ticket=stapled\nInfo.plist entries=22\nTeamIdentifier=B3W25FX5H5\nRuntime Version=27.0.0\nSealed Resources version=2 rules=13 files=5\nInternal requirements count=1 size=180\nTotal signatures=1\nChosen signature=1\n",
|
||||||
|
"installedSignatureVerified": true,
|
||||||
|
"installedStapleValidated": true,
|
||||||
|
"gatekeeperAccepted": true,
|
||||||
|
"pluginDiscovery": {
|
||||||
|
"bundleIdentifier": "com.dayu.beatygril.fxplug",
|
||||||
|
"formalPath": "/Library/Plug-Ins/FxPlug/beatygril.app/Contents/PlugIns/beatygril XPC Service.pluginkit",
|
||||||
|
"output": " com.dayu.beatygril.fxplug(1.1)\t97262612-DD61-47F2-B25E-D82B95C893EE\t2026-10-22 07:15:35 +0000\t/Library/Plug-Ins/FxPlug/beatygril.app/Contents/PlugIns/beatygril XPC Service.pluginkit\n (1 plug-in)\n"
|
||||||
|
},
|
||||||
|
"removedGeneratedPaths": [
|
||||||
|
"/Users/dayu/Documents/PaintFCP/build/delivery/previous-beatygril.app",
|
||||||
|
"/tmp/paintfcp-build-20261021",
|
||||||
|
"/Users/dayu/Documents/PaintFCP/build/delivery/notarization.zip"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
{
|
||||||
|
"status": "verified",
|
||||||
|
"date": "2026-10-22",
|
||||||
|
"timezone": "Asia/Shanghai",
|
||||||
|
"installedProduct": {
|
||||||
|
"path": "/Library/Plug-Ins/FxPlug/beatygril.app",
|
||||||
|
"version": "1.3.0",
|
||||||
|
"build": "15"
|
||||||
|
},
|
||||||
|
"signatureAllArchitecturesVerified": true,
|
||||||
|
"duplicateInventory": {
|
||||||
|
"scope": "Listed local paths and existing explicit related PaintFCP paths on mounted volumes; Spotlight additionally queried. Not a full scan of every volume.",
|
||||||
|
"roots": [
|
||||||
|
"/Applications",
|
||||||
|
"/Library/Plug-Ins/FxPlug",
|
||||||
|
"/Users/dayu/Library/Plug-Ins/FxPlug",
|
||||||
|
"/Users/dayu/Documents/PaintFCP",
|
||||||
|
"/Users/dayu/Desktop",
|
||||||
|
"/Users/dayu/Downloads",
|
||||||
|
"/Users/dayu/Library/Developer/Xcode/DerivedData",
|
||||||
|
"/tmp/paintfcp-build-20261021"
|
||||||
|
],
|
||||||
|
"products": [
|
||||||
|
{
|
||||||
|
"path": "/Library/Plug-Ins/FxPlug/beatygril.app",
|
||||||
|
"version": "1.3.0",
|
||||||
|
"build": "15"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"errors": []
|
||||||
|
},
|
||||||
|
"temporaryPathsRemoved": [
|
||||||
|
"/tmp/paintfcp-build-20261021",
|
||||||
|
"build/delivery/previous-beatygril.app",
|
||||||
|
"build/delivery/notarization.zip"
|
||||||
|
],
|
||||||
|
"nativeEffectQA": "pending_user"
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"status": "waiting_for_host_exit",
|
||||||
|
"bundleIdentifier": "com.dayu.beatygril",
|
||||||
|
"installationPath": "/Library/Plug-Ins/FxPlug/beatygril.app",
|
||||||
|
"version": "1.3.0",
|
||||||
|
"build": "15",
|
||||||
|
"source": {
|
||||||
|
"commit": "2f2c081980dc610ac810f8daf92da8966f310802",
|
||||||
|
"trackedWorktreeChanges": []
|
||||||
|
},
|
||||||
|
"notarization": {
|
||||||
|
"id": "ca96837f-bf33-44fa-8d58-799026ce5c7d",
|
||||||
|
"message": "Processing complete",
|
||||||
|
"status": "Accepted"
|
||||||
|
},
|
||||||
|
"signingVerified": true,
|
||||||
|
"stapleValidated": true,
|
||||||
|
"stagingPluginUnregistered": true,
|
||||||
|
"nativeEffectQA": "pending_user",
|
||||||
|
"note": "Prepared Release is Accepted and stapled; installation is waiting for existing user hosts to close. The installed product has not been replaced."
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"status": "verified",
|
||||||
|
"date": "2026-10-22",
|
||||||
|
"commit": "2f2c081980dc610ac810f8daf92da8966f310802",
|
||||||
|
"remote": "https://dayu.media/git/dayu/paintfcp-fxplug.git",
|
||||||
|
"branch": "codex/new-beauty-controls",
|
||||||
|
"trackedFilesCompared": 205,
|
||||||
|
"gitFsckFullStrict": true,
|
||||||
|
"lfs": false,
|
||||||
|
"submodules": false,
|
||||||
|
"resources": "All tracked source, template and artwork bytes match; user QA media/signing credentials are external and not required to compile source."
|
||||||
|
}
|
||||||
+287
@@ -0,0 +1,287 @@
|
|||||||
|
# 大鱼人像美化项目交接
|
||||||
|
|
||||||
|
更新时间:2026-10-22,Asia/Shanghai。项目根目录:`/Users/dayu/Documents/PaintFCP`。
|
||||||
|
|
||||||
|
当前正式安装的 APP 为 **1.3.0(构建 15)**,已完成通用 Release 编译、正式签名、Apple 公证、票据验证及唯一正式路径安装。新增磨皮参数迁移与 1636 项自动检查已完成,原生效果视觉仍待用户实测。公开文章、PKG 与 GitHub Release 继续对应 **1.2.0**;本次未更新这些发行资料,也未覆盖模板或宣传图。
|
||||||
|
|
||||||
|
代码由 **OpenAI + Codex 全程编写,站长仅参与调试和测试**。文章和仓库须保留这个表述,不使用“站长原创代码”等说法。免费版本无需账号、激活码或付费授权;“自动注册”指系统插件注册。
|
||||||
|
|
||||||
|
1.2.0 交接整理前,工作区干净,`main`、`origin/main` 与 `github/main` 均为 `189a5d7634aa69db54330c90f8711b05d8e44b10`。这是文档新增前的基线;最新文档节点以 `git log -1` 和远端引用为准。
|
||||||
|
|
||||||
|
## 先读和先查
|
||||||
|
|
||||||
|
接手时先查看 `git status --short --branch`、`git remote -v` 和最新提交,确认没有其他聊天的未提交改动。只读核对正式 APP 版本与模板目录,再决定是否需要编译或操作宿主。单纯修改文档不需要重建、重新公证或打开 Motion/FCP。
|
||||||
|
|
||||||
|
当前状态以本交接、[恢复说明](../RECOVERY.md)、[验证记录](验证记录.md) 和最新版本回执为入口。`docs/analysis` 与旧版本回执保留了研究过程,其中“待修复”“待测试”可能是历史状态;例如 1.2.0 的 `release-plan.md` 是发布前计划,实际完成情况看 `native-qa.json` 和交付回执。
|
||||||
|
|
||||||
|
| 入口 | 用途 |
|
||||||
|
| --- | --- |
|
||||||
|
| [1.3.0 交付回执](verification/v1.3.0/delivery-receipt.json) | 当前安装版、签名源码、公证、清理及原生视觉待测范围 |
|
||||||
|
| [README](../README.md) | 产品介绍、使用与版本变化 |
|
||||||
|
| [恢复说明](../RECOVERY.md) | 精确源码节点、依赖、远端恢复记录 |
|
||||||
|
| [GitHub 同步说明](GitHub同步说明.md) | 两个远端、公开克隆及 Release 验证 |
|
||||||
|
| [1.2.0 交付回执](verification/v1.2.0/delivery-receipt.json) | 签名源码、公证、正式安装与清理结果 |
|
||||||
|
| [1.2.0 原生验收](verification/v1.2.0/native-qa.json) | 启动器与 FCP 实测范围 |
|
||||||
|
| [1.1.2 原生验收](verification/v1.1.2/native-qa.json) | 面板、短脸、下颌骨和连续预览验收 |
|
||||||
|
| [第三方资料与许可](../THIRD_PARTY_NOTICES.md) | MIT 范围、Apple 依赖与宣传素材边界 |
|
||||||
|
|
||||||
|
## 固定身份与安装位置
|
||||||
|
|
||||||
|
| 项目 | 当前值 |
|
||||||
|
| --- | --- |
|
||||||
|
| 显示名称 | 大鱼人像美化 |
|
||||||
|
| Xcode 产品名 | `beatygril`,保留现有拼写 |
|
||||||
|
| APP 版本 | `1.3.0` / `15`,arm64 与 x86_64 通用版本 |
|
||||||
|
| 唯一正式 APP | `/Library/Plug-Ins/FxPlug/beatygril.app` |
|
||||||
|
| 主 Bundle ID | `com.dayu.beatygril` |
|
||||||
|
| 嵌套插件 | `Contents/PlugIns/beatygril XPC Service.pluginkit` |
|
||||||
|
| 插件 Bundle ID | `com.dayu.beatygril.fxplug` |
|
||||||
|
| 滤镜 UUID | `FB14B0E6-59B2-4836-916F-CD0C559E9241` |
|
||||||
|
| 自定义面板参数 ID | `210` |
|
||||||
|
| FCP 效果分类 | 大鱼磨皮瘦脸 → 大鱼人像美化 |
|
||||||
|
| Motion 原生滤镜分类 | 大鱼 → 大鱼人像美化 |
|
||||||
|
| 签名团队 | `B3W25FX5H5` |
|
||||||
|
|
||||||
|
当前 FCP 模板为:
|
||||||
|
|
||||||
|
```text
|
||||||
|
/Users/dayu/Movies/Motion Templates.localized/Effects.localized/大鱼磨皮瘦脸/大鱼人像美化/大鱼人像美化.moef
|
||||||
|
```
|
||||||
|
|
||||||
|
源码模板仍在 `Templates/大鱼人像美化/`,脚本和测试使用这个路径。磁盘安装分类与源码目录层级不同是有意保留。旧的 `Effects.localized/大鱼/大鱼人像美化` 不是当前安装位置。
|
||||||
|
|
||||||
|
不要在 `/Applications`、用户级 FxPlug、Debug 或其他目录再留同产品外层 APP。替换前核对 Bundle ID 和来源;嵌套插件、Framework 及安装包内封装的有效载荷不属于重复安装。
|
||||||
|
|
||||||
|
## 源码结构
|
||||||
|
|
||||||
|
Xcode 工程:`beatygril/beatygril.xcodeproj`。外层 scheme 为 `Wrapper Application`,插件 target 为 `XPC Service`。
|
||||||
|
|
||||||
|
下表插件文件均位于 `beatygril/beatygril/Plugin/`。
|
||||||
|
|
||||||
|
| 文件或目录 | 职责与修改入口 |
|
||||||
|
| --- | --- |
|
||||||
|
| `beatygrilPlugIn.m` | FxPlug 参数、宿主 action、图像适配、渲染状态与整体混合 |
|
||||||
|
| `PGFaceProcessor.m` | Vision 检测、76 点、自动跟随、几何稳定、局部变形和几何蒙版 |
|
||||||
|
| `PGBeautyRenderer.m` | 磨皮、肤色、细节、牙齿和眼睛等外观处理 |
|
||||||
|
| `PGInspectorView.m` | AppKit 卡片、滑杆、数值框、开关、重置和连续提交 |
|
||||||
|
| `PGInspectorState.m` | 面板完整状态、NSSecureCoding、复制与插值 |
|
||||||
|
| `PGFaceSelectOSC.m` | 画布选脸与检测框等屏幕控件 |
|
||||||
|
| `PGParameterIDs.h`、`PGBeautyTypes.h` | 稳定参数 ID 和共享设置结构 |
|
||||||
|
| `../Wrapper Application/AppDelegate.m` | 主 APP 说明图、系统注册与三秒退出 |
|
||||||
|
| `../Wrapper Application/Resources/LauncherInfo.png` | 主 APP 古风说明图 |
|
||||||
|
| `Templates/大鱼人像美化/`(仓库根) | FCP 效果工程、标准占位图及缩略图 |
|
||||||
|
| `tests/`、`scripts/test*.sh`(仓库根) | 离屏像素、几何、宿主适配与面板测试 |
|
||||||
|
|
||||||
|
几何和外观内核由 Objective-C 源码构造 Core Image kernel;没有复制目标插件的 metallib 或私有模型。Xcode 使用文件系统同步分组,添加文件时核对 target 排除项,防止把插件实现编入外层 APP,或把启动器资源与 `AppDelegate.m` 编入插件。
|
||||||
|
|
||||||
|
## 自动跟随与脸型修复
|
||||||
|
|
||||||
|
### 识别和转脸
|
||||||
|
|
||||||
|
当前使用 Apple Vision `VNDetectFaceLandmarksRequestRevision3` 与 `VNRequestFaceLandmarksConstellation76Points`。实时渲染自动检测并关联主要人脸,结合防跳范围、关键点稳定、失检保持和几何历史;不需要先点击分析。多人模式另走多人几何处理。
|
||||||
|
|
||||||
|
目标插件的静态证据支持 Vision 点阵与局部二维变形,已检查的瘦脸路径没有证明存在显式 yaw 切换或完整 3D 姿态模型。侧脸适应主要来自轮廓与五官点随姿态改变,不能宣称已确定某个第三方神经网络或完全恢复原版模型。证据入口见[脸型与转脸分析](analysis/脸型变形与转脸分析.md)和[自动跟随报告](analysis/auto-follow-report.json)。
|
||||||
|
|
||||||
|
本项目几何历史只取当前请求之前的近时记录,原版还存在近未来历史分支;冷启动、热缓存、乱序渲染、同尺寸换源和多人交叉仍有差异边界。旧离线分析字段保留兼容,但当前新面板的渲染统一使用实时自动跟随。
|
||||||
|
|
||||||
|
### 已完成的四项局部算法
|
||||||
|
|
||||||
|
| 控制项 | 当前实现 | 防止回归的要点 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| 瘦脸 | 76 点、17 组轮廓到鼻部或瞳孔锚点的局部逆向采样 | 不恢复整脸框水平挤压;保留尺寸增益、组顺序和逐步 clamp |
|
||||||
|
| 下巴 | `68→38`、`67→37`、`66→36` 三组局部位移 | 正值上收、负值延伸,保留正负不同系数 |
|
||||||
|
| 下颌骨 | 六组相邻轮廓半径的顺序局部位移 | 半径来自相邻轮廓点,方向锚点为 P29;不能同时求和替代逐步更新 |
|
||||||
|
| 短脸 | P62 至 P72 的下半脸闭合区域,沿 P46−P48 鼻梁轴位移 | 正值收短、负值延长;不是太阳穴区域或脸框纵向压缩 |
|
||||||
|
|
||||||
|
这四项共享由实际图像像素尺寸计算的增益。完整合法 76 点缺失时,四项安全直通,不退回猜测的脸框场。图像与磨皮、肤色、牙齿、眼睛蒙版共用几何变形路径,避免效果与蒙版错位。
|
||||||
|
|
||||||
|
**仍为近似实现的五项:大眼、窄脸、V 脸、颧骨、小头。** 原版完整链为 `thin → narrow → V → jaw → cheek → chin → short → small-head → eyes`,本项目尚未完全恢复整条组合链的数学及顺序。用户单项验收不代表所有参数组合逐像素等价。详细索引、系数和独立数值场验证见[下巴与坐标分析](analysis/下巴与坐标分析.md)、[短脸与下颌骨分析](analysis/短脸与下颌骨分析.md)。
|
||||||
|
|
||||||
|
### 坐标约定
|
||||||
|
|
||||||
|
宿主 CI/IOSurface 链统一按 Y 向上处理;归档坐标标记为 `hostCoordinateSpace=iosurface-ci-y-up-v1`。不要仅看到 `TOP_LEFT` 标志就再翻转图像。局部几何中需要的归一化 Y 向下 UV 在同一 kernel 内成对转换,不改变宿主图像方向。
|
||||||
|
|
||||||
|
旧无标记分析档案不能靠补字符串伪装为新坐标。涉及位置的修改应同时检查非零 extent、图像宽高比、真实侧脸、歪头、正负参数和上下原点标签。历史“下巴作用在眼睛”“短脸和下颌骨作用在太阳穴”的问题已有相应修复,不应再用移动检测框来掩盖坐标或点索引问题。
|
||||||
|
|
||||||
|
## 面板与连续预览
|
||||||
|
|
||||||
|
界面由 AppKit 自绘磨皮、美白、瘦脸卡片及原生输入控件构成。FCP 模板只发布自定义面板 210 和两项署名信息,手动分析和多余平铺参数已隐藏;隐藏参数 ID 与旧归档仍须保留,以免破坏工程兼容。
|
||||||
|
|
||||||
|
面板交互必须在真实视图对应的 **宿主 action 内** 获取读写接口,不能缓存接口后在 action 外使用。每个发生变化的拖动步进都要在松手之前完成自己的 action;把整次拖动包在一个直到 mouse-up 才结束的 action 中,会再次造成松手后才更新。重复相同步进可去重。
|
||||||
|
|
||||||
|
`PGInspectorState` 保存完整且可安全归档的状态。未编辑面板时兼容旧文档参数,首次编辑采集当前设置;旧版各项独立关键帧曲线尚未自动迁移为新面板曲线。修改时同时检查数值 Return 提交、焦点切换、单项重置、宿主撤销和保存重开,不只看滑杆外观。
|
||||||
|
|
||||||
|
用户于 2026-10-22 明确确认“拖动中已持续更新”。该回复未指定宿主,覆盖至少一个原生宿主的按住拖动体验;宿主调度可能合并快速更新,不能写成每个鼠标事件必定显示一帧。
|
||||||
|
|
||||||
|
## 验证状态与开发命令
|
||||||
|
|
||||||
|
在项目根执行:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
./scripts/test.sh
|
||||||
|
PAINTFCP_HOST_ASAN=1 ./scripts/test-host.sh
|
||||||
|
PAINTFCP_OSC_ASAN=1 ./scripts/test-osc.sh
|
||||||
|
PAINTFCP_INSPECTOR_ASAN=1 ./scripts/test-inspector.sh
|
||||||
|
python3 scripts/inspector-template.py audit 'Templates/大鱼人像美化/大鱼人像美化.moef'
|
||||||
|
```
|
||||||
|
|
||||||
|
1.1.2 完整回归为 **1551 项**:Beauty 49、Face 116、几何 796、宿主适配 ASan 335、OSC ASan 98、Inspector ASan 157。额外真实帧 ASan 专项 886 项包含重复基础用例,未计入 1551。28 种控制与原点组合核对了解剖位置,改变原点标签时输出逐字节一致。详见[自动验证汇总](verification/v1.1.2/automated-tests.json)。
|
||||||
|
|
||||||
|
| 验证范围 | 已完成结果与边界 |
|
||||||
|
| --- | --- |
|
||||||
|
| Motion 1.1.2 | 正式插件、数值输入、重置、撤销、焦点和保存关闭重开;短脸与下颌骨隔离画面检查 |
|
||||||
|
| FCP 1.1.2 | 正式面板启用、数值与重置;短脸和下颌骨分别隔离渲染,画面有响应 |
|
||||||
|
| 连续预览 | 模拟宿主确认 mouse-up 前发布中间状态;用户实际拖动已确认 |
|
||||||
|
| 主 APP 1.2.0 | 古风说明图和无障碍文字正确,窗口显示后计时三秒退出,系统注册成功 |
|
||||||
|
| FCP 1.2.0 | 新启动后显示新缩略图,QA 工程加载滤镜、亮度调到 20%,画面正常无黑屏 |
|
||||||
|
| Motion 1.2.0 | 未重新进行该版界面视觉验收;渲染和面板源码沿用已验收 1.1.2 |
|
||||||
|
| 安装包 | 有效载荷与正式 APP、四个模板文件一致;隔离脚本验证初装、替换锁定图和失败回滚 |
|
||||||
|
|
||||||
|
1.2.0 只修改外层启动器、版本与宣传资源,没有重新运行全部 1.1.2 自动测试,不能把历史测试写成该版全量重跑。原生 VoiceOver、旧独立关键帧迁移、多脸交叉、重遮挡和所有极端姿态仍未完整验收。
|
||||||
|
|
||||||
|
现有 PKG **未通过真实 installer 再次安装到当前用户目录**:APP 由 `deliver.py` 安装,缩略图另行同步;PKG 的安装脚本在隔离目录执行,重定向用户身份、替代 chown 并跳过其中的真实 FxPlug 验证。正式 APP 的签名与发现另有真实验证。不要把隔离测试表述成完整系统安装实测。
|
||||||
|
|
||||||
|
用户测试视频位于:
|
||||||
|
|
||||||
|
```text
|
||||||
|
/Volumes/DayuMedia 4TB/LCQ荒野乱斗/LCQ赛事回放/teen-with-blue-hair-smiling-at-beach-2026-10-02-18-55-48-utc.mov
|
||||||
|
```
|
||||||
|
|
||||||
|
该视频、宿主 QA 工程、截图、完整 AX 和人脸坐标仅保留在本机或忽略的 `build/` 下,不是构建依赖,不要上传。仓库自动测试另有带来源说明的 NASA 公共领域样本。宿主验收在独立 QA 工程进行,避免修改用户工作时间线。
|
||||||
|
|
||||||
|
## 宣传图和缩略图
|
||||||
|
|
||||||
|
最终设计为虚构成年古风人物的面部近景,深青玉色与金色、下颌收缩线和箭头、跟随角标。文字包含“自动瘦脸、自动跟随、自动美颜”,已按用户反馈减小字号。保持与目标宣传人物的区别,不回用目标肖像。
|
||||||
|
|
||||||
|
| 资源 | 文件 |
|
||||||
|
| --- | --- |
|
||||||
|
| 项目封面 | `Assets/Artwork/portrait-cover.png` |
|
||||||
|
| APP 说明图 | `beatygril/beatygril/Wrapper Application/Resources/LauncherInfo.png` |
|
||||||
|
| 大缩略图 | `Templates/大鱼人像美化/large.png`,640×360 |
|
||||||
|
| 小缩略图 | `Templates/大鱼人像美化/small.png`,192×108 |
|
||||||
|
| 生成记录 | `docs/design/artwork-manifest.json`、`artwork-prompt.json` |
|
||||||
|
|
||||||
|
这些是生成的宣传插画,不是真实美颜效果前后对比。文字已烘焙进 PNG,没有需要额外分发的可编辑字体依赖。
|
||||||
|
|
||||||
|
运行时模板目录必须包含 `.moef`、`Drop Zone.tiff`、`large.png`、`small.png` 四个文件。仅两张 PNG 设置 BSD `uchg`,不锁工程或目录。Git 和普通 ZIP 不保存该标志,手工恢复后需要重新设置:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
chflags uchg "$HOME/Movies/Motion Templates.localized/Effects.localized/大鱼磨皮瘦脸/大鱼人像美化/large.png" "$HOME/Movies/Motion Templates.localized/Effects.localized/大鱼磨皮瘦脸/大鱼人像美化/small.png"
|
||||||
|
ls -lO "$HOME/Movies/Motion Templates.localized/Effects.localized/大鱼磨皮瘦脸/大鱼人像美化"
|
||||||
|
```
|
||||||
|
|
||||||
|
替换图前仅解除对应 PNG 的 `uchg`,替换后重锁。同步 `.moef` 前先比较用户磁盘最新文件与源码,保留手改,不能盲目覆盖。当前四文件 SHA-256 见[安装包验证](verification/v1.2.0/pkg-verification.json)。本次交接核对时,正式 APP 为 1.2.0(14),两张安装缩略图均有 `uchg`。
|
||||||
|
|
||||||
|
## Release 构建签名与安装
|
||||||
|
|
||||||
|
依赖为 Xcode 27.1、FxPlug SDK 4.3.4、PluginManager 1.7.4,最低 macOS 13。头文件和链接信息来自 `/Library/Developer/SDKs/FxPlug.sdk/Library/Frameworks`;嵌入的可运行框架来自 `/Library/Developer/Frameworks`。不要把 SDK 链接存根当作运行框架。
|
||||||
|
|
||||||
|
完整正式交付入口:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
python3 scripts/deliver.py
|
||||||
|
```
|
||||||
|
|
||||||
|
脚本获取 `/Users/dayu/.codex/app-delivery-locks/com.dayu.beatygril.lock`,使用固定暂存 `/tmp/paintfcp-build-20261021`,记录到 `build/delivery/`。它构建通用 Release,由内到外签名 Framework、嵌套插件、外层 APP,验证严格签名,提交公证并要求 `Accepted`,粘附并验证票据,等待宿主退出后替换唯一正式路径,检查 Gatekeeper 与 PluginKit。默认停在 `HOST_VALIDATION_PENDING`,完成实际宿主验收及副本盘点后才回车进入清理。
|
||||||
|
|
||||||
|
| 选项 | 用途 |
|
||||||
|
| --- | --- |
|
||||||
|
| `--wait-for-hosts` | 保持交付锁,只读等待 Motion/FCP 自行退出,不操作或结束宿主 |
|
||||||
|
| `--user-tests` | 静态安装验证后清理已知生成副本,把原生效果验收明确保留给用户 |
|
||||||
|
| `--resume-prepared` | 仅恢复已有 Accepted、stapled 暂存产物;核对 CDHash、版本和产品源码未改变 |
|
||||||
|
|
||||||
|
运行脚本前先提交并推送待交付源码。用户已长期授权正式 Release 的签名、公证、安装,不必对相同范围重复询问。用户曾明确授权接手 Motion/FCP 验收,并允许需要释放插件时直接退出 Motion;涉及 FCP 工作工程仍应保护用户内容。本次交接没有操作宿主。
|
||||||
|
|
||||||
|
签名身份为 `Developer ID Application: zhong luo (B3W25FX5H5)`,安装包身份为同团队的 `Developer ID Installer`。每次重新检查有效证书和私钥,不能退回临时签名。公证使用登录钥匙串中的 profile `codex-dayu-notary-B3W25FX5H5`;密码和私钥不在仓库,不能输出到聊天、日志或命令行参数。
|
||||||
|
|
||||||
|
1.2.0 APP 公证 ID:`3999fe91-a80e-49d3-9ed0-4003b82f6f54`;PKG 公证 ID:`96a3d1be-a751-4fb8-b062-33a09c01ddf2`,两者状态均为 Accepted。完整记录在 `docs/verification/v1.2.0/`。
|
||||||
|
|
||||||
|
交付锁不能绕过,也不能删除活跃持有者的锁。签名、公证或权限失败时保留原安装版。完成后只清理核对身份属于本产品的生成 APP、旧 APP 和公证 ZIP;最后的有界扫描覆盖 `/Applications`、两个 FxPlug 目录、项目及固定暂存路径,只发现正式 APP,不代表已经扫描所有卷。
|
||||||
|
|
||||||
|
## PKG 与发布资料
|
||||||
|
|
||||||
|
正式包:`releases/1.2.0/大鱼人像美化-1.2.0.pkg`,**5,011,005 bytes**。
|
||||||
|
|
||||||
|
```text
|
||||||
|
SHA-256 5bd77c28b3a7e54f3d4060be1f16bcb145d9bc4d6b9cfb9eb137c01130440243
|
||||||
|
```
|
||||||
|
|
||||||
|
GitHub Release 下载名为 `Dayu-Portrait-1.2.0.pkg`,字节相同,已匿名下载并核对哈希、Installer 签名和公证票据。
|
||||||
|
|
||||||
|
后续打包使用 `/Applications/PKG工坊.app/Contents/MacOS/PKG工坊`,本次版本为 CLI 1.4(8)。先读 `/Users/dayu/.codex/skills/dayu-publish-article/SKILL.md` 的当前打包规范及 CLI 帮助,使用独立发布副本。
|
||||||
|
|
||||||
|
资源输入应是分类根 `大鱼磨皮瘦脸`,内含 `大鱼人像美化` 和上述四文件;另外加入唯一正式已公证 APP。不要把更外层“发布版”容器作为分类输入,也不要把 `PARAMETERS.json`、`VALIDATION.json`、开发 README 混入运行模板。PKG postinstall 负责备份、解除旧图锁、复制、重锁并验证;失败回滚当前模板项目,不能扩大为整个安装包的原子回滚。
|
||||||
|
|
||||||
|
`scripts/verify-release-package.py` 是 **1.2.0 专用验证脚本**:依赖已展开的 `build/pkg-expanded/Payload.pkg`、当前正式 APP 和固定 `build/pkg-isolation`;不是接收任意包路径的通用工具。先阅读源码并准备展开目录,验证后清理展开出的额外 APP。当前 `.gitignore` 只为 `releases/1.2.0/*.pkg` 放行,下一版本的二进制入库或 Release 附件策略需明确处理。
|
||||||
|
|
||||||
|
文章及发布入口:
|
||||||
|
|
||||||
|
- [公开源码仓库](https://github.com/FAMEDaYu/FCP-BeatyGril)
|
||||||
|
- [1.2.0 GitHub Release](https://github.com/FAMEDaYu/FCP-BeatyGril/releases/tag/v1.2.0)
|
||||||
|
- [大鱼站文章 15593](https://dayu.media/15593/)
|
||||||
|
- [大鱼 Git 备份](https://dayu.media/git/dayu/paintfcp-fxplug)
|
||||||
|
|
||||||
|
文章为“免费资源”,价格为 0,非会员可获取,特色封面为当前古风图;正文已有 GitHub 仓库和 Release 链接,保留免费开源、安装注册、三秒退出、故障排查及准确署名。FCPX APP 列表同步本次未启用。
|
||||||
|
|
||||||
|
文章另附历史源码 ZIP,对应 `788ba0d5514595cf349af33074055d70b0754185`,包含构建源码,排除 PKG 和 Git 历史;不是最新 main 快照。后续源码和署名修订以 GitHub main 为准。
|
||||||
|
|
||||||
|
`scripts/publish-release.py` 用于首次发布并保留 `build/publish-operation.json` 状态;已有 `post_id` 时不会重新提交正文,不要将它当成通用文章更新器。更新 15593 时先读最新正文及元数据,只提交所需字段,保持免费权限、下载、SEO 和其他未改字段;密钥只在进程内存中使用。具体发布流程继续遵循上述技能,不因接手文档自动重发。
|
||||||
|
|
||||||
|
## Git 保存与恢复
|
||||||
|
|
||||||
|
| 本地远端 | 地址 | 说明 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `origin` | `https://dayu.media/git/dayu/paintfcp-fxplug.git` | 保持为原远端及 main 上游;网站 Git 网关仍要求登录和有效会员 |
|
||||||
|
| `github` | `https://github.com/FAMEDaYu/FCP-BeatyGril.git` | 公开仓库,匿名克隆可用,完整历史和七个标签已保存 |
|
||||||
|
|
||||||
|
本项目没有 LFS 或子模块。不要改写原远端、强推、覆盖现有标签或把双端同步描述成自动定时同步。
|
||||||
|
|
||||||
|
| 节点 | 完整提交 SHA |
|
||||||
|
| --- | --- |
|
||||||
|
| 1.3.0 正式签名源码(codex/new-beauty-controls) | `2f2c081980dc610ac810f8daf92da8966f310802` |
|
||||||
|
| 1.2.0 正式签名源码 | `696d5f39b30404ed5d1d4c25f61d17475c7fc91b` |
|
||||||
|
| `v1.2.0` 发布标签 | `3c7261c3a861b9f4704cfb36055d7b500c239dab` |
|
||||||
|
| 1.1.2 已验收渲染及面板源码 | `a2e33e8e00a99b95dc29d67a819cb0897eda5756` |
|
||||||
|
| GitHub 独立匿名恢复节点 | `356683b41af702d6c0143ad7fa5f540d752d6e14` |
|
||||||
|
|
||||||
|
标签包含相应发布资料,签名源码节点表示实际构建代码;两者不同是正常的。后续文章、署名、GitHub 和交接文档提交没有改变已签名产品。GitHub 恢复核验覆盖 197 个受跟踪文件、全部七标签及 strict/full fsck;大鱼 `v1.2.0` 恢复核验为该历史节点的 183 文件,不把不同节点的文件数混用。
|
||||||
|
|
||||||
|
恢复正式发行源码可以:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
git clone https://github.com/FAMEDaYu/FCP-BeatyGril.git
|
||||||
|
cd FCP-BeatyGril
|
||||||
|
git switch --detach v1.2.0
|
||||||
|
```
|
||||||
|
|
||||||
|
需要最新文档时留在 main。继续开发则基于所需节点创建 `codex/` 前缀分支。Apple SDK、有效签名证书和公证凭据需在新机器单独安装或恢复;Git 不包含它们。
|
||||||
|
|
||||||
|
保存本任务文件可用 `python3 scripts/checkpoint.py '提交说明' <明确文件...>`,该脚本只对当前分支提交、推送并核验 **origin**,不会推送 GitHub,也不要在外层已持有同一 origin 锁时嵌套调用它。GitHub 还需单独推送当前分支,发布时明确推送新的标签,两端都读回对应引用与本地 SHA 比较。
|
||||||
|
|
||||||
|
提交和推送使用 `/Users/dayu/.codex/git-backup-locks/` 中的共享互斥锁。锁名为无凭据、转小写并去尾斜线的完整 HTTPS 仓库地址(保留 `.git`)的 SHA-256 加 `.lock`,`owner.json` 记录 PID、cwd、remote;不清理其他仍在工作的持有者。
|
||||||
|
|
||||||
|
本机 GitHub CLI 为 `/opt/homebrew/bin/gh`,本次已由用户登录 `FAMEDaYu`,HTTPS 凭据在钥匙串中。接手时用 `gh auth status` 确认;连接器曾使用其他只读账号,不能用连接器权限推断 CLI 登录失效,也不打印令牌。大鱼仓库管理使用 `dayu-git` CLI,指南在 `/Users/dayu/Documents/dayu-git/README.md` 与 `API.md`。
|
||||||
|
|
||||||
|
恢复范围仅覆盖已经提交、推送并核验的节点与实际上传的必要资源。用户素材、忽略的 build、私钥不在此范围。大鱼服务器当前只有手动备份,不能据此承诺本机与服务器同时损坏也可恢复。
|
||||||
|
|
||||||
|
## 后续开发优先入口
|
||||||
|
|
||||||
|
当前无需自动执行以下工作;收到相应需求后再开展。
|
||||||
|
|
||||||
|
1. 用户继续反馈脸型差异时,先隔离单项、核对实际加载版本、原点与 76 点解剖索引,再分析目标对应局部数学。优先补齐仍近似的五项及组合顺序,不能仅调大整体强度。
|
||||||
|
2. 跟随异常时,围绕多人交叉、遮挡、切镜、冷缓存、乱序和同尺寸换源制作明确样本,比较主体关联和历史选择,而不是先增加手动分析按钮。
|
||||||
|
3. 面板改动时,覆盖真实 action 生命周期、持续步进、输入焦点、撤销、重置、保存重开及旧工程;补充原生 VoiceOver 和实际关键帧交互验收。
|
||||||
|
4. 新版本发布时,沿用固定身份和唯一正式路径,记录本次真实签名源码,完成 APP/PKG 公证与恢复验证,更新原文章和两个远端,不覆盖旧标签。
|
||||||
|
|
||||||
|
目标参考为 `/Applications/MSE18V2.app` 及用户原 MS 模板,仅用于只读分析。现有 REA、CPU 和 AIR 证据在 `docs/analysis/`;新分析按 `reverse-engineer-anything` 技能继续。不上传或打包原版 APP、模型、metallib、用户视频和完整人脸坐标。
|
||||||
|
|
||||||
|
2026-10-22 用户已纠正“瘦身”为新增面板参数,目标仍是 `/Users/dayu/Downloads/MSE18V2.app`(2.4.0 / 20260724150000)。现沿 `codex/new-beauty-controls` 接入连续五官保护、非肤色区保护和局部微细锐化,保留旧 30/17/4 参数及归档;见[新版磨皮参数迁移](analysis/新版磨皮参数迁移.md)。1.3.0(15)已正式安装,签名、票据、Gatekeeper、PluginKit 正式路径和已知临时副本清理均验证通过。原生视觉由用户测试,见新交付回执。[原瘦身目标核对](analysis/瘦身迁移目标核对.md)只保留历史,不再是待澄清项。
|
||||||
|
|
||||||
|
|
||||||
|
## 1.3.0 接续说明
|
||||||
|
|
||||||
|
用户已保存并退出 Motion/FCP,交付脚本在宿主全部退出后安装新 APP。Apple 公证 ID 为 `ca96837f-bf33-44fa-8d58-799026ce5c7d`,状态 Accepted;最终签名源码为 `2f2c081980dc610ac810f8daf92da8966f310802`。交付标签 `v1.3.0` 包含后续回执和恢复文档,源码分支为 `codex/new-beauty-controls`,main 仍保留原发行状态。
|
||||||
|
|
||||||
|
新增稳定 float ID:75 五官保护量、76 非肤色区保护、77 局部微细锐化。旧 bool 30、独立全局磨皮 17 和全局锐化 4 均保留;后两项在更多设置中,不能重新解释其历史数值。PGBeautySettings v2 只追加三个 float,接受明确的 v1/160 字节旧状态。PGInspectorState 仍为 schema 1,缺 75 时从旧 bool 导入;旧 bool 为 true 时不得覆盖已读到的新浮点值。
|
||||||
|
|
||||||
|
1636 项检查与离屏面板已验,含 ASan 宿主/OSC/Inspector;本次未在原生宿主中重新操作新滑杆或验收效果视觉。正式插件发现只核验 PluginKit 路径,不把它写作 FCP 或 Motion 实际加载/画面通过。当前无需重新安装、重做封面或更新 1.2.0 安装包;用户反馈实际效果后继续针对性处理。
|
||||||
+7
@@ -131,3 +131,10 @@ Motion:独立普通视频项目,源PTS0、1920×1080/25fps/10秒,媒体通
|
|||||||
## 1.1.2 连续预览用户验收补充(2026-10-22)
|
## 1.1.2 连续预览用户验收补充(2026-10-22)
|
||||||
|
|
||||||
用户明确回复“拖动中已持续更新”,确认最终1.1.2(13)按住滑杆拖动时画面能够持续更新,关闭上节待反馈项。此项证据来自用户原生实测;未指定具体宿主,不声称两个宿主分别完成按住拖动验收,也没有测量每个鼠标事件的独立排帧。本次仅更新验收记录,不改变产品源码、安装版或既有发布标签。
|
用户明确回复“拖动中已持续更新”,确认最终1.1.2(13)按住滑杆拖动时画面能够持续更新,关闭上节待反馈项。此项证据来自用户原生实测;未指定具体宿主,不声称两个宿主分别完成按住拖动验收,也没有测量每个鼠标事件的独立排帧。本次仅更新验收记录,不改变产品源码、安装版或既有发布标签。
|
||||||
|
|
||||||
|
|
||||||
|
## 1.3.0(15)新增磨皮参数与正式安装
|
||||||
|
|
||||||
|
按用户纠正后的截图需求,迁移连续五官保护、非肤色区保护和局部微细锐化;保留旧 bool、全局磨皮/锐化 ID 与旧工程行为。正式签名源码 `2f2c081980dc610ac810f8daf92da8966f310802`。1636 项通过:57 Beauty、116 Face、796 几何、341 宿主适配 ASan、98 OSC ASan、228 Inspector ASan(含 1 项可选 PNG 输出)。保护 0/50/100 像素响应、色彩覆盖隔离、透明度、v1 状态迁移、新控件归档/插值/输入/重置/旁路/松手前发布均有检查;离屏面板已查看。
|
||||||
|
|
||||||
|
通用 Release 正式签名、公证 Accepted(`ca96837f-bf33-44fa-8d58-799026ce5c7d`)、票据、双架构严格签名、安装身份、Gatekeeper、PluginKit 正式路径与临时副本清理通过。独立从 origin 克隆签名源码节点,205 个受跟踪文件逐字节匹配且 strict/full fsck 通过。本次没有原生新滑杆/效果视觉验收,也没有生成新 PKG 或更新文章;用户已保存并退出宿主后才执行安装。详见 `verification/v1.3.0/` 与 `analysis/新版磨皮参数迁移.md`。
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
# 大鱼人像美化 1.2.0(14)
|
||||||
|
|
||||||
|
- 发布文章:https://dayu.media/15593/
|
||||||
|
- 公开源码:https://github.com/FAMEDaYu/FCP-BeatyGril
|
||||||
|
- 大鱼备份:https://dayu.media/git/dayu/paintfcp-fxplug
|
||||||
|
- GitHub Release:https://github.com/FAMEDaYu/FCP-BeatyGril/releases/tag/v1.2.0
|
||||||
|
- 大鱼 Release:https://dayu.media/git/dayu/paintfcp-fxplug/releases/tag/v1.2.0
|
||||||
|
- 标签完整 SHA:3c7261c3a861b9f4704cfb36055d7b500c239dab
|
||||||
|
- 正式 APP 源码:696d5f39b30404ed5d1d4c25f61d17475c7fc91b
|
||||||
|
- 源码快照节点:788ba0d5514595cf349af33074055d70b0754185
|
||||||
|
- PKG:大鱼人像美化-1.2.0.pkg,5,011,005 bytes
|
||||||
|
- PKG SHA-256:5bd77c28b3a7e54f3d4060be1f16bcb145d9bc4d6b9cfb9eb137c01130440243
|
||||||
|
- APP Apple 公证:Accepted 3999fe91-a80e-49d3-9ed0-4003b82f6f54
|
||||||
|
- PKG Apple 公证:Accepted 96a3d1be-a751-4fb8-b062-33a09c01ddf2
|
||||||
|
|
||||||
|
安装包本体已推入 Git,并附在 Release 下载区;文章另有免费安装包和免 Git 登录源码快照。仓库属性为 public,但站点统一 Git 网关仍要求登录 / 会员;这不是已解决的匿名 Git 访问。
|
||||||
|
|
||||||
|
APP 与 PKG 均完成正式签名、公证、票据和 Gatekeeper 核验。APP 已安装到唯一正式路径;PKG 做了解包、逐字节载荷对照及隔离模板脚本测试,没有在用户当前模板目录运行本 PKG 安装器。实际模板目录中的两张图已直接更新并锁定。FCP 已显示新缩略图,正式插件可加载并修改亮度;Motion未重新进行1.2.0原生视觉测试,渲染/面板保持1.1.2验收源码。
|
||||||
|
|
||||||
|
验证详情见 docs/verification/v1.2.0。生成宣传图与提示词见 docs/design,未使用目标商业插件人像。
|
||||||
|
|
||||||
|
GitHub 提供完整历史和七个版本标签;独立匿名克隆恢复节点356683b41af702d6c0143ad7fa5f540d752d6e14核验197文件,Release安装包匿名下载哈希、签名和票据均通过。GitHub源码与Release链接已补充至文章15593。代码由 OpenAI + Codex 全程编写,站长仅参与调试和测试。
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
{
|
||||||
|
"title": "大鱼人像美化 1.2.0:FCP / Motion 免费开源自动美颜插件",
|
||||||
|
"slug": "dayu-portrait-beauty",
|
||||||
|
"status": "publish",
|
||||||
|
"content": "<!-- wp:paragraph -->\n<p>大鱼人像美化是一款适用于 Final Cut Pro 与 Motion 的免费开源人像插件,把磨皮、美白和脸型调整放进同一块原生卡片面板。插件会识别当前画面中的人脸并自动跟随,无需先点击分析;你可以按素材需要调整强度,拖动滑杆时持续查看效果。</p>\n<!-- /wp:paragraph -->\n<!-- wp:paragraph -->\n<p><strong>代码由 OpenAI + Codex 全程编写,站长仅参与调试和测试。</strong></p>\n<!-- /wp:paragraph -->\n<!-- wp:image {\"sizeSlug\":\"full\"} -->\n<figure class=\"wp-block-image size-full\"><img src=\"{{media.cover.url}}\" alt=\"大鱼人像美化古风宣传封面,展示自动瘦脸、自动跟随与自动美颜\"/><figcaption>古风宣传插画;面部线条用于示意瘦脸与跟随功能。</figcaption></figure>\n<!-- /wp:image -->\n<!-- wp:heading -->\n<h2 class=\"wp-block-heading\">可以做什么</h2>\n<!-- /wp:heading -->\n<!-- wp:list -->\n<ul class=\"wp-block-list\"><li>磨皮:局部磨皮、全局磨皮与五官保护,强度分别控制。</li><li>美白:肤色预设、肤色亮度、皮肤色相和牙齿美白。</li><li>脸型:瘦脸、大眼、短脸、窄脸、下巴、V 脸、下颌骨、颧骨与小头调整。</li><li>自动跟随:默认跟随主要人脸,也提供多人模式、防跳脸范围与检测框预览。</li><li>实时预览:滑杆、数值、单项开关和重置集中在同一面板,便于反复对比。</li></ul>\n<!-- /wp:list -->\n<!-- wp:paragraph -->\n<p>自动识别负责定位处理区域,效果强度由你控制。建议先从较小的数值开始;多人交叉、严重遮挡和极端侧脸的结果需要结合具体素材检查。</p>\n<!-- /wp:paragraph -->\n<!-- wp:heading -->\n<h2 class=\"wp-block-heading\">版本与下载</h2>\n<!-- /wp:heading -->\n<!-- wp:paragraph -->\n<p>当前版本 <strong>1.2.0(14)</strong>,支持 macOS 13 或更高版本,包含 Apple Silicon 与 Intel 两种架构。下载包包含 FxPlug4 程序和一个配套 FCP 效果模板。APP 与 PKG 均已完成 Developer ID 正式签名、Apple 公证和票据粘附;安装包为所有用户免费资源。</p>\n<!-- /wp:paragraph -->\n<!-- wp:paragraph -->\n<p>本次更新加入古风封面与锁定缩略图、免费开源说明页和主 APP 三秒自动退出。人脸处理与面板交互沿用 1.1.2 已验收实现,不需要重新运行手动分析。</p>\n<!-- /wp:paragraph -->\n<!-- wp:heading -->\n<h2 class=\"wp-block-heading\">安装</h2>\n<!-- /wp:heading -->\n<!-- wp:paragraph -->\n<p>安装前先保存工作并退出 Final Cut Pro 和 Motion,安装期间保持关闭。运行本页提供的 PKG,按安装器提示完成安装;它会把程序安装到 <code>/Library/Plug-Ins/FxPlug/beatygril.app</code>,把配套效果安装到当前登录用户的 Motion Templates 目录,并自动验证和修复系统插件注册。</p>\n<!-- /wp:paragraph -->\n<!-- wp:paragraph -->\n<p>本插件免费开源,<strong>无需激活码、付费注册或账号授权</strong>。主 APP 打开时显示说明图,窗口显示三秒后自动退出;宿主中的滤镜由独立服务运行。安装器还会重新锁定两张缩略图,避免被宿主自动替换。</p>\n<!-- /wp:paragraph -->\n<!-- wp:image {\"sizeSlug\":\"full\"} -->\n<figure class=\"wp-block-image size-full\"><img src=\"https://dayu.media/wp-content/uploads/2026/10/1792604743-1c99c2ba53c7614.png\" alt=\"主程序说明页:免费开源,自动注册,无需激活码,启动三秒后自动退出\"/></figure>\n<!-- /wp:image -->\n<!-- wp:heading -->\n<h2 class=\"wp-block-heading\">使用</h2>\n<!-- /wp:heading -->\n<!-- wp:paragraph -->\n<p>Final Cut Pro:打开<strong>效果</strong>浏览器,找到<strong>大鱼磨皮瘦脸</strong>分类,将<strong>大鱼人像美化</strong>添加到人像片段。Motion:在原生滤镜资源库的<strong>大鱼</strong>分类中选择<strong>大鱼人像美化</strong>。</p>\n<!-- /wp:paragraph -->\n<!-- wp:paragraph -->\n<p>在检查器中按需调节磨皮、美白和脸型。每项左侧的眼睛可暂时停用该项,数值会保留;右侧回转箭头用于重置。人脸会随画面自动更新,开启检测框预览可以检查跟随位置。多人模式建议先用短片段观察稳定性。</p>\n<!-- /wp:paragraph -->\n<!-- wp:heading -->\n<h2 class=\"wp-block-heading\">常见问题</h2>\n<!-- /wp:heading -->\n<!-- wp:paragraph -->\n<p>如果效果没有出现或显示红色警告,先退出 Final Cut Pro 和 Motion,再打开一次主 APP,等待其自动退出,然后重新启动宿主。可在“终端”执行下面这条命令:</p>\n<!-- /wp:paragraph -->\n<!-- wp:code -->\n<pre class=\"wp-block-code\"><code>open '/Library/Plug-Ins/FxPlug/beatygril.app'</code></pre>\n<!-- /wp:code -->\n<!-- wp:paragraph -->\n<p>该流程可重新触发系统注册,并不能解决所有报错。仍异常时请核对 macOS、Line truncated
|
||||||
|
"excerpt": "磨皮、美白、瘦脸与自动人脸跟随,原生卡片面板实时预览。免费开源,自动注册,无需激活码;提供签名公证安装包和源码。",
|
||||||
|
"categories": [
|
||||||
|
3
|
||||||
|
],
|
||||||
|
"tags": [],
|
||||||
|
"resource": {
|
||||||
|
"cao_status": true,
|
||||||
|
"cao_price": 0,
|
||||||
|
"cao_vip_rate": 1,
|
||||||
|
"cao_close_novip_pay": false,
|
||||||
|
"cao_expire_day": 0,
|
||||||
|
"cao_pwd": "",
|
||||||
|
"cao_info": [
|
||||||
|
{
|
||||||
|
"title": "版本",
|
||||||
|
"desc": "1.2.0(14)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "宿主",
|
||||||
|
"desc": "Final Cut Pro / Motion"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "系统",
|
||||||
|
"desc": "macOS 13 或更高版本"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "架构",
|
||||||
|
"desc": "Apple Silicon / Intel"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "授权",
|
||||||
|
"desc": "免费开源,无需激活码"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"seo": {
|
||||||
|
"title": {
|
||||||
|
"enabled": true,
|
||||||
|
"value": "大鱼人像美化 1.2.0|FCP / Motion 免费开源自动美颜插件"
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"enabled": true,
|
||||||
|
"value": "大鱼人像美化是适用于 Final Cut Pro 和 Motion 的免费开源 FxPlug4 插件,支持磨皮、美白、自动人脸跟随及瘦脸调整。提供 Apple 签名公证 PKG 安装包和源码,安装自动注册,无需激活码,并附具体安装、使用及红色警告排查说明。"
|
||||||
|
},
|
||||||
|
"keywords": {
|
||||||
|
"enabled": true,
|
||||||
|
"value": "大鱼人像美化,FCP美颜插件,Motion磨皮,自动瘦脸,自动跟随,FxPlug4,免费开源"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,9 @@
|
|||||||
<!-- wp:paragraph -->
|
<!-- wp:paragraph -->
|
||||||
<p>大鱼人像美化是一款适用于 Final Cut Pro 与 Motion 的免费开源人像插件,把磨皮、美白和脸型调整放进同一块原生卡片面板。插件会识别当前画面中的人脸并自动跟随,无需先点击分析;你可以按素材需要调整强度,拖动滑杆时持续查看效果。</p>
|
<p>大鱼人像美化是一款适用于 Final Cut Pro 与 Motion 的免费开源人像插件,把磨皮、美白和脸型调整放进同一块原生卡片面板。插件会识别当前画面中的人脸并自动跟随,无需先点击分析;你可以按素材需要调整强度,拖动滑杆时持续查看效果。</p>
|
||||||
<!-- /wp:paragraph -->
|
<!-- /wp:paragraph -->
|
||||||
|
<!-- wp:paragraph -->
|
||||||
|
<p><strong>代码由 OpenAI + Codex 全程编写,站长仅参与调试和测试。</strong></p>
|
||||||
|
<!-- /wp:paragraph -->
|
||||||
<!-- wp:image {"sizeSlug":"full"} -->
|
<!-- wp:image {"sizeSlug":"full"} -->
|
||||||
<figure class="wp-block-image size-full"><img src="{{media.cover.url}}" alt="大鱼人像美化古风宣传封面,展示自动瘦脸、自动跟随与自动美颜"/><figcaption>古风宣传插画;面部线条用于示意瘦脸与跟随功能。</figcaption></figure>
|
<figure class="wp-block-image size-full"><img src="{{media.cover.url}}" alt="大鱼人像美化古风宣传封面,展示自动瘦脸、自动跟随与自动美颜"/><figcaption>古风宣传插画;面部线条用于示意瘦脸与跟随功能。</figcaption></figure>
|
||||||
<!-- /wp:image -->
|
<!-- /wp:image -->
|
||||||
@@ -59,8 +62,8 @@
|
|||||||
<h2 class="wp-block-heading">源码与开源许可</h2>
|
<h2 class="wp-block-heading">源码与开源许可</h2>
|
||||||
<!-- /wp:heading -->
|
<!-- /wp:heading -->
|
||||||
<!-- wp:paragraph -->
|
<!-- wp:paragraph -->
|
||||||
<p>源码仓库:<a href="https://dayu.media/git/dayu/paintfcp-fxplug" target="_blank" rel="noreferrer noopener">大鱼人像美化 · paintfcp-fxplug</a>。仓库已设为公开属性;目前大鱼站的统一 Git 网关仍要求登录并校验会员。为方便所有读者获取源码,另外提供 <a href="{{source_url}}">1.2.0 源码快照 ZIP(免费、免 Git 登录)</a>,可直接下载研究和自行编译。</p>
|
<p>公开源码仓库:<a href="https://github.com/FAMEDaYu/FCP-BeatyGril" target="_blank" rel="noreferrer noopener">GitHub · FCP-BeatyGril</a>,可直接浏览源码、完整提交历史和版本标签。<a href="https://github.com/FAMEDaYu/FCP-BeatyGril/releases/tag/v1.2.0" target="_blank" rel="noreferrer noopener">1.2.0 Release</a> 提供正式签名公证安装包。大鱼站仓库作为备份保留:<a href="https://dayu.media/git/dayu/paintfcp-fxplug" target="_blank" rel="noreferrer noopener">paintfcp-fxplug</a>(该入口仍受站点登录与会员网关限制)。也可下载 <a href="{{source_url}}">1.2.0 源码快照 ZIP</a>,源码与安装包均免费提供。</p>
|
||||||
<!-- /wp:paragraph -->
|
<!-- /wp:paragraph -->
|
||||||
<!-- wp:paragraph -->
|
<!-- wp:paragraph -->
|
||||||
<p>项目原创代码采用 MIT 许可,源码包含 Xcode 工程、Objective-C / Metal 实现、模板及构建与验证说明。Apple SDK、运行框架及样本按各自许可使用,详见源码中的 LICENSE 和 THIRD_PARTY_NOTICES.md。自行编译需要 Xcode 与 FxPlug SDK;证书和签名私钥不包含在源码中。</p>
|
<p>项目代码按仓库中的 MIT 许可文件提供,源码包含 Xcode 工程、Objective-C / Metal 实现、模板及构建与验证说明。Apple SDK、运行框架及样本按各自许可使用,详见源码中的 LICENSE 和 THIRD_PARTY_NOTICES.md。自行编译需要 Xcode 与 FxPlug SDK;证书和签名私钥不包含在源码中。</p>
|
||||||
<!-- /wp:paragraph -->
|
<!-- /wp:paragraph -->
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
Secrets are read from macOS Keychain into memory; no auth material is persisted.
|
Secrets are read from macOS Keychain into memory; no auth material is persisted.
|
||||||
"""
|
"""
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import base64,hashlib,html,json,mimetypes,ssl,subprocess,urllib.error,urllib.parse,urllib.request,uuid
|
import base64,hashlib,html,json,mimetypes,ssl,subprocess,urllib.error,urllib.parse,urllib.request,uuid,zipfile
|
||||||
ROOT=Path(__file__).resolve().parent.parent
|
ROOT=Path(__file__).resolve().parent.parent
|
||||||
STATE=ROOT/'build/publish-operation.json';STATE.parent.mkdir(exist_ok=True)
|
STATE=ROOT/'build/publish-operation.json';STATE.parent.mkdir(exist_ok=True)
|
||||||
state=json.loads(STATE.read_text()) if STATE.exists() else {'operation':str(uuid.uuid4())}
|
state=json.loads(STATE.read_text()) if STATE.exists() else {'operation':str(uuid.uuid4())}
|
||||||
@@ -15,10 +15,10 @@ class NoRedirect(urllib.request.HTTPRedirectHandler):
|
|||||||
def redirect_request(self,*args,**kwargs):return None
|
def redirect_request(self,*args,**kwargs):return None
|
||||||
opener=urllib.request.build_opener(NoRedirect,urllib.request.HTTPSHandler(context=ssl.create_default_context(cafile='/etc/ssl/cert.pem')))
|
opener=urllib.request.build_opener(NoRedirect,urllib.request.HTTPSHandler(context=ssl.create_default_context(cafile='/etc/ssl/cert.pem')))
|
||||||
def request(path,method='GET',body=None,headers=None):
|
def request(path,method='GET',body=None,headers=None):
|
||||||
assert path.startswith('/wp-json/') and '\n' not in path
|
assert path.startswith(('/wp/v2/','/dayu-publisher/v1/')) and '\n' not in path
|
||||||
h={'Authorization':auth,'Accept':'application/json'};h.update(headers or {})
|
h={'Authorization':auth,'Accept':'application/json'};h.update(headers or {})
|
||||||
if isinstance(body,dict):body=json.dumps(body,ensure_ascii=False).encode();h['Content-Type']='application/json'
|
if isinstance(body,dict):body=json.dumps(body,ensure_ascii=False).encode();h['Content-Type']='application/json'
|
||||||
req=urllib.request.Request('https://dayu.media'+path,data=body,headers=h,method=method)
|
req=urllib.request.Request('https://dayu.media/wp-json'+path,data=body,headers=h,method=method)
|
||||||
with opener.open(req,timeout=180) as f:return json.load(f)
|
with opener.open(req,timeout=180) as f:return json.load(f)
|
||||||
def upload(name,path,mime):
|
def upload(name,path,mime):
|
||||||
if name in state:return state[name]
|
if name in state:return state[name]
|
||||||
@@ -30,8 +30,12 @@ def upload(name,path,mime):
|
|||||||
with urllib.request.urlopen(item['source_url'],context=ssl.create_default_context(cafile='/etc/ssl/cert.pem'),timeout=90) as r:data=r.read()
|
with urllib.request.urlopen(item['source_url'],context=ssl.create_default_context(cafile='/etc/ssl/cert.pem'),timeout=90) as r:data=r.read()
|
||||||
if hashlib.sha256(data).digest()==hashlib.sha256(path.read_bytes()).digest():
|
if hashlib.sha256(data).digest()==hashlib.sha256(path.read_bytes()).digest():
|
||||||
state[name]={'id':item['id'],'url':item['source_url']};save();return state[name]
|
state[name]={'id':item['id'],'url':item['source_url']};save();return state[name]
|
||||||
result=request('/wp/v2/media','POST',path.read_bytes(),{'Content-Type':mime,'Content-Disposition':"attachment; filename*=UTF-8''"+urllib.parse.quote(filename)})
|
result=request('/wp/v2/media','POST',path.read_bytes(),{'Content-Type':mime,'Content-Disposition':'attachment; filename="'+filename+'"'})
|
||||||
state[name]={'id':result['id'],'url':result['source_url']};save();return state[name]
|
state[name]={'id':result['id'],'url':result['source_url']};save();return state[name]
|
||||||
|
source_zip=ROOT/'build/Dayu-Portrait-1.2.0-source.zip'
|
||||||
|
assert zipfile.is_zipfile(source_zip), 'Build a valid reviewed source ZIP before any upload'
|
||||||
|
with zipfile.ZipFile(source_zip) as z:
|
||||||
|
assert z.testzip() is None and any(n.endswith('/Wrapper Application/AppDelegate.m') for n in z.namelist())
|
||||||
status=request('/dayu-publisher/v1/status');assert status['can_publish'] and status['can_upload'] and 'seo' in status['fields']
|
status=request('/dayu-publisher/v1/status');assert status['can_publish'] and status['can_upload'] and 'seo' in status['fields']
|
||||||
source=upload('source',ROOT/'build/Dayu-Portrait-1.2.0-source.zip','application/zip')
|
source=upload('source',ROOT/'build/Dayu-Portrait-1.2.0-source.zip','application/zip')
|
||||||
card=upload('card',ROOT/'beatygril/beatygril/Wrapper Application/Resources/LauncherInfo.png','image/png')
|
card=upload('card',ROOT/'beatygril/beatygril/Wrapper Application/Resources/LauncherInfo.png','image/png')
|
||||||
|
|||||||
@@ -103,6 +103,46 @@ int main(void) { @autoreleasepool {
|
|||||||
s=PGBeautyDefaults();s.midtoneDetail=1;s.maskSoftness=0;s.maskEdgeProtect=0;s.featureProtection=1;
|
s=PGBeautyDefaults();s.midtoneDetail=1;s.maskSoftness=0;s.maskEdgeProtect=0;s.featureProtection=1;
|
||||||
float *protected=Render(context,[renderer processImage:source settings:s skinMask:white teethMask:nil eyeMask:half error:&error],extent);
|
float *protected=Render(context,[renderer processImage:source settings:s skinMask:white teethMask:nil eyeMask:half error:&error],extent);
|
||||||
Check(Difference(base,protected,6,40)<0.00001 && Texture(protected,56,88)<Texture(base,56,88)*0.82,@"Feature protection retains eye detail while smoothing nearby skin");free(protected);
|
Check(Difference(base,protected,6,40)<0.00001 && Texture(protected,56,88)<Texture(base,56,88)*0.82,@"Feature protection retains eye detail while smoothing nearby skin");free(protected);
|
||||||
|
// Independent protection controls use fractional coverage; neither changes
|
||||||
|
// exposure coverage nor invents smoothing when the smoothing amount is zero.
|
||||||
|
s=PGBeautyDefaults();s.midtoneDetail=1;s.maskSoftness=0;s.maskEdgeProtect=0;
|
||||||
|
float *featureFull=Render(context,[renderer processImage:source settings:s skinMask:white teethMask:nil eyeMask:half error:&error],extent);
|
||||||
|
s.featureProtectionAmount=.5;
|
||||||
|
float *featureHalf=Render(context,[renderer processImage:source settings:s skinMask:white teethMask:nil eyeMask:half error:&error],extent);
|
||||||
|
s.featureProtectionAmount=0;
|
||||||
|
float *featureNone=Render(context,[renderer processImage:source settings:s skinMask:white teethMask:nil eyeMask:half error:&error],extent);
|
||||||
|
float fullDelta=Difference(base,featureFull,6,40),halfDelta=Difference(base,featureHalf,6,40),noneDelta=Difference(base,featureNone,6,40);
|
||||||
|
Check(fullDelta<.00001 && halfDelta>fullDelta+.001 && noneDelta>halfDelta+.001,@"Five-feature protection has distinct ordered 0/50/100 percent pixel effects");
|
||||||
|
Check(Difference(featureFull,featureNone,56,88)<noneDelta*.25,@"Feature slider primarily changes marked features rather than nearby skin texture");
|
||||||
|
free(featureFull);free(featureHalf);free(featureNone);
|
||||||
|
s=PGBeautyDefaults();s.midtoneDetail=1;s.maskSoftness=0;s.maskEdgeProtect=0;s.featureProtection=0;
|
||||||
|
float *skinFull=Render(context,[renderer processImage:source settings:s skinMask:half teethMask:nil eyeMask:nil error:&error],extent);
|
||||||
|
s.nonSkinProtection=.5;
|
||||||
|
float *skinHalf=Render(context,[renderer processImage:source settings:s skinMask:half teethMask:nil eyeMask:nil error:&error],extent);
|
||||||
|
s.nonSkinProtection=0;
|
||||||
|
float *skinNone=Render(context,[renderer processImage:source settings:s skinMask:half teethMask:nil eyeMask:nil error:&error],extent);
|
||||||
|
Check(Difference(base,skinFull,56,88)<.00001 && Difference(base,skinHalf,56,88)>.001 && Difference(base,skinNone,56,88)>Difference(base,skinHalf,56,88)+.001,@"Non-skin protection progressively admits background into the local smoothing mask");
|
||||||
|
Check(Difference(skinFull,skinNone,6,40)<.00001,@"Non-skin protection leaves already-covered skin strength unchanged");
|
||||||
|
free(skinFull);free(skinHalf);free(skinNone);
|
||||||
|
s.midtoneDetail=0;
|
||||||
|
float *noSmoothing=Render(context,[renderer processImage:source settings:s skinMask:half teethMask:nil eyeMask:nil error:&error],extent);
|
||||||
|
Check(Difference(base,noSmoothing,4,W-4)<.00001,@"Releasing non-skin protection alone does not enable an independent global smoothing effect");free(noSmoothing);
|
||||||
|
s=PGBeautyDefaults();s.microDetail=1;s.maskSoftness=0;s.maskEdgeProtect=0;
|
||||||
|
float *micro=Render(context,[renderer processImage:source settings:s skinMask:half teethMask:nil eyeMask:nil error:&error],extent);
|
||||||
|
Check(Texture(micro,6,40)>Texture(base,6,40)*1.05 && Difference(base,micro,56,88)<.00001,@"Micro detail recovers texture within smoothing support, including without smoothing");
|
||||||
|
free(micro);
|
||||||
|
s=PGBeautyDefaults();s.skinBrightness=.7;s.maskSoftness=0;s.maskEdgeProtect=0;
|
||||||
|
float *colorProtected=Render(context,[renderer processImage:source settings:s skinMask:half colorMask:half teethMask:nil eyeMask:nil error:&error],extent);
|
||||||
|
s.nonSkinProtection=0;s.featureProtectionAmount=0;
|
||||||
|
float *colorReleased=Render(context,[renderer processImage:source settings:s skinMask:half colorMask:half teethMask:nil eyeMask:nil error:&error],extent);
|
||||||
|
Check(Difference(colorProtected,colorReleased,4,W-4)<.00001,@"Both new protection sliders leave skin exposure coverage unchanged");
|
||||||
|
free(colorProtected);free(colorReleased);
|
||||||
|
s=PGBeautyDefaults();s.skinBrightness=.7;s.maskSoftness=0;s.maskEdgeProtect=0;
|
||||||
|
colorProtected=Render(context,[renderer processImage:source settings:s skinMask:white teethMask:half eyeMask:half error:&error],extent);
|
||||||
|
s.featureProtectionAmount=0;s.featureProtection=0;s.nonSkinProtection=0;
|
||||||
|
colorReleased=Render(context,[renderer processImage:source settings:s skinMask:white teethMask:half eyeMask:half error:&error],extent);
|
||||||
|
Check(Difference(colorProtected,colorReleased,4,W-4)<.00001,@"Standalone renderer also keeps eye/teeth protection independent from exposure when the legacy bool changes");
|
||||||
|
free(colorProtected);free(colorReleased);
|
||||||
// A skin exposure adjustment must stay continuous across detail-protection
|
// A skin exposure adjustment must stay continuous across detail-protection
|
||||||
// holes. This models the eye/nose islands reported on the real beach clip.
|
// holes. This models the eye/nose islands reported on the real beach clip.
|
||||||
s=PGBeautyDefaults();s.skinBrightness=1;s.featureProtection=1;
|
s=PGBeautyDefaults();s.skinBrightness=1;s.featureProtection=1;
|
||||||
@@ -192,6 +232,7 @@ int main(void) { @autoreleasepool {
|
|||||||
// Every operation must preserve transparency rather than creating halos/opaque pixels.
|
// Every operation must preserve transparency rather than creating halos/opaque pixels.
|
||||||
for(int i=0;i<W*H;i++){p[i*4]*=0.4;p[i*4+1]*=0.4;p[i*4+2]*=0.4;p[i*4+3]=0.4;}
|
for(int i=0;i<W*H;i++){p[i*4]*=0.4;p[i*4+1]*=0.4;p[i*4+2]*=0.4;p[i*4+3]=0.4;}
|
||||||
CIImage *transparent=Image(p,extent);s=PGBeautyDefaults();s.midtoneDetail=1;s.skinBrightness=1;s.teethWhitening=1;s.eyeBrightening=1;s.skinTonePreset=3;s.highPassDetail=1;
|
CIImage *transparent=Image(p,extent);s=PGBeautyDefaults();s.midtoneDetail=1;s.skinBrightness=1;s.teethWhitening=1;s.eyeBrightening=1;s.skinTonePreset=3;s.highPassDetail=1;
|
||||||
|
s.microDetail=.7;s.nonSkinProtection=.3;s.featureProtectionAmount=.4;
|
||||||
float *alpha=Render(context,[renderer processImage:transparent settings:s skinMask:white teethMask:half eyeMask:half error:&error],extent);
|
float *alpha=Render(context,[renderer processImage:transparent settings:s skinMask:white teethMask:half eyeMask:half error:&error],extent);
|
||||||
BOOL alphaOK=YES,finite=YES;
|
BOOL alphaOK=YES,finite=YES;
|
||||||
for(int i=0;i<W*H;i++){if(fabsf(alpha[i*4+3]-0.4f)>0.0002)alphaOK=NO;for(int c=0;c<4;c++)if(!isfinite(alpha[i*4+c]))finite=NO;}
|
for(int i=0;i<W*H;i++){if(fabsf(alpha[i*4+3]-0.4f)>0.0002)alphaOK=NO;for(int c=0;c<4;c++)if(!isfinite(alpha[i*4+c]))finite=NO;}
|
||||||
|
|||||||
+11
-3
@@ -469,7 +469,9 @@ int main(int argc, const char *argv[]) {
|
|||||||
Check([plugin addParametersWithError:&error] && !error, @"host creates complete parameter set");
|
Check([plugin addParametersWithError:&error] && !error, @"host creates complete parameter set");
|
||||||
UInt32 identifiers[] = {PGAmount,PGBrightness,PGRadius,PGSharpness,PGMix,PGThreshold,PGSoftness,PGEdgeProtect,PGShowMask,PGMidtone,PGHue,PGSaturation,PGHighPass,PGThin,PGFaceBoxes,PGSingleFace,PGSelection,PGShort,PGNarrow,PGChin,PGVFace,PGJaw,PGCheekbone,PGSmallHead,PGSmallHeadRange,PGFeatureProtect,PGTeeth,PGEyeBright,PGBigEye,PGTrackBoxes,PGFastMulti,PGJumpGuard,PGEvenTone,PGTonePreset,PGInvertMask,PGDebugChannel,PGTrackEnabled,PGTarget,PGAnalyzeForward,PGAnalyzeBackward,PGClearTrack,PGStatus,PGTrackData1,PGTrackData2,PGTrackData3,PGSmoothingEnabled,PGColorEnabled,PGShapeEnabled,PGDetailEnabled,PGInspectorPanel};
|
UInt32 identifiers[] = {PGAmount,PGBrightness,PGRadius,PGSharpness,PGMix,PGThreshold,PGSoftness,PGEdgeProtect,PGShowMask,PGMidtone,PGHue,PGSaturation,PGHighPass,PGThin,PGFaceBoxes,PGSingleFace,PGSelection,PGShort,PGNarrow,PGChin,PGVFace,PGJaw,PGCheekbone,PGSmallHead,PGSmallHeadRange,PGFeatureProtect,PGTeeth,PGEyeBright,PGBigEye,PGTrackBoxes,PGFastMulti,PGJumpGuard,PGEvenTone,PGTonePreset,PGInvertMask,PGDebugChannel,PGTrackEnabled,PGTarget,PGAnalyzeForward,PGAnalyzeBackward,PGClearTrack,PGStatus,PGTrackData1,PGTrackData2,PGTrackData3,PGSmoothingEnabled,PGColorEnabled,PGShapeEnabled,PGDetailEnabled,PGInspectorPanel};
|
||||||
for (NSUInteger i = 0; i < sizeof(identifiers) / sizeof(UInt32); i++) Check(api.definitions[@(identifiers[i])] != nil, [NSString stringWithFormat:@"required parameter %u exists", identifiers[i]]);
|
for (NSUInteger i = 0; i < sizeof(identifiers) / sizeof(UInt32); i++) Check(api.definitions[@(identifiers[i])] != nil, [NSString stringWithFormat:@"required parameter %u exists", identifiers[i]]);
|
||||||
Check(api.depth == 0 && !api.duplicate && api.definitions.count == sizeof(identifiers) / sizeof(UInt32) + 6, @"parameter groups close and IDs are unique");
|
for(NSNumber *identifier in @[@(PGFeatureProtectionAmount),@(PGNonSkinProtection),@(PGMicroDetail)])
|
||||||
|
Check(api.definitions[identifier]!=nil,@"new protection/detail float parameter is registered");
|
||||||
|
Check(api.depth == 0 && !api.duplicate && api.definitions.count == sizeof(identifiers) / sizeof(UInt32) + 10, @"parameter groups close and IDs are unique");
|
||||||
Check([api.values[@(PGMidtone)] doubleValue] == 0 && [api.values[@(PGMix)] doubleValue] == 1, @"default detail and mix form identity filter");
|
Check([api.values[@(PGMidtone)] doubleValue] == 0 && [api.values[@(PGMix)] doubleValue] == 1, @"default detail and mix form identity filter");
|
||||||
Check([api.definitions[@(PGShort)][@"min"] doubleValue] == -1 && [api.definitions[@(PGShort)][@"max"] doubleValue] == 1, @"signed face control exposes both directions");
|
Check([api.definitions[@(PGShort)][@"min"] doubleValue] == -1 && [api.definitions[@(PGShort)][@"max"] doubleValue] == 1, @"signed face control exposes both directions");
|
||||||
Check([api.definitions[@(PGTrackData1)][@"flags"] unsignedLongLongValue] & kFxParameterFlag_HIDDEN, @"persistent tracking data stays hidden in host UI");
|
Check([api.definitions[@(PGTrackData1)][@"flags"] unsignedLongLongValue] & kFxParameterFlag_HIDDEN, @"persistent tracking data stays hidden in host UI");
|
||||||
@@ -480,7 +482,7 @@ int main(int argc, const char *argv[]) {
|
|||||||
NSDictionary *decoded = [NSPropertyListSerialization propertyListWithData:state options:0 format:nil error:nil];
|
NSDictionary *decoded = [NSPropertyListSerialization propertyListWithData:state options:0 format:nil error:nil];
|
||||||
Check([decoded[@"tracks"] count] == 3 && [decoded[@"settings"] length] == sizeof(PGBeautySettings), @"snapshot contains fixed settings schema and three target slots");
|
Check([decoded[@"tracks"] count] == 3 && [decoded[@"settings"] length] == sizeof(PGBeautySettings), @"snapshot contains fixed settings schema and three target slots");
|
||||||
PGBeautySettings settings; [decoded[@"settings"] getBytes:&settings length:sizeof(settings)];
|
PGBeautySettings settings; [decoded[@"settings"] getBytes:&settings length:sizeof(settings)];
|
||||||
Check(settings.version == 1 && settings.mix == 1 && settings.midtoneDetail == 0, @"snapshot round-trips default numeric state");
|
Check(settings.version == PGBeautySettingsVersion && settings.mix == 1 && settings.midtoneDetail == 0 && settings.featureProtectionAmount==1 && settings.nonSkinProtection==1 && settings.microDetail==0, @"snapshot round-trips v2 with neutral defaults for the new controls");
|
||||||
api.values[@(PGThin)] = @5; api.values[@(PGRadius)] = @(-9); api.values[@(PGSelection)] = @[@(-1), @2];
|
api.values[@(PGThin)] = @5; api.values[@(PGRadius)] = @(-9); api.values[@(PGSelection)] = @[@(-1), @2];
|
||||||
settings = [plugin settingsAtTime:api.hostTime];
|
settings = [plugin settingsAtTime:api.hostTime];
|
||||||
Check(settings.faceThin == 1 && settings.radius == 1 && settings.selectionX == 0 && settings.selectionY == 1, @"host parameter limits and normalized selection are enforced");
|
Check(settings.faceThin == 1 && settings.radius == 1 && settings.selectionX == 0 && settings.selectionY == 1, @"host parameter limits and normalized selection are enforced");
|
||||||
@@ -493,6 +495,12 @@ int main(int argc, const char *argv[]) {
|
|||||||
NSUInteger difference = MaximumDifference(SurfaceBytes(source), SurfaceBytes(destination));
|
NSUInteger difference = MaximumDifference(SurfaceBytes(source), SurfaceBytes(destination));
|
||||||
printf("Default IOSurface maximum byte difference: %lu\n", (unsigned long)difference);
|
printf("Default IOSurface maximum byte difference: %lu\n", (unsigned long)difference);
|
||||||
Check(difference <= 1, @"default adapter output preserves gamma-video numeric samples");
|
Check(difference <= 1, @"default adapter output preserves gamma-video numeric samples");
|
||||||
|
PGBeautySettings old=PGBeautyDefaults();old.version=1;
|
||||||
|
NSData *legacy=PlistState(@{@"settings":[NSData dataWithBytes:&old length:PGBeautySettingsV1Size],@"tracks":@[@"",@"",@""]});
|
||||||
|
Check(Render(plugin,destination,@[source],legacy,kCMTimeZero,&error,NULL) && MaximumDifference(SurfaceBytes(source),SurfaceBytes(destination))<=1,@"160-byte v1 snapshots migrate with neutral new controls and preserve original pixels");
|
||||||
|
RejectState(plugin,destination,source,PlistState(@{@"settings":[NSData dataWithBytes:&old length:sizeof(old)],@"tracks":@[@"",@"",@""]}),@"v1 with the new structure length is rejected instead of guessing its layout");
|
||||||
|
old.version=PGBeautySettingsVersion;
|
||||||
|
RejectState(plugin,destination,source,PlistState(@{@"settings":[NSData dataWithBytes:&old length:PGBeautySettingsV1Size],@"tracks":@[@"",@"",@""]}),@"v2 with a truncated legacy-sized payload is rejected");
|
||||||
CGColorSpaceRelease(destination.space); destination.space = CGColorSpaceCreateWithName(kCGColorSpaceLinearSRGB);
|
CGColorSpaceRelease(destination.space); destination.space = CGColorSpaceCreateWithName(kCGColorSpaceLinearSRGB);
|
||||||
Check(Render(plugin, destination, @[source], state, kCMTimeZero, &error, NULL), @"numeric host render accepts destination color-space metadata");
|
Check(Render(plugin, destination, @[source], state, kCMTimeZero, &error, NULL), @"numeric host render accepts destination color-space metadata");
|
||||||
difference = MaximumDifference(SurfaceBytes(source), SurfaceBytes(destination));
|
difference = MaximumDifference(SurfaceBytes(source), SurfaceBytes(destination));
|
||||||
@@ -510,7 +518,7 @@ int main(int argc, const char *argv[]) {
|
|||||||
RejectState(plugin, destination, source, PlistState(@{@"settings":[NSData data], @"tracks":@[@"",@"",@""]}), @"wrong settings size is rejected safely");
|
RejectState(plugin, destination, source, PlistState(@{@"settings":[NSData data], @"tracks":@[@"",@"",@""]}), @"wrong settings size is rejected safely");
|
||||||
settings = PGBeautyDefaults(); settings.version = 99;
|
settings = PGBeautyDefaults(); settings.version = 99;
|
||||||
RejectState(plugin, destination, source, PlistState(@{@"settings":[NSData dataWithBytes:&settings length:sizeof(settings)], @"tracks":@[@"",@"",@""]}), @"unknown saved settings version is rejected safely");
|
RejectState(plugin, destination, source, PlistState(@{@"settings":[NSData dataWithBytes:&settings length:sizeof(settings)], @"tracks":@[@"",@"",@""]}), @"unknown saved settings version is rejected safely");
|
||||||
settings.version = 1;
|
settings.version = PGBeautySettingsVersion;
|
||||||
RejectState(plugin, destination, source, PlistState(@{@"settings":[NSData dataWithBytes:&settings length:sizeof(settings)], @"tracks":@[@"",@""]}), @"wrong target slot count is rejected safely");
|
RejectState(plugin, destination, source, PlistState(@{@"settings":[NSData dataWithBytes:&settings length:sizeof(settings)], @"tracks":@[@"",@""]}), @"wrong target slot count is rejected safely");
|
||||||
RejectState(plugin, destination, source, PlistState(@{@"settings":[NSData dataWithBytes:&settings length:sizeof(settings)], @"tracks":@[@"",@7,@""]}), @"non-string target slot is rejected safely");
|
RejectState(plugin, destination, source, PlistState(@{@"settings":[NSData dataWithBytes:&settings length:sizeof(settings)], @"tracks":@[@"",@7,@""]}), @"non-string target slot is rejected safely");
|
||||||
for (NSUInteger top = 0; top < 2; top++) {
|
for (NSUInteger top = 0; top < 2; top++) {
|
||||||
|
|||||||
+112
-14
@@ -52,6 +52,9 @@ static NSControl *FindControl(NSView *view,Class type,NSString *label) {
|
|||||||
}
|
}
|
||||||
static void Fire(NSControl *control) {[control sendAction:control.action to:control.target];}
|
static void Fire(NSControl *control) {[control sendAction:control.action to:control.target];}
|
||||||
static BOOL Near(double a,double b) {return fabs(a-b)<.00001;}
|
static BOOL Near(double a,double b) {return fabs(a-b)<.00001;}
|
||||||
|
static PGInspectorState *State(NSDictionary *values,NSSet *disabled) {
|
||||||
|
return [PGInspectorState stateWithValues:values disabledParameters:disabled?:[NSSet set] hasEdits:YES];
|
||||||
|
}
|
||||||
static void RegistrationTests(void) {
|
static void RegistrationTests(void) {
|
||||||
PGHostAPI *api=[PGHostAPI new];beatygrilPlugIn *plugin=[[beatygrilPlugIn alloc]initWithAPIManager:api];[plugin addParametersWithError:nil];
|
PGHostAPI *api=[PGHostAPI new];beatygrilPlugIn *plugin=[[beatygrilPlugIn alloc]initWithAPIManager:api];[plugin addParametersWithError:nil];
|
||||||
NSUInteger visible=0;for(NSNumber *key in api.definitions){NSDictionary *d=api.definitions[key];if(!([d[@"flags"]unsignedIntValue]&kFxParameterFlag_HIDDEN)){visible++;Check(key.unsignedIntValue==PGInspectorPanel,@"only the custom panel is visible in the native parameter list");}}
|
NSUInteger visible=0;for(NSNumber *key in api.definitions){NSDictionary *d=api.definitions[key];if(!([d[@"flags"]unsignedIntValue]&kFxParameterFlag_HIDDEN)){visible++;Check(key.unsignedIntValue==PGInspectorPanel,@"only the custom panel is visible in the native parameter list");}}
|
||||||
@@ -65,11 +68,47 @@ static void RegistrationTests(void) {
|
|||||||
NSNumber *key=d[@"id"];Check(api.definitions[key]!=nil,@"every inspector control maps to an existing stable parameter ID");
|
NSNumber *key=d[@"id"];Check(api.definitions[key]!=nil,@"every inspector control maps to an existing stable parameter ID");
|
||||||
if([d[@"kind"]isEqual:@"slider"])Check(Near([d[@"min"]doubleValue],[api.definitions[key][@"min"]doubleValue]) && Near([d[@"max"]doubleValue],[api.definitions[key][@"max"]doubleValue]),@"UI and renderer enforce the same slider range");
|
if([d[@"kind"]isEqual:@"slider"])Check(Near([d[@"min"]doubleValue],[api.definitions[key][@"min"]doubleValue]) && Near([d[@"max"]doubleValue],[api.definitions[key][@"max"]doubleValue]),@"UI and renderer enforce the same slider range");
|
||||||
}
|
}
|
||||||
|
NSMutableArray *smoothIDs=[NSMutableArray new];
|
||||||
|
for(NSDictionary *d in PGInspectorControls())if([d[@"section"]isEqual:@"smooth"] && ![d[@"kind"]isEqual:@"master"])[smoothIDs addObject:d[@"id"]];
|
||||||
|
Check([smoothIDs isEqual:@[@(PGMidtone),@(PGMicroDetail),@(PGFeatureProtectionAmount),@(PGNonSkinProtection)]],@"primary smoothing card exposes the four new controls in reference order");
|
||||||
|
Check(PGInspectorDescriptor(PGFeatureProtect)==nil && [PGInspectorDescriptor(PGHighPass)[@"section"]isEqual:@"more"] && [PGInspectorDescriptor(PGSharpness)[@"section"]isEqual:@"more"],@"legacy boolean stays hidden while legacy global smoothing and sharpening remain available in more settings");
|
||||||
api.values[@(PGTrackEnabled)]=@YES;api.values[@(PGSingleFace)]=@YES;api.values[@(PGShowMask)]=@YES;
|
api.values[@(PGTrackEnabled)]=@YES;api.values[@(PGSingleFace)]=@YES;api.values[@(PGShowMask)]=@YES;
|
||||||
PGBeautySettings s=[plugin settingsAtTime:kCMTimeZero];Check(!s.trackingEnabled && !s.singleFace && !s.showMask,@"old manual analysis, selection and debug flags cannot override automatic output");
|
PGBeautySettings s=[plugin settingsAtTime:kCMTimeZero];Check(!s.trackingEnabled && !s.singleFace && !s.showMask,@"old manual analysis, selection and debug flags cannot override automatic output");
|
||||||
NSData *snapshot=Snapshot(plugin,kCMTimeZero);NSDictionary *plist=[NSPropertyListSerialization propertyListWithData:snapshot options:0 format:nil error:nil];
|
NSData *snapshot=Snapshot(plugin,kCMTimeZero);NSDictionary *plist=[NSPropertyListSerialization propertyListWithData:snapshot options:0 format:nil error:nil];
|
||||||
Check([plist[@"tracks"]isEqual:@[@"",@"",@""]],@"automatic render snapshots do not transfer persisted manual-analysis payloads");
|
Check([plist[@"tracks"]isEqual:@[@"",@"",@""]],@"automatic render snapshots do not transfer persisted manual-analysis payloads");
|
||||||
}
|
}
|
||||||
|
static void BeautyControlModelTests(void) {
|
||||||
|
PGInspectorState *defaults=State(@{},nil);
|
||||||
|
Check([defaults valueForParameter:PGFeatureProtectionAmount]==1 && [defaults valueForParameter:PGNonSkinProtection]==1 && [defaults valueForParameter:PGMicroDetail]==0,@"new controls default to full five-feature/non-skin protection and zero micro detail");
|
||||||
|
for(NSNumber *legacy in @[@NO,@YES]) {
|
||||||
|
PGInspectorState *migrated=State(@{@(PGFeatureProtect):legacy,@(PGHighPass):@.63},nil);
|
||||||
|
PGBeautySettings s=PGSettingsWithInspectorState(PGBeautyDefaults(),migrated);
|
||||||
|
Check([migrated valueForParameter:PGFeatureProtectionAmount]==legacy.boolValue && s.featureProtection==legacy.boolValue && Near(s.highPassDetail,.63) && s.nonSkinProtection==1,@"old schema dictionaries migrate five-feature bool without reinterpreting legacy global smoothing");
|
||||||
|
}
|
||||||
|
PGInspectorState *explicit=State(@{@(PGFeatureProtect):@NO,@(PGFeatureProtectionAmount):@.37},nil);
|
||||||
|
Check(Near([explicit valueForParameter:PGFeatureProtectionAmount],.37),@"an explicit numeric protection setting takes precedence over the old boolean");
|
||||||
|
NSDictionary *values=@{@(PGFeatureProtectionAmount):@.25,@(PGNonSkinProtection):@.6,@(PGMicroDetail):@.8,@(PGMidtone):@.7,@(PGHighPass):@.4,@(PGSharpness):@.5,@(PGHue):@12,@(PGBrightness):@.3,@(PGTonePreset):@2};
|
||||||
|
PGInspectorState *a=State(values,nil);
|
||||||
|
NSData *archive=[NSKeyedArchiver archivedDataWithRootObject:a requiringSecureCoding:YES error:nil];
|
||||||
|
PGInspectorState *restored=[NSKeyedUnarchiver unarchivedObjectOfClass:PGInspectorState.class fromData:archive error:nil];
|
||||||
|
Check([restored isEqual:a] && Near([restored valueForParameter:PGFeatureProtectionAmount],.25) && Near([restored valueForParameter:PGNonSkinProtection],.6) && Near([restored valueForParameter:PGMicroDetail],.8),@"secure document archive retains all three independent numeric controls");
|
||||||
|
NSMutableDictionary *right=[values mutableCopy];right[@(PGFeatureProtectionAmount)]=@.75;right[@(PGNonSkinProtection)]=@.2;right[@(PGMicroDetail)]=@.4;
|
||||||
|
PGInspectorState *mid=(PGInspectorState *)[a interpolateBetween:State(right,nil) withWeight:.5];
|
||||||
|
Check(Near([mid valueForParameter:PGFeatureProtectionAmount],.5) && Near([mid valueForParameter:PGNonSkinProtection],.4) && Near([mid valueForParameter:PGMicroDetail],.6),@"new floating-point protection and micro-detail keyframes interpolate independently");
|
||||||
|
for(NSNumber *key in @[@(PGFeatureProtectionAmount),@(PGNonSkinProtection),@(PGMicroDetail)]) {
|
||||||
|
PGInspectorState *bypassed=State(values,[NSSet setWithObject:key]);
|
||||||
|
PGBeautySettings s=PGSettingsWithInspectorState(PGBeautyDefaults(),bypassed);
|
||||||
|
Check(Near(s.featureProtectionAmount,key.unsignedIntValue==PGFeatureProtectionAmount?0:.25) && Near(s.nonSkinProtection,key.unsignedIntValue==PGNonSkinProtection?0:.6) && Near(s.microDetail,key.unsignedIntValue==PGMicroDetail?0:.8),@"each new bypass affects only its own render setting");
|
||||||
|
Check(s.featureProtection==(s.featureProtectionAmount>0) && Near(s.skinHue,12) && Near(s.skinBrightness,.3) && s.skinTonePreset==2 && Near(s.highPassDetail,.4) && Near(s.sharpness,.5),@"protection bypass preserves color and legacy detail settings and derives the old feature bool");
|
||||||
|
}
|
||||||
|
NSMutableDictionary *off=[values mutableCopy];off[@(PGSmoothingEnabled)]=@0;
|
||||||
|
PGBeautySettings s=PGSettingsWithInspectorState(PGBeautyDefaults(),State(off,nil));
|
||||||
|
Check(s.amount==0 && s.midtoneDetail==0 && s.highPassDetail==0 && s.microDetail==0 && Near(s.skinHue,12) && Near(s.skinBrightness,.3),@"smoothing master bypass also disables micro detail while preserving color adjustments");
|
||||||
|
PGInspectorState *untouched=[PGInspectorState stateWithValues:@{@(PGFeatureProtect):@NO} disabledParameters:[NSSet set] hasEdits:NO];
|
||||||
|
PGBeautySettings original=PGBeautyDefaults();original.featureProtectionAmount=.8;original.nonSkinProtection=.2;original.microDetail=.7;
|
||||||
|
PGBeautySettings result=PGSettingsWithInspectorState(original,untouched);
|
||||||
|
Check(memcmp(&original,&result,sizeof(result))==0,@"an untouched migrated inspector still leaves existing document settings unchanged");
|
||||||
|
}
|
||||||
static void ModelTests(void) {
|
static void ModelTests(void) {
|
||||||
PGInspectorState *plain=[PGInspectorState stateWithValues:@{} disabledParameters:[NSSet set] hasEdits:NO];
|
PGInspectorState *plain=[PGInspectorState stateWithValues:@{} disabledParameters:[NSSet set] hasEdits:NO];
|
||||||
PGBeautySettings original=PGBeautyDefaults();original.midtoneDetail=.5;original.faceChin=-.3;
|
PGBeautySettings original=PGBeautyDefaults();original.midtoneDetail=.5;original.faceChin=-.3;
|
||||||
@@ -191,13 +230,51 @@ static void ViewInteractionTests(void) {
|
|||||||
Fire(number);
|
Fire(number);
|
||||||
Check(number.currentEditor==nil && Near([(PGInspectorState *)api.values[@(PGInspectorPanel)] valueForParameter:PGChin],.25),@"Return commits once and releases the actual AppKit editor before host commands");
|
Check(number.currentEditor==nil && Near([(PGInspectorState *)api.values[@(PGInspectorPanel)] valueForParameter:PGChin],.25),@"Return commits once and releases the actual AppKit editor before host commands");
|
||||||
[window close];
|
[window close];
|
||||||
NSButton *preview=(NSButton *)FindControl(view,NSButton.class,@"人脸框预览");preview.state=NSControlStateValueOn;Fire(preview);s=api.values[@(PGInspectorPanel)];Check([s valueForParameter:PGTrackBoxes]==1,@"preview switch events change the automatic face box setting");
|
NSButton *preview=(NSButton *)FindControl(view,NSButton.class,@"检测框预览");preview.state=NSControlStateValueOn;Fire(preview);s=api.values[@(PGInspectorPanel)];Check([s valueForParameter:PGTrackBoxes]==1,@"preview switch events change the automatic face box setting");
|
||||||
for(NSNumber *width in @[@280,@320,@380,@520]) {
|
for(NSNumber *width in @[@280,@320,@380,@520]) {
|
||||||
[view setFrameSize:NSMakeSize(width.doubleValue,20)];[view layoutSubtreeIfNeeded];
|
[view setFrameSize:NSMakeSize(width.doubleValue,20)];[view layoutSubtreeIfNeeded];
|
||||||
Check(view.frame.size.height>500 && Near(view.frame.size.height,view.fittingSize.height) && Near(view.frame.size.height,view.intrinsicContentSize.height),@"width changes produce a complete consistent intrinsic/fitting height");
|
Check(view.frame.size.height>500 && Near(view.frame.size.height,view.fittingSize.height) && Near(view.frame.size.height,view.intrinsicContentSize.height),@"width changes produce a complete consistent intrinsic/fitting height");
|
||||||
for(NSView *card in view.subviews)if(!card.hidden)Check(NSMaxY(card.frame)<=NSHeight(view.bounds)+1 && NSMaxX(card.frame)<=NSWidth(view.bounds)+1,@"every visible card and footer remains inside the panel at all inspector widths");
|
for(NSView *card in view.subviews)if(!card.hidden)Check(NSMaxY(card.frame)<=NSHeight(view.bounds)+1 && NSMaxX(card.frame)<=NSWidth(view.bounds)+1,@"every visible card and footer remains inside the panel at all inspector widths");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
static void BeautyControlInteractionTests(void) {
|
||||||
|
PGInspectorTestAPI *api=[PGInspectorTestAPI new];beatygrilPlugIn *plugin=[[beatygrilPlugIn alloc]initWithAPIManager:api];[plugin addParametersWithError:nil];
|
||||||
|
api.values[@(PGFeatureProtectionAmount)]=@.37;api.values[@(PGHighPass)]=@.63;api.values[@(PGHue)]=@12;api.values[@(PGBrightness)]=@.3;
|
||||||
|
PGInspectorView *view=(PGInspectorView *)[plugin createViewForParameterID:PGInspectorPanel];[view refreshFromHost];
|
||||||
|
Check(Near([view.controller.state valueForParameter:PGFeatureProtectionAmount],.37) && !view.controller.state.hasEdits,@"an untouched inspector preserves native fractional protection when the legacy boolean is enabled");
|
||||||
|
[view.controller setValue:.35 forParameter:PGBrightness];
|
||||||
|
Check(Near([(PGInspectorState *)api.values[@(PGInspectorPanel)] valueForParameter:PGFeatureProtectionAmount],.37) && Near([api.values[@(PGFeatureProtectionAmount)]doubleValue],.37),@"first unrelated edit captures native fractional protection without replacing it with the legacy true value");
|
||||||
|
api.values[@(PGInspectorPanel)]=[PGInspectorState stateWithValues:@{} disabledParameters:[NSSet set] hasEdits:NO];
|
||||||
|
api.values[@(PGFeatureProtect)]=@NO;[view hostParameterChanged];
|
||||||
|
Check([view.controller.state valueForParameter:PGFeatureProtectionAmount]==0 && !view.controller.state.hasEdits,@"untouched inspector imports the old host five-feature boolean into the new numeric control");
|
||||||
|
[view.controller setValue:.35 forParameter:PGBrightness];
|
||||||
|
Check(![api.values[@(PGFeatureProtect)]boolValue] && Near([view.controller.state valueForParameter:PGFeatureProtectionAmount],0),@"first unrelated edit preserves disabled legacy feature protection");
|
||||||
|
NSArray *ids=@[@(PGFeatureProtectionAmount),@(PGNonSkinProtection),@(PGMicroDetail)],*names=@[@"五官保护",@"非肤色区保护",@"微细锐化"];
|
||||||
|
for(NSUInteger i=0;i<ids.count;i++) {
|
||||||
|
UInt32 key=[ids[i]unsignedIntValue];NSString *name=names[i];
|
||||||
|
NSSlider *slider=(NSSlider *)FindControl(view,NSSlider.class,name);
|
||||||
|
NSTextField *number=(NSTextField *)FindControl(view,NSTextField.class,[name stringByAppendingString:@"数值"]);
|
||||||
|
Check(slider && number && slider.enabled && number.enabled,@"each new control exposes an enabled native slider and numeric field");
|
||||||
|
number.stringValue=@"27%";Fire(number);
|
||||||
|
Check(Near([view.controller.state valueForParameter:key],.27) && [number.stringValue isEqual:@"27%"],@"new numeric controls accept direct percentages without inverse interpretation");
|
||||||
|
slider.doubleValue=.42;Fire(slider);
|
||||||
|
Check(Near([view.controller.state valueForParameter:key],.42) && [number.stringValue isEqual:@"42%"],@"actual new slider events publish their direct amount and update the percentage display");
|
||||||
|
NSButton *eye=(NSButton *)FindControl(view,NSButton.class,[@"启用 / 停用 · "stringByAppendingString:name]);Fire(eye);
|
||||||
|
Check(![view.controller.state isParameterEnabled:key] && Near([view.controller.state valueForParameter:key],.42) && !slider.enabled,@"new individual bypass preserves its value and disables its native controls");
|
||||||
|
if(key==PGFeatureProtectionAmount)Check(![api.values[@(PGFeatureProtect)]boolValue],@"bypassed numeric feature protection mirrors false into the legacy hidden boolean");
|
||||||
|
NSButton *reset=(NSButton *)FindControl(view,NSButton.class,[@"重置 · "stringByAppendingString:name]);Fire(reset);
|
||||||
|
Check([view.controller.state isParameterEnabled:key] && [view.controller.state valueForParameter:key]==(key==PGMicroDetail?0:1) && slider.enabled,@"new individual reset restores the proper default and clears bypass");
|
||||||
|
}
|
||||||
|
Check([api.values[@(PGFeatureProtect)]boolValue] && Near([view.controller.state valueForParameter:PGHighPass],.63) && Near([view.controller.state valueForParameter:PGHue],12) && Near([view.controller.state valueForParameter:PGBrightness],.35),@"new resets restore the legacy feature bool without modifying global smoothing, hue or brightness");
|
||||||
|
NSButton *multi=(NSButton *)FindControl(view,NSButton.class,@"多人模式");Check([multi.toolTip containsString:@"易抖动"],@"multi-face mode exposes its instability notice as a tooltip");
|
||||||
|
[view.controller setValue:.6 forParameter:PGMicroDetail];
|
||||||
|
NSButton *master=(NSButton *)FindControl(view,NSButton.class,@"磨皮");master.state=NSControlStateValueOff;Fire(master);
|
||||||
|
PGBeautySettings s=PGSettingsWithInspectorState(PGBeautyDefaults(),view.controller.state);
|
||||||
|
Check(s.microDetail==0 && s.midtoneDetail==0 && s.highPassDetail==0 && Near(s.skinHue,12) && Near(s.skinBrightness,.35),@"actual smoothing master event disables new detail processing independently from whitening");
|
||||||
|
master.state=NSControlStateValueOn;Fire(master);
|
||||||
|
Check(Near(PGSettingsWithInspectorState(PGBeautyDefaults(),view.controller.state).microDetail,.6),@"reenabling the master restores the stored micro-detail amount");
|
||||||
|
Check(api.actionDepth==0 && api.actionStarts==api.actionEnds && api.accessViolations==0,@"all new numeric, bypass, reset and master interactions use balanced host actions");
|
||||||
|
}
|
||||||
static void HostHandshakeTests(void) {
|
static void HostHandshakeTests(void) {
|
||||||
PGInspectorTestAPI *api=[PGInspectorTestAPI new];beatygrilPlugIn *plugin=[[beatygrilPlugIn alloc]initWithAPIManager:api];[plugin addParametersWithError:nil];
|
PGInspectorTestAPI *api=[PGInspectorTestAPI new];beatygrilPlugIn *plugin=[[beatygrilPlugIn alloc]initWithAPIManager:api];[plugin addParametersWithError:nil];
|
||||||
api.values[@(PGMidtone)]=@.5;
|
api.values[@(PGMidtone)]=@.5;
|
||||||
@@ -219,24 +296,45 @@ static void ContinuousPreviewTests(void) {
|
|||||||
PGInspectorTestAPI *api=[PGInspectorTestAPI new];beatygrilPlugIn *plugin=[[beatygrilPlugIn alloc]initWithAPIManager:api];[plugin addParametersWithError:nil];
|
PGInspectorTestAPI *api=[PGInspectorTestAPI new];beatygrilPlugIn *plugin=[[beatygrilPlugIn alloc]initWithAPIManager:api];[plugin addParametersWithError:nil];
|
||||||
api.parameterAPIsRequireAction=YES;api.actionRequiresView=YES;
|
api.parameterAPIsRequireAction=YES;api.actionRequiresView=YES;
|
||||||
PGInspectorView *view=(PGInspectorView *)[plugin createViewForParameterID:PGInspectorPanel];[view refreshFromHost];
|
PGInspectorView *view=(PGInspectorView *)[plugin createViewForParameterID:PGInspectorPanel];[view refreshFromHost];
|
||||||
NSSlider *slider=(NSSlider *)FindControl(view,NSSlider.class,@"短脸");
|
NSArray *names=@[@"短脸",@"五官保护",@"非肤色区保护",@"微细锐化"],*ids=@[@(PGShort),@(PGFeatureProtectionAmount),@(PGNonSkinProtection),@(PGMicroDetail)];
|
||||||
void (^began)(void)=[slider valueForKey:@"interactionBegan"], (^ended)(void)=[slider valueForKey:@"interactionEnded"];
|
for(NSUInteger i=0;i<names.count;i++) {
|
||||||
Check(slider.continuous && began && ended,@"native slider emits continuous events and exposes balanced gesture callbacks");
|
UInt32 key=[ids[i]unsignedIntValue];NSSlider *slider=(NSSlider *)FindControl(view,NSSlider.class,names[i]);
|
||||||
began();
|
void (^began)(void)=[slider valueForKey:@"interactionBegan"], (^ended)(void)=[slider valueForKey:@"interactionEnded"];
|
||||||
for(NSNumber *value in @[@.2,@.5,@1]) {
|
Check(slider.continuous && began && ended,@"native slider emits continuous events and exposes balanced gesture callbacks");
|
||||||
NSUInteger publications=api.previewPublications;slider.doubleValue=value.doubleValue;Fire(slider);
|
began();
|
||||||
PGInspectorState *visible=api.previewValues[@(PGInspectorPanel)];
|
for(NSNumber *value in @[@.2,@.5,@1]) {
|
||||||
Check(api.actionDepth==0 && api.previewPublications==publications+1 && Near([visible valueForParameter:PGShort],value.doubleValue),@"every drag step publishes a completed host snapshot before mouse-up");
|
NSUInteger publications=api.previewPublications;slider.doubleValue=value.doubleValue;Fire(slider);
|
||||||
Check(visible && Near(PGSettingsWithInspectorState(PGBeautyDefaults(),visible).faceShort,value.doubleValue),@"published frame settings contain each intermediate short-face value while gesture is still open");
|
PGInspectorState *visible=api.previewValues[@(PGInspectorPanel)];
|
||||||
|
Check(api.actionDepth==0 && api.previewPublications==publications+1 && Near([visible valueForParameter:key],value.doubleValue),@"every old and new slider step publishes a completed host snapshot before mouse-up");
|
||||||
|
PGBeautySettings s=PGSettingsWithInspectorState(PGBeautyDefaults(),visible);
|
||||||
|
double actual=key==PGShort?s.faceShort:key==PGFeatureProtectionAmount?s.featureProtectionAmount:key==PGNonSkinProtection?s.nonSkinProtection:s.microDetail;
|
||||||
|
Check(visible && Near(actual,value.doubleValue),@"published frame settings contain each intermediate new protection/detail value while gesture is still open");
|
||||||
|
}
|
||||||
|
NSUInteger publications=api.previewPublications;Fire(slider);
|
||||||
|
Check(api.previewPublications==publications,@"unchanged drag events do not enqueue redundant previews");
|
||||||
|
ended();
|
||||||
}
|
}
|
||||||
NSUInteger publications=api.previewPublications;Fire(slider);
|
|
||||||
Check(api.previewPublications==publications,@"unchanged drag events do not enqueue redundant previews");
|
|
||||||
ended();
|
|
||||||
Check(api.actionDepth==0 && api.actionStarts==api.actionEnds && api.accessViolations==0 && api.invalidActionSenders==0,@"step-wise publication preserves action-scoped APIs and the actual view sender");
|
Check(api.actionDepth==0 && api.actionStarts==api.actionEnds && api.accessViolations==0 && api.invalidActionSenders==0,@"step-wise publication preserves action-scoped APIs and the actual view sender");
|
||||||
}
|
}
|
||||||
|
static void OptionalInspectorPreview(void) {
|
||||||
|
NSString *path=NSProcessInfo.processInfo.environment[@"PAINTFCP_INSPECTOR_PNG"];
|
||||||
|
if(!path.length)return;
|
||||||
|
PGHostAPI *api=[PGHostAPI new];beatygrilPlugIn *plugin=[[beatygrilPlugIn alloc]initWithAPIManager:api];[plugin addParametersWithError:nil];
|
||||||
|
api.values[@(PGMidtone)]=@.4;
|
||||||
|
PGInspectorView *view=(PGInspectorView *)[plugin createViewForParameterID:PGInspectorPanel];[view refreshFromHost];
|
||||||
|
[view setFrameSize:NSMakeSize(380,20)];
|
||||||
|
NSWindow *window=[[NSWindow alloc]initWithContentRect:view.bounds styleMask:NSWindowStyleMaskBorderless backing:NSBackingStoreBuffered defer:NO];
|
||||||
|
window.releasedWhenClosed=NO;window.appearance=[NSAppearance appearanceNamed:NSAppearanceNameDarkAqua];window.contentView=view;
|
||||||
|
[window setContentSize:view.frame.size];[view layoutSubtreeIfNeeded];[window displayIfNeeded];
|
||||||
|
NSBitmapImageRep *bitmap=[view bitmapImageRepForCachingDisplayInRect:view.bounds];
|
||||||
|
[view cacheDisplayInRect:view.bounds toBitmapImageRep:bitmap];
|
||||||
|
NSData *png=[bitmap representationUsingType:NSBitmapImageFileTypePNG properties:@{}];
|
||||||
|
Check(png.length>0 && [png writeToFile:path atomically:YES],@"optional offscreen inspector PNG is written to the requested review path");
|
||||||
|
[window close];
|
||||||
|
}
|
||||||
int main(int argc,const char **argv) {@autoreleasepool {
|
int main(int argc,const char **argv) {@autoreleasepool {
|
||||||
[NSApplication sharedApplication];[NSApp setActivationPolicy:NSApplicationActivationPolicyProhibited];
|
[NSApplication sharedApplication];[NSApp setActivationPolicy:NSApplicationActivationPolicyProhibited];
|
||||||
RegistrationTests();ModelTests();ControllerTests();ViewInteractionTests();HostHandshakeTests();ContinuousPreviewTests();
|
RegistrationTests();ModelTests();BeautyControlModelTests();ControllerTests();ViewInteractionTests();BeautyControlInteractionTests();HostHandshakeTests();ContinuousPreviewTests();OptionalInspectorPreview();
|
||||||
if(argc>2) {
|
if(argc>2) {
|
||||||
NSData *inner=[NSData dataWithContentsOfFile:@(argv[1])],*outer=[NSData dataWithContentsOfFile:@(argv[2])];NSError *error=nil;
|
NSData *inner=[NSData dataWithContentsOfFile:@(argv[1])],*outer=[NSData dataWithContentsOfFile:@(argv[2])];NSError *error=nil;
|
||||||
NSKeyedUnarchiver *reader=[[NSKeyedUnarchiver alloc]initForReadingFromData:inner error:&error];reader.requiresSecureCoding=YES;
|
NSKeyedUnarchiver *reader=[[NSKeyedUnarchiver alloc]initForReadingFromData:inner error:&error];reader.requiresSecureCoding=YES;
|
||||||
|
|||||||
在新工单中引用
屏蔽一个用户