Fix skin exposure mask islands and OSC coordinates; prepare 1.0.2

这个提交包含在:
dayu 于 2026-10-21 18:05:20 +08:00 提交
1 父节点 dd04a471e1
当前提交 d561537eed
19 个文件已更改
+364 -35

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

+1 -1
查看文件
@@ -1,5 +1,5 @@
{
"version": "1.0.1",
"version": "1.0.2",
"pluginUUID": "FB14B0E6-59B2-4836-916F-CD0C559E9241",
"filterObject": "510000005",
"visiblePublished": 46,
+2 -2
查看文件
@@ -1,6 +1,6 @@
# 大鱼人像美化 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画面输入,不能当作该模板已经可用的证据。
本次仅恢复标准效果源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黑屏复测。
+2 -2
查看文件
@@ -1,6 +1,6 @@
{
"version": "1.0.1",
"sha256": "2f7d1e0bf91a6dab82f5dde0a20d971a75abd2056a48104d861e9c112870aae4",
"version": "1.0.2",
"sha256": "fe637290212481c34cbfd8f4de5bf09101a3fbe88f4db3e55b35d5375f3ac1ba",
"filterUUID": "FB14B0E6-59B2-4836-916F-CD0C559E9241",
"publishedFunctionalControls": 46,
"informationalControls": 2,
@@ -46,7 +46,7 @@
<flags>1</flags>
</template>
<build />
<description>大鱼人像美化 1.0.1:人像磨皮、肤色、脸型、五官保护和三目标人脸跟踪。模板中频磨皮默认为50%。</description>
<description>大鱼人像美化 1.0.2:人像磨皮、肤色、脸型、五官保护和三目标人脸跟踪。模板中频磨皮默认为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.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" />
<baseFlags>8589934608</baseFlags>
<parameter name="磨皮" id="100" flags="8589938704">
+4 -4
查看文件
@@ -17,9 +17,9 @@
<key>CFBundlePackageType</key>
<string>XPC!</string>
<key>CFBundleShortVersionString</key>
<string>1.0.1</string>
<string>1.0.2</string>
<key>CFBundleVersion</key>
<string>3</string>
<string>4</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.1</string>
<string>1.0.2</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.1</string>
<string>1.0.2</string>
<key>protocolNames</key>
<array>
<string>FxOnScreenControl</string>
@@ -14,6 +14,14 @@ NS_ASSUME_NONNULL_BEGIN
teethMask:(nullable CIImage *)teethMask
eyeMask:(nullable CIImage *)eyeMask
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
NS_ASSUME_NONNULL_END
+27 -3
查看文件
@@ -82,6 +82,13 @@ static NSDictionary<NSString *, CIColorKernel *> *PGKernels(void) {
"kernel vec4 pgMaskedMix(__sample source,__sample effect,__sample mask,float amount) {"
"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)); }");
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(@
"kernel vec4 pgEven(__sample source,__sample broad,float amount) {"
"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
skinMask:(CIImage *)skinMask teethMask:(CIImage *)teethMask
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;
CGRect extent = image.extent;
if (!image || CGRectIsEmpty(extent) || CGRectIsInfinite(extent) ||
@@ -145,7 +157,7 @@ static NSDictionary<NSString *, CIColorKernel *> *PGKernels(void) {
float mix = PGClamp(s.mix, 0, 1);
if (mix == 0 && !s.showMask) return image;
NSDictionary *k = PGKernels();
if (k.count != 10) {
if (k.count != 11) {
if (error) *error = [NSError errorWithDomain:@"PGBeautyRenderer" code:2
userInfo:@{NSLocalizedDescriptionKey:@"The beauty image kernels could not be compiled."}];
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;
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;
if (local*amount>0) {
// 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 (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 (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"
withInputParameters:@{kCIInputSharpnessKey:@(sharpness*amount)}];
if (s.skinTonePreset>0 && s.skinTonePreset<=6) {
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) {
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.
@property (nonatomic, copy, readonly) NSArray<VNFaceObservation *> *faces;
@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 *eyeMask;
@property (nonatomic, strong, readonly) CIImage *image;
+4 -1
查看文件
@@ -10,6 +10,7 @@ static NSString * const PGFaceErrorDomain = @"media.dayu.PaintFCP.Face";
@interface PGFaceFrame ()
@property (nonatomic, copy, readwrite) NSArray<VNFaceObservation *> *faces;
@property (nonatomic, strong, readwrite) CIImage *skinMask;
@property (nonatomic, strong, readwrite) CIImage *colorMask;
@property (nonatomic, strong, readwrite) CIImage *teethMask;
@property (nonatomic, strong, readwrite) CIImage *eyeMask;
@property (nonatomic, strong, readwrite) CIImage *image;
@@ -340,15 +341,17 @@ static CIImage *PGMaximum(CIImage *a, CIImage *b) {
overlay = [outline imageByCompositingOverImage:overlay];
}
}
CIImage *colorCoverage=skin;
if (settings.featureProtection) skin = [_subtract applyWithExtent:extent arguments:@[skin, protection]];
PGFaceFrame *frame = [[PGFaceFrame alloc] init];
frame.faces = observations ?: @[];
frame.image = [self warpImage:image 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.eyeMask = [self warpImage:eyes geometry:geometry settings:settings extent: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."}];
return nil;
}
+49 -16
查看文件
@@ -2,9 +2,12 @@
#import <CoreImage/CoreImage.h>
#import <IOSurface/IOSurface.h>
#import "PGParameterIDs.h"
#include <math.h>
@interface PGFaceSelectOSC : NSObject <FxOnScreenControl_v4>
@property (assign) id<PROAPIAccessing> apiManager;
@property BOOL dragging;
@property BOOL undoOpen;
@end
@implementation PGFaceSelectOSC
- (instancetype)initWithAPIManager:(id<PROAPIAccessing>)api {
@@ -14,21 +17,33 @@
- (id<FxOnScreenControlAPI_v4>)oscAPI {return [_apiManager apiForProtocol:@protocol(FxOnScreenControlAPI_v4)];}
- (BOOL)selectionEnabledAtTime:(CMTime)time {
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 {
if(!tile.ioSurface||width<=0||height<=0) return;
CIImage *output=[[CIImage imageWithColor:CIColor.clearColor] imageByCroppingToRect:CGRectMake(0,0,width,height)];
if([self selectionEnabledAtTime:time]) {
size_t surfaceW=IOSurfaceGetWidth((__bridge IOSurfaceRef)tile.ioSurface),surfaceH=IOSurfaceGetHeight((__bridge IOSurfaceRef)tile.ioSurface);
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;
id<FxParameterRetrievalAPI_v6> params=[_apiManager apiForProtocol:@protocol(FxParameterRetrievalAPI_v6)];
[params getXValue:&x YValue:&y fromParameter:PGSelection atTime:time];
CGRect bounds=[[self oscAPI] objectBounds];double canvasX=width*.5,canvasY=height*.5;
[[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]);
size_t surfaceW=IOSurfaceGetWidth((__bridge IOSurfaceRef)tile.ioSurface),surfaceH=IOSurfaceGetHeight((__bridge IOSurfaceRef)tile.ioSurface);
CGFloat sx=(CGFloat)surfaceW/width,sy=(CGFloat)surfaceH/height;
canvasX*=sx;canvasY*=sy;CGFloat radius=10*scale;
BOOL valid=[params getXValue:&x YValue:&y fromParameter:PGSelection atTime:time] && isfinite(x) && isfinite(y);
double canvasX=NAN,canvasY=NAN;
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];
// CANVAS coordinates already address the OSC surface. Callback width and
// height may describe the effect image and must not rescale this point.
double backing=[[self oscAPI] backingScaleFactor];
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];
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;
@@ -37,27 +52,45 @@
CIImage *h=[[CIImage imageWithColor:color] imageByCroppingToRect:CGRectMake(canvasX-16*scale,canvasY-scale,32*scale,2*scale)];
output=[[h imageByCompositingOverImage:v] imageByCompositingOverImage:ring];
}
}
CIContext *context=[CIContext contextWithOptions:@{kCIContextCacheIntermediates:@NO}];
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)];
[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 {
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 {
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)];
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;
}
- (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:@"选择人脸"];
[self setSelectionX:x y:y time:time update:update];
if(update) *update=NO;
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 {
[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)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;}
+1 -1
查看文件
@@ -250,7 +250,7 @@ static CIImage *PGImageFromTile(FxImageTile *tile) {
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; }
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; }
overlay=frame.overlayImage;
}
@@ -17,9 +17,9 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0.1</string>
<string>1.0.2</string>
<key>CFBundleVersion</key>
<string>3</string>
<string>4</string>
<key>LSMinimumSystemVersion</key>
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
<key>NSHumanReadableCopyright</key>
+5 -1
查看文件
@@ -20,6 +20,10 @@ REA 直接打开内嵌插件。原包为 arm64/x86_64 通用 Mach-O,Ghidra 无
|人脸与跟踪|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 面部区域构建软蒙版。
- 瘦脸、大眼、短脸、窄脸、下巴、V 脸、下颌、颧骨、小头和小头范围。
- 全脸/选中脸、画面点击选脸、多人高速检测、防跳脸范围、脸框和跟踪框。
- 三个独立跟踪目标,向前/向后分析,清除当前目标;精确 CMTime 数据持久化到宿主工程,可逆序和任意顺序读取。
- 作为扩展提供三个独立跟踪目标,向前/向后分析,清除当前目标;精确 CMTime 数据持久化到宿主工程,可逆序和任意顺序读取。
- 蒙版阈值/柔度/边缘保护、皮肤/牙齿/眼睛通道预览与反转、包含几何变形的整体混合。
- 保留透明度、完整输入范围、非零图像原点与宿主上下原点;原生 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
}
+10
查看文件
@@ -45,6 +45,13 @@ try:
raise RuntimeError('Apple did not accept notarization; original installed app retained')
run(['xcrun','stapler','staple',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():
info=plistlib.loads((installed/'Contents/Info.plist').read_bytes())
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')
(logs/'installed-signature.txt').write_text(signature)
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:
if installed.exists():shutil.rmtree(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"
+24
查看文件
@@ -103,6 +103,30 @@ int main(void) { @autoreleasepool {
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);
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;
float *strictMask=Render(context,[renderer processImage:source settings:s skinMask:half teethMask:nil eyeMask:nil error:&error],extent);
s.maskThreshold=0;
+2
查看文件
@@ -79,6 +79,8 @@ int main(int argc, const char *argv[]) {
PGBeautySettings unprotected = settings; unprotected.featureProtection = 0;
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([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;
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;
}}