Fix normalized OSC coordinates and verify Motion pixel setter compatibility

这个提交包含在:
dayu 于 2026-10-21 18:22:35 +08:00 提交
1 父节点 71a583e306
当前提交 22cd96a772
2 个文件已更改
+120 -33

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

+74 -20
查看文件
@@ -8,6 +8,9 @@
@property (assign) id<PROAPIAccessing> apiManager;
@property BOOL dragging;
@property BOOL undoOpen;
@property BOOL pointSetterUsesPixels;
@property BOOL loggedCoordinates;
@property BOOL loggedPointSetter;
@end
@implementation PGFaceSelectOSC
- (instancetype)initWithAPIManager:(id<PROAPIAccessing>)api {
@@ -19,27 +22,28 @@
BOOL selected=NO;id<FxParameterRetrievalAPI_v6> api=[_apiManager apiForProtocol:@protocol(FxParameterRetrievalAPI_v6)];
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;
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]) {
if([self selectionEnabledAtTime:time] && [self oscAPI]) {
double x=.5,y=.5;
id<FxParameterRetrievalAPI_v6> params=[_apiManager apiForProtocol:@protocol(FxParameterRetrievalAPI_v6)];
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.
// FxPlug OBJECT space is normalized 0..1, exactly like point parameters.
// objectBounds is a pixel/document rectangle and must not be applied here.
if(valid) [[self oscAPI] convertPointFromSpace:kFxDrawingCoordinates_OBJECT fromX:x fromY:y toSpace:kFxDrawingCoordinates_CANVAS toX:&canvasX toY:&canvasY];
if(!_loggedCoordinates) {
_loggedCoordinates=YES;
CGRect objectBounds=[[self oscAPI] objectBounds];
NSLog(@"PGFaceOSC coordinates selection=(%.9g,%.9g) objectBounds=(%.9g,%.9g,%.9g,%.9g) canvas=(%.9g,%.9g) surface=%zux%zu origin=%lu",
x,y,objectBounds.origin.x,objectBounds.origin.y,objectBounds.size.width,objectBounds.size.height,
canvasX,canvasY,surfaceW,surfaceH,(unsigned long)tile.imageOrigin);
}
// CANVAS coordinates address the OSC surface. Callback width/height can
// describe the effect image and must not rescale the converted point.
double backing=[[self oscAPI] backingScaleFactor];
CGFloat scale=isfinite(backing)?MAX(1,backing):1;
CGFloat radius=10*scale;
@@ -59,21 +63,71 @@
[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) return;*part=0;CGRect bounds=CGRectZero;
if(isfinite(x)&&isfinite(y)&&[self selectionEnabledAtTime:time]&&[self validObjectBounds:&bounds]&&CGRectContainsPoint(bounds,CGPointMake(x,y))) *part=1;
if(!part) return;*part=0;
if(isfinite(x)&&isfinite(y)&&x>=0&&x<=1&&y>=0&&y<=1&&[self selectionEnabledAtTime:time]&&[self oscAPI]) *part=1;
}
- (BOOL)pointWrite:(id<FxParameterSettingAPI_v5>)setter x:(double)x y:(double)y time:(CMTime)time {
return setter && isfinite(x) && isfinite(y) && [setter setXValue:x YValue:y toParameter:PGSelection atTime:time];
}
- (BOOL)readPoint:(id<FxParameterRetrievalAPI_v6>)reader x:(double *)x y:(double *)y time:(CMTime)time {
return reader && [reader getXValue:x YValue:y fromParameter:PGSelection atTime:time] && isfinite(*x) && isfinite(*y);
}
- (BOOL)pointPixelSize:(CGSize *)size {
id<FxOnScreenControlAPI_v4> api=[self oscAPI];if(!api || !size)return NO;
CGRect bounds=[api objectBounds];
if(!isfinite(bounds.size.width)||!isfinite(bounds.size.height)||bounds.size.width<=4||bounds.size.height<=4)return NO;
*size=bounds.size;return YES;
}
- (void)setSelectionX:(double)x y:(double)y time:(CMTime)time update:(BOOL *)update {
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;
if(!isfinite(x)||!isfinite(y)) return;
double nx=fmin(1,fmax(0,x)),ny=fmin(1,fmax(0,y));
id<FxParameterSettingAPI_v5> setter=[_apiManager apiForProtocol:@protocol(FxParameterSettingAPI_v5)];
id<FxParameterRetrievalAPI_v6> reader=[_apiManager apiForProtocol:@protocol(FxParameterRetrievalAPI_v6)];
double previousX=NAN,previousY=NAN;
if(!setter || ![self readPoint:reader x:&previousX y:&previousY time:time])return;
CGSize pixels=CGSizeZero;
if(_pointSetterUsesPixels && ![self pointPixelSize:&pixels])return;
double rawX=_pointSetterUsesPixels ? (nx-.5)*pixels.width : nx;
double rawY=_pointSetterUsesPixels ? (ny-.5)*pixels.height : ny;
BOOL accepted=[self pointWrite:setter x:rawX y:rawY time:time];
BOOL changed=accepted,pixelUnits=_pointSetterUsesPixels;
double actualX=NAN,actualY=NAN;
BOOL readBack=accepted && [self readPoint:reader x:&actualX y:&actualY time:time];
const double tolerance=0.00001;
BOOL verified=readBack && fabs(actualX-nx)<=tolerance && fabs(actualY-ny)<=tolerance;
// Motion's FxPlug 4 bridge can normalize point reads while its setter still
// writes centered pixel values directly to OZChannel2D. Detect that exact
// response instead of choosing units from the app version or coordinate size.
if(readBack && !verified && !_pointSetterUsesPixels && [self pointPixelSize:&pixels] &&
fabs(actualX-(.5+nx/pixels.width))<=tolerance &&
fabs(actualY-(.5+ny/pixels.height))<=tolerance) {
pixelUnits=YES;
rawX=(nx-.5)*pixels.width;rawY=(ny-.5)*pixels.height;
accepted=[self pointWrite:setter x:rawX y:rawY time:time];changed=changed||accepted;
actualX=actualY=NAN;
readBack=accepted && [self readPoint:reader x:&actualX y:&actualY time:time];
verified=readBack && fabs(actualX-nx)<=tolerance && fabs(actualY-ny)<=tolerance;
if(verified)_pointSetterUsesPixels=YES;
}
if(!verified && changed) {
// Restore the previous point when verification fails. A failure must not
// look like a successful drag or silently retain the calibration point.
double restoreX=pixelUnits ? (previousX-.5)*pixels.width : previousX;
double restoreY=pixelUnits ? (previousY-.5)*pixels.height : previousY;
[self pointWrite:setter x:restoreX y:restoreY time:time];
}
if(!_loggedPointSetter) {
_loggedPointSetter=YES;
NSLog(@"PGFaceOSC point-write desired=(%.9g,%.9g) setter=(%.9g,%.9g) readback=(%.9g,%.9g) mode=%@ verified=%d",
nx,ny,rawX,rawY,actualX,actualY,_pointSetterUsesPixels?@"centered-pixels":@"normalized",verified);
}
if(update)*update=verified;
}
- (void)mouseDownAtPositionX:(double)x positionY:(double)y activePart:(NSInteger)part modifiers:(FxModifierKeys)keys forceUpdate:(BOOL *)update atTime:(CMTime)time {
if(update) *update=NO;
if(part!=1 || !isfinite(x) || !isfinite(y) || ![self selectionEnabledAtTime:time]) return;
CGRect bounds=CGRectZero;if(![self validObjectBounds:&bounds]) return;
if(![self oscAPI]) return;
id<FxUndoAPI> undo=[_apiManager apiForProtocol:@protocol(FxUndoAPI)];
if(_undoOpen) [undo endUndoGroup];
_undoOpen=[undo startUndoGroup:@"选择人脸"];