Fix FxPlug CI orientation and preserve incompatible legacy tracks; prepare tested 1.0.6 Release
这个提交包含在:
1 父节点
16525d62e5
当前提交
ad54726b17
18 个文件已更改
+12145
-25
没有与搜索条件相匹配的文件
@@ -28,3 +28,9 @@ 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,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链按宿主原点重复翻转,修正下巴三组局部位移及正负方向。旧版未标记坐标空间的分析记录保留但不再重放,自动回退实时跟随;需要离线记录时重新分析当前目标。
|
||||
@@ -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": "Source repair under validation, not yet installed."
|
||||
},
|
||||
"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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -39,12 +39,28 @@
|
||||
"face": 116,
|
||||
"thin_chin": 394,
|
||||
"failures": 0,
|
||||
"maximum_uv_oracle_error": 1.09828022e-05
|
||||
"maximum_uv_oracle_error": 1.09828022e-05,
|
||||
"realVideoASanChecks": 494,
|
||||
"realVideoFrames": 11,
|
||||
"independentAnatomicalChecks": 12,
|
||||
"fullChecks": 904,
|
||||
"hostAdapterASanChecks": 247,
|
||||
"oscASanChecks": 98,
|
||||
"allOriginTagsPixelIdentical": true,
|
||||
"maxByteErrorVsIndependentCIReference": 2
|
||||
},
|
||||
"limitations": [
|
||||
"Seven other shape controls remain approximations.",
|
||||
"Combined analytic controls and multi-face resampling order is not fully reference equivalent.",
|
||||
"FxPlug adapter imageOrigin handling is under separate investigation.",
|
||||
"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
|
||||
}
|
||||
}
|
||||
文件差异过大,未显示。
加载差异
@@ -21,3 +21,18 @@
|
||||
纯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检查通过,含重复基础用例,不与完整回归简单相加。用户照片、帧及完整点位均没有入库。
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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,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,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,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
|
||||
+8
@@ -61,3 +61,11 @@ 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已安装。
|
||||
+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];
|
||||
|
||||
在新工单中引用
屏蔽一个用户