4 次代码提交
共修改 33 个文件,包含 13654 行新增和 40 行删除

没有与搜索条件相匹配的文件

+8
查看文件
@@ -28,3 +28,11 @@ xcodebuild -project beatygril/beatygril.xcodeproj -scheme 'Wrapper Application'
瘦脸现固定使用Vision revision 3的76点配置,以17组轮廓到鼻部/瞳孔锚点的局部逆向采样替代脸框水平挤压,包含原版尺寸增益与滑块强度映射。完整点阵贯通实时稳定与离线存储;旧分析记录没有完整76点时跳过瘦脸,重新分析可补齐。其他脸型项仍使用原有近似实现,不能将本次对齐扩张为全部脸型逐像素等价。
离屏验证新增224项:含48组脸部投影/尺寸/歪头与独立17-pin数值场对照、数据恢复和缺点保护;完整回归604项通过。Motion/FCP界面由用户自测。可运行 `python3 scripts/deliver.py --wait-for-hosts --user-tests`,签名公证后只读等待宿主自然退出,安装验证与清理无需操作宿主界面。
## 1.0.6 下巴与图像坐标修复
下巴改为76点中68→38、67→37、66→36的三组局部位移,正值上收、负值延伸,保留原版正负不同系数及尺寸增益。Core Image/IOSurface输入输出移除按TOP_LEFT额外翻转,和原版图像链对齐。修复依据与离屏复现见[下巴与坐标分析](docs/analysis/下巴与坐标分析.md)。七类其余脸型项仍是近似实现。
旧版分析记录无法区分是否包含额外翻转,新版保留原字符串但暂用实时跟随;重新分析即可写入新坐标标记并恢复离线跟踪。最终Motion/FCP画面由用户自行测试。
1.0.6(8)已完成正式签名、Apple公证、票据、安装和模板同步。904项完整回归及独立远程恢复核验通过;最终宿主视觉由用户自测。交付回执在`docs/verification/v1.0.6/`,恢复标签为`v1.0.6`。
+11 -1
查看文件
@@ -12,7 +12,7 @@
```sh
git clone https://dayu.media/git/dayu/paintfcp-fxplug.git
cd paintfcp-fxplug
git switch --detach v1.0.5
git switch --detach v1.0.6
```
需要继续编辑时,基于该提交创建新分支。安装 Xcode 与 Apple FxPlug 4 SDK;盘点版本和构建入口见 README.md。SDK/framework 为 Apple 外部开发依赖,应重新安装匹配版本。证书、私钥和公证凭据需从独立加密备份恢复或重新申请;Git 不包含它们。
@@ -48,3 +48,13 @@ FCP模板从 `Templates/大鱼人像美化/` 恢复,将 `.moef` 和 `Drop Zone
从最终标签恢复后,关闭Motion/FCP,运行 `python3 scripts/deliver.py --wait-for-hosts --user-tests` 可完成签名公证安装、只读宿主退出等待、静态发现及副本清理;它不会操作宿主界面。若上次因非产品错误在Accepted且stapled后停止,可加 `--resume-prepared`,脚本必须重新核对签名CDHash、版本、票据及产品源码未变才会继续。缺少原staging或回执时应完整重新构建,不能伪造回执。
1.0.5瘦脸需要完整76点;旧离线记录缺该字段时保留其余功能并跳过瘦脸。关闭使用跟踪数据可使用当前帧自动76点,或重新分析生成新记录。没有引入原版metallib或CoreML模型,运行只依赖本产品及Apple系统框架。最终安装、配套模板和扫描范围见验证记录。
## 1.0.6 恢复入口
本次签名构建源码SHA为`ad54726b17fabd11f46bc6121e664eac38960a23`,APP版本1.0.6、build8;最终发布标签为`v1.0.6`。恢复该SHA可重建相同产品源码,最终标签另包含交付文档与回执。Release恢复仍用上节同一构建/公证命令、固定Bundle ID和正式安装路径;用户自测模式不会操作Motion/FCP界面。
新增下巴三pin与图像适配轴修复;新分析档案使用`hostCoordinateSpace=iosurface-ci-y-up-v1`。旧未标记档案保留原字符串但回退实时跟随,重新分析后才能用于离线重放,不能直接补字段伪装成新档案。七类剩余脸型项及组合顺序仍有近似边界。
`ad54726b17fabd11f46bc6121e664eac38960a23`已独立从上述远程克隆,87文件的byte/blob/mode与14项资源一致,strict/full fsck和559项核心测试通过,无LFS、子模块或APP产物。临时恢复目录已清理,完整报告见`docs/verification/v1.0.6/recovery-ad54726.json`。
1.0.6已完成正式签名、公证(Accepted ab0271c8-0c4b-4b10-9474-cebe9b55385b)、票据粘附、唯一正式目录安装和配套模板同步;临时APP/旧备份APP/公证ZIP已清理。完整回执及有界副本扫描见`docs/verification/v1.0.6/installed-verification.json`。未操作Motion/FCP界面,最终视觉验收保留为用户自测。
+3 -1
查看文件
@@ -1,6 +1,6 @@
# 大鱼人像美化 FCP 效果模板
版本:1.0.5。滤镜 UUID:`FB14B0E6-59B2-4836-916F-CD0C559E9241`。模板必须与已安装的大鱼人像美化 FxPlug APP 配套使用。
版本:1.0.6。滤镜 UUID:`FB14B0E6-59B2-4836-916F-CD0C559E9241`。模板必须与已安装的大鱼人像美化 FxPlug APP 配套使用。
## 默认数值
@@ -33,3 +33,5 @@ Apple项目、群组、Effect Source和滤镜节点骨架来源于用户自有
本次已经静态核验Source Media→clip→相对文件链存在、文件字节一致、48项发布仍解析、隐藏数据未发布和保存选择状态;修复后已在FCP独立验收项目新增实例,确认输入替换、实际人像和提亮画面正常;Mix=0逐字节直通由宿主适配器IOSurface回归测试覆盖,未将其混称为FCP截图逐像素核验。
1.0.5将瘦脸改为76点驱动的17组局部位移,供用户在Motion/FCP自测最大瘦脸与转脸。旧跟踪记录若没有全点阵,需重新分析后才有瘦脸,或关闭“使用跟踪数据”采用当前帧自动检测。
1.0.6移除Core Image/IOSurface链按宿主原点重复翻转,修正下巴三组局部位移及正负方向。旧版未标记坐标空间的分析记录保留但不再重放,自动回退实时跟随;需要离线记录时重新分析当前目标。
+19 -4
查看文件
@@ -1,6 +1,6 @@
{
"version": "1.0.5",
"sha256": "ba08de6916cd837b3733aa0c6fcb78f2e8224431d25e1310089ae67d18445f2d",
"version": "1.0.6",
"sha256": "30ca8edf36801264c5de2c753c40b720aba8052633620713a12401098fcd2409",
"filterUUID": "FB14B0E6-59B2-4836-916F-CD0C559E9241",
"publishedFunctionalControls": 46,
"informationalControls": 2,
@@ -27,8 +27,9 @@
"date": "2026-10-21",
"FCP": "Restored input-chain template shows supplied real video; effect parameters and forward analysis responded in independent QA project.",
"Motion": "1.0.4 installed formal app loaded the real-video QA project and rendered brightness/geometry with a primary box at different playhead positions; offline tracking disabled.",
"scope": "1.0.5 native visual QA is reserved for user self-testing; no Motion/FCP UI operations in the slimming repair. Historical input/brightness validation remains scoped to prior releases.",
"version105": "Formal 1.0.5 (7) signed/notarized/stapled/installed and PluginKit formal path verified. User closed hosts before replacement; no Motion/FCP UI operated. Native visual testing pending user."
"scope": "1.0.6 coordinate/chin repair native visual QA is reserved for user self-testing; no Motion/FCP UI operations. Historical host tests remain scoped to their versions.",
"version105": "Formal 1.0.5 (7) signed/notarized/stapled/installed and PluginKit formal path verified. User closed hosts before replacement; no Motion/FCP UI operated. Native visual testing pending user.",
"version106": "Formal 1.0.6 (8) universal Release signed/notarized/stapled/installed, identity/Gatekeeper/formal PluginKit path verified. User closed hosts; companion moef and TIFF synchronized and byte-verified. Generated outer APPs and notarization ZIP removed. No Motion/FCP UI operations; native visual QA pending user."
},
"slimValidation": {
"offlineFullChecks": 604,
@@ -36,5 +37,19 @@
"extraRealVideoASanChecks": 264,
"sampledVideoFrames": 11,
"maxUVOracleError": 1.09828022e-05
},
"coordinateChinValidation": {
"offlineFullChecks": 904,
"thinChinChecks": 394,
"hostAdapterChecksASan": 247,
"oscChecksASan": 98,
"extraRealVideoASanChecks": 494,
"sampledVideoFrames": 11,
"independentAnatomicalChecks": 12,
"originCombinations": 16,
"allOriginTagsPixelIdentical": true,
"maxByteErrorVsIndependentCIReference": 2,
"maxUVOracleError": 1.09828022e-05,
"nativeVisualQA": "pending user"
}
}
@@ -46,7 +46,7 @@
<flags>1</flags>
</template>
<build />
<description>大鱼人像美化 1.0.5:人像磨皮、肤色、脸型、五官保护和三目标人脸跟踪。模板中频磨皮默认为50%。</description>
<description>大鱼人像美化 1.0.6:人像磨皮、肤色、脸型、五官保护和三目标人脸跟踪。模板中频磨皮默认为50%。</description>
<scene>
<sceneSettings>
<width>1920</width>
@@ -298,7 +298,7 @@
<parameter name="Width" id="313" flags="8589934610" default="1" value="1920" />
<parameter name="Height" id="314" flags="8589934610" default="1" value="1080" />
</parameter>
<filter name="大鱼人像美化" id="510000005" factoryID="6" pluginUUID="FB14B0E6-59B2-4836-916F-CD0C559E9241" pluginVersion="1.0.5" pluginName="大鱼人像美化" pluginDynamicParams="0">
<filter name="大鱼人像美化" id="510000005" factoryID="6" pluginUUID="FB14B0E6-59B2-4836-916F-CD0C559E9241" pluginVersion="1.0.6" pluginName="大鱼人像美化" pluginDynamicParams="0">
<timing in="0 1 1 0" out="299 30 1 0" offset="0 1 1 0" />
<baseFlags>8589934608</baseFlags>
<parameter name="磨皮" id="100" flags="8589938704">
+4 -4
查看文件
@@ -17,9 +17,9 @@
<key>CFBundlePackageType</key>
<string>XPC!</string>
<key>CFBundleShortVersionString</key>
<string>1.0.5</string>
<string>1.0.6</string>
<key>CFBundleVersion</key>
<string>7</string>
<string>8</string>
<key>NSHumanReadableCopyright</key>
<string></string>
<key>PlugInKit</key>
@@ -72,7 +72,7 @@
<key>uuid</key>
<string>FB14B0E6-59B2-4836-916F-CD0C559E9241</string>
<key>version</key>
<string>1.0.5</string>
<string>1.0.6</string>
</dict>
<dict>
<key>className</key>
@@ -84,7 +84,7 @@
<key>uuid</key>
<string>E0E432C8-3D71-4ABA-B006-E7F519E06C87</string>
<key>version</key>
<string>1.0.5</string>
<string>1.0.6</string>
<key>protocolNames</key>
<array>
<string>FxOnScreenControl</string>
+29 -11
查看文件
@@ -13,6 +13,7 @@ static const uint8_t PGThinPins[17][2] = {
{72,48},{62,48},{71,48},{63,48},{70,49},{64,49},
{69,49},{65,49},{68,52},{66,52},{67,47}
};
static const uint8_t PGChinPins[3][2] = {{68,38},{67,37},{66,36}};
@interface PGFaceFrame ()
@property (nonatomic, copy, readwrite) NSArray<VNFaceObservation *> *faces;
@@ -124,7 +125,7 @@ static NSDictionary *PGSanitizedGeometry(id value) {
result[name] = points;
}
// Old archives and partially detected faces remain usable by the other
// controls. Missing or malformed full topology only disables local thinning.
// controls. Missing or malformed full topology disables local thin/chin pins.
NSArray *points76 = PGSanitizedPoints76(value[@"points76"]);
if (points76) result[@"points76"] = points76;
return [result copy];
@@ -343,8 +344,10 @@ static NSDictionary *PGStableGeometry(NSDictionary *current, NSDictionary *previ
_lock = [[NSLock alloc] init];
_realtime = [PGRealtimeFaceTracker new];
_targets = [NSMutableArray arrayWithObjects:[NSMutableDictionary dictionary], [NSMutableDictionary dictionary], [NSMutableDictionary dictionary], nil];
// Local thinning reconstructs the recovered 17-pin coordinate math.
// The other eight controls retain this project's analytic approximation.
// Local thinning and chin use the recovered 17/3-pin coordinate math.
// The other seven controls retain this project's analytic approximation.
// Chin follows those analytic body controls and precedes the eye warp;
// this combined order is not a reconstruction of the full reference chain.
// Each pin changes the sampling coordinate, with only one image resample.
NSMutableString *source = [NSMutableString stringWithString:
@"vec2 pgThinPin(vec2 q, vec4 control, float ratio) {"
@@ -352,8 +355,9 @@ static NSDictionary *PGStableGeometry(NSDictionary *current, NSDictionary *previ
" if(radius<=0.0001) return q;"
" float weight=clamp(1.0-length(q-control.xy)/radius,0.0,1.0);"
" return clamp(q-direction*ratio*weight,vec2(0.0),vec2(1.0)); }"
" kernel vec2 pgFaceWarp(vec2 c, vec2 size, vec2 le, vec2 re, vec2 er, float thinRatio, float bigEye, float shortFace, float narrow, float chin, float vFace, float jaw, float cheek, float smallHead, float headRange, vec2 imageOrigin, vec2 imageSize"];
" kernel vec2 pgFaceWarp(vec2 c, vec2 size, vec2 le, vec2 re, vec2 er, float thinRatio, float bigEye, float shortFace, float narrow, float chinRatio, float vFace, float jaw, float cheek, float smallHead, float headRange, vec2 imageOrigin, vec2 imageSize"];
for (NSUInteger i=0; i<17; i++) [source appendFormat:@", vec4 pin%lu",(unsigned long)i];
for (NSUInteger i=0; i<3; i++) [source appendFormat:@", vec4 chinPin%lu",(unsigned long)i];
[source appendString:@") { vec2 p=destCoord();"
" if(thinRatio>0.000001) { vec2 q=(p-imageOrigin)/imageSize; q.y=1.0-q.y;"];
for (NSUInteger i=0; i<17; i++) [source appendFormat:@" q=pgThinPin(q,pin%lu,thinRatio);",(unsigned long)i];
@@ -367,7 +371,10 @@ static NSDictionary *PGStableGeometry(NSDictionary *current, NSDictionary *previ
" float head = 1.0-smoothstep(0.38,0.75+0.32*headRange,length(n*vec2(0.9,0.65)));"
" p += (p-c)*smallHead*0.18*head;"
" p.x += size.x*n.x*(narrow*0.30*body + vFace*0.24*jawBand + jaw*0.20*jawBand + cheek*0.18*cheekBand)*sides;"
" float cn=n.x/0.30; p.y += size.y*(-shortFace*0.16*lower + chin*0.13*exp(-cn*cn)*jawBand - vFace*0.025*lower);"
" p.y += size.y*(-shortFace*0.16*lower - vFace*0.025*lower);"
" if(abs(chinRatio)>0.000001) { vec2 q=(p-imageOrigin)/imageSize; q.y=1.0-q.y;"];
for (NSUInteger i=0; i<3; i++) [source appendFormat:@" q=pgThinPin(q,chinPin%lu,chinRatio);",(unsigned long)i];
[source appendString:@" q.y=1.0-q.y; p=imageOrigin+q*imageSize; }"
" vec2 dl = (p-le)/er; float wl = 1.0-smoothstep(0.0,1.0,length(dl));"
" p -= (p-le)*bigEye*0.33*wl*wl;"
" vec2 dr = (p-re)/er; float wr = 1.0-smoothstep(0.0,1.0,length(dr));"
@@ -491,7 +498,7 @@ static NSDictionary *PGStableGeometry(NSDictionary *current, NSDictionary *previ
for (NSDictionary *face in geometry) {
CGRect box = PGArrayRect(face[@"box"]), pixels = PGToPixels(box, extent);
NSArray *points76 = PGSanitizedPoints76(face[@"points76"]);
float thinRatio = 0;
float thinRatio = 0, chinRatio = 0;
if (points76) {
double width = hypot(([points76[75][0] doubleValue]-[points76[59][0] doubleValue])*extent.size.width,
([points76[75][1] doubleValue]-[points76[59][1] doubleValue])*extent.size.height);
@@ -501,18 +508,20 @@ static NSDictionary *PGStableGeometry(NSDictionary *current, NSDictionary *previ
([points76[67][1] doubleValue]-midY)*extent.size.height);
double gain = fmin(2.25,fmax(1,280/fmax(1,fmax(width,height))));
thinRatio = PGClamp(s.faceThin,0,1)*.025*gain;
float chin = PGClamp(s.faceChin,-1,1);
chinRatio = chin*(chin>0 ? .04 : .02)*gain;
}
BOOL otherControls = s.faceBigEye || s.faceShort || s.faceNarrow || s.faceChin || s.faceV || s.faceJaw || s.faceCheekbone || s.faceSmallHead;
// No full topology means no guessed bbox thinning. In particular, legacy
// archives with thinning alone remain an exact image/mask pass-through.
if (thinRatio <= .000001 && !otherControls) continue;
BOOL otherControls = s.faceBigEye || s.faceShort || s.faceNarrow || s.faceV || s.faceJaw || s.faceCheekbone || s.faceSmallHead;
// No full topology means no guessed bbox thin/chin deformation. Legacy
// archives using only those controls remain exact image/mask pass-through.
if (thinRatio <= .000001 && fabsf(chinRatio) <= .000001 && !otherControls) continue;
CGRect le = PGRegionBounds(face, @"leftEye", PGInFace(box, 0.16, 0.53, 0.25, 0.15));
CGRect re = PGRegionBounds(face, @"rightEye", PGInFace(box, 0.59, 0.53, 0.25, 0.15));
CGPoint left = PGCenter(PGToPixels(le, extent)), right = PGCenter(PGToPixels(re, extent));
NSMutableArray *arguments = [@[PGVector(PGCenter(pixels)), [CIVector vectorWithX:pixels.size.width Y:pixels.size.height],
PGVector(left), PGVector(right), [CIVector vectorWithX:MAX(2, pixels.size.width * 0.24) Y:MAX(2, pixels.size.height * 0.17)],
@(thinRatio), @(PGClamp(s.faceBigEye, 0, 1)), @(PGClamp(s.faceShort, -1, 1)),
@(PGClamp(s.faceNarrow, -1, 1)), @(PGClamp(s.faceChin, -1, 1)), @(PGClamp(s.faceV, 0, 1)),
@(PGClamp(s.faceNarrow, -1, 1)), @(chinRatio), @(PGClamp(s.faceV, 0, 1)),
@(PGClamp(s.faceJaw, 0, 1)), @(PGClamp(s.faceCheekbone, 0, 1)), @(PGClamp(s.faceSmallHead, 0, 1)), @(PGClamp(s.faceSmallHeadRange, 0, 1)),
[CIVector vectorWithX:extent.origin.x Y:extent.origin.y], [CIVector vectorWithX:extent.size.width Y:extent.size.height]] mutableCopy];
for (NSUInteger i=0; i<17; i++) {
@@ -524,6 +533,15 @@ static NSDictionary *PGStableGeometry(NSDictionary *current, NSDictionary *previ
}
[arguments addObject:[CIVector vectorWithX:ox Y:oy Z:ax W:ay]];
}
for (NSUInteger i=0; i<3; i++) {
double ox=0,oy=0,ax=0,ay=0;
if (points76) {
NSArray *origin=points76[PGChinPins[i][0]],*anchor=points76[PGChinPins[i][1]];
ox=[origin[0] doubleValue];oy=1-[origin[1] doubleValue];
ax=[anchor[0] doubleValue];ay=1-[anchor[1] doubleValue];
}
[arguments addObject:[CIVector vectorWithX:ox Y:oy Z:ax W:ay]];
}
CIImage *input = [result imageByClampingToExtent];
result = [_warp applyWithExtent:extent roiCallback:^CGRect(int index, CGRect rect) { (void)index; (void)rect; return extent; } inputImage:input arguments:arguments];
if (!result) return nil;
+32 -7
查看文件
@@ -52,15 +52,29 @@ static BOOL PGFail(NSError **error, NSString *message) {
userInfo:@{NSLocalizedDescriptionKey:message}];
return NO;
}
static NSString * const PGHostCoordinateSpace = @"iosurface-ci-y-up-v1";
static BOOL PGCompatibleHostTrack(NSData *data) {
if (!data.length || data.length > 128*1024*1024) return NO;
NSDictionary *archive = [NSPropertyListSerialization propertyListWithData:data options:NSPropertyListImmutable format:nil error:nil];
return [archive isKindOfClass:NSDictionary.class] && [archive[@"hostCoordinateSpace"] isEqual:PGHostCoordinateSpace];
}
static NSData *PGMarkHostTrack(NSData *data) {
if (!data) return nil;
NSMutableDictionary *archive = [NSPropertyListSerialization propertyListWithData:data options:NSPropertyListMutableContainers format:nil error:nil];
if (![archive isKindOfClass:NSMutableDictionary.class]) return nil;
archive[@"hostCoordinateSpace"] = PGHostCoordinateSpace;
return [NSPropertyListSerialization dataWithPropertyList:archive format:NSPropertyListBinaryFormat_v1_0 options:0 error:nil];
}
static CIImage *PGImageFromTile(FxImageTile *tile) {
if (!tile.ioSurface) return nil;
// The host already supplies video-gamma RGB. Keep those numeric values in
// every stage; implicit Core Image linearization changes mask thresholds.
NSDictionary *options = @{kCIImageColorSpace:NSNull.null};
CIImage *image = [CIImage imageWithIOSurface:(__bridge IOSurfaceRef)tile.ioSurface options:options];
if (tile.imageOrigin == kFxImageOrigin_TOP_LEFT) {
image = [image imageByApplyingTransform:CGAffineTransformMake(1,0,0,-1,0,image.extent.size.height)];
}
// Match the reference's CI/IOSurface path. imageOrigin describes the host's
// coordinate axes; it is not an instruction to reverse IOSurface scanlines.
// An extra input/output flip can hide in passthrough while inverting faces
// inside Vision and the analytic shape/mask stages.
FxRect b = tile.tilePixelBounds;
return [image imageByApplyingTransform:CGAffineTransformMakeTranslation(b.left,b.bottom)];
}
@@ -76,6 +90,7 @@ static CIImage *PGImageFromTile(FxImageTile *tile) {
@property BOOL analyzing;
@property BOOL analysisForward;
@property (strong) NSArray<NSString *> *loadedTracks;
@property uint32_t compatibleTrackMask;
@property BOOL reportedRender;
@end
@@ -239,13 +254,22 @@ static CIImage *PGImageFromTile(FxImageTile *tile) {
@synchronized(_faceProcessor) {
NSArray *tracks=plist[@"tracks"];
if(![tracks isKindOfClass:NSArray.class]||tracks.count!=3) return PGFail(error,@"跟踪状态无效。");
for(id track in tracks) if(![track isKindOfClass:NSString.class]) return PGFail(error,@"跟踪记录无效。");
if(![_loadedTracks isEqualToArray:tracks]) {
_compatibleTrackMask=0;
for(NSUInteger i=0;i<3;i++) {
if(![tracks[i] isKindOfClass:NSString.class]) return PGFail(error,@"跟踪记录无效。");
[_faceProcessor setTrackingData:[[NSData alloc] initWithBase64EncodedString:tracks[i] options:0] forTarget:i];
NSData *data=[[NSData alloc] initWithBase64EncodedString:tracks[i] options:0];
BOOL compatible=PGCompatibleHostTrack(data);
if(compatible) _compatibleTrackMask |= 1u<<i;
[_faceProcessor setTrackingData:compatible?data:nil forTarget:i];
}
_loadedTracks=[tracks copy];
}
// Previous releases did not record which image-axis contract their
// analysis used. Never replay potentially inverted coordinates.
// Preserve the user's saved data and use realtime follow until they
// reanalyze that target under this coordinate contract.
if(s.trackingEnabled && !(_compatibleTrackMask & (1u<<MIN(s.targetIndex,2)))) s.trackingEnabled=0;
NSError *stageError=nil;
PGFaceFrame *frame=[_faceProcessor processImage:input settings:s time:time error:&stageError];
if(!frame) { os_log_error(PGRenderLog(), "face processing failed: %{public}@",stageError); if(error)*error=stageError; return NO; }
@@ -263,7 +287,6 @@ static CIImage *PGImageFromTile(FxImageTile *tile) {
FxRect b=destination.tilePixelBounds;
output=[output imageByApplyingTransform:CGAffineTransformMakeTranslation(-b.left,-b.bottom)];
size_t width=IOSurfaceGetWidth((__bridge IOSurfaceRef)destination.ioSurface),height=IOSurfaceGetHeight((__bridge IOSurfaceRef)destination.ioSurface);
if(destination.imageOrigin==kFxImageOrigin_TOP_LEFT) output=[output imageByApplyingTransform:CGAffineTransformMake(1,0,0,-1,0,height)];
@try {[context render:output toIOSurface:(__bridge IOSurfaceRef)destination.ioSurface bounds:CGRectMake(0,0,width,height) colorSpace:nil];}
@catch(NSException *exception) {return PGFail(error,exception.reason?:@"图像处理失败。");}
return YES;
@@ -287,7 +310,8 @@ static CIImage *PGImageFromTile(FxImageTile *tile) {
// A separate processor owns the mutable analysis session. Render snapshots
// cannot overwrite records being accumulated by the analyzer.
_analysisFaceProcessor=[PGFaceProcessor new];
[_analysisFaceProcessor setTrackingData:[[NSData alloc] initWithBase64EncodedString:tracks[s.targetIndex] options:0] forTarget:s.targetIndex];
NSData *previous=[[NSData alloc] initWithBase64EncodedString:tracks[s.targetIndex] options:0];
[_analysisFaceProcessor setTrackingData:PGCompatibleHostTrack(previous)?previous:nil forTarget:s.targetIndex];
_analysisSettings=s;_analysisSettings.trackingEnabled=1;_analysisSettings.singleFace=1;
_analysisForward=forward;_analyzing=YES;
BOOL ok=forward?[api startForwardAnalysis:kFxAnalysisLocation_CPU error:&error]:[api startBackwardAnalysis:kFxAnalysisLocation_CPU error:&error];
@@ -332,6 +356,7 @@ static CIImage *PGImageFromTile(FxImageTile *tile) {
id<FxParameterSettingAPI_v5> api=[_apiManager apiForProtocol:@protocol(FxParameterSettingAPI_v5)];
NSData *data=nil;
@synchronized(_analysisFaceProcessor) {data=[_analysisFaceProcessor trackingDataForTarget:_analysisSettings.targetIndex];}
data=PGMarkHostTrack(data);
@synchronized(_faceProcessor) {_loadedTracks=nil;}
if(data.length>64*1024*1024) {
_analyzing=NO;
@@ -17,9 +17,9 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0.5</string>
<string>1.0.6</string>
<key>CFBundleVersion</key>
<string>7</string>
<string>8</string>
<key>LSMinimumSystemVersion</key>
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
<key>NSHumanReadableCopyright</key>
+75
查看文件
@@ -0,0 +1,75 @@
{
"date": "2026-10-21",
"target_sha256": "f01e8c0b7e2676b2961950b0e48efdd2589af218e4b36de7d39f5d1c76852bfb",
"claim_type": "static CPU assembly/constants and Metal AIR reconstruction, independently tested; no native-host observation",
"chin_pins": [
[
68,
38
],
[
67,
37
],
[
66,
36
]
],
"chin_parameter_index": 5,
"signed_slider_range": [
-1,
1
],
"positive_scale": 0.04,
"negative_scale": 0.02,
"cpu_evidence": [
"ev_640ccb714c5ce00d3864844af319fc574d223810dde08443d2ef2e8f1e989c47",
"ev_8bc31392c07788d2482546121297c3f177d527282275ef21fd3e5edef9921544"
],
"air_sha256": "f97705b88f5491057df2117aef457ffca84e11020574cd57742b0670db4484d8",
"air_file_local_ignored": "build/pose-analysis/face-thin.air.txt",
"coordinate_contract": "Vision normalized y-up; upload pins and sampling coordinate converted once to y-down UV, result returned to CI y-up",
"gain": "clamp(280/max(length((P75-P59)*imageSize),2*length((P67-(P74+P60)/2)*imageSize),1),1,2.25)",
"pin_formula": "q=clamp(q-(anchor-origin)*ratio*clamp(1-distance(q,origin)/distance(anchor,origin),0,1),0,1)",
"positive_visual_direction": "chin toward lower lip (shorter)",
"negative_visual_direction": "chin away from lower lip (longer)",
"verification": {
"beauty": 49,
"face": 116,
"thin_chin": 394,
"failures": 0,
"maximum_uv_oracle_error": 1.09828022e-05,
"realVideoASanChecks": 494,
"realVideoFrames": 11,
"independentAnatomicalChecks": 12,
"fullChecks": 904,
"hostAdapterASanChecks": 247,
"oscASanChecks": 98,
"allOriginTagsPixelIdentical": true,
"maxByteErrorVsIndependentCIReference": 2,
"oldFlipNegativeControl": {
"checks": 120,
"expectedFailures": 30,
"topTopDefaultMaxByteError": 0,
"topTopBigEyeMaxByteError": 66,
"topTopChinPositiveMaxByteError": 52,
"topTopChinNegativeMaxByteError": 21,
"scope": "temporary current source with only two old flips restored; three-pin chin already fixed; not same as old Gaussian eye-hotspot counterexample"
}
},
"limitations": [
"Seven other shape controls remain approximations.",
"Combined analytic controls and multi-face resampling order is not fully reference equivalent.",
"Motion/FCP final visual validation is reserved for user."
],
"adapterCorrection": {
"change": "remove imageOrigin-based input/output flips to match reference CI/IOSurface path",
"legacyTrackPolicy": "preserve unmarked strings but use realtime follow; reanalysis writes hostCoordinateSpace=iosurface-ci-y-up-v1",
"referenceOutputEvidence": [
"ev_88a71ec62fade296495f1e3f5428cdb15ad4cda43866e09fb040fa3a4bdb0440",
"ev_110d76f7958decb0c56b7daafe292d60a8bc56cd035b40a997f61395cb91ebfe"
],
"nativeBufferCaptured": false
}
}
+1
查看文件
@@ -0,0 +1 @@
{"artifacts":[{"architecture":"arm64","digest":{"sha256":"f01e8c0b7e2676b2961950b0e48efdd2589af218e4b36de7d39f5d1c76852bfb"},"format":"mach-o"}],"captures":[],"environments":[],"providers":[{"id":"ghidra","name":"Ghidra","version":"12.1.4"}],"records":[{"analysis_profile":{"digest":"20565088524a2d0359b9b96f46ac48e679d774c7b0092c56abc06de8fdbec11c","parameters":{"analyzer_preset":"ghidra-default","annotation_policy":"atomic-function-entry-metadata-v1","architecture":"arm64","available_architectures":["arm64"],"compiler_spec_id":"auto-default","decompiler_jump_loads":true,"function_body_evidence":"complete-inclusive-ranges-v1","function_boundary_observations":"ghidra-terminal-call-limitations-v1","function_references":"complete-body-and-entry-reference-manager-v2","import_mode":"ephemeral-source-immutable","instruction_flow_evidence":"decoded-return-pcode-v1","jump_table_evidence":"typed-case-default-blocks-v1","language_id":"auto-from-header","load_image_observations":"source-mappings-entry-context-v2","loader":"auto-from-header","location_resolution":"explicit-address-exact-entry-symbol-first-v3","no_return_repair":"returning-imports-decoded-return-v2","process_launch":"inspected-jvm-launch-support-v1","string_inventory_evidence":"defined-data-coverage-v1","target_format":"mach-o","target_kind":"executable"},"provider":{"id":"ghidra","name":"Ghidra","version":"12.1.4"}},"authority":"shipped-artifact","confidence":"observed","environment":null,"evidence_id":"ev_640ccb714c5ce00d3864844af319fc574d223810dde08443d2ef2e8f1e989c47","evidence_links":[],"limitations":["The session serves operations only after Ghidra reports default auto-analysis complete; incomplete analysis does not expose partial results.","The imported Program and temporary project are ephemeral and deleted on close. Annotation operations edit session database metadata; original executable bytes are never written.","Cancelling an active Ghidra request terminates its ephemeral database and discards session annotations. The selected target remains open; the next provider query imports the original artifact again.","Default-space addresses use lowercase 0x-prefixed hexadecimal; other address spaces use <percent-encoded-space>:0x<hex>.","Bytes reflect Ghidra's initialized memory, including loader fixups. Reads stop at an unmapped or uninitialized byte and report completeness explicitly.","macOS sessions require a matching executable Ghidra native decompiler; REA checks for it but does not build native components or change Gatekeeper quarantine state."],"locations":[],"normalized_result":{"address":"0x10005f9c0","bytes_hex":"7b14ae47e17a943f7b14ae47e17aa43f","complete":true,"requested_bytes":16,"returned_bytes":16},"operation":"read_bytes","parameters":{"address":"0x10005f9c0","length":16},"predicate_type":"rea.analysis","provider":{"id":"ghidra","name":"Ghidra","version":"12.1.4"},"raw_result":{"address":"0x10005f9c0","bytes_hex":"7b14ae47e17a943f7b14ae47e17aa43f","complete":true,"requested_bytes":16,"returned_bytes":16},"subject":{"architecture":"arm64","digest":{"sha256":"f01e8c0b7e2676b2961950b0e48efdd2589af218e4b36de7d39f5d1c76852bfb"},"format":"mach-o","local_path":"/private/tmp/paintfcp-rea.b3j9Te/MSE18V2-arm64","name":"MSE18V2-arm64"}},{"analysis_profile":{"digest":"20565088524a2d0359b9b96f46ac48e679d774c7b0092c56abc06de8fdbec11c","parameters":{"analyzer_preset":"ghidra-default","annotation_policy":"atomic-function-entry-metadata-v1","architecture":"arm64","available_architectures":["arm64"],"compiler_spec_id":"auto-default","decompiler_jump_loads":true,"function_body_evidence":"complete-inclusive-ranges-v1","function_boundary_observations":"ghidra-terminal-call-limitations-v1","function_references":"complete-body-and-entry-reference-manager-v2","import_mode":"ephemeral-source-immutable","instruction_flow_evidence":"decoded-return-pcode-v1","jump_table_evidence":"typed-case-default-blocks-v1","language_id":"auto-from-header","load_image_observations":"source-mappings-entry-context-v2","loader":"auto-from-header","location_resolution":"explicit-address-exact-entry-symbol-first-v3","no_return_repair":"returning-imports-decoded-return-v2","process_launch":"inspected-jvm-launch-support-v1","string_inventory_evidence":"defined-data-coverage-v1","target_format":"mach-o","target_kind":"executable"},"provider":{"id":"ghidra","name":"Ghidra","version":"12.1.4"}},"authority":"shipped-artifact","confidence":"observed","environment":null,"evidence_id":"ev_8bc31392c07788d2482546121297c3f177d527282275ef21fd3e5edef9921544","evidence_links":[],"limitations":["The session serves operations only after Ghidra reports default auto-analysis complete; incomplete analysis does not expose partial results.","The imported Program and temporary project are ephemeral and deleted on close. Annotation operations edit session database metadata; original executable bytes are never written.","Cancelling an active Ghidra request terminates its ephemeral database and discards Line truncated
文件差异过大,未显示。 加载差异
+42
查看文件
@@ -0,0 +1,42 @@
# 下巴控制与图像坐标核对
2026-10-21,接用户反馈“调整下巴却调整眼睛”。本轮不操作、启动或退出Motion/FCP。以已保存的原版AIR和新REA CPU汇编/常量证据继续分析;没有执行原版或复制其二进制到产品。
## 已确认的下巴差异
参数发布、PGChin=24、settings.faceChin与GPU参数位置没有串到大眼。1.0.5的下巴仍用脸框高斯场,存在两项错误:作用范围延伸到嘴部甚至眼睑,下巴正负方向与原版相反。独立真实照片上,旧公式的最大位移出现在嘴附近;侧脸真实下巴位移接近零。这些结论来自离屏照片/坐标场探针,不能代替实际宿主当帧诊断。
原版`0x1000279f4`汇编将下巴滑块约束到[-1,1],正值乘0.04、负值乘0.02,写入脸型结构第5项(byte offset20);常量位于`0x10005f9c0`。REA记录:`ev_8bc31392c07788d2482546121297c3f177d527282275ef21fd3e5edef9921544`与`ev_640ccb714c5ce00d3864844af319fc574d223810dde08443d2ef2e8f1e989c47`,完整小型证据包见[rea-chin.json](rea-chin.json)。原件身份与上一轮一致,arm64切片SHA-256为`f01e8c0b7e2676b2961950b0e48efdd2589af218e4b36de7d39f5d1c76852bfb`。
`mssFaceShapePinCoordinate`的AIR分支读取第5项及共同尺寸增益,顺序执行68→38、67→37、66→36三组局部采样位移。独立Vision区域核对:66/67/68在下巴轮廓,36/37/38在下唇。其半径、线性衰减、逐步clamp与17组瘦脸相同;支持负值。视觉正值缩短下巴、负值拉长,方向随下巴→嘴唇向量变化,不固定沿画面Y轴。细节和边界见[机器报告](chin-report.json)。
## 修正与测试
已移除旧下巴高斯项,改用原版三组点、正负系数、尺寸增益。完整76点缺失或不合法时跳过下巴,避免旧档案使用猜测框位置;其他七类脸型控制仍是现有近似实现,组合顺序未宣称全部恢复。
核心回归559项通过:美化49、人脸116、瘦脸/下巴394。新增正负下巴、半强度、左右侧脸投影、歪头、三种宽高比、非零原点、瘦脸叠加、坏点与旧档案保护。独立double采样场最大UV误差1.09828022e-5。真实NASA人像额外检查下巴位移、正负向量符号和两侧瞳孔保持不动;以坐标位移判定,避免只检查“像素有改变”。
## 图像适配层待核对项
纯CI图像中,76点落在真实瞳孔和下巴,没有全局Y轴反置。但FxPlug适配层曾根据imageOrigin在输入、输出各翻转一次。以相同物理top-first照片字节离屏构造TOP_LEFT输入输出时,旧代码默认直通仍精确一致,却能复现下巴作用到眼部的现象;这说明直通测试无法排除中间处理倒置。
imageOrigin描述坐标轴方向,不能单凭枚举推断IOSurface的物理行序。原版render主入口包装CIImage后仅见正尺度和位移,仍需核对其输出helper,不能仅凭主函数的有界检索宣称原版整个链条无翻转。原生FCP本次实际缓冲行序未捕获,最后视觉交由用户自测。临时照片、完整人脸坐标和探针输出均留在被忽略的build目录。
## 原版输出链闭环与适配修复
继续REA分析两个输出helper后,确认主入口所见的无额外翻转不是仅靠字符串缺失作出的猜测:
- `0x10002911c`直接调用`CIContext render:toIOSurface:bounds:colorSpace:`,没有修改图像方向。证据`ev_88a71ec62fade296495f1e3f5428cdb15ad4cda43866e09fb040fa3a4bdb0440`。
- `0x1000291b4`备用路径先render到BGRA8/RGBAh/RGBAf位图,再按源、目的行步长同时正向递增复制到IOSurface,没有倒行。证据`ev_110d76f7958decb0c56b7daafe292d60a8bc56cd035b40a997f61395cb91ebfe`,另有汇编交叉核对。完整记录见[rea-image-orientation.json](rea-image-orientation.json)。
已移除我们输入/输出两处基于TOP_LEFT的额外翻转,保持与原版同类CI→IOSurface路径一致。独立探针在相同物理top-first字节下验证默认/大眼/下巴正负×四种源/目的标签共16组,全部与upright CI参考逐字节一致;旧代码在相同构造下出现下巴到眼部的热点,且默认直通0误差不能揭示内部倒置。这里验证的是物理缓冲构造与修复链,未捕获本次实际FCP缓冲。
Apple[FxImageOrigin](https://developer.apple.com/documentation/professional_video_applications/fximageorigin)和本机SDK说明其描述坐标轴方向;[FxPlug渲染说明](https://developer.apple.com/documentation/professional-video-applications/rendering-in-fxplug)确认FxPlug4经IOSurface传入图像。文档本身未规定每个宿主的物理行序,本次改动依据为原版链和独立物理测试,不能把文档扩张成原生内存捕获。
旧档案没有记录图像适配坐标契约,无法安全判断是否由旧翻转链产生。adapter现为新分析结果加`hostCoordinateSpace=iosurface-ci-y-up-v1`;未标记记录保留原字符串,渲染和续分析均不载入旧坐标。当前目标暂用实时跟随,新分析后恢复离线记录。不改通用PGFaceProcessor的旧档案读取能力,也不把用户记录直接删除或静默改写成新坐标。
独立生理热点检查:NASA、实际素材0秒、4秒侧脸三张图均在真实下巴1.2–2.9像素内获得最大位移,实际下巴正值上收、负值延伸,双瞳孔采样位移不超过0.00003像素;12项专项检查通过。另同一素材11帧共494项瘦脸/下巴ASan检查通过,含重复基础用例,不与完整回归简单相加。用户照片、帧及完整点位均没有入库。
## 新回归的负控
在临时副本只恢复旧的输入/输出翻转、保留已修正三pin后,120项解剖子测试产生30个预期失败:混合原点标签出现方向错误,TOP/TOP默认仍0误差,但大眼最大差66/255、下巴正值52/255、负值21/255,均被独立参考和跨标签逐字节一致性检出。此时三pin下巴热点仍在下巴,因为点驱动形变可随图像反射;“眼部热点”是此前旧高斯下巴与额外翻转共同构成的反例,二者不能混为同一代码状态。正式当前实现247项宿主适配ASan检查全部通过。
@@ -82,3 +82,5 @@ xcrun clang -fobjc-arc -Wall -Wextra \
## 后续修复状态
1.0.5已实现并安装76点/17组局部瘦脸,旧的矩形框瘦脸已移除。上文“安装版未修复”为本次分析开始时1.0.4的历史状态。新增独立数值场与真实素材回归,签名公证及安装记录见 `../验证记录.md`。Motion/FCP最终视觉由用户自测,其他8类脸型控制与快速多人重叠的既有近似边界仍保留。
1.0.6继续分析下巴与宿主图像轴,新增原版三pin下巴及CI/IOSurface无额外翻转修复,详见[下巴与坐标分析](下巴与坐标分析.md)。其余七类控制仍为近似实现。
@@ -0,0 +1,15 @@
{
"checks": 12,
"cases": 3,
"includes_profile": true,
"processor_sha256": "fe4f2a6fc28799cc9b81832baaa2d6739320bde9def175ecc8a4494d004d6b28",
"max_pupil_sampling_displacement_px": 2.8653327774463833e-05,
"peak_distance_to_real_chin_px": [
1.1827483260479077,
2.8140858161593
],
"positive_visual_direction": "toward lower lip",
"negative_visual_direction": "away from lower lip",
"actual_photos_and_full_geometry": "ignored local build only",
"scope": "standalone actual PNG anatomy; bypasses FxPlug adapter; no native host operation"
}
+5
查看文件
@@ -0,0 +1,5 @@
Beauty renderer: 49 checks, 0 failures (Metal).
2026-10-21 20:48:04.999 face-tests[9473:5455578] FBBA: creating VNFaceBBoxAligner from VNFaceDetectorRevision2: VNFaceDetectorRevision2
Face processing: 116 checks passed
2026-10-21 20:48:08.750 slim-tests[9480:5455684] FBBA: creating VNFaceBBoxAligner from VNFaceDetectorRevision2: VNFaceDetectorRevision2
PASS: 394 slim alignment checks; maximum UV oracle error 1.09828022e-05
@@ -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": "ad54726b17fabd11f46bc6121e664eac38960a23",
"trackedWorktreeChanges": []
},
"updatedAt": "2026-10-21T13:53:56.294416+00:00",
"version": "1.0.6",
"build": "8",
"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=66df95f2ce931ca96f52ee5cfa5ad664be49a6c8\nCandidateCDHashFull sha256=66df95f2ce931ca96f52ee5cfa5ad664be49a6c89b8cc1c206d68f0c162b8348\nHash choices=sha256\nCMSDigest=66df95f2ce931ca96f52ee5cfa5ad664be49a6c89b8cc1c206d68f0c162b8348\nCMSDigestType=2\nCDHash=66df95f2ce931ca96f52ee5cfa5ad664be49a6c8\nSignature size=8969\nAuthority=Developer ID Application: zhong luo (B3W25FX5H5)\nAuthority=Developer ID Certification Authority\nAuthority=Apple Root CA\nTimestamp=Oct 21, 2026 at 20:57:26\nInfo.plist entries=22\nTeamIdentifier=B3W25FX5H5\nRuntime Version=27.0.0\nSealed Resources version=2 rules=13 files=4\nInternal requirements count=1 size=180\nTotal signatures=1\nChosen signature=1\n",
"notarization": {
"id": "ab0271c8-0c4b-4b10-9474-cebe9b55385b",
"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=66df95f2ce931ca96f52ee5cfa5ad664be49a6c8\nCandidateCDHashFull sha256=66df95f2ce931ca96f52ee5cfa5ad664be49a6c89b8cc1c206d68f0c162b8348\nHash choices=sha256\nCMSDigest=66df95f2ce931ca96f52ee5cfa5ad664be49a6c89b8cc1c206d68f0c162b8348\nCMSDigestType=2\nCDHash=66df95f2ce931ca96f52ee5cfa5ad664be49a6c8\nSignature size=8969\nAuthority=Developer ID Application: zhong luo (B3W25FX5H5)\nAuthority=Developer ID Certification Authority\nAuthority=Apple Root CA\nTimestamp=Oct 21, 2026 at 20:57:26\nNotarization Ticket=stapled\nInfo.plist entries=22\nTeamIdentifier=B3W25FX5H5\nRuntime Version=27.0.0\nSealed Resources version=2 rules=13 files=4\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)\tA9F36A44-1E5E-4AB3-A2FD-5C66A76DB8BB\t2026-10-21 13:53:56 +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,48 @@
2026-10-21 20:56:23.542 negative-control[10726:5467718] FBBA: creating VNFaceBBoxAligner from VNFaceDetectorRevision2: VNFaceDetectorRevision2
FAIL: default sourceBOTTOM/destinationTOP matches independent upright CI output
FAIL: default sourceBOTTOM/destinationTOP is byte-identical across coordinate tags
FAIL: default sourceBOTTOM/destinationTOP preserves the independently decoded photo
FAIL: default sourceTOP/destinationBOTTOM matches independent upright CI output
FAIL: default sourceTOP/destinationBOTTOM is byte-identical across coordinate tags
FAIL: default sourceTOP/destinationBOTTOM preserves the independently decoded photo
FAIL: big-eye sourceBOTTOM/destinationTOP matches independent upright CI output
FAIL: big-eye sourceBOTTOM/destinationTOP is byte-identical across coordinate tags
FAIL: big-eye sourceBOTTOM/destinationTOP changes the two real eye regions
FAIL: big-eye sourceTOP/destinationBOTTOM matches independent upright CI output
FAIL: big-eye sourceTOP/destinationBOTTOM is byte-identical across coordinate tags
FAIL: big-eye sourceTOP/destinationBOTTOM changes the two real eye regions
FAIL: big-eye sourceTOP/destinationTOP matches independent upright CI output
FAIL: big-eye sourceTOP/destinationTOP is byte-identical across coordinate tags
FAIL: chin-positive sourceBOTTOM/destinationTOP matches independent upright CI output
FAIL: chin-positive sourceBOTTOM/destinationTOP is byte-identical across coordinate tags
FAIL: chin-positive sourceBOTTOM/destinationTOP changes the real chin rather than eyes or mirrored anatomy
FAIL: chin-positive sourceTOP/destinationBOTTOM matches independent upright CI output
FAIL: chin-positive sourceTOP/destinationBOTTOM is byte-identical across coordinate tags
FAIL: chin-positive sourceTOP/destinationBOTTOM changes the real chin rather than eyes or mirrored anatomy
FAIL: chin-positive sourceTOP/destinationTOP matches independent upright CI output
FAIL: chin-positive sourceTOP/destinationTOP is byte-identical across coordinate tags
FAIL: chin-negative sourceBOTTOM/destinationTOP matches independent upright CI output
FAIL: chin-negative sourceBOTTOM/destinationTOP is byte-identical across coordinate tags
FAIL: chin-negative sourceBOTTOM/destinationTOP changes the real chin rather than eyes or mirrored anatomy
FAIL: chin-negative sourceTOP/destinationBOTTOM matches independent upright CI output
FAIL: chin-negative sourceTOP/destinationBOTTOM is byte-identical across coordinate tags
FAIL: chin-negative sourceTOP/destinationBOTTOM changes the real chin rather than eyes or mirrored anatomy
FAIL: chin-negative sourceTOP/destinationTOP matches independent upright CI output
FAIL: chin-negative sourceTOP/destinationTOP is byte-identical across coordinate tags
Anatomy default 0/0 max=0 mean=0.0000000 eye=0/0 chin=0 inverted=0
Anatomy default 0/1 max=255 mean=75.5590439 eye=512210/458308 chin=640530 inverted=2678038
Anatomy default 1/0 max=255 mean=75.5590439 eye=512210/458308 chin=640530 inverted=2678038
Anatomy default 1/1 max=0 mean=0.0000000 eye=0/0 chin=0 inverted=0
Anatomy big-eye 0/0 max=2 mean=0.0008917 eye=33510/33880 chin=0 inverted=0
Anatomy big-eye 0/1 max=255 mean=75.5509195 eye=512210/458308 chin=640530 inverted=2673702
Anatomy big-eye 1/0 max=255 mean=75.5510902 eye=512210/458308 chin=640530 inverted=2673881
Anatomy big-eye 1/1 max=66 mean=0.0180340 eye=30841/32051 chin=0 inverted=0
Anatomy chin-positive 0/0 max=1 mean=0.0001459 eye=0/0 chin=116812 inverted=0
Anatomy chin-positive 0/1 max=255 mean=75.5574808 eye=512210/458308 chin=640530 inverted=2671244
Anatomy chin-positive 1/0 max=255 mean=75.5593863 eye=512210/458308 chin=640530 inverted=2671753
Anatomy chin-positive 1/1 max=52 mean=0.0127611 eye=0/0 chin=121860 inverted=0
Anatomy chin-negative 0/0 max=1 mean=0.0000849 eye=0/0 chin=60426 inverted=0
Anatomy chin-negative 0/1 max=255 mean=75.5518637 eye=512210/458308 chin=640530 inverted=2681787
Anatomy chin-negative 1/0 max=255 mean=75.5510559 eye=512210/458308 chin=640530 inverted=2681767
Anatomy chin-negative 1/1 max=21 mean=0.0061512 eye=0/0 chin=63433 inverted=0
Restored origin flips negative control: 120 checks, 30 expected failures
+21
查看文件
@@ -0,0 +1,21 @@
2026-10-21 20:55:46.681 host-tests[10689:5467036] FBBA: creating VNFaceBBoxAligner from VNFaceDetectorRevision2: VNFaceDetectorRevision2
Default IOSurface maximum byte difference: 0
Alternate destination tag maximum byte difference: 0
Anatomy default 0/0 max=0 mean=0.0000000 eye=0/0 chin=0 inverted=0
Anatomy default 0/1 max=0 mean=0.0000000 eye=0/0 chin=0 inverted=0
Anatomy default 1/0 max=0 mean=0.0000000 eye=0/0 chin=0 inverted=0
Anatomy default 1/1 max=0 mean=0.0000000 eye=0/0 chin=0 inverted=0
Anatomy big-eye 0/0 max=2 mean=0.0008917 eye=33510/33880 chin=0 inverted=0
Anatomy big-eye 0/1 max=2 mean=0.0008917 eye=33510/33880 chin=0 inverted=0
Anatomy big-eye 1/0 max=2 mean=0.0008917 eye=33510/33880 chin=0 inverted=0
Anatomy big-eye 1/1 max=2 mean=0.0008917 eye=33510/33880 chin=0 inverted=0
Anatomy chin-positive 0/0 max=1 mean=0.0001459 eye=0/0 chin=116812 inverted=0
Anatomy chin-positive 0/1 max=1 mean=0.0001459 eye=0/0 chin=116812 inverted=0
Anatomy chin-positive 1/0 max=1 mean=0.0001459 eye=0/0 chin=116812 inverted=0
Anatomy chin-positive 1/1 max=1 mean=0.0001459 eye=0/0 chin=116812 inverted=0
Anatomy chin-negative 0/0 max=1 mean=0.0000849 eye=0/0 chin=60426 inverted=0
Anatomy chin-negative 0/1 max=1 mean=0.0000849 eye=0/0 chin=60426 inverted=0
Anatomy chin-negative 1/0 max=1 mean=0.0000849 eye=0/0 chin=60426 inverted=0
Anatomy chin-negative 1/1 max=1 mean=0.0000849 eye=0/0 chin=60426 inverted=0
Legacy fallback upright comparison: max=2 mean=0.0010357
Host adapter: 247 checks, 0 failures
@@ -0,0 +1,2 @@
com.dayu.beatygril.fxplug(1.1) A9F36A44-1E5E-4AB3-A2FD-5C66A76DB8BB 2026-10-21 13:53:56 +0000 /Library/Plug-Ins/FxPlug/beatygril.app/Contents/PlugIns/beatygril XPC Service.pluginkit
(1 plug-in)
@@ -0,0 +1,24 @@
Executable=/Library/Plug-Ins/FxPlug/beatygril.app/Contents/MacOS/beatygril
Identifier=com.dayu.beatygril
Format=app bundle with Mach-O universal (x86_64 arm64)
CodeDirectory v=20500 size=478 flags=0x10000(runtime) hashes=4+7 location=embedded
Hash type=sha256 size=32
CandidateCDHash sha256=66df95f2ce931ca96f52ee5cfa5ad664be49a6c8
CandidateCDHashFull sha256=66df95f2ce931ca96f52ee5cfa5ad664be49a6c89b8cc1c206d68f0c162b8348
Hash choices=sha256
CMSDigest=66df95f2ce931ca96f52ee5cfa5ad664be49a6c89b8cc1c206d68f0c162b8348
CMSDigestType=2
CDHash=66df95f2ce931ca96f52ee5cfa5ad664be49a6c8
Signature size=8969
Authority=Developer ID Application: zhong luo (B3W25FX5H5)
Authority=Developer ID Certification Authority
Authority=Apple Root CA
Timestamp=Oct 21, 2026 at 20:57:26
Notarization Ticket=stapled
Info.plist entries=22
TeamIdentifier=B3W25FX5H5
Runtime Version=27.0.0
Sealed Resources version=2 rules=13 files=4
Internal requirements count=1 size=180
Total signatures=1
Chosen signature=1
@@ -0,0 +1,81 @@
{
"version": "1.0.6",
"build": "8",
"uiOperations": false,
"nativeVisualQA": "pending user",
"installationPath": "/Library/Plug-Ins/FxPlug/beatygril.app",
"codesign": {
"exitCode": 0,
"stdout": "",
"stderr": ""
},
"staple": {
"exitCode": 0,
"stdout": "Processing: /Library/Plug-Ins/FxPlug/beatygril.app\nThe validate action worked!",
"stderr": ""
},
"gatekeeper": {
"exitCode": 0,
"stdout": "",
"stderr": "/Library/Plug-Ins/FxPlug/beatygril.app: accepted\nsource=Notarized Developer ID"
},
"identity": [
{
"path": "/Library/Plug-Ins/FxPlug/beatygril.app",
"bundleIdentifier": "com.dayu.beatygril",
"version": "1.0.6",
"build": "8",
"archs": "x86_64 arm64",
"signature": "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=66df95f2ce931ca96f52ee5cfa5ad664be49a6c8\nCandidateCDHashFull sha256=66df95f2ce931ca96f52ee5cfa5ad664be49a6c89b8cc1c206d68f0c162b8348\nHash choices=sha256\nCMSDigest=66df95f2ce931ca96f52ee5cfa5ad664be49a6c89b8cc1c206d68f0c162b8348\nCMSDigestType=2\nCDHash=66df95f2ce931ca96f52ee5cfa5ad664be49a6c8\nSignature size=8969\nAuthority=Developer ID Application: zhong luo (B3W25FX5H5)\nAuthority=Developer ID Certification Authority\nAuthority=Apple Root CA\nTimestamp=Oct 21, 2026 at 20:57:26\nNotarization Ticket=stapled\nInfo.plist entries=22\nTeamIdentifier=B3W25FX5H5\nRuntime Version=27.0.0\nSealed Resources version=2 rules=13 files=4\nInternal requirements count=1 size=180\nTotal signatures=1\nChosen signature=1\n"
},
{
"path": "/Library/Plug-Ins/FxPlug/beatygril.app/Contents/PlugIns/beatygril XPC Service.pluginkit",
"bundleIdentifier": "com.dayu.beatygril.fxplug",
"version": "1.0.6",
"build": "8",
"archs": "x86_64 arm64",
"signature": "Executable=/Library/Plug-Ins/FxPlug/beatygril.app/Contents/PlugIns/beatygril XPC Service.pluginkit/Contents/MacOS/beatygril XPC Service\nIdentifier=com.dayu.beatygril.fxplug\nFormat=bundle with Mach-O universal (x86_64 arm64)\nCodeDirectory v=20500 size=645 flags=0x10000(runtime) hashes=13+3 location=embedded\nHash type=sha256 size=32\nCandidateCDHash sha256=ded7ed887bb9c90fbf0c9e9b25a5b279c97e1da6\nCandidateCDHashFull sha256=ded7ed887bb9c90fbf0c9e9b25a5b279c97e1da6809e5a5bf42bd0dd56d42dfd\nHash choices=sha256\nCMSDigest=ded7ed887bb9c90fbf0c9e9b25a5b279c97e1da6809e5a5bf42bd0dd56d42dfd\nCMSDigestType=2\nCDHash=ded7ed887bb9c90fbf0c9e9b25a5b279c97e1da6\nSignature size=8969\nAuthority=Developer ID Application: zhong luo (B3W25FX5H5)\nAuthority=Developer ID Certification Authority\nAuthority=Apple Root CA\nTimestamp=Oct 21, 2026 at 20:57:25\nInfo.plist entries=26\nTeamIdentifier=B3W25FX5H5\nRuntime Version=27.0.0\nSealed Resources version=2 rules=13 files=4\nInternal requirements count=1 size=188\nTotal signatures=1\nChosen signature=1\n"
}
],
"template": {
"previousTemplateSHA256": "ba08de6916cd837b3733aa0c6fcb78f2e8224431d25e1310089ae67d18445f2d",
"files": [
{
"path": "/Users/dayu/Movies/Motion Templates.localized/Effects.localized/大鱼/大鱼人像美化/大鱼人像美化.moef",
"bytes": 22996,
"sha256": "30ca8edf36801264c5de2c753c40b720aba8052633620713a12401098fcd2409"
},
{
"path": "/Users/dayu/Movies/Motion Templates.localized/Effects.localized/大鱼/大鱼人像美化/Drop Zone.tiff",
"bytes": 420128,
"sha256": "a7f8e3a2a431dbb3ce9e777040bb55e701cfe9ab9632e8cbb10359732bc8692e"
}
],
"installedWithHostsClosed": true
},
"discovery": " com.dayu.beatygril.fxplug(1.1)\tA9F36A44-1E5E-4AB3-A2FD-5C66A76DB8BB\t2026-10-21 13:53:56 +0000\t/Library/Plug-Ins/FxPlug/beatygril.app/Contents/PlugIns/beatygril XPC Service.pluginkit\n (1 plug-in)\n",
"duplicateInventory": {
"scopes": [
"/Users/dayu/Documents/PaintFCP",
"/Users/dayu/.codex/worktrees",
"/Users/dayu/Library/Developer/Xcode/DerivedData",
"/Users/dayu/Desktop",
"/Users/dayu/Downloads",
"/Applications",
"/Library/Plug-Ins/FxPlug",
"/Users/dayu/Library/Plug-Ins/FxPlug",
"/private/tmp/paintfcp-format-probe.PsxvYQ",
"/private/tmp/paintfcp-y-review.KpPi8T",
"/private/tmp/paintfcp-live-review.ItH4EG",
"/private/tmp/paintfcp-slim-field.6fDdJj",
"/private/tmp/paintfcp-chin-smoke.rfco16hq",
"/private/tmp/paintfcp-rea.b3j9Te",
"/private/tmp/paintfcp-slim-smoke.3ncdDe"
],
"matchingOuterApps": [
"/Library/Plug-Ins/FxPlug/beatygril.app"
],
"errors": []
},
"status": "installed, formal identity/ticket/discovery/template verified; user visual QA pending"
}
@@ -0,0 +1 @@
{"id":"ab0271c8-0c4b-4b10-9474-cebe9b55385b","message":"Processing complete","status":"Accepted"}
+21
查看文件
@@ -0,0 +1,21 @@
2026-10-21 20:53:14.697 osc-tests[10458:5463835] PGFaceOSC point-write desired=(0.25,0.75) setter=(0.25,0.75) readback=(0.25,0.75) mode=normalized verified=1
2026-10-21 20:53:14.705 osc-tests[10458:5463835] PGFaceOSC coordinates selection=(0.25,0.75) objectBounds=(-100,-50,200,100) canvas=(80,180) surface=320x240 origin=0
2026-10-21 20:53:15.118 osc-tests[10458:5463835] PGFaceOSC point-write desired=(0.8,0.3) setter=(1152,-432) readback=(0.8,0.3) mode=centered-pixels verified=1
2026-10-21 20:53:15.118 osc-tests[10458:5463835] PGFaceOSC coordinates selection=(0.7,0.4) objectBounds=(-1920,-1080,3840,2160) canvas=(152,78) surface=320x240 origin=0
2026-10-21 20:53:15.168 osc-tests[10458:5463835] PGFaceOSC point-write desired=(0.8,0.3) setter=(0.8,0.3) readback=(0.12345,0.87654) mode=normalized verified=0
2026-10-21 20:53:15.168 osc-tests[10458:5463835] PGFaceOSC point-write desired=(0.75,0.25) setter=(0.75,0.25) readback=(0.75,0.25) mode=normalized verified=1
2026-10-21 20:53:15.168 osc-tests[10458:5463835] PGFaceOSC coordinates selection=(0.75,0.25) objectBounds=(-100,-50,200,100) canvas=(160,60) surface=320x240 origin=0
2026-10-21 20:53:15.218 osc-tests[10458:5463835] PGFaceOSC point-write desired=(0.625,0.375) setter=(0.625,0.375) readback=(0.625,0.375) mode=normalized verified=1
2026-10-21 20:53:15.218 osc-tests[10458:5463835] PGFaceOSC coordinates selection=(0.625,0.375) objectBounds=(-100,-50,200,100) canvas=(240,60) surface=320x240 origin=0
2026-10-21 20:53:15.268 osc-tests[10458:5463835] PGFaceOSC point-write desired=(0.25,0.75) setter=(0.25,0.75) readback=(0.25,0.75) mode=normalized verified=1
2026-10-21 20:53:15.268 osc-tests[10458:5463835] PGFaceOSC coordinates selection=(0.25,0.75) objectBounds=(-100,-50,200,100) canvas=(144,180) surface=320x240 origin=0
2026-10-21 20:53:15.318 osc-tests[10458:5463835] PGFaceOSC point-write desired=(0.5,0.5) setter=(0.5,0.5) readback=(0.5,0.5) mode=normalized verified=1
2026-10-21 20:53:15.318 osc-tests[10458:5463835] PGFaceOSC coordinates selection=(0,1) objectBounds=(-100,-50,200,100) canvas=(100,140) surface=320x240 origin=0
2026-10-21 20:53:15.369 osc-tests[10458:5463835] PGFaceOSC coordinates selection=(0.25,0.75) objectBounds=(-100,-50,200,100) canvas=(nan,nan) surface=320x240 origin=0
2026-10-21 20:53:15.418 osc-tests[10458:5463835] PGFaceOSC point-write desired=(0.5,0.5) setter=(0.5,0.5) readback=(0.5,0.5) mode=normalized verified=1
2026-10-21 20:53:15.418 osc-tests[10458:5463835] PGFaceOSC coordinates selection=(0.25,0.75) objectBounds=(-100,-50,200,100) canvas=(inf,nan) surface=320x240 origin=0
2026-10-21 20:53:15.467 osc-tests[10458:5463835] PGFaceOSC point-write desired=(0.5,0.5) setter=(0.5,0.5) readback=(0.5,0.5) mode=normalized verified=1
2026-10-21 20:53:15.467 osc-tests[10458:5463835] PGFaceOSC coordinates selection=(0.25,0.75) objectBounds=(-100,-50,200,100) canvas=(80,nan) surface=320x240 origin=0
2026-10-21 20:53:15.517 osc-tests[10458:5463835] PGFaceOSC point-write desired=(0.5,0.5) setter=(0.5,0.5) readback=(0.5,0.5) mode=normalized verified=1
2026-10-21 20:53:15.517 osc-tests[10458:5463835] PGFaceOSC point-write desired=(0.5,0.5) setter=(0.5,0.5) readback=(0.5,0.5) mode=normalized verified=1
OSC adapter: 98 checks, 0 failures
@@ -0,0 +1,5 @@
Beauty renderer: 49 checks, 0 failures (Metal).
2026-10-21 20:58:16.457 face-tests[11385:5472531] FBBA: creating VNFaceBBoxAligner from VNFaceDetectorRevision2: VNFaceDetectorRevision2
Face processing: 116 checks passed
2026-10-21 20:58:19.882 slim-tests[11391:5472584] FBBA: creating VNFaceBBoxAligner from VNFaceDetectorRevision2: VNFaceDetectorRevision2
PASS: 394 slim alignment checks; maximum UV oracle error 1.09828022e-05
仅有空白字符变更。
文件差异过大,未显示。 加载差异
@@ -0,0 +1,22 @@
PASS: independent remote recovery
Remote: https://dayu.media/git/dayu/paintfcp-fxplug.git
SHA: ad54726b17fabd11f46bc6121e664eac38960a23
Temporary clone: /tmp/paintfcp-recovery-ad54726.nARdL7/checkout
Temporary clone removed: True
Tracked files: 87
Git blob / checked-out bytes / modes mismatches: 0
Git fsck --strict --full exit: 0
LFS pointers: 0; submodules: 0
Resource files verified: 14; missing required: 0
Drop Zone.tiff: Templates/大鱼人像美化/Drop Zone.tiff: TIFF image data, big-endian, direntries=21, height=1200, bps=0, compression=LZW, PhotometricIntepretation=RGB, orientation=upper-left, width=1200
Core test exit: 0
Core counts: Beauty49 + Face116 + Slim394 =559; failures0
Maximum UV oracle error: 1.09828022e-05
Core test duration: 5.394s
ASan requested: false; sanitizer diagnostics: 0
Restored worktree after tests: clean
APP outputs:0; no xcodebuild build / host operations / root source edits / push
All modes exactly match Git; scripts/test.sh, test-host.sh, test-osc.sh and deliver.py restore executable.
External Apple SDK/frameworks are installed on this machine and must be installed separately when recovering.
Finished UTC: 2026-10-21T12:58:52.192540+00:00
Detailed JSON: /Users/dayu/Documents/PaintFCP/build/pose-analysis/recovery-v106/recovery-ad54726.json
@@ -0,0 +1,2 @@
2026-10-21 20:50:27.412 slim-chin-asan[10123:5460221] FBBA: creating VNFaceBBoxAligner from VNFaceDetectorRevision2: VNFaceDetectorRevision2
PASS: 494 slim alignment checks; maximum UV oracle error 1.09828022e-05
+20
查看文件
@@ -61,3 +61,23 @@ Motion:独立普通视频项目,源PTS0、1920×1080/25fps/10秒,媒体通
额外用用户视频11帧(含4秒及18秒明显侧脸)运行264项瘦脸ASan检查,通过且未发现内存错误;此数包含重复的220项基础数值/数据检查,不与604简单相加。独立恢复从远程abe97ed0cbd5a2b5ffda6ebdb486d3bff9170964克隆,69文件/9资源和模式一致、strict fsck通过,clone内389项Metal/人脸/瘦脸测试通过。签名构建源码就是该节点;后续交付脚本补充了已公证产物校验恢复与重复注销兼容,产品源码未再变化。
正式安装APP仅位于 `/Library/Plug-Ins/FxPlug/beatygril.app`;配套1.0.5模板及占位资源已同步并逐字节核验。临时构建、旧版APP备份、公证ZIP已清理;项目、DerivedData、桌面、下载、Applications及系统/用户FxPlug目录扫描只发现该正式APP,无扫描错误。原生效果验收状态为用户自测待完成。票据、发现、恢复与检测报告见 `docs/verification/v1.0.5/`。
## 1.0.6 下巴与图像轴修复(构建前)
用户反馈“下巴调整作用到眼睛”。纯CI76点物理位置并未全局反置,但FxPlug适配器曾将TOP_LEFT当作物理行序,在输入和输出各多翻转一次;默认直通会抵消,无法验证中间Vision和变形的解剖方向。独立top-first照片构造复现下巴跑向眼部;原版REA输入及两条CI→IOSurface输出路径均没有同样额外翻转,现删除这两处。未捕获本次实际FCP缓冲,原生视觉由用户自测。
下巴另有旧框高斯算法和正负符号错误,现按原版68→38、67→37、66→36三pin、正0.04/负0.02和共同尺寸增益对齐。数学场、真实图物理下巴/瞳孔、正负符号与侧脸热点均已检查;其余七类控制及全部组合顺序未宣称完整恢复。新分析记录写入显式坐标标记;旧未标记记录保留但不再重放,自动回退实时跟随,重新分析可恢复离线记录。
本轮不操作、启动或退出Motion/FCP。完整分析证据见[下巴与坐标分析](analysis/下巴与坐标分析.md)。完整904项通过(美化49、人脸116、瘦脸/下巴394、宿主适配247、OSC98),宿主适配和OSC启用ASan。宿主适配16种默认/大眼/下巴正负×源/目的原点组合彼此逐字节一致,与独立upright文件参考最大差2/255;独立文件解码和IOSurface检测路径存在微小数值差,阈值另限制均差<0.005/255,并检查实际解剖热点。额外用户视频11帧494项ASan通过,含重复基础用例,不与904简单相加。用户帧和完整点位未上传远程。当前正式APP仍为1.0.5,不能将构建前验证描述为1.0.6已安装。
1.0.6(8)通用Release的签名构建源码为`ad54726b17fabd11f46bc6121e664eac38960a23`,构建开始时工作区干净。Developer ID逐层签名/严格验证、Apple公证Accepted(ab0271c8-0c4b-4b10-9474-cebe9b55385b)、票据粘附/验证均已通过。检测到Motion仍运行,当前在产品交付锁内只读等待用户保存退出,尚未替换正式1.0.5。
签名源码已从大鱼远程独立克隆恢复:87个受跟踪文件的字节、blob和模式全部一致,14项资源完整,strict/full fsck通过,无LFS/子模块;clone内559项核心测试通过,未构建APP或操作宿主,临时克隆已清理。报告在`verification/v1.0.6/recovery-ad54726.json`。
## 1.0.6 正式安装完成
用户确认保存并退出Motion/FCP后,安装流程完成1.0.6(8)替换,正式APP为`/Library/Plug-Ins/FxPlug/beatygril.app`。安装版APP与嵌套插件均为x86_64/arm64,Developer ID团队B3W25FX5H5、可信时间戳和Hardened Runtime正确;codesign严格验证、票据验证、Gatekeeper与PluginKit正式路径发现通过。全过程没有操作、启动或退出宿主界面,原生视觉仍由用户自测。
配套1.0.6效果模板及Drop Zone.tiff已在宿主退出时同步并逐字节核验;moef SHA-256为`30ca8edf36801264c5de2c753c40b720aba8052633620713a12401098fcd2409`。模板仍保留固定UUID及原有参数发布。旧未标记的跟踪数据会回退实时跟随;需要离线重放时请重新分析目标。
临时Release APP、旧APP备份和公证ZIP已清理。项目、Codex工作树、DerivedData、桌面、下载、Applications、系统/用户FxPlug目录及本次paintfcp临时目录扫描仅发现正式路径外层APP,扫描错误0;没有将这项有界扫描表述为所有卷的完整搜索。完整回执、签名、发现、模板摘要和扫描范围在`verification/v1.0.6/`。最终发布标签为`v1.0.6`,产品源码与签名构建节点ad54726一致;其后的提交仅更新交付文档、模板说明和验证回执。
+2 -1
查看文件
@@ -323,7 +323,8 @@ int main(int argc, const char *argv[]) {
*controls[i] = 1;
PGFaceFrame *warped = [processor processImage:image settings:changed time:kCMTimeZero error:&error];
BOOL changedPixels=![Pixels(context, warped.image) isEqualToData:base];
Check(warped != nil && (i==0 ? !changedPixels : changedPixels), i==0 ? @"thin safely skips a face with no indexed landmarks" : [@"geometry control changes pixels: " stringByAppendingString:names[i]]);
BOOL requiresPoints=(i==0 || i==4);
Check(warped != nil && (requiresPoints ? !changedPixels : changedPixels), requiresPoints ? @"thin/chin safely skip a face with no indexed landmarks" : [@"geometry control changes pixels: " stringByAppendingString:names[i]]);
Check(CGRectEqualToRect(warped.image.extent, image.extent), @"warp preserves image extent");
}
settings.faceShort = 0.8;
+211 -2
查看文件
@@ -7,6 +7,9 @@
#import <ImageIO/ImageIO.h>
#import "beatygrilPlugIn.h"
#import "PGParameterIDs.h"
#import "PGFaceProcessor.h"
#import "PGBeautyRenderer.h"
#include <math.h>
// This adapter simulates host callbacks, but uses real IOSurface buffers and the
// production FxPlug entry points. It does not simulate FCP plugin discovery/UI.
@@ -173,6 +176,12 @@ static NSUInteger MaximumDifference(NSData *a, NSData *b) {
for (NSUInteger i = 0; i < a.length; i++) maximum = MAX(maximum, (NSUInteger)abs(x[i] - y[i]));
return maximum;
}
static double MeanDifference(NSData *a, NSData *b) {
if (!a.length || a.length != b.length) return INFINITY;
const uint8_t *x = a.bytes, *y = b.bytes; double total = 0;
for (NSUInteger i = 0; i < a.length; i++) total += abs(x[i]-y[i]);
return total/a.length;
}
static NSData *ExpectedCrop(PGHostTile *source, NSUInteger x, NSUInteger y, NSUInteger width, NSUInteger height, BOOL flip) {
NSData *sourceBytes = SurfaceBytes(source); size_t sourceWidth = IOSurfaceGetWidth((__bridge IOSurfaceRef)source.storage);
NSMutableData *result = [NSMutableData dataWithLength:width * height * 4];
@@ -200,6 +209,204 @@ static void RejectState(beatygrilPlugIn *plugin, PGHostTile *destination, PGHost
Check(!rendered && !escaped && error != nil, description);
}
static NSData *PlistState(id value) { return [NSPropertyListSerialization dataWithPropertyList:value format:NSPropertyListBinaryFormat_v1_0 options:0 error:nil]; }
// Decode independently of CI/IOSurface, so a pair of adapter flips cannot
// silently cancel in our test input and expected output. ImageIO + Quartz gives
// the NASA fixture top-first BGRA bytes, also cross-checked against Pillow.
static NSData *DecodedPortrait(NSString *path, size_t *width, size_t *height) {
CGImageSourceRef source = CGImageSourceCreateWithURL((__bridge CFURLRef)[NSURL fileURLWithPath:path], NULL);
CGImageRef image = source ? CGImageSourceCreateImageAtIndex(source, 0, NULL) : NULL;
if (source) CFRelease(source);
if (!image) return nil;
*width = CGImageGetWidth(image); *height = CGImageGetHeight(image);
NSMutableData *bytes = [NSMutableData dataWithLength:*width * *height * 4];
CGColorSpaceRef space = CGColorSpaceCreateWithName(kCGColorSpaceSRGB);
CGContextRef context = CGBitmapContextCreate(bytes.mutableBytes, *width, *height, 8, *width * 4, space,
kCGBitmapByteOrder32Little | kCGImageAlphaPremultipliedFirst);
if (context) {
CGContextSetBlendMode(context, kCGBlendModeCopy);
CGContextSetInterpolationQuality(context, kCGInterpolationNone);
CGContextDrawImage(context, CGRectMake(0, 0, *width, *height), image);
CGContextRelease(context);
}
CGColorSpaceRelease(space); CGImageRelease(image);
return context ? bytes : nil;
}
static BOOL StoreSurfaceBytes(PGHostTile *tile, NSData *bytes) {
IOSurfaceRef surface = (__bridge IOSurfaceRef)tile.storage;
size_t width = IOSurfaceGetWidth(surface), height = IOSurfaceGetHeight(surface);
if (bytes.length != width * height * 4) return NO;
IOSurfaceLock(surface, 0, NULL);
for (size_t row = 0; row < height; row++)
memcpy((uint8_t *)IOSurfaceGetBaseAddress(surface) + row * IOSurfaceGetBytesPerRow(surface),
(const uint8_t *)bytes.bytes + row * width * 4, width * 4);
IOSurfaceUnlock(surface, 0, NULL);
return YES;
}
static CIContext *NumericContext(void) {
return [CIContext contextWithMTLDevice:MTLCreateSystemDefaultDevice() options:@{
kCIContextWorkingColorSpace:NSNull.null, kCIContextOutputColorSpace:NSNull.null}];
}
static NSData *ImageBytes(CIContext *context, CIImage *image) {
if (!image) return nil;
size_t width = (size_t)image.extent.size.width, height = (size_t)image.extent.size.height;
NSMutableData *bytes = [NSMutableData dataWithLength:width * height * 4];
[context render:image toBitmap:bytes.mutableBytes rowBytes:width * 4 bounds:image.extent format:kCIFormatBGRA8 colorSpace:nil];
return bytes;
}
static CIImage *ProcessedReference(CIImage *image, PGBeautySettings settings, CMTime time, NSData *track) {
PGFaceProcessor *faces = [PGFaceProcessor new];
if (track) [faces setTrackingData:track forTarget:settings.targetIndex];
NSError *error = nil;
PGFaceFrame *frame = [faces processImage:image settings:settings time:time error:&error];
if (!frame) return nil;
return [[PGBeautyRenderer new] processImage:frame.image settings:settings skinMask:frame.skinMask
colorMask:frame.colorMask teethMask:frame.teethMask eyeMask:frame.eyeMask error:&error];
}
static CGPoint LandmarkCenter(VNFaceLandmarkRegion2D *region, CGRect box, CGSize size) {
CGPoint center = CGPointZero;
if (!region.pointCount || !region.normalizedPoints) return center;
for (NSUInteger i = 0; i < region.pointCount; i++) {
center.x += region.normalizedPoints[i].x; center.y += region.normalizedPoints[i].y;
}
center.x /= region.pointCount; center.y /= region.pointCount;
return CGPointMake((box.origin.x + center.x * box.size.width) * size.width,
(1 - box.origin.y - center.y * box.size.height) * size.height);
}
static double ChangeMass(NSData *baseline, NSData *changed, size_t width, CGRect region) {
if (baseline.length != changed.length || !width) return INFINITY;
const uint8_t *a = baseline.bytes, *b = changed.bytes;
size_t height = baseline.length / (width * 4); double total = 0;
for (size_t row = 0; row < height; row++) for (size_t x = 0; x < width; x++)
if (CGRectContainsPoint(region, CGPointMake(x + .5, row + .5))) {
size_t i = (row * width + x) * 4;
for (size_t c = 0; c < 3; c++) total += abs(a[i+c] - b[i+c]);
}
return total;
}
static void PortraitAnatomyTests(NSString *fixture, CIImage *portrait) {
size_t width = 0, height = 0;
NSData *canonical = DecodedPortrait(fixture, &width, &height);
Check(canonical != nil && width == 512 && height == 512, @"independent ImageIO/Quartz decodes the 512px NASA fixture");
if (!canonical || width != 512 || height != 512 || !portrait) return;
// Asymmetric samples pin the decoder's physical row/column order. These
// values match Pillow's RGB decode exactly, independently of Core Image.
const size_t positions[][2] = {{16,16},{100,250},{205,100},{222,178},{480,450}};
const uint8_t samples[][4] = {{35,6,14,255},{93,132,234,255},{11,35,58,255},{42,73,102,255},{2,1,3,255}};
BOOL samplesMatch = YES;
for (size_t i = 0; i < 5; i++)
samplesMatch &= memcmp((const uint8_t *)canonical.bytes + (positions[i][1]*width + positions[i][0])*4, samples[i], 4) == 0;
Check(samplesMatch, @"independent asymmetric pixels establish top-first BGRA and the horizontal direction");
CIContext *context = NumericContext();
Check(MaximumDifference(canonical, ImageBytes(context, portrait)) <= 1, @"upright CI file decode agrees with independent physical source bytes");
PGFaceFrame *detected = [[PGFaceProcessor new] processImage:portrait settings:PGBeautyDefaults() time:kCMTimeZero error:nil];
VNFaceObservation *face = detected.faces.firstObject;
Check(face.landmarks.allPoints.pointCount == 76, @"real portrait provides the 76-point anatomy used by chin pins");
if (face.landmarks.allPoints.pointCount != 76) return;
CGSize size = CGSizeMake(width, height); CGRect box = face.boundingBox;
CGPoint left = LandmarkCenter(face.landmarks.leftEye, box, size), right = LandmarkCenter(face.landmarks.rightEye, box, size);
CGPoint chin = face.landmarks.allPoints.normalizedPoints[67];
chin = CGPointMake((box.origin.x + chin.x * box.size.width)*width, (1-box.origin.y-chin.y*box.size.height)*height);
CGFloat fw = box.size.width*width, fh = box.size.height*height;
CGRect leftEye = CGRectMake(left.x-fw*.24, left.y-fh*.17, fw*.48, fh*.34);
CGRect rightEye = CGRectMake(right.x-fw*.24, right.y-fh*.17, fw*.48, fh*.34);
CGRect chinArea = CGRectMake(chin.x-fw*.30, chin.y-fh*.22, fw*.60, fh*.44);
CGRect upsideDownFace = CGRectMake(box.origin.x*width, box.origin.y*height, fw, fh);
const UInt32 controls[] = {0, PGBigEye, PGChin, PGChin};
const double values[] = {0, 1, 1, -1};
NSArray<NSString *> *names = @[@"default", @"big-eye", @"chin-positive", @"chin-negative"];
for (NSUInteger effect = 0; effect < 4; effect++) {
PGBeautySettings settings = PGBeautyDefaults();
if (effect == 1) settings.faceBigEye = 1;
else if (effect > 1) settings.faceChin = values[effect];
NSData *expected = ImageBytes(context, ProcessedReference(portrait, settings, kCMTimeZero, nil));
Check(expected.length == canonical.length, [names[effect] stringByAppendingString:@" independent upright face/beauty chain renders"]);
if (expected.length != canonical.length) continue;
NSData *originBaseline = nil;
for (NSUInteger sourceTop = 0; sourceTop < 2; sourceTop++) for (NSUInteger destinationTop = 0; destinationTop < 2; destinationTop++) {
@autoreleasepool {
PGHostAPI *api = [PGHostAPI new]; beatygrilPlugIn *plugin = [[beatygrilPlugIn alloc] initWithAPIManager:api];
Check([plugin addParametersWithError:nil], @"anatomy case creates the real plugin parameters");
if (effect) api.values[@(controls[effect])] = @(values[effect]);
PGHostTile *input = Tile(width, height, 37, -21, sourceTop ? kFxImageOrigin_TOP_LEFT : kFxImageOrigin_BOTTOM_LEFT);
PGHostTile *output = Tile(width, height, 37, -21, destinationTop ? kFxImageOrigin_TOP_LEFT : kFxImageOrigin_BOTTOM_LEFT);
// imageOrigin changes coordinate semantics, never these bytes.
Check(StoreSurfaceBytes(input, canonical), @"all origin combinations receive identical independently decoded physical rows");
Check(Render(plugin, output, @[input], Snapshot(plugin, kCMTimeZero), kCMTimeZero, nil, NULL), @"active anatomy case renders with nonzero tile origin");
NSData *actual = SurfaceBytes(output); NSUInteger difference = MaximumDifference(actual, expected);
NSString *label = [NSString stringWithFormat:@"%@ source%@/destination%@", names[effect], sourceTop?@"TOP":@"BOTTOM", destinationTop?@"TOP":@"BOTTOM"];
// Vision's file-image vs IOSurface path and translated float
// coordinates can round by a few byte levels at high contrast.
// Bound that error tightly; changing only origin tags must be
// exactly invariant, independently of this reference tolerance.
Check(difference <= 4 && MeanDifference(actual,expected) < .005, [label stringByAppendingString:@" matches independent upright CI output"]);
if (!originBaseline) originBaseline = actual;
Check(MaximumDifference(actual,originBaseline) == 0, [label stringByAppendingString:@" is byte-identical across coordinate tags"]);
double lm = ChangeMass(canonical, actual, width, leftEye), rm = ChangeMass(canonical, actual, width, rightEye);
double cm = ChangeMass(canonical, actual, width, chinArea), um = ChangeMass(canonical, actual, width, upsideDownFace);
BOOL located = effect == 0 ? MaximumDifference(canonical, actual) <= 1 :
(effect == 1 ? lm > 100 && rm > 100 && cm < (lm+rm)*.01 && um == 0 :
cm > 100 && cm > 4*(lm+rm) && um == 0);
Check(located, [label stringByAppendingString:effect == 1 ? @" changes the two real eye regions" : (effect ? @" changes the real chin rather than eyes or mirrored anatomy" : @" preserves the independently decoded photo")]);
printf("Anatomy %-13s %lu/%lu max=%lu mean=%.7f eye=%.0f/%.0f chin=%.0f inverted=%.0f\n", names[effect].UTF8String,
(unsigned long)sourceTop, (unsigned long)destinationTop, (unsigned long)difference, MeanDifference(actual,expected), lm, rm, cm, um);
}
}
}
}
static void TrackingCoordinateTests(NSDictionary *archive, NSDictionary *hostValues, PGHostTile *source, CIImage *portrait, CMTime time) {
Check([archive[@"hostCoordinateSpace"] isEqual:@"iosurface-ci-y-up-v1"], @"new host analysis stamps its explicit IOSurface/CI coordinate contract");
NSMutableDictionary *forged = [NSPropertyListSerialization propertyListWithData:PlistState(archive)
options:NSPropertyListMutableContainers format:nil error:nil];
if (![forged[@"frames"] isKindOfClass:NSDictionary.class]) { Check(NO, @"analysis archive supplies frames for compatibility regression"); return; }
// Retain fully valid topology but move the saved face away from the actual
// portrait. A falsely replayed legacy archive must produce a visible error.
for (NSMutableDictionary *frame in [forged[@"frames"] allValues]) for (NSMutableDictionary *face in frame[@"faces"]) {
NSMutableArray *box = [face[@"box"] mutableCopy]; box[0] = @([box[0] doubleValue]+.2); box[1] = @([box[1] doubleValue]-.25); face[@"box"] = box;
for (NSString *name in @[@"leftEye",@"rightEye",@"leftBrow",@"rightBrow",@"nose",@"lips",@"mouth",@"contour",@"points76"]) {
if (!face[name]) continue;
NSMutableArray *points = [NSMutableArray new];
for (NSArray *point in face[name]) [points addObject:@[@([point[0] doubleValue]+.2),@([point[1] doubleValue]-.25)]];
face[name] = points;
}
}
[forged removeObjectForKey:@"hostCoordinateSpace"];
NSString *legacyString = [PlistState(forged) base64EncodedStringWithOptions:0];
NSMutableDictionary *values = [hostValues mutableCopy];
values[@(PGTarget)] = @2; values[@(PGTrackEnabled)] = @YES; values[@(PGBigEye)] = @1; values[@(PGChin)] = @1;
values[@(PGTrackData3)] = legacyString;
PGHostAPI *legacyAPI = [PGHostAPI new]; legacyAPI.hostTime = time;
beatygrilPlugIn *legacy = [[beatygrilPlugIn alloc] initWithAPIManager:legacyAPI]; [legacy addParametersWithError:nil]; legacyAPI.values = [values mutableCopy];
PGHostTile *output = Tile(512,512,0,0,kFxImageOrigin_TOP_LEFT);
Check(Render(legacy, output, @[source], Snapshot(legacy,time), time, nil, NULL), @"unmarked old coordinate archive renders through the real adapter");
NSData *legacyBytes = SurfaceBytes(output);
PGBeautySettings realtime = [legacy settingsAtTime:time]; realtime.trackingEnabled = 0;
CIContext *context = NumericContext();
NSData *expectedLive = ImageBytes(context, ProcessedReference(portrait,realtime,time,nil));
printf("Legacy fallback upright comparison: max=%lu mean=%.7f\n",(unsigned long)MaximumDifference(legacyBytes,expectedLive),MeanDifference(legacyBytes,expectedLive));
Check(expectedLive.length && MaximumDifference(legacyBytes,expectedLive) <= 4 && MeanDifference(legacyBytes,expectedLive) < .005, @"unmarked forged face geometry falls back to realtime upright detection");
Check([legacyAPI.values[@(PGTrackData3)] isEqualToString:legacyString] && [legacyAPI.values[@(PGTrackEnabled)] boolValue], @"legacy fallback preserves the user's original archive string and enabled parameter");
forged[@"hostCoordinateSpace"] = @"iosurface-ci-y-up-v1";
NSData *trustedTrack = PlistState(forged);
PGHostAPI *trustedAPI = [PGHostAPI new]; trustedAPI.hostTime = time;
beatygrilPlugIn *trusted = [[beatygrilPlugIn alloc] initWithAPIManager:trustedAPI]; [trusted addParametersWithError:nil];
trustedAPI.values = [values mutableCopy]; trustedAPI.values[@(PGTrackData3)] = [trustedTrack base64EncodedStringWithOptions:0];
Check(Render(trusted, output, @[source], Snapshot(trusted,time), time, nil, NULL), @"fresh plugin restores a marked coordinate archive");
NSData *trustedBytes = SurfaceBytes(output);
PGBeautySettings tracked = [trusted settingsAtTime:time];
NSData *expectedSaved = ImageBytes(context, ProcessedReference(portrait,tracked,time,trustedTrack));
Check(expectedSaved.length && MaximumDifference(trustedBytes,expectedSaved) <= 1, @"marked saved geometry matches independent exact-time archive replay");
Check(MaximumDifference(trustedBytes,expectedLive) > 5, @"forged marked geometry is observably different, making the legacy non-replay assertion meaningful");
// Reanalysis of an unmarked target starts a fresh compatible archive.
[legacy analyzeForward];
CMTime next = CMTimeAdd(time,CMTimeMake(10,24));
[legacy setupAnalysisForTimeRange:CMTimeRangeMake(time,CMTimeMake(240,24)) frameDuration:CMTimeMake(1,24) error:nil];
BOOL analyzed = [legacy analyzeFrame:source atTime:next error:nil] && [legacy cleanupAnalysis:nil];
NSData *resaved = [[NSData alloc] initWithBase64EncodedString:legacyAPI.values[@(PGTrackData3)] options:0];
NSDictionary *fresh = resaved ? [NSPropertyListSerialization propertyListWithData:resaved options:0 format:nil error:nil] : nil;
Check(analyzed && [fresh[@"frames"] count] == 1 && [fresh[@"hostCoordinateSpace"] isEqual:@"iosurface-ci-y-up-v1"], @"reanalysis replaces incompatible geometry with one newly marked frame");
}
static void WritePortrait(PGHostTile *tile, CIImage *image) {
CIContext *context = [CIContext contextWithOptions:@{kCIContextWorkingColorSpace:NSNull.null, kCIContextOutputColorSpace:NSNull.null}];
[context render:image toIOSurface:(__bridge IOSurfaceRef)tile.storage bounds:image.extent colorSpace:nil];
@@ -283,7 +490,7 @@ int main(int argc, const char *argv[]) {
Check(MaximumDifference(SurfaceBytes(input), SurfaceBytes(output)) <= 1, top ? @"top-left tile preserves sample rows at nonzero origin" : @"bottom-left tile preserves sample rows at nonzero origin");
output.origin = top ? kFxImageOrigin_BOTTOM_LEFT : kFxImageOrigin_TOP_LEFT;
Check(Render(plugin, output, @[input], state, kCMTimeZero, &error, NULL), @"opposite-origin tile renders");
Check(MaximumDifference(ExpectedCrop(input, 0, 0, 48, 32, YES), SurfaceBytes(output)) <= 1, @"opposite image origins flip rows exactly once");
Check(MaximumDifference(SurfaceBytes(input), SurfaceBytes(output)) <= 1, @"opposite coordinate tags preserve the same physical IOSurface rows");
}
PGHostTile *full = Tile(48, 32, 37, -21, kFxImageOrigin_BOTTOM_LEFT), *crop = Tile(16, 12, 43, -17, kFxImageOrigin_BOTTOM_LEFT); Fill(full);
Check(Render(plugin, crop, @[full], state, kCMTimeZero, &error, NULL), @"destination sub-tile renders from full input buffer");
@@ -298,11 +505,12 @@ int main(int argc, const char *argv[]) {
Check(cropDifference <= 1, @"destination sub-tile translates global pixel bounds correctly");
full.origin = kFxImageOrigin_TOP_LEFT; crop.origin = kFxImageOrigin_TOP_LEFT;
Check(Render(plugin, crop, @[full], state, kCMTimeZero, &error, NULL), @"top-left destination sub-tile renders");
Check(MaximumDifference(ExpectedCrop(full, 6, 4, 16, 12, NO), SurfaceBytes(crop)) <= 1, @"top-left sub-tile selects correct physical source rows");
Check(MaximumDifference(ExpectedCrop(full, 6, 16, 16, 12, NO), SurfaceBytes(crop)) <= 1, @"top-left coordinate tag does not reverse the sub-tile's physical source rows");
NSString *fixture = argc > 1 ? @(argv[1]) : @"tests/fixtures/astronaut.png";
CIImage *portrait = [CIImage imageWithContentsOfURL:[NSURL fileURLWithPath:fixture] options:@{kCIImageColorSpace:NSNull.null}];
Check(portrait != nil, @"NASA portrait opens for full adapter test");
PortraitAnatomyTests(fixture, portrait);
PGHostTile *portraitSource = Tile(512, 512, 0, 0, kFxImageOrigin_BOTTOM_LEFT), *portraitOutput = Tile(512, 512, 0, 0, kFxImageOrigin_BOTTOM_LEFT);
WritePortrait(portraitSource, portrait);
api.values[@(PGThin)] = @0.8; api.values[@(PGBigEye)] = @0.6; api.values[@(PGMidtone)] = @0.8; api.values[@(PGBrightness)] = @0.25; api.values[@(PGMix)] = @0;
@@ -339,6 +547,7 @@ int main(int argc, const char *argv[]) {
NSData *tracking = [[NSData alloc] initWithBase64EncodedString:target options:0];
NSDictionary *trackingArchive = tracking ? [NSPropertyListSerialization propertyListWithData:tracking options:0 format:nil error:nil] : nil;
Check([trackingArchive[@"frames"] count] == 2, @"old render snapshot does not erase either newly analyzed frame");
TrackingCoordinateTests(trackingArchive,api.values,portraitSource,portrait,api.hostTime);
Check([api.values[@(PGTrackEnabled)] boolValue] && CMTimeCompare(api.lastSetBoolTime, api.hostTime) == 0, @"analysis enables saved target at host seed time");
NSData *trackedState = Snapshot(plugin, api.hostTime);
beatygrilPlugIn *restoredPlugin = [[beatygrilPlugIn alloc] initWithAPIManager:api];
+65 -5
查看文件
@@ -25,6 +25,7 @@ static NSDictionary *Geometry(double yaw,double roll) {
for(NSUInteger i=0;i<76;i++)[p addObject:@[@.5,@.5]];
p[6]=@[@.34,@.65];p[13]=@[@.66,@.65];
p[47]=@[@.5,@.60];p[48]=@[@.5,@.52];p[49]=@[@.5,@.45];p[52]=@[@.5,@.42];
p[36]=@[@.54,@.36];p[37]=@[@.5,@.35];p[38]=@[@.46,@.36];
for(NSUInteger i=0;i<17;i++) {
double t=M_PI+M_PI*i/16;
p[59+i]=@[@(.5+.29*cos(t)),@(.65+.43*sin(t))];
@@ -60,6 +61,21 @@ static CGPoint Oracle(CGPoint pixel,NSDictionary *g,CGRect extent,double thin) {
}
return CGPointMake(q.x,1-q.y);
}
static CGPoint ChinOracle(CGPoint pixel,NSDictionary *g,CGRect e,double thin,double chin) {
CGPoint q=Oracle(pixel,g,e,thin);q.y=1-q.y;
NSArray *raw=g[@"points76"];CGPoint points[76];
for(NSUInteger i=0;i<76;i++){points[i]=ReadPoint(raw[i]);points[i].y=1-points[i].y;}
double width=hypot((points[75].x-points[59].x)*e.size.width,(points[75].y-points[59].y)*e.size.height);
double height=2*hypot((points[67].x-(points[74].x+points[60].x)/2)*e.size.width,(points[67].y-(points[74].y+points[60].y)/2)*e.size.height);
double ratio=Clamp(chin,-1,1)*(chin>0?.04:.02)*Clamp(280/fmax(1,fmax(width,height)),1,2.25);
for(NSUInteger i=0;i<3;i++) {
CGPoint a=points[68-i],b=points[38-i];double r=hypot(a.x-b.x,a.y-b.y);
if(r<=.0001)continue;
double w=Clamp(1-hypot(q.x-a.x,q.y-a.y)/r,0,1);
q.x=Clamp(q.x-(b.x-a.x)*ratio*w,0,1);q.y=Clamp(q.y-(b.y-a.y)*ratio*w,0,1);
}
return CGPointMake(q.x,1-q.y);
}
static CIImage *CoordinateImage(CGRect extent) {
NSUInteger w=extent.size.width,h=extent.size.height;
NSMutableData *d=[NSMutableData dataWithLength:w*h*4*sizeof(float)];float *p=d.mutableBytes;
@@ -77,8 +93,8 @@ static NSData *Pixels(CIContext *c,CIImage *image) {
static NSData *Archive(NSDictionary *geometry) {
return [NSPropertyListSerialization dataWithPropertyList:@{@"version":@1,@"frames":@{@"0/1/0":@{@"value":@0,@"scale":@1,@"epoch":@0,@"faces":@[geometry]}}} format:NSPropertyListBinaryFormat_v1_0 options:0 error:nil];
}
static void NumericCase(CIContext *c,PGFaceProcessor *p,CGRect e,NSDictionary *g,double amount,NSString *name) {
PGBeautySettings s=PGBeautyDefaults();s.faceThin=amount;
static void NumericSettingsCase(CIContext *c,PGFaceProcessor *p,CGRect e,NSDictionary *g,double amount,double chin,NSString *name) {
PGBeautySettings s=PGBeautyDefaults();s.faceThin=amount;s.faceChin=chin;
CIImage *input=CoordinateImage(e),*output=[p warpImage:input geometry:@[g] settings:s extent:e];
Check(output!=nil && CGRectEqualToRect(output.extent,e),[name stringByAppendingString:@" preserves extent"]);
NSData *data=Pixels(c,output);const float *pixels=data.bytes;double worst=0,maxMove=0;
@@ -86,7 +102,7 @@ static void NumericCase(CIContext *c,PGFaceProcessor *p,CGRect e,NSDictionary *g
NSUInteger w=e.size.width,h=e.size.height;
for(NSUInteger y=3;y<h-3;y+=7)for(NSUInteger x=3;x<w-3;x+=7) {
// Bitmap rows are top first, while destCoord / geometry are y up.
CGPoint q=Oracle(CGPointMake(e.origin.x+x+.5,e.origin.y+h-y-.5),g,e,amount);
CGPoint q=ChinOracle(CGPointMake(e.origin.x+x+.5,e.origin.y+h-y-.5),g,e,amount,chin);
double ex=Clamp(q.x,.5/w,1-.5/w),ey=Clamp(1-q.y,.5/h,1-.5/h);
NSUInteger k=(y*w+x)*4;
double difference=fmax(fabs(pixels[k]-ex),fabs(pixels[k+1]-ey));
@@ -95,11 +111,43 @@ static void NumericCase(CIContext *c,PGFaceProcessor *p,CGRect e,NSDictionary *g
}
if(worst>0.00006)fprintf(stderr,"%s oracle max error=%g at %lu,%lu expected=(%g,%g) actual=(%g,%g)\n",name.UTF8String,worst,(unsigned long)worstX,(unsigned long)worstY,worstExpected.x,worstExpected.y,worstActual.x,worstActual.y);
maxOracleError=fmax(maxOracleError,worst);
Check(worst<0.00006,[name stringByAppendingString:@" GPU inverse field matches independent 17-pin oracle"]);
Check(amount==0 || maxMove>.0001,[name stringByAppendingString:@" exercises a nonzero displacement"]);
Check(worst<0.00006,[name stringByAppendingString:@" GPU field matches the independent thin/chin oracle"]);
Check((amount==0 && chin==0) || maxMove>.0001,[name stringByAppendingString:@" exercises a nonzero displacement"]);
NSUInteger corner=4*((h-3)*w+(w-3));
Check(fabs(pixels[corner]-(w-2.5)/w)<1e-6 && fabs(pixels[corner+1]-(h-2.5)/h)<1e-6,[name stringByAppendingString:@" preserves distant background"]);
}
static void NumericCase(CIContext *c,PGFaceProcessor *p,CGRect e,NSDictionary *g,double amount,NSString *name) {
NumericSettingsCase(c,p,e,g,amount,0,name);
}
static CGPoint Displacement(CIContext *c,CIImage *ramp,CIImage *warped,CGPoint uv) {
CGRect e=ramp.extent;CGRect point=CGRectMake(floor(e.origin.x+uv.x*e.size.width),floor(e.origin.y+uv.y*e.size.height),1,1);
float a[4],b[4];
[c render:ramp toBitmap:a rowBytes:sizeof(a) bounds:point format:kCIFormatRGBAf colorSpace:nil];
[c render:warped toBitmap:b rowBytes:sizeof(b) bounds:point format:kCIFormatRGBAf colorSpace:nil];
return CGPointMake((b[0]-a[0])*e.size.width,-(b[1]-a[1])*e.size.height);
}
static void AnatomicalChecks(CIContext *c,PGFaceProcessor *p,NSDictionary *g,CGRect e) {
CIImage *ramp=CoordinateImage(e);NSArray *points=g[@"points76"];
CGPoint chin=ReadPoint(points[67]),lip=ReadPoint(points[37]);
double ax=(lip.x-chin.x)*e.size.width,ay=(lip.y-chin.y)*e.size.height;
for(int sign=-1;sign<=1;sign+=2) {
PGBeautySettings s=PGBeautyDefaults();s.faceChin=sign;
CIImage *out=[p warpImage:ramp geometry:@[g] settings:s extent:e];
CGPoint d=Displacement(c,ramp,out,chin);
Check(hypot(d.x,d.y)>.05,@"chin control moves the detected physical chin landmark");
Check(sign*(d.x*ax+d.y*ay)<0,@"signed chin direction follows chin-to-lip anchor instead of a fixed image Y axis");
BOOL outside=YES,eyesUnchanged=YES;
for(NSNumber *index in @[@6,@13]) {
CGPoint eye=ReadPoint(points[index.unsignedIntegerValue]);
for(NSUInteger i=0;i<3;i++) {
CGPoint a=ReadPoint(points[68-i]),b=ReadPoint(points[38-i]);
if(hypot(eye.x-a.x,eye.y-a.y)<=hypot(a.x-b.x,a.y-b.y)+.003)outside=NO;
}
CGPoint shift=Displacement(c,ramp,out,eye);eyesUnchanged &= hypot(shift.x,shift.y)<.002;
}
Check(outside && eyesUnchanged,@"both physical pupils outside chin supports remain unchanged");
}
}
int main(int argc,const char *argv[]) {@autoreleasepool {
CIContext *c=[CIContext contextWithMTLDevice:MTLCreateSystemDefaultDevice() options:@{kCIContextWorkingColorSpace:NSNull.null,kCIContextOutputColorSpace:NSNull.null}];
@@ -112,6 +160,15 @@ int main(int argc,const char *argv[]) {@autoreleasepool {
NumericCase(c,p,extents[e],Geometry(yaw[y],roll[r]),1,name);
}
NumericCase(c,p,extents[1],Geometry(.6,.2),.5,@"half strength");
for(NSUInteger e=0;e<3;e++)for(NSUInteger y=0;y<3;y++)for(NSUInteger r=0;r<2;r++)for(int sign=-1;sign<=1;sign+=2) {
double angle=y==0?0:(y==1?1.4:-1.4);
NumericSettingsCase(c,p,extents[e],Geometry(angle,r?.45:0),0,sign,
[NSString stringWithFormat:@"chin size%lu yaw%lu roll%lu sign%d",(unsigned long)e,(unsigned long)y,(unsigned long)r,sign]);
}
NumericSettingsCase(c,p,extents[1],Geometry(.4,.2),.8,1,@"thin plus positive chin");
NumericSettingsCase(c,p,extents[2],Geometry(-.6,-.2),1,-1,@"thin plus negative chin");
NumericSettingsCase(c,p,extents[0],Geometry(0,0),0,.5,@"half positive chin");
NumericSettingsCase(c,p,extents[0],Geometry(0,0),0,-.5,@"half negative chin");
CIImage *image=CoordinateImage(extents[1]);NSData *source=Pixels(c,image);
PGBeautySettings s=PGBeautyDefaults();s.faceThin=1;s.trackingEnabled=1;NSError *error=nil;
NSDictionary *g=Geometry(.9,.15);[p setTrackingData:Archive(g) forTarget:0];
@@ -136,6 +193,8 @@ int main(int argc,const char *argv[]) {@autoreleasepool {
[p setTrackingData:Archive(bad) forTarget:0];frame=[p processImage:image settings:s time:kCMTimeZero error:&error];
Check(frame!=nil && frame.deformationGeometry.count==1,@"legacy/malformed indexed points retain the valid face record");
Check([Pixels(c,frame.image) isEqualToData:source],@"legacy/malformed indexed points never trigger a guessed symmetric thin warp");
PGBeautySettings chinOnly=s;chinOnly.faceThin=0;chinOnly.faceChin=1;
Check([Pixels(c,[p processImage:image settings:chinOnly time:kCMTimeZero error:&error].image) isEqualToData:source],@"legacy/malformed indexed points never trigger guessed bbox chin deformation");
PGBeautySettings other=s;other.faceNarrow=.5;
Check(![Pixels(c,[p processImage:image settings:other time:kCMTimeZero error:&error].image) isEqualToData:source],@"other controls remain available in legacy archives");
}
@@ -158,6 +217,7 @@ int main(int argc,const char *argv[]) {@autoreleasepool {
Check([real analyzeImage:portrait settings:rs time:kCMTimeZero error:&error],@"real pose analysis saves the complete geometry");
PGFaceProcessor *copy=[PGFaceProcessor new];[copy setTrackingData:[real trackingDataForTarget:0] forTarget:0];rs.trackingEnabled=1;
Check([Pixels(c,[copy processImage:portrait settings:rs time:kCMTimeZero error:&error].image) isEqualToData:Pixels(c,f.image)],@"real pose replays from persisted 76 points with identical pixels");
AnatomicalChecks(c,real,f.deformationGeometry.firstObject,portrait.extent);
}}
printf("PASS: %lu slim alignment checks; maximum UV oracle error %.9g\n",(unsigned long)checks,maxOracleError);return 0;
}}