Fix skin exposure mask islands and OSC coordinates; prepare 1.0.2
这个提交包含在:
1 父节点
dd04a471e1
当前提交
d561537eed
19 个文件已更改
+364
-35
没有与搜索条件相匹配的文件
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"version": "1.0.1",
|
"version": "1.0.2",
|
||||||
"pluginUUID": "FB14B0E6-59B2-4836-916F-CD0C559E9241",
|
"pluginUUID": "FB14B0E6-59B2-4836-916F-CD0C559E9241",
|
||||||
"filterObject": "510000005",
|
"filterObject": "510000005",
|
||||||
"visiblePublished": 46,
|
"visiblePublished": 46,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# 大鱼人像美化 FCP 效果模板
|
# 大鱼人像美化 FCP 效果模板
|
||||||
|
|
||||||
版本:1.0.1。滤镜 UUID:`FB14B0E6-59B2-4836-916F-CD0C559E9241`。模板必须与已安装的大鱼人像美化 FxPlug APP 配套使用。
|
版本:1.0.2。滤镜 UUID:`FB14B0E6-59B2-4836-916F-CD0C559E9241`。模板必须与已安装的大鱼人像美化 FxPlug APP 配套使用。
|
||||||
|
|
||||||
## 默认数值
|
## 默认数值
|
||||||
|
|
||||||
@@ -28,6 +28,6 @@ Apple项目、群组、Effect Source和滤镜节点骨架来源于用户自有
|
|||||||
|
|
||||||
前版生成稿(SHA256 `3b7984e11a92487834ab67121fd3814bf13c22b77a70851298efa84206f91478`)将效果源的 Source Media 设为0,并删除了原生clip/footage。用户在FCP应用后实测整幅黑屏。这是模板装配错误;保留Type=3本身并不能证明FCP输入替换链完整。此前XML解析、发布路径和无缺媒体检查通过,也没有覆盖真实FCP画面输入,不能当作该模板已经可用的证据。
|
前版生成稿(SHA256 `3b7984e11a92487834ab67121fd3814bf13c22b77a70851298efa84206f91478`)将效果源的 Source Media 设为0,并删除了原生clip/footage。用户在FCP应用后实测整幅黑屏。这是模板装配错误;保留Type=3本身并不能证明FCP输入替换链完整。此前XML解析、发布路径和无缺媒体检查通过,也没有覆盖真实FCP画面输入,不能当作该模板已经可用的证据。
|
||||||
|
|
||||||
本次仅恢复标准效果源clip/footage和Source Media关联,并随包加入标准`Drop Zone.tiff`;滤镜算法、49个插件参数、48个发布入口、Type=3/Drop Zone=1及版本1.0.1均保留。该占位图从Apple Motion的Ozone.framework标准资源复制,实际运行只引用本模板内的相对副本。图像SHA256为 `a7f8e3a2a431dbb3ce9e777040bb55e701cfe9ab9632e8cbb10359732bc8692e`,420128字节。
|
本次仅恢复标准效果源clip/footage和Source Media关联,并随包加入标准`Drop Zone.tiff`;滤镜算法、49个插件参数、48个发布入口、Type=3/Drop Zone=1及版本1.0.2均保留。该占位图从Apple Motion的Ozone.framework标准资源复制,实际运行只引用本模板内的相对副本。图像SHA256为 `a7f8e3a2a431dbb3ce9e777040bb55e701cfe9ab9632e8cbb10359732bc8692e`,420128字节。
|
||||||
|
|
||||||
本次已经静态核验Source Media→clip→相对文件链存在、文件字节一致、48项发布仍解析、隐藏数据未发布和保存选择状态;修复后的FCP新增实例、输入替换、Mix=0直通及实际人像渲染需由主任务重新实测。不得将“恢复链条”描述为已经通过FCP黑屏复测。
|
本次已经静态核验Source Media→clip→相对文件链存在、文件字节一致、48项发布仍解析、隐藏数据未发布和保存选择状态;修复后的FCP新增实例、输入替换、Mix=0直通及实际人像渲染需由主任务重新实测。不得将“恢复链条”描述为已经通过FCP黑屏复测。
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"version": "1.0.1",
|
"version": "1.0.2",
|
||||||
"sha256": "2f7d1e0bf91a6dab82f5dde0a20d971a75abd2056a48104d861e9c112870aae4",
|
"sha256": "fe637290212481c34cbfd8f4de5bf09101a3fbe88f4db3e55b35d5375f3ac1ba",
|
||||||
"filterUUID": "FB14B0E6-59B2-4836-916F-CD0C559E9241",
|
"filterUUID": "FB14B0E6-59B2-4836-916F-CD0C559E9241",
|
||||||
"publishedFunctionalControls": 46,
|
"publishedFunctionalControls": 46,
|
||||||
"informationalControls": 2,
|
"informationalControls": 2,
|
||||||
|
|||||||
@@ -46,7 +46,7 @@
|
|||||||
<flags>1</flags>
|
<flags>1</flags>
|
||||||
</template>
|
</template>
|
||||||
<build />
|
<build />
|
||||||
<description>大鱼人像美化 1.0.1:人像磨皮、肤色、脸型、五官保护和三目标人脸跟踪。模板中频磨皮默认为50%。</description>
|
<description>大鱼人像美化 1.0.2:人像磨皮、肤色、脸型、五官保护和三目标人脸跟踪。模板中频磨皮默认为50%。</description>
|
||||||
<scene>
|
<scene>
|
||||||
<sceneSettings>
|
<sceneSettings>
|
||||||
<width>1920</width>
|
<width>1920</width>
|
||||||
@@ -298,7 +298,7 @@
|
|||||||
<parameter name="Width" id="313" flags="8589934610" default="1" value="1920" />
|
<parameter name="Width" id="313" flags="8589934610" default="1" value="1920" />
|
||||||
<parameter name="Height" id="314" flags="8589934610" default="1" value="1080" />
|
<parameter name="Height" id="314" flags="8589934610" default="1" value="1080" />
|
||||||
</parameter>
|
</parameter>
|
||||||
<filter name="大鱼人像美化" id="510000005" factoryID="6" pluginUUID="FB14B0E6-59B2-4836-916F-CD0C559E9241" pluginVersion="1.0.1" pluginName="大鱼人像美化" pluginDynamicParams="0">
|
<filter name="大鱼人像美化" id="510000005" factoryID="6" pluginUUID="FB14B0E6-59B2-4836-916F-CD0C559E9241" pluginVersion="1.0.2" pluginName="大鱼人像美化" pluginDynamicParams="0">
|
||||||
<timing in="0 1 1 0" out="299 30 1 0" offset="0 1 1 0" />
|
<timing in="0 1 1 0" out="299 30 1 0" offset="0 1 1 0" />
|
||||||
<baseFlags>8589934608</baseFlags>
|
<baseFlags>8589934608</baseFlags>
|
||||||
<parameter name="磨皮" id="100" flags="8589938704">
|
<parameter name="磨皮" id="100" flags="8589938704">
|
||||||
|
|||||||
@@ -17,9 +17,9 @@
|
|||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>XPC!</string>
|
<string>XPC!</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>1.0.1</string>
|
<string>1.0.2</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>3</string>
|
<string>4</string>
|
||||||
<key>NSHumanReadableCopyright</key>
|
<key>NSHumanReadableCopyright</key>
|
||||||
<string></string>
|
<string></string>
|
||||||
<key>PlugInKit</key>
|
<key>PlugInKit</key>
|
||||||
@@ -72,7 +72,7 @@
|
|||||||
<key>uuid</key>
|
<key>uuid</key>
|
||||||
<string>FB14B0E6-59B2-4836-916F-CD0C559E9241</string>
|
<string>FB14B0E6-59B2-4836-916F-CD0C559E9241</string>
|
||||||
<key>version</key>
|
<key>version</key>
|
||||||
<string>1.0.1</string>
|
<string>1.0.2</string>
|
||||||
</dict>
|
</dict>
|
||||||
<dict>
|
<dict>
|
||||||
<key>className</key>
|
<key>className</key>
|
||||||
@@ -84,7 +84,7 @@
|
|||||||
<key>uuid</key>
|
<key>uuid</key>
|
||||||
<string>E0E432C8-3D71-4ABA-B006-E7F519E06C87</string>
|
<string>E0E432C8-3D71-4ABA-B006-E7F519E06C87</string>
|
||||||
<key>version</key>
|
<key>version</key>
|
||||||
<string>1.0.1</string>
|
<string>1.0.2</string>
|
||||||
<key>protocolNames</key>
|
<key>protocolNames</key>
|
||||||
<array>
|
<array>
|
||||||
<string>FxOnScreenControl</string>
|
<string>FxOnScreenControl</string>
|
||||||
|
|||||||
@@ -14,6 +14,14 @@ NS_ASSUME_NONNULL_BEGIN
|
|||||||
teethMask:(nullable CIImage *)teethMask
|
teethMask:(nullable CIImage *)teethMask
|
||||||
eyeMask:(nullable CIImage *)eyeMask
|
eyeMask:(nullable CIImage *)eyeMask
|
||||||
error:(NSError * _Nullable * _Nullable)error;
|
error:(NSError * _Nullable * _Nullable)error;
|
||||||
|
/// Face-based callers supply uncut coverage for smoothly blended color changes.
|
||||||
|
- (nullable CIImage *)processImage:(CIImage *)image
|
||||||
|
settings:(PGBeautySettings)settings
|
||||||
|
skinMask:(nullable CIImage *)skinMask
|
||||||
|
colorMask:(nullable CIImage *)colorMask
|
||||||
|
teethMask:(nullable CIImage *)teethMask
|
||||||
|
eyeMask:(nullable CIImage *)eyeMask
|
||||||
|
error:(NSError * _Nullable * _Nullable)error;
|
||||||
@end
|
@end
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_END
|
NS_ASSUME_NONNULL_END
|
||||||
@@ -82,6 +82,13 @@ static NSDictionary<NSString *, CIColorKernel *> *PGKernels(void) {
|
|||||||
"kernel vec4 pgMaskedMix(__sample source,__sample effect,__sample mask,float amount) {"
|
"kernel vec4 pgMaskedMix(__sample source,__sample effect,__sample mask,float amount) {"
|
||||||
"vec4 s=unpremultiply(source); vec4 e=unpremultiply(effect);"
|
"vec4 s=unpremultiply(source); vec4 e=unpremultiply(effect);"
|
||||||
"return premultiply(vec4(mix(s.rgb,e.rgb,clamp(mask.r*amount,0.0,1.0)),s.a)); }");
|
"return premultiply(vec4(mix(s.rgb,e.rgb,clamp(mask.r*amount,0.0,1.0)),s.a)); }");
|
||||||
|
k[@"colorCoverage"] = PGKernel(@
|
||||||
|
"kernel vec4 pgColorCoverage(__sample coverage,__sample image,float invert) {"
|
||||||
|
"vec3 c=unpremultiply(image).rgb; float y=dot(c,vec3(0.299,0.587,0.114));"
|
||||||
|
"float cb=(c.b-y)*0.564+0.5; float cr=(c.r-y)*0.713+0.5;"
|
||||||
|
"float likelihood=exp(-pow((cb-0.415)/0.115,2.0)-pow((cr-0.565)/0.135,2.0));"
|
||||||
|
"float v=clamp(coverage.r,0.0,1.0)*smoothstep(0.05,0.60,likelihood);"
|
||||||
|
"v=mix(v,1.0-v,invert); return vec4(v,v,v,1.0); }");
|
||||||
k[@"even"] = PGKernel(@
|
k[@"even"] = PGKernel(@
|
||||||
"kernel vec4 pgEven(__sample source,__sample broad,float amount) {"
|
"kernel vec4 pgEven(__sample source,__sample broad,float amount) {"
|
||||||
"vec4 s=unpremultiply(source); vec3 b=unpremultiply(broad).rgb;"
|
"vec4 s=unpremultiply(source); vec3 b=unpremultiply(broad).rgb;"
|
||||||
@@ -133,6 +140,11 @@ static NSDictionary<NSString *, CIColorKernel *> *PGKernels(void) {
|
|||||||
- (CIImage *)processImage:(CIImage *)image settings:(PGBeautySettings)s
|
- (CIImage *)processImage:(CIImage *)image settings:(PGBeautySettings)s
|
||||||
skinMask:(CIImage *)skinMask teethMask:(CIImage *)teethMask
|
skinMask:(CIImage *)skinMask teethMask:(CIImage *)teethMask
|
||||||
eyeMask:(CIImage *)eyeMask error:(NSError **)error {
|
eyeMask:(CIImage *)eyeMask error:(NSError **)error {
|
||||||
|
return [self processImage:image settings:s skinMask:skinMask colorMask:nil teethMask:teethMask eyeMask:eyeMask error:error];
|
||||||
|
}
|
||||||
|
- (CIImage *)processImage:(CIImage *)image settings:(PGBeautySettings)s
|
||||||
|
skinMask:(CIImage *)skinMask colorMask:(CIImage *)colorMask
|
||||||
|
teethMask:(CIImage *)teethMask eyeMask:(CIImage *)eyeMask error:(NSError **)error {
|
||||||
if (error) *error = nil;
|
if (error) *error = nil;
|
||||||
CGRect extent = image.extent;
|
CGRect extent = image.extent;
|
||||||
if (!image || CGRectIsEmpty(extent) || CGRectIsInfinite(extent) ||
|
if (!image || CGRectIsEmpty(extent) || CGRectIsInfinite(extent) ||
|
||||||
@@ -145,7 +157,7 @@ static NSDictionary<NSString *, CIColorKernel *> *PGKernels(void) {
|
|||||||
float mix = PGClamp(s.mix, 0, 1);
|
float mix = PGClamp(s.mix, 0, 1);
|
||||||
if (mix == 0 && !s.showMask) return image;
|
if (mix == 0 && !s.showMask) return image;
|
||||||
NSDictionary *k = PGKernels();
|
NSDictionary *k = PGKernels();
|
||||||
if (k.count != 10) {
|
if (k.count != 11) {
|
||||||
if (error) *error = [NSError errorWithDomain:@"PGBeautyRenderer" code:2
|
if (error) *error = [NSError errorWithDomain:@"PGBeautyRenderer" code:2
|
||||||
userInfo:@{NSLocalizedDescriptionKey:@"The beauty image kernels could not be compiled."}];
|
userInfo:@{NSLocalizedDescriptionKey:@"The beauty image kernels could not be compiled."}];
|
||||||
return nil;
|
return nil;
|
||||||
@@ -188,6 +200,18 @@ static NSDictionary<NSString *, CIColorKernel *> *PGKernels(void) {
|
|||||||
float invert = s.debugChannel==1 || s.debugChannel==2 ? (s.invertMask ? 1 : 0) : 0;
|
float invert = s.debugChannel==1 || s.debugChannel==2 ? (s.invertMask ? 1 : 0) : 0;
|
||||||
return PGApply(k[@"debug"],extent,@[image,preview,@(invert)]);
|
return PGApply(k[@"debug"],extent,@[image,preview,@(invert)]);
|
||||||
}
|
}
|
||||||
|
// Detail protection may contain small eye, brow, nose and lip cutouts.
|
||||||
|
// Exposure and color must not reuse those holes or sharpen a face ellipse
|
||||||
|
// with the detail threshold; that creates visible rings and islands.
|
||||||
|
CIImage *toneMask=mask;
|
||||||
|
BOOL changesColor=brightness>0 || hue!=0 || saturation!=0 || even>0 ||
|
||||||
|
(s.skinTonePreset>0 && s.skinTonePreset<=6);
|
||||||
|
if(colorMask && changesColor) {
|
||||||
|
CGFloat feather=MAX(3,MIN(extent.size.width,extent.size.height)*0.03);
|
||||||
|
CIImage *support=[colorMask imageByCroppingToRect:extent];
|
||||||
|
toneMask=PGApply(k[@"colorCoverage"],extent,@[PGBlur(support,feather,extent),
|
||||||
|
PGBlur(image,MAX(1,feather*0.15),extent),@(s.invertMask?1:0)]);
|
||||||
|
}
|
||||||
CIImage *result = image;
|
CIImage *result = image;
|
||||||
if (local*amount>0) {
|
if (local*amount>0) {
|
||||||
// The shipped helper derives both blur scales from the detail strength.
|
// The shipped helper derives both blur scales from the detail strength.
|
||||||
@@ -207,12 +231,12 @@ static NSDictionary<NSString *, CIColorKernel *> *PGKernels(void) {
|
|||||||
if (hue!=0) color = [color imageByApplyingFilter:@"CIHueAdjust" withInputParameters:@{kCIInputAngleKey:@(hue*M_PI/180.0)}];
|
if (hue!=0) color = [color imageByApplyingFilter:@"CIHueAdjust" withInputParameters:@{kCIInputAngleKey:@(hue*M_PI/180.0)}];
|
||||||
if (saturation!=0) color = [color imageByApplyingFilter:@"CIColorControls" withInputParameters:@{kCIInputSaturationKey:@(1+saturation/100.f)}];
|
if (saturation!=0) color = [color imageByApplyingFilter:@"CIColorControls" withInputParameters:@{kCIInputSaturationKey:@(1+saturation/100.f)}];
|
||||||
if (even>0) color = PGApply(k[@"even"],extent,@[color,PGBlur(color,radius*1.8,extent),@(even)]);
|
if (even>0) color = PGApply(k[@"even"],extent,@[color,PGBlur(color,radius*1.8,extent),@(even)]);
|
||||||
if (color!=result) result = PGApply(k[@"maskedMix"],extent,@[result,color,mask,@1]);
|
if (color!=result) result = PGApply(k[@"maskedMix"],extent,@[result,color,toneMask,@1]);
|
||||||
if (sharpness*amount>0) result = [result imageByApplyingFilter:@"CISharpenLuminance"
|
if (sharpness*amount>0) result = [result imageByApplyingFilter:@"CISharpenLuminance"
|
||||||
withInputParameters:@{kCIInputSharpnessKey:@(sharpness*amount)}];
|
withInputParameters:@{kCIInputSharpnessKey:@(sharpness*amount)}];
|
||||||
if (s.skinTonePreset>0 && s.skinTonePreset<=6) {
|
if (s.skinTonePreset>0 && s.skinTonePreset<=6) {
|
||||||
CIImage *preset = PGApply(k[@"preset"],extent,@[result,@(s.skinTonePreset)]);
|
CIImage *preset = PGApply(k[@"preset"],extent,@[result,@(s.skinTonePreset)]);
|
||||||
result = PGApply(k[@"maskedMix"],extent,@[result,preset,mask,@1]);
|
result = PGApply(k[@"maskedMix"],extent,@[result,preset,toneMask,@1]);
|
||||||
}
|
}
|
||||||
if (global>0) {
|
if (global>0) {
|
||||||
CGFloat fine = fmin(7.4,2.275+3.15*pow(global,0.72)+0.95*global);
|
CGFloat fine = fmin(7.4,2.275+3.15*pow(global,0.72)+0.95*global);
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ NS_ASSUME_NONNULL_BEGIN
|
|||||||
/// observations retain their bounding box; render geometry is stored separately.
|
/// observations retain their bounding box; render geometry is stored separately.
|
||||||
@property (nonatomic, copy, readonly) NSArray<VNFaceObservation *> *faces;
|
@property (nonatomic, copy, readonly) NSArray<VNFaceObservation *> *faces;
|
||||||
@property (nonatomic, strong, readonly) CIImage *skinMask;
|
@property (nonatomic, strong, readonly) CIImage *skinMask;
|
||||||
|
/// Continuous face support before eye/nose/lip detail-protection cutouts.
|
||||||
|
@property (nonatomic, strong, readonly) CIImage *colorMask;
|
||||||
@property (nonatomic, strong, readonly) CIImage *teethMask;
|
@property (nonatomic, strong, readonly) CIImage *teethMask;
|
||||||
@property (nonatomic, strong, readonly) CIImage *eyeMask;
|
@property (nonatomic, strong, readonly) CIImage *eyeMask;
|
||||||
@property (nonatomic, strong, readonly) CIImage *image;
|
@property (nonatomic, strong, readonly) CIImage *image;
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ static NSString * const PGFaceErrorDomain = @"media.dayu.PaintFCP.Face";
|
|||||||
@interface PGFaceFrame ()
|
@interface PGFaceFrame ()
|
||||||
@property (nonatomic, copy, readwrite) NSArray<VNFaceObservation *> *faces;
|
@property (nonatomic, copy, readwrite) NSArray<VNFaceObservation *> *faces;
|
||||||
@property (nonatomic, strong, readwrite) CIImage *skinMask;
|
@property (nonatomic, strong, readwrite) CIImage *skinMask;
|
||||||
|
@property (nonatomic, strong, readwrite) CIImage *colorMask;
|
||||||
@property (nonatomic, strong, readwrite) CIImage *teethMask;
|
@property (nonatomic, strong, readwrite) CIImage *teethMask;
|
||||||
@property (nonatomic, strong, readwrite) CIImage *eyeMask;
|
@property (nonatomic, strong, readwrite) CIImage *eyeMask;
|
||||||
@property (nonatomic, strong, readwrite) CIImage *image;
|
@property (nonatomic, strong, readwrite) CIImage *image;
|
||||||
@@ -340,15 +341,17 @@ static CIImage *PGMaximum(CIImage *a, CIImage *b) {
|
|||||||
overlay = [outline imageByCompositingOverImage:overlay];
|
overlay = [outline imageByCompositingOverImage:overlay];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
CIImage *colorCoverage=skin;
|
||||||
if (settings.featureProtection) skin = [_subtract applyWithExtent:extent arguments:@[skin, protection]];
|
if (settings.featureProtection) skin = [_subtract applyWithExtent:extent arguments:@[skin, protection]];
|
||||||
PGFaceFrame *frame = [[PGFaceFrame alloc] init];
|
PGFaceFrame *frame = [[PGFaceFrame alloc] init];
|
||||||
frame.faces = observations ?: @[];
|
frame.faces = observations ?: @[];
|
||||||
frame.image = [self warpImage:image geometry:geometry settings:settings extent:extent];
|
frame.image = [self warpImage:image geometry:geometry settings:settings extent:extent];
|
||||||
frame.skinMask = [self warpImage:skin geometry:geometry settings:settings extent:extent];
|
frame.skinMask = [self warpImage:skin geometry:geometry settings:settings extent:extent];
|
||||||
|
frame.colorMask = [self warpImage:colorCoverage geometry:geometry settings:settings extent:extent];
|
||||||
frame.teethMask = [self warpImage:teeth geometry:geometry settings:settings extent:extent];
|
frame.teethMask = [self warpImage:teeth geometry:geometry settings:settings extent:extent];
|
||||||
frame.eyeMask = [self warpImage:eyes geometry:geometry settings:settings extent:extent];
|
frame.eyeMask = [self warpImage:eyes geometry:geometry settings:settings extent:extent];
|
||||||
frame.overlayImage = [overlay imageByCroppingToRect:extent];
|
frame.overlayImage = [overlay imageByCroppingToRect:extent];
|
||||||
if (!frame.image || !frame.skinMask || !frame.teethMask || !frame.eyeMask) {
|
if (!frame.image || !frame.skinMask || !frame.colorMask || !frame.teethMask || !frame.eyeMask) {
|
||||||
if (error) *error = [NSError errorWithDomain:PGFaceErrorDomain code:3 userInfo:@{NSLocalizedDescriptionKey:@"Unable to apply face geometry."}];
|
if (error) *error = [NSError errorWithDomain:PGFaceErrorDomain code:3 userInfo:@{NSLocalizedDescriptionKey:@"Unable to apply face geometry."}];
|
||||||
return nil;
|
return nil;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,9 +2,12 @@
|
|||||||
#import <CoreImage/CoreImage.h>
|
#import <CoreImage/CoreImage.h>
|
||||||
#import <IOSurface/IOSurface.h>
|
#import <IOSurface/IOSurface.h>
|
||||||
#import "PGParameterIDs.h"
|
#import "PGParameterIDs.h"
|
||||||
|
#include <math.h>
|
||||||
|
|
||||||
@interface PGFaceSelectOSC : NSObject <FxOnScreenControl_v4>
|
@interface PGFaceSelectOSC : NSObject <FxOnScreenControl_v4>
|
||||||
@property (assign) id<PROAPIAccessing> apiManager;
|
@property (assign) id<PROAPIAccessing> apiManager;
|
||||||
|
@property BOOL dragging;
|
||||||
|
@property BOOL undoOpen;
|
||||||
@end
|
@end
|
||||||
@implementation PGFaceSelectOSC
|
@implementation PGFaceSelectOSC
|
||||||
- (instancetype)initWithAPIManager:(id<PROAPIAccessing>)api {
|
- (instancetype)initWithAPIManager:(id<PROAPIAccessing>)api {
|
||||||
@@ -14,21 +17,33 @@
|
|||||||
- (id<FxOnScreenControlAPI_v4>)oscAPI {return [_apiManager apiForProtocol:@protocol(FxOnScreenControlAPI_v4)];}
|
- (id<FxOnScreenControlAPI_v4>)oscAPI {return [_apiManager apiForProtocol:@protocol(FxOnScreenControlAPI_v4)];}
|
||||||
- (BOOL)selectionEnabledAtTime:(CMTime)time {
|
- (BOOL)selectionEnabledAtTime:(CMTime)time {
|
||||||
BOOL selected=NO;id<FxParameterRetrievalAPI_v6> api=[_apiManager apiForProtocol:@protocol(FxParameterRetrievalAPI_v6)];
|
BOOL selected=NO;id<FxParameterRetrievalAPI_v6> api=[_apiManager apiForProtocol:@protocol(FxParameterRetrievalAPI_v6)];
|
||||||
[api getBoolValue:&selected fromParameter:PGSingleFace atTime:time];return selected;
|
return api && [api getBoolValue:&selected fromParameter:PGSingleFace atTime:time] && selected;
|
||||||
|
}
|
||||||
|
- (BOOL)validObjectBounds:(CGRect *)bounds {
|
||||||
|
id<FxOnScreenControlAPI_v4> api=[self oscAPI];
|
||||||
|
if(!api || !bounds) return NO;
|
||||||
|
CGRect b=[api objectBounds];
|
||||||
|
if(!isfinite(b.origin.x)||!isfinite(b.origin.y)||!isfinite(b.size.width)||!isfinite(b.size.height)||b.size.width<=0||b.size.height<=0) return NO;
|
||||||
|
*bounds=b;return YES;
|
||||||
}
|
}
|
||||||
- (void)drawOSCWithWidth:(NSInteger)width height:(NSInteger)height activePart:(NSInteger)part destinationImage:(FxImageTile *)tile atTime:(CMTime)time {
|
- (void)drawOSCWithWidth:(NSInteger)width height:(NSInteger)height activePart:(NSInteger)part destinationImage:(FxImageTile *)tile atTime:(CMTime)time {
|
||||||
if(!tile.ioSurface||width<=0||height<=0) return;
|
if(!tile.ioSurface||width<=0||height<=0) return;
|
||||||
CIImage *output=[[CIImage imageWithColor:CIColor.clearColor] imageByCroppingToRect:CGRectMake(0,0,width,height)];
|
size_t surfaceW=IOSurfaceGetWidth((__bridge IOSurfaceRef)tile.ioSurface),surfaceH=IOSurfaceGetHeight((__bridge IOSurfaceRef)tile.ioSurface);
|
||||||
if([self selectionEnabledAtTime:time]) {
|
if(!surfaceW||!surfaceH) return;
|
||||||
|
CIImage *output=[[CIImage imageWithColor:CIColor.clearColor] imageByCroppingToRect:CGRectMake(0,0,surfaceW,surfaceH)];
|
||||||
|
CGRect bounds=CGRectZero;
|
||||||
|
if([self selectionEnabledAtTime:time] && [self validObjectBounds:&bounds]) {
|
||||||
double x=.5,y=.5;
|
double x=.5,y=.5;
|
||||||
id<FxParameterRetrievalAPI_v6> params=[_apiManager apiForProtocol:@protocol(FxParameterRetrievalAPI_v6)];
|
id<FxParameterRetrievalAPI_v6> params=[_apiManager apiForProtocol:@protocol(FxParameterRetrievalAPI_v6)];
|
||||||
[params getXValue:&x YValue:&y fromParameter:PGSelection atTime:time];
|
BOOL valid=[params getXValue:&x YValue:&y fromParameter:PGSelection atTime:time] && isfinite(x) && isfinite(y);
|
||||||
CGRect bounds=[[self oscAPI] objectBounds];double canvasX=width*.5,canvasY=height*.5;
|
double canvasX=NAN,canvasY=NAN;
|
||||||
[[self oscAPI] convertPointFromSpace:kFxDrawingCoordinates_OBJECT fromX:bounds.origin.x+x*bounds.size.width fromY:bounds.origin.y+y*bounds.size.height toSpace:kFxDrawingCoordinates_CANVAS toX:&canvasX toY:&canvasY];
|
if(valid) [[self oscAPI] convertPointFromSpace:kFxDrawingCoordinates_OBJECT fromX:bounds.origin.x+x*bounds.size.width fromY:bounds.origin.y+y*bounds.size.height toSpace:kFxDrawingCoordinates_CANVAS toX:&canvasX toY:&canvasY];
|
||||||
CGFloat scale=MAX(1,[[self oscAPI] backingScaleFactor]);
|
// CANVAS coordinates already address the OSC surface. Callback width and
|
||||||
size_t surfaceW=IOSurfaceGetWidth((__bridge IOSurfaceRef)tile.ioSurface),surfaceH=IOSurfaceGetHeight((__bridge IOSurfaceRef)tile.ioSurface);
|
// height may describe the effect image and must not rescale this point.
|
||||||
CGFloat sx=(CGFloat)surfaceW/width,sy=(CGFloat)surfaceH/height;
|
double backing=[[self oscAPI] backingScaleFactor];
|
||||||
canvasX*=sx;canvasY*=sy;CGFloat radius=10*scale;
|
CGFloat scale=isfinite(backing)?MAX(1,backing):1;
|
||||||
|
CGFloat radius=10*scale;
|
||||||
|
if(valid && isfinite(canvasX) && isfinite(canvasY)) {
|
||||||
CIColor *color=part?[CIColor colorWithRed:1 green:.8 blue:.2]:[CIColor colorWithRed:.1 green:.9 blue:.8];
|
CIColor *color=part?[CIColor colorWithRed:1 green:.8 blue:.2]:[CIColor colorWithRed:.1 green:.9 blue:.8];
|
||||||
CIImage *ring=[CIFilter filterWithName:@"CIRadialGradient" withInputParameters:@{@"inputCenter":[CIVector vectorWithX:canvasX Y:canvasY],@"inputRadius0":@(radius-2),@"inputRadius1":@(radius),@"inputColor0":color,@"inputColor1":CIColor.clearColor}].outputImage;
|
CIImage *ring=[CIFilter filterWithName:@"CIRadialGradient" withInputParameters:@{@"inputCenter":[CIVector vectorWithX:canvasX Y:canvasY],@"inputRadius0":@(radius-2),@"inputRadius1":@(radius),@"inputColor0":color,@"inputColor1":CIColor.clearColor}].outputImage;
|
||||||
CIImage *hole=[CIFilter filterWithName:@"CIRadialGradient" withInputParameters:@{@"inputCenter":[CIVector vectorWithX:canvasX Y:canvasY],@"inputRadius0":@(radius-3),@"inputRadius1":@(radius-2),@"inputColor0":CIColor.blackColor,@"inputColor1":CIColor.whiteColor}].outputImage;
|
CIImage *hole=[CIFilter filterWithName:@"CIRadialGradient" withInputParameters:@{@"inputCenter":[CIVector vectorWithX:canvasX Y:canvasY],@"inputRadius0":@(radius-3),@"inputRadius1":@(radius-2),@"inputColor0":CIColor.blackColor,@"inputColor1":CIColor.whiteColor}].outputImage;
|
||||||
@@ -37,27 +52,45 @@
|
|||||||
CIImage *h=[[CIImage imageWithColor:color] imageByCroppingToRect:CGRectMake(canvasX-16*scale,canvasY-scale,32*scale,2*scale)];
|
CIImage *h=[[CIImage imageWithColor:color] imageByCroppingToRect:CGRectMake(canvasX-16*scale,canvasY-scale,32*scale,2*scale)];
|
||||||
output=[[h imageByCompositingOverImage:v] imageByCompositingOverImage:ring];
|
output=[[h imageByCompositingOverImage:v] imageByCompositingOverImage:ring];
|
||||||
}
|
}
|
||||||
|
}
|
||||||
CIContext *context=[CIContext contextWithOptions:@{kCIContextCacheIntermediates:@NO}];
|
CIContext *context=[CIContext contextWithOptions:@{kCIContextCacheIntermediates:@NO}];
|
||||||
size_t w=IOSurfaceGetWidth((__bridge IOSurfaceRef)tile.ioSurface),h=IOSurfaceGetHeight((__bridge IOSurfaceRef)tile.ioSurface);
|
size_t w=IOSurfaceGetWidth((__bridge IOSurfaceRef)tile.ioSurface),h=IOSurfaceGetHeight((__bridge IOSurfaceRef)tile.ioSurface);
|
||||||
if(tile.imageOrigin==kFxImageOrigin_TOP_LEFT) output=[output imageByApplyingTransform:CGAffineTransformMake(1,0,0,-1,0,h)];
|
if(tile.imageOrigin==kFxImageOrigin_TOP_LEFT) output=[output imageByApplyingTransform:CGAffineTransformMake(1,0,0,-1,0,h)];
|
||||||
[context render:output toIOSurface:(__bridge IOSurfaceRef)tile.ioSurface bounds:CGRectMake(0,0,w,h) colorSpace:tile.colorSpace];
|
[context render:output toIOSurface:(__bridge IOSurfaceRef)tile.ioSurface bounds:CGRectMake(0,0,w,h) colorSpace:tile.colorSpace];
|
||||||
}
|
}
|
||||||
- (void)hitTestOSCAtMousePositionX:(double)x mousePositionY:(double)y activePart:(NSInteger *)part atTime:(CMTime)time {
|
- (void)hitTestOSCAtMousePositionX:(double)x mousePositionY:(double)y activePart:(NSInteger *)part atTime:(CMTime)time {
|
||||||
if(part) *part=([self selectionEnabledAtTime:time]&&CGRectContainsPoint([[self oscAPI] objectBounds],CGPointMake(x,y)))?1:0;
|
if(!part) return;*part=0;CGRect bounds=CGRectZero;
|
||||||
|
if(isfinite(x)&&isfinite(y)&&[self selectionEnabledAtTime:time]&&[self validObjectBounds:&bounds]&&CGRectContainsPoint(bounds,CGPointMake(x,y))) *part=1;
|
||||||
}
|
}
|
||||||
- (void)setSelectionX:(double)x y:(double)y time:(CMTime)time update:(BOOL *)update {
|
- (void)setSelectionX:(double)x y:(double)y time:(CMTime)time update:(BOOL *)update {
|
||||||
CGRect bounds=[[self oscAPI] objectBounds];if(bounds.size.width<=0||bounds.size.height<=0) return;
|
if(update) *update=NO;
|
||||||
|
CGRect bounds=CGRectZero;
|
||||||
|
if(!isfinite(x)||!isfinite(y)||![self validObjectBounds:&bounds]) return;
|
||||||
id<FxParameterSettingAPI_v5> api=[_apiManager apiForProtocol:@protocol(FxParameterSettingAPI_v5)];
|
id<FxParameterSettingAPI_v5> api=[_apiManager apiForProtocol:@protocol(FxParameterSettingAPI_v5)];
|
||||||
BOOL ok=[api setXValue:MIN(1,MAX(0,(x-bounds.origin.x)/bounds.size.width)) YValue:MIN(1,MAX(0,(y-bounds.origin.y)/bounds.size.height)) toParameter:PGSelection atTime:time];
|
BOOL ok=[api setXValue:MIN(1,MAX(0,(x-bounds.origin.x)/bounds.size.width)) YValue:MIN(1,MAX(0,(y-bounds.origin.y)/bounds.size.height)) toParameter:PGSelection atTime:time];
|
||||||
if(update) *update=ok;
|
if(update) *update=ok;
|
||||||
}
|
}
|
||||||
- (void)mouseDownAtPositionX:(double)x positionY:(double)y activePart:(NSInteger)part modifiers:(FxModifierKeys)keys forceUpdate:(BOOL *)update atTime:(CMTime)time {
|
- (void)mouseDownAtPositionX:(double)x positionY:(double)y activePart:(NSInteger)part modifiers:(FxModifierKeys)keys forceUpdate:(BOOL *)update atTime:(CMTime)time {
|
||||||
id<FxUndoAPI> undo=[_apiManager apiForProtocol:@protocol(FxUndoAPI)];[undo startUndoGroup:@"选择人脸"];
|
if(update) *update=NO;
|
||||||
[self setSelectionX:x y:y time:time update:update];
|
if(part!=1 || !isfinite(x) || !isfinite(y) || ![self selectionEnabledAtTime:time]) return;
|
||||||
|
CGRect bounds=CGRectZero;if(![self validObjectBounds:&bounds]) return;
|
||||||
|
id<FxUndoAPI> undo=[_apiManager apiForProtocol:@protocol(FxUndoAPI)];
|
||||||
|
if(_undoOpen) [undo endUndoGroup];
|
||||||
|
_undoOpen=[undo startUndoGroup:@"选择人脸"];
|
||||||
|
BOOL changed=NO;[self setSelectionX:x y:y time:time update:&changed];
|
||||||
|
_dragging=changed;
|
||||||
|
if(!changed && _undoOpen) {[undo endUndoGroup];_undoOpen=NO;}
|
||||||
|
if(update) *update=changed;
|
||||||
|
}
|
||||||
|
- (void)mouseDraggedAtPositionX:(double)x positionY:(double)y activePart:(NSInteger)part modifiers:(FxModifierKeys)keys forceUpdate:(BOOL *)update atTime:(CMTime)time {
|
||||||
|
if(update) *update=NO;
|
||||||
|
if(_dragging && part==1 && [self selectionEnabledAtTime:time]) [self setSelectionX:x y:y time:time update:update];
|
||||||
}
|
}
|
||||||
- (void)mouseDraggedAtPositionX:(double)x positionY:(double)y activePart:(NSInteger)part modifiers:(FxModifierKeys)keys forceUpdate:(BOOL *)update atTime:(CMTime)time {[self setSelectionX:x y:y time:time update:update];}
|
|
||||||
- (void)mouseUpAtPositionX:(double)x positionY:(double)y activePart:(NSInteger)part modifiers:(FxModifierKeys)keys forceUpdate:(BOOL *)update atTime:(CMTime)time {
|
- (void)mouseUpAtPositionX:(double)x positionY:(double)y activePart:(NSInteger)part modifiers:(FxModifierKeys)keys forceUpdate:(BOOL *)update atTime:(CMTime)time {
|
||||||
[self setSelectionX:x y:y time:time update:update];id<FxUndoAPI> undo=[_apiManager apiForProtocol:@protocol(FxUndoAPI)];[undo endUndoGroup];
|
if(update) *update=NO;
|
||||||
|
if(_dragging && part==1 && [self selectionEnabledAtTime:time]) [self setSelectionX:x y:y time:time update:update];
|
||||||
|
_dragging=NO;
|
||||||
|
if(_undoOpen) {id<FxUndoAPI> undo=[_apiManager apiForProtocol:@protocol(FxUndoAPI)];[undo endUndoGroup];_undoOpen=NO;}
|
||||||
}
|
}
|
||||||
- (void)keyDownAtPositionX:(double)x positionY:(double)y keyPressed:(unsigned short)key modifiers:(FxModifierKeys)modifiers forceUpdate:(BOOL *)update didHandle:(BOOL *)handled atTime:(CMTime)time {if(handled)*handled=NO;if(update)*update=NO;}
|
- (void)keyDownAtPositionX:(double)x positionY:(double)y keyPressed:(unsigned short)key modifiers:(FxModifierKeys)modifiers forceUpdate:(BOOL *)update didHandle:(BOOL *)handled atTime:(CMTime)time {if(handled)*handled=NO;if(update)*update=NO;}
|
||||||
- (void)keyUpAtPositionX:(double)x positionY:(double)y keyPressed:(unsigned short)key modifiers:(FxModifierKeys)modifiers forceUpdate:(BOOL *)update didHandle:(BOOL *)handled atTime:(CMTime)time {if(handled)*handled=NO;if(update)*update=NO;}
|
- (void)keyUpAtPositionX:(double)x positionY:(double)y keyPressed:(unsigned short)key modifiers:(FxModifierKeys)modifiers forceUpdate:(BOOL *)update didHandle:(BOOL *)handled atTime:(CMTime)time {if(handled)*handled=NO;if(update)*update=NO;}
|
||||||
|
|||||||
@@ -250,7 +250,7 @@ static CIImage *PGImageFromTile(FxImageTile *tile) {
|
|||||||
PGFaceFrame *frame=[_faceProcessor processImage:input settings:s time:time error:&stageError];
|
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; }
|
if(!frame) { os_log_error(PGRenderLog(), "face processing failed: %{public}@",stageError); if(error)*error=stageError; return NO; }
|
||||||
PGBeautySettings beauty=s; beauty.mix=1;
|
PGBeautySettings beauty=s; beauty.mix=1;
|
||||||
output=[_beautyRenderer processImage:frame.image settings:beauty skinMask:frame.skinMask teethMask:frame.teethMask eyeMask:frame.eyeMask error:&stageError];
|
output=[_beautyRenderer processImage:frame.image settings:beauty skinMask:frame.skinMask colorMask:frame.colorMask teethMask:frame.teethMask eyeMask:frame.eyeMask error:&stageError];
|
||||||
if(!output) { os_log_error(PGRenderLog(), "beauty processing failed: %{public}@",stageError); if(error)*error=stageError; return NO; }
|
if(!output) { os_log_error(PGRenderLog(), "beauty processing failed: %{public}@",stageError); if(error)*error=stageError; return NO; }
|
||||||
overlay=frame.overlayImage;
|
overlay=frame.overlayImage;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,9 +17,9 @@
|
|||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>APPL</string>
|
<string>APPL</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>1.0.1</string>
|
<string>1.0.2</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>3</string>
|
<string>4</string>
|
||||||
<key>LSMinimumSystemVersion</key>
|
<key>LSMinimumSystemVersion</key>
|
||||||
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
|
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
|
||||||
<key>NSHumanReadableCopyright</key>
|
<key>NSHumanReadableCopyright</key>
|
||||||
|
|||||||
@@ -20,6 +20,10 @@ REA 直接打开内嵌插件。原包为 arm64/x86_64 通用 Mach-O,Ghidra 无
|
|||||||
|人脸与跟踪|Vision landmarks、原版 Metal 位移网格、OpenCV 动态库、历史脸框/关键点、选择及分析数据均有证据。`renderDestinationImage:` 及函数检索见插件证据包|部分回答;运动遮挡、切镜与完整身份逻辑未等价验证|
|
|人脸与跟踪|Vision landmarks、原版 Metal 位移网格、OpenCV 动态库、历史脸框/关键点、选择及分析数据均有证据。`renderDestinationImage:` 及函数检索见插件证据包|部分回答;运动遮挡、切镜与完整身份逻辑未等价验证|
|
||||||
|像素一致性|未获得原版相同输入/参数的可信渲染对照|未验证,不能声称逐像素一致|
|
|像素一致性|未获得原版相同输入/参数的可信渲染对照|未验证,不能声称逐像素一致|
|
||||||
|
|
||||||
|
## 原版 FCP 模板的实际发布范围
|
||||||
|
|
||||||
|
补充检查用户指定的 `~/Movies/Motion Templates.localized/Effects.localized/💙MS超级美颜/MS超级美颜V2/MS超级美颜V2.moef`:共15个发布入口,包括空名自定义面板入口、脸型、脸框预览、白牙、亮眼和混合,没有跟踪目标、跟踪开关或前向/后向分析按钮。清单和模板摘要见 [发布参数记录](reference-template-parameters.json)。二进制内部的分析/跟踪代码不能据此解释成当前FCP模板已开放的功能;本项目的三目标分析与跟踪是独立扩展,没有完成原版同类功能的用户界面或运动等价验证。
|
||||||
|
|
||||||
## 已实现的功能
|
## 已实现的功能
|
||||||
|
|
||||||
- 中频磨皮、强度、半径、高频细节平滑、锐化、五官保护及各大功能分组开关。
|
- 中频磨皮、强度、半径、高频细节平滑、锐化、五官保护及各大功能分组开关。
|
||||||
@@ -27,7 +31,7 @@ REA 直接打开内嵌插件。原包为 arm64/x86_64 通用 Mach-O,Ghidra 无
|
|||||||
- 牙齿美白和眼睛提亮,基于 Vision 面部区域构建软蒙版。
|
- 牙齿美白和眼睛提亮,基于 Vision 面部区域构建软蒙版。
|
||||||
- 瘦脸、大眼、短脸、窄脸、下巴、V 脸、下颌、颧骨、小头和小头范围。
|
- 瘦脸、大眼、短脸、窄脸、下巴、V 脸、下颌、颧骨、小头和小头范围。
|
||||||
- 全脸/选中脸、画面点击选脸、多人高速检测、防跳脸范围、脸框和跟踪框。
|
- 全脸/选中脸、画面点击选脸、多人高速检测、防跳脸范围、脸框和跟踪框。
|
||||||
- 三个独立跟踪目标,向前/向后分析,清除当前目标;精确 CMTime 数据持久化到宿主工程,可逆序和任意顺序读取。
|
- 作为扩展提供三个独立跟踪目标,向前/向后分析,清除当前目标;精确 CMTime 数据持久化到宿主工程,可逆序和任意顺序读取。
|
||||||
- 蒙版阈值/柔度/边缘保护、皮肤/牙齿/眼睛通道预览与反转、包含几何变形的整体混合。
|
- 蒙版阈值/柔度/边缘保护、皮肤/牙齿/眼睛通道预览与反转、包含几何变形的整体混合。
|
||||||
- 保留透明度、完整输入范围、非零图像原点与宿主上下原点;原生 FxPlug 参数可由宿主保存和制作关键帧。
|
- 保留透明度、完整输入范围、非零图像原点与宿主上下原点;原生 FxPlug 参数可由宿主保存和制作关键帧。
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,83 @@
|
|||||||
|
{
|
||||||
|
"path": "/Users/dayu/Movies/Motion Templates.localized/Effects.localized/💙MS超级美颜/MS超级美颜V2/MS超级美颜V2.moef",
|
||||||
|
"sha256": "5c20b387f6a6438614a9182d3f93f67473881d433a25f7c3b557d063f55a5d92",
|
||||||
|
"published_count": 15,
|
||||||
|
"published": [
|
||||||
|
{
|
||||||
|
"object": "10030",
|
||||||
|
"channel": "./210",
|
||||||
|
"name": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"object": "10030",
|
||||||
|
"channel": "./18",
|
||||||
|
"name": "瘦脸"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"object": "10030",
|
||||||
|
"channel": "./33",
|
||||||
|
"name": "大眼"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"object": "10030",
|
||||||
|
"channel": "./22",
|
||||||
|
"name": "短脸"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"object": "10030",
|
||||||
|
"channel": "./23",
|
||||||
|
"name": "窄脸"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"object": "10030",
|
||||||
|
"channel": "./24",
|
||||||
|
"name": "下巴"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"object": "10030",
|
||||||
|
"channel": "./25",
|
||||||
|
"name": "V脸"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"object": "10030",
|
||||||
|
"channel": "./26",
|
||||||
|
"name": "下颚骨"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"object": "10030",
|
||||||
|
"channel": "./27",
|
||||||
|
"name": "颧骨"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"object": "10030",
|
||||||
|
"channel": "./28",
|
||||||
|
"name": "小头"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"object": "10030",
|
||||||
|
"channel": "./29",
|
||||||
|
"name": "小头范围"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"object": "10030",
|
||||||
|
"channel": "./19",
|
||||||
|
"name": "人脸框预览"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"object": "10030",
|
||||||
|
"channel": "./31",
|
||||||
|
"name": "白牙"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"object": "10030",
|
||||||
|
"channel": "./32",
|
||||||
|
"name": "亮眼"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"object": "10030",
|
||||||
|
"channel": "./10001",
|
||||||
|
"name": "混合"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"tracking_controls_published": false
|
||||||
|
}
|
||||||
@@ -45,6 +45,13 @@ try:
|
|||||||
raise RuntimeError('Apple did not accept notarization; original installed app retained')
|
raise RuntimeError('Apple did not accept notarization; original installed app retained')
|
||||||
run(['xcrun','stapler','staple',str(app)])
|
run(['xcrun','stapler','staple',str(app)])
|
||||||
run(['xcrun','stapler','validate',str(app)])
|
run(['xcrun','stapler','validate',str(app)])
|
||||||
|
# A live host retains its old XPC/OSC connection. Replacing the bundle while
|
||||||
|
# that connection exists can deadlock host shutdown or select stale code.
|
||||||
|
running=run(['ps','-axo','comm=']).splitlines()
|
||||||
|
hosts={'/Applications/Motion.app/Contents/MacOS/Motion',
|
||||||
|
'/Applications/Final Cut Pro.app/Contents/MacOS/Final Cut Pro'}
|
||||||
|
if hosts.intersection(line.strip() for line in running):
|
||||||
|
raise RuntimeError('Close Motion and Final Cut Pro before installing; installed app retained')
|
||||||
if installed.exists():
|
if installed.exists():
|
||||||
info=plistlib.loads((installed/'Contents/Info.plist').read_bytes())
|
info=plistlib.loads((installed/'Contents/Info.plist').read_bytes())
|
||||||
if info.get('CFBundleIdentifier')!=bundle_id:raise RuntimeError('The installation path belongs to another product')
|
if info.get('CFBundleIdentifier')!=bundle_id:raise RuntimeError('The installation path belongs to another product')
|
||||||
@@ -60,6 +67,9 @@ try:
|
|||||||
if info.get('CFBundleIdentifier')!=bundle_id or 'TeamIdentifier=B3W25FX5H5' not in signature:raise RuntimeError('Installed app identity mismatch')
|
if info.get('CFBundleIdentifier')!=bundle_id or 'TeamIdentifier=B3W25FX5H5' not in signature:raise RuntimeError('Installed app identity mismatch')
|
||||||
(logs/'installed-signature.txt').write_text(signature)
|
(logs/'installed-signature.txt').write_text(signature)
|
||||||
run(['pluginkit','-a',str(installed/'Contents/PlugIns/beatygril XPC Service.pluginkit')])
|
run(['pluginkit','-a',str(installed/'Contents/PlugIns/beatygril XPC Service.pluginkit')])
|
||||||
|
# Xcode may register its build product. Only the installed plugin should
|
||||||
|
# be discoverable while native host validation is in progress.
|
||||||
|
run(['pluginkit','-r',str(plugin)])
|
||||||
except Exception:
|
except Exception:
|
||||||
if installed.exists():shutil.rmtree(installed)
|
if installed.exists():shutil.rmtree(installed)
|
||||||
if previous.exists():shutil.move(str(previous),str(installed))
|
if previous.exists():shutil.move(str(previous),str(installed))
|
||||||
|
|||||||
可执行文件
+20
@@ -0,0 +1,20 @@
|
|||||||
|
#!/bin/zsh
|
||||||
|
set -euo pipefail
|
||||||
|
cd "${0:A:h:h}"
|
||||||
|
output_dir=$(mktemp -d /tmp/paintfcp-osc-tests.XXXXXX)
|
||||||
|
trap 'rm -rf "$output_dir"' EXIT
|
||||||
|
plugin_dir="$PWD/beatygril/beatygril/Plugin"
|
||||||
|
sdk_frameworks=/Library/Developer/SDKs/FxPlug.sdk/Library/Frameworks
|
||||||
|
runtime_frameworks=/Library/Developer/Frameworks
|
||||||
|
compiler_extra=()
|
||||||
|
if [[ "${PAINTFCP_OSC_ASAN:-0}" == 1 ]]; then
|
||||||
|
compiler_extra=(-fsanitize=address -g)
|
||||||
|
fi
|
||||||
|
xcrun clang "${compiler_extra[@]}" -fobjc-arc -fmodules -Wall -Wextra -Wno-unused-parameter \
|
||||||
|
-mmacosx-version-min=13.0 -I "$plugin_dir" -F "$sdk_frameworks" -F "$runtime_frameworks" \
|
||||||
|
-Wl,-rpath,"$runtime_frameworks" \
|
||||||
|
"$plugin_dir/PGFaceSelectOSC.m" tests/osc_tests.m \
|
||||||
|
-framework Foundation -framework CoreImage -framework CoreGraphics \
|
||||||
|
-framework CoreMedia -framework CoreVideo -framework IOSurface \
|
||||||
|
-framework FxPlug -framework PluginManager -o "$output_dir/osc-tests"
|
||||||
|
"$output_dir/osc-tests" "$plugin_dir/Info.plist"
|
||||||
@@ -103,6 +103,30 @@ int main(void) { @autoreleasepool {
|
|||||||
s=PGBeautyDefaults();s.midtoneDetail=1;s.maskSoftness=0;s.maskEdgeProtect=0;s.featureProtection=1;
|
s=PGBeautyDefaults();s.midtoneDetail=1;s.maskSoftness=0;s.maskEdgeProtect=0;s.featureProtection=1;
|
||||||
float *protected=Render(context,[renderer processImage:source settings:s skinMask:white teethMask:nil eyeMask:half error:&error],extent);
|
float *protected=Render(context,[renderer processImage:source settings:s skinMask:white teethMask:nil eyeMask:half error:&error],extent);
|
||||||
Check(Difference(base,protected,6,40)<0.00001 && Texture(protected,56,88)<Texture(base,56,88)*0.82,@"Feature protection retains eye detail while smoothing nearby skin");free(protected);
|
Check(Difference(base,protected,6,40)<0.00001 && Texture(protected,56,88)<Texture(base,56,88)*0.82,@"Feature protection retains eye detail while smoothing nearby skin");free(protected);
|
||||||
|
// A skin exposure adjustment must stay continuous across detail-protection
|
||||||
|
// holes. This models the eye/nose islands reported on the real beach clip.
|
||||||
|
s=PGBeautyDefaults();s.skinBrightness=1;s.featureProtection=1;
|
||||||
|
float *continuous=Render(context,[renderer processImage:source settings:s skinMask:half colorMask:white teethMask:half eyeMask:half error:&error],extent);
|
||||||
|
Check(Difference(base,continuous,6,40)>0.08 && Difference(base,continuous,56,88)>0.08,@"Skin exposure does not leave dark detail-protection holes");
|
||||||
|
s.featureProtection=0;
|
||||||
|
float *unprotectedColor=Render(context,[renderer processImage:source settings:s skinMask:half colorMask:white teethMask:half eyeMask:half error:&error],extent);
|
||||||
|
Check(Difference(continuous,unprotectedColor,4,W-4)<0.00001,@"Feature protection changes detail, not face exposure coverage");
|
||||||
|
free(continuous);free(unprotectedColor);
|
||||||
|
CIImage *flatSkin=[[CIImage imageWithColor:[CIColor colorWithRed:0.56 green:0.40 blue:0.31 alpha:1]] imageByCroppingToRect:extent];
|
||||||
|
float *flatPixels=Render(context,flatSkin,extent);
|
||||||
|
float *feathered=Render(context,[renderer processImage:flatSkin settings:s skinMask:half colorMask:half teethMask:nil eyeMask:nil error:&error],extent);
|
||||||
|
float largestStep=0;
|
||||||
|
for(int x=42;x<54;x++) {
|
||||||
|
int i=(32*W+x)*4,j=i+4;
|
||||||
|
largestStep=fmaxf(largestStep,fabsf((feathered[i]-flatPixels[i])-(feathered[j]-flatPixels[j])));
|
||||||
|
}
|
||||||
|
if(largestStep>=0.09) fprintf(stderr,"Face exposure boundary maximum step: %.6f\n",largestStep);
|
||||||
|
Check(largestStep<0.09,@"Face color coverage feathers a hard support boundary without a visible exposure step");
|
||||||
|
Check(Difference(flatPixels,feathered,64,88)<0.00001,@"Feathered face exposure stays local");free(feathered);free(flatPixels);
|
||||||
|
CIImage *blueHair=[[CIImage imageWithColor:[CIColor colorWithRed:0.03 green:0.16 blue:0.70 alpha:1]] imageByCroppingToRect:extent];
|
||||||
|
float *blueBefore=Render(context,blueHair,extent);
|
||||||
|
float *blueAfter=Render(context,[renderer processImage:blueHair settings:s skinMask:white colorMask:white teethMask:nil eyeMask:nil error:&error],extent);
|
||||||
|
Check(Difference(blueBefore,blueAfter,4,W-4)<0.00001,@"Skin exposure excludes saturated blue hair within face support");free(blueBefore);free(blueAfter);
|
||||||
s=PGBeautyDefaults();s.showMask=1;s.maskSoftness=0;s.maskEdgeProtect=0;s.maskThreshold=255;
|
s=PGBeautyDefaults();s.showMask=1;s.maskSoftness=0;s.maskEdgeProtect=0;s.maskThreshold=255;
|
||||||
float *strictMask=Render(context,[renderer processImage:source settings:s skinMask:half teethMask:nil eyeMask:nil error:&error],extent);
|
float *strictMask=Render(context,[renderer processImage:source settings:s skinMask:half teethMask:nil eyeMask:nil error:&error],extent);
|
||||||
s.maskThreshold=0;
|
s.maskThreshold=0;
|
||||||
|
|||||||
@@ -79,6 +79,8 @@ int main(int argc, const char *argv[]) {
|
|||||||
PGBeautySettings unprotected = settings; unprotected.featureProtection = 0;
|
PGBeautySettings unprotected = settings; unprotected.featureProtection = 0;
|
||||||
PGFaceFrame *withoutProtection = [processor processImage:image settings:unprotected time:kCMTimeZero error:&error];
|
PGFaceFrame *withoutProtection = [processor processImage:image settings:unprotected time:kCMTimeZero error:&error];
|
||||||
Check(Channel(context, frame.skinMask, 23, 71, 0) < Channel(context, withoutProtection.skinMask, 23, 71, 0), @"feature protection removes eye from skin smoothing");
|
Check(Channel(context, frame.skinMask, 23, 71, 0) < Channel(context, withoutProtection.skinMask, 23, 71, 0), @"feature protection removes eye from skin smoothing");
|
||||||
|
Check([Pixels(context,frame.colorMask) isEqualToData:Pixels(context,withoutProtection.colorMask)], @"color support remains continuous when detail protection is toggled");
|
||||||
|
Check(Channel(context,frame.colorMask,23,71,0)>Channel(context,frame.skinMask,23,71,0), @"color support retains coverage inside eye protection cutouts");
|
||||||
|
|
||||||
settings.singleFace = 1; settings.selectionX = 0.78; settings.selectionY = 0.45;
|
settings.singleFace = 1; settings.selectionX = 0.78; settings.selectionY = 0.45;
|
||||||
frame = [processor processImage:image settings:settings time:kCMTimeZero error:&error];
|
frame = [processor processImage:image settings:settings time:kCMTimeZero error:&error];
|
||||||
|
|||||||
+116
@@ -0,0 +1,116 @@
|
|||||||
|
#import <Foundation/Foundation.h>
|
||||||
|
#import <FxPlug/FxPlugSDK.h>
|
||||||
|
#import <CoreImage/CoreImage.h>
|
||||||
|
#import <CoreVideo/CoreVideo.h>
|
||||||
|
#import <IOSurface/IOSurface.h>
|
||||||
|
#import "PGParameterIDs.h"
|
||||||
|
#include <math.h>
|
||||||
|
|
||||||
|
@interface PGFaceSelectOSC : NSObject <FxOnScreenControl_v4>
|
||||||
|
- (instancetype)initWithAPIManager:(id<PROAPIAccessing>)api;
|
||||||
|
@end
|
||||||
|
|
||||||
|
@interface PGOSCAPI : NSObject <PROAPIAccessing>
|
||||||
|
@property BOOL enabled, rejectRead, rejectPoint, rejectWrite, rejectUndo;
|
||||||
|
@property BOOL disableOSC, disableParams, disableSetting, disableUndo;
|
||||||
|
@property CGRect bounds;
|
||||||
|
@property double x,y,backing;
|
||||||
|
@property NSUInteger writes,undoStarts,undoEnds;
|
||||||
|
@property double lastX,lastY;
|
||||||
|
@property CMTime lastTime;
|
||||||
|
@end
|
||||||
|
@implementation PGOSCAPI
|
||||||
|
- (instancetype)init {if((self=[super init])){_enabled=YES;_bounds=CGRectMake(-100,-50,200,100);_x=.25;_y=.75;_backing=1;}return self;}
|
||||||
|
- (id)apiForProtocol:(Protocol *)protocol {
|
||||||
|
NSString *name=NSStringFromProtocol(protocol);
|
||||||
|
if([name isEqual:@"FxOnScreenControlAPI_v4"])return _disableOSC?nil:self;
|
||||||
|
if([name isEqual:@"FxParameterRetrievalAPI_v6"])return _disableParams?nil:self;
|
||||||
|
if([name isEqual:@"FxParameterSettingAPI_v5"])return _disableSetting?nil:self;
|
||||||
|
if([name isEqual:@"FxUndoAPI"])return _disableUndo?nil:self;
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
- (BOOL)getBoolValue:(BOOL *)value fromParameter:(UInt32)identifier atTime:(CMTime)time {
|
||||||
|
if(identifier!=PGSingleFace||_rejectRead)return NO;*value=_enabled;return YES;
|
||||||
|
}
|
||||||
|
- (BOOL)getXValue:(double *)x YValue:(double *)y fromParameter:(UInt32)identifier atTime:(CMTime)time {
|
||||||
|
if(identifier!=PGSelection||_rejectPoint)return NO;*x=_x;*y=_y;return YES;
|
||||||
|
}
|
||||||
|
- (BOOL)setXValue:(double)x YValue:(double)y toParameter:(UInt32)identifier atTime:(CMTime)time {
|
||||||
|
if(identifier!=PGSelection||_rejectWrite)return NO;
|
||||||
|
_writes++;_lastX=x;_lastY=y;_lastTime=time;return YES;
|
||||||
|
}
|
||||||
|
- (CGRect)objectBounds {return _bounds;}
|
||||||
|
- (double)backingScaleFactor {return _backing;}
|
||||||
|
- (void)convertPointFromSpace:(FxDrawingCoordinates)from fromX:(double)x fromY:(double)y toSpace:(FxDrawingCoordinates)to toX:(double *)tx toY:(double *)ty {
|
||||||
|
*tx=x+200;*ty=y+100;
|
||||||
|
}
|
||||||
|
- (BOOL)startUndoGroup:(NSString *)name {_undoStarts++;return !_rejectUndo;}
|
||||||
|
- (BOOL)endUndoGroup {_undoEnds++;return YES;}
|
||||||
|
@end
|
||||||
|
|
||||||
|
@interface PGOSCTile : FxImageTile
|
||||||
|
@property (strong) IOSurface *surface;
|
||||||
|
@property FxImageOrigin origin;
|
||||||
|
@end
|
||||||
|
@implementation PGOSCTile
|
||||||
|
- (IOSurface *)ioSurface {return _surface;}
|
||||||
|
- (FxImageOrigin)imageOrigin {return _origin;}
|
||||||
|
- (CGColorSpaceRef)colorSpace {return nil;}
|
||||||
|
@end
|
||||||
|
static NSUInteger Checks=0,Failures=0;
|
||||||
|
static void Check(BOOL value,NSString *message) {Checks++;if(!value){Failures++;fprintf(stderr,"FAIL: %s\n",message.UTF8String);}}
|
||||||
|
static PGOSCTile *Tile(void) {
|
||||||
|
IOSurfaceRef surface=IOSurfaceCreate((__bridge CFDictionaryRef)@{(id)kIOSurfaceWidth:@320,(id)kIOSurfaceHeight:@240,(id)kIOSurfaceBytesPerElement:@4,(id)kIOSurfaceBytesPerRow:@1280,(id)kIOSurfacePixelFormat:@(kCVPixelFormatType_32BGRA),(id)kIOSurfaceAllocSize:@(320*240*4)});
|
||||||
|
PGOSCTile *tile=[PGOSCTile new];tile.surface=CFBridgingRelease(surface);return tile;
|
||||||
|
}
|
||||||
|
static float AlphaAt(PGOSCTile *tile,int x,int y) {
|
||||||
|
CIImage *image=[CIImage imageWithIOSurface:(__bridge IOSurfaceRef)tile.surface options:@{kCIImageColorSpace:NSNull.null}];
|
||||||
|
CIContext *context=[CIContext contextWithOptions:@{kCIContextWorkingColorSpace:NSNull.null,kCIContextOutputColorSpace:NSNull.null}];
|
||||||
|
float p[4]={0};[context render:image toBitmap:p rowBytes:sizeof(p) bounds:CGRectMake(x,y,1,1) format:kCIFormatRGBAf colorSpace:nil];return p[3];
|
||||||
|
}
|
||||||
|
int main(int argc,const char **argv) {@autoreleasepool {
|
||||||
|
PGOSCAPI *api=[PGOSCAPI new];PGFaceSelectOSC *osc=[[PGFaceSelectOSC alloc] initWithAPIManager:api];
|
||||||
|
CMTime time=CMTimeMake(121,24);NSInteger part=-1;BOOL update=YES,handled=YES;
|
||||||
|
Check([osc conformsToProtocol:@protocol(FxOnScreenControl_v4)],@"OSC class conforms to the real FxPlug v4 protocol");
|
||||||
|
NSArray *required=@[@"drawingCoordinates",@"drawOSCWithWidth:height:activePart:destinationImage:atTime:",@"hitTestOSCAtMousePositionX:mousePositionY:activePart:atTime:",@"mouseDownAtPositionX:positionY:activePart:modifiers:forceUpdate:atTime:",@"mouseDraggedAtPositionX:positionY:activePart:modifiers:forceUpdate:atTime:",@"mouseUpAtPositionX:positionY:activePart:modifiers:forceUpdate:atTime:",@"keyDownAtPositionX:positionY:keyPressed:modifiers:forceUpdate:didHandle:atTime:",@"keyUpAtPositionX:positionY:keyPressed:modifiers:forceUpdate:didHandle:atTime:"];
|
||||||
|
for(NSString *selector in required)Check([osc respondsToSelector:NSSelectorFromString(selector)],[NSString stringWithFormat:@"Required callback exists: %@",selector]);
|
||||||
|
Check(osc.drawingCoordinates==kFxDrawingCoordinates_OBJECT,@"Mouse callbacks use declared object coordinates");
|
||||||
|
NSDictionary *info=argc>1 ? [NSDictionary dictionaryWithContentsOfFile:[NSString stringWithUTF8String:argv[1]]] : nil;
|
||||||
|
NSArray *entries=info[@"ProPlugPlugInList"];NSDictionary *control=nil;NSMutableArray *filters=[NSMutableArray new];NSMutableSet *ids=[NSMutableSet new];BOOL unique=YES;
|
||||||
|
for(NSDictionary *entry in entries){if([ids containsObject:entry[@"uuid"]])unique=NO;[ids addObject:entry[@"uuid"]];if([entry[@"protocolNames"] containsObject:@"FxOnScreenControl"])control=entry;if([entry[@"protocolNames"] containsObject:@"FxFilter"])[filters addObject:entry];}
|
||||||
|
Check(unique && control && filters.count==1,@"Registration has unique filter and dedicated OSC identities");
|
||||||
|
Check([control[@"className"] isEqual:@"PGFaceSelectOSC"] && NSClassFromString(control[@"className"])==PGFaceSelectOSC.class,@"Registered OSC resolves to the compiled class");
|
||||||
|
Check([control[@"supportedPlugins"] isEqual:@[filters.firstObject[@"uuid"]]],@"Dedicated OSC links to the released filter UUID");
|
||||||
|
api.enabled=NO;[osc hitTestOSCAtMousePositionX:0 mousePositionY:0 activePart:&part atTime:time];Check(part==0,@"Disabled selection does not claim mouse events");
|
||||||
|
[osc mouseDownAtPositionX:0 positionY:0 activePart:1 modifiers:0 forceUpdate:&update atTime:time];Check(!update && api.writes==0 && api.undoStarts==0,@"Disabled mouse-down writes nothing and opens no undo group");
|
||||||
|
api.enabled=YES;[osc hitTestOSCAtMousePositionX:0 mousePositionY:0 activePart:&part atTime:time];Check(part==1,@"Enabled selection accepts a finite point in object bounds");
|
||||||
|
[osc hitTestOSCAtMousePositionX:1000 mousePositionY:0 activePart:&part atTime:time];Check(part==0,@"Outside-object hit test is empty");
|
||||||
|
[osc hitTestOSCAtMousePositionX:NAN mousePositionY:0 activePart:&part atTime:time];Check(part==0,@"Nonfinite hit coordinates are rejected");
|
||||||
|
[osc hitTestOSCAtMousePositionX:0 mousePositionY:0 activePart:NULL atTime:time];Check(YES,@"Null hit-test output is safe");
|
||||||
|
[osc mouseDownAtPositionX:0 positionY:0 activePart:0 modifiers:0 forceUpdate:&update atTime:time];Check(!update && api.writes==0,@"Inactive part does not start selection");
|
||||||
|
update=YES;[osc mouseUpAtPositionX:0 positionY:0 activePart:1 modifiers:0 forceUpdate:&update atTime:time];Check(!update && api.undoEnds==0,@"Mouse-up without mouse-down leaves undo balanced");
|
||||||
|
[osc mouseDownAtPositionX:-50 positionY:25 activePart:1 modifiers:0 forceUpdate:&update atTime:time];Check(update && fabs(api.lastX-.25)<1e-8 && fabs(api.lastY-.75)<1e-8,@"Mouse-down normalizes nonzero object bounds");
|
||||||
|
Check(CMTimeCompare(api.lastTime,time)==0 && api.undoStarts==1,@"Selection writes use callback time and open one undo group");
|
||||||
|
[osc mouseDraggedAtPositionX:500 positionY:-500 activePart:1 modifiers:0 forceUpdate:&update atTime:time];Check(update && api.lastX==1 && api.lastY==0,@"Dragging clamps normalized selection to the source");
|
||||||
|
[osc mouseUpAtPositionX:0 positionY:0 activePart:1 modifiers:0 forceUpdate:&update atTime:time];Check(update && api.undoEnds==1,@"Gesture completes its matching undo group");
|
||||||
|
NSUInteger writes=api.writes;[osc mouseDraggedAtPositionX:0 positionY:0 activePart:1 modifiers:0 forceUpdate:&update atTime:time];Check(!update && api.writes==writes,@"Stray drag after release writes nothing");
|
||||||
|
api.bounds=CGRectMake(0,0,NAN,100);[osc hitTestOSCAtMousePositionX:0 mousePositionY:0 activePart:&part atTime:time];Check(part==0,@"Invalid host bounds produce an empty hit");
|
||||||
|
update=YES;[osc mouseDownAtPositionX:0 positionY:0 activePart:1 modifiers:0 forceUpdate:&update atTime:time];Check(!update && api.writes==writes,@"Invalid bounds initialize forceUpdate and avoid parameter writes");api.bounds=CGRectMake(-100,-50,200,100);
|
||||||
|
api.rejectWrite=YES;[osc mouseDownAtPositionX:0 positionY:0 activePart:1 modifiers:0 forceUpdate:&update atTime:time];Check(!update && api.undoStarts==api.undoEnds,@"Rejected parameter write closes the opened undo group");api.rejectWrite=NO;
|
||||||
|
api.rejectUndo=YES;NSUInteger ends=api.undoEnds;[osc mouseDownAtPositionX:0 positionY:0 activePart:1 modifiers:0 forceUpdate:&update atTime:time];[osc mouseUpAtPositionX:0 positionY:0 activePart:1 modifiers:0 forceUpdate:&update atTime:time];Check(api.undoEnds==ends,@"Failed undo start is never paired with an invalid end");api.rejectUndo=NO;
|
||||||
|
for(int disabled=0;disabled<3;disabled++){
|
||||||
|
api.disableOSC=disabled==0;api.disableParams=disabled==1;api.disableSetting=disabled==2;
|
||||||
|
writes=api.writes;update=YES;[osc mouseDownAtPositionX:0 positionY:0 activePart:1 modifiers:0 forceUpdate:&update atTime:time];Check(!update && api.writes==writes,[NSString stringWithFormat:@"Missing host API %d is handled safely",disabled]);
|
||||||
|
}
|
||||||
|
api.disableOSC=api.disableParams=api.disableSetting=NO;
|
||||||
|
[osc keyDownAtPositionX:0 positionY:0 keyPressed:0 modifiers:0 forceUpdate:&update didHandle:&handled atTime:time];Check(!update && !handled,@"Unrecognized key-down clears outputs");update=handled=YES;
|
||||||
|
[osc keyUpAtPositionX:0 positionY:0 keyPressed:0 modifiers:0 forceUpdate:&update didHandle:&handled atTime:time];Check(!update && !handled,@"Unrecognized key-up clears outputs");
|
||||||
|
PGOSCTile *tile=Tile();api.x=.25;api.y=.75;
|
||||||
|
[osc drawOSCWithWidth:1920 height:1080 activePart:0 destinationImage:tile atTime:time];
|
||||||
|
Check(AlphaAt(tile,150,125)>.9,@"Canvas conversion directly positions the visible handle despite effect/surface size mismatch");
|
||||||
|
Check(AlphaAt(tile,25,28)<.01,@"OSC surface is not incorrectly rescaled by effect image dimensions");
|
||||||
|
api.enabled=NO;[osc drawOSCWithWidth:1920 height:1080 activePart:0 destinationImage:tile atTime:time];Check(AlphaAt(tile,150,125)<.01,@"Disabled drawing clears the prior handle");
|
||||||
|
api.enabled=YES;api.backing=NAN;[osc drawOSCWithWidth:1920 height:1080 activePart:0 destinationImage:tile atTime:time];Check(AlphaAt(tile,150,125)>.9,@"Nonfinite backing scale uses a finite fallback");
|
||||||
|
api.x=NAN;[osc drawOSCWithWidth:1920 height:1080 activePart:0 destinationImage:tile atTime:time];Check(AlphaAt(tile,150,125)<.01,@"Nonfinite stored selection does not create invalid drawing filters");
|
||||||
|
printf("OSC adapter: %lu checks, %lu failures\n",(unsigned long)Checks,(unsigned long)Failures);return Failures?1:0;
|
||||||
|
}}
|
||||||
在新工单中引用
屏蔽一个用户