Use explicit canvas-to-object conversion for native OSC events

这个提交包含在:
dayu 于 2026-10-21 18:30:16 +08:00 提交
1 父节点 22cd96a772
当前提交 77d386825c
2 个文件已更改
+127 -23

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

+26 -7
查看文件
@@ -16,8 +16,20 @@
- (instancetype)initWithAPIManager:(id<PROAPIAccessing>)api { - (instancetype)initWithAPIManager:(id<PROAPIAccessing>)api {
if((self=[super init])) _apiManager=api; return self; if((self=[super init])) _apiManager=api; return self;
} }
- (FxDrawingCoordinates)drawingCoordinates {return kFxDrawingCoordinates_OBJECT;} - (FxDrawingCoordinates)drawingCoordinates {return kFxDrawingCoordinates_CANVAS;}
- (id<FxOnScreenControlAPI_v4>)oscAPI {return [_apiManager apiForProtocol:@protocol(FxOnScreenControlAPI_v4)];} - (id<FxOnScreenControlAPI_v4>)oscAPI {return [_apiManager apiForProtocol:@protocol(FxOnScreenControlAPI_v4)];}
- (BOOL)objectPointFromCanvasX:(double)x y:(double)y objectX:(double *)objectX objectY:(double *)objectY {
id<FxOnScreenControlAPI_v4> api=[self oscAPI];
if(!api || !objectX || !objectY || !isfinite(x) || !isfinite(y)) return NO;
// Request CANVAS events explicitly, then let the host invert its current
// zoom, pan and object transform. The conversion API has no success result;
// untouched or nonfinite outputs must never become a parameter write.
double nx=NAN,ny=NAN;
[api convertPointFromSpace:kFxDrawingCoordinates_CANVAS fromX:x fromY:y
toSpace:kFxDrawingCoordinates_OBJECT toX:&nx toY:&ny];
if(!isfinite(nx) || !isfinite(ny)) return NO;
*objectX=nx;*objectY=ny;return YES;
}
- (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)];
return api && [api getBoolValue:&selected fromParameter:PGSingleFace atTime:time] && selected; return api && [api getBoolValue:&selected fromParameter:PGSingleFace atTime:time] && selected;
@@ -64,7 +76,9 @@
} }
- (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) return;*part=0; 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; double nx=NAN,ny=NAN;
if([self selectionEnabledAtTime:time] && [self objectPointFromCanvasX:x y:y objectX:&nx objectY:&ny] &&
nx>=0 && nx<=1 && ny>=0 && ny<=1) *part=1;
} }
- (BOOL)pointWrite:(id<FxParameterSettingAPI_v5>)setter x:(double)x y:(double)y time:(CMTime)time { - (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]; return setter && isfinite(x) && isfinite(y) && [setter setXValue:x YValue:y toParameter:PGSelection atTime:time];
@@ -126,23 +140,28 @@
} }
- (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 {
if(update) *update=NO; if(update) *update=NO;
if(part!=1 || !isfinite(x) || !isfinite(y) || ![self selectionEnabledAtTime:time]) return; double nx=NAN,ny=NAN;
if(![self oscAPI]) return; if(part!=1 || ![self selectionEnabledAtTime:time] ||
![self objectPointFromCanvasX:x y:y objectX:&nx objectY:&ny] || nx<0 || nx>1 || ny<0 || ny>1) return;
id<FxUndoAPI> undo=[_apiManager apiForProtocol:@protocol(FxUndoAPI)]; id<FxUndoAPI> undo=[_apiManager apiForProtocol:@protocol(FxUndoAPI)];
if(_undoOpen) [undo endUndoGroup]; if(_undoOpen) [undo endUndoGroup];
_undoOpen=[undo startUndoGroup:@"选择人脸"]; _undoOpen=[undo startUndoGroup:@"选择人脸"];
BOOL changed=NO;[self setSelectionX:x y:y time:time update:&changed]; BOOL changed=NO;[self setSelectionX:nx y:ny time:time update:&changed];
_dragging=changed; _dragging=changed;
if(!changed && _undoOpen) {[undo endUndoGroup];_undoOpen=NO;} if(!changed && _undoOpen) {[undo endUndoGroup];_undoOpen=NO;}
if(update) *update=changed; if(update) *update=changed;
} }
- (void)mouseDraggedAtPositionX:(double)x positionY:(double)y activePart:(NSInteger)part modifiers:(FxModifierKeys)keys forceUpdate:(BOOL *)update atTime:(CMTime)time { - (void)mouseDraggedAtPositionX:(double)x positionY:(double)y activePart:(NSInteger)part modifiers:(FxModifierKeys)keys forceUpdate:(BOOL *)update atTime:(CMTime)time {
if(update) *update=NO; if(update) *update=NO;
if(_dragging && part==1 && [self selectionEnabledAtTime:time]) [self setSelectionX:x y:y time:time update:update]; double nx=NAN,ny=NAN;
if(_dragging && part==1 && [self selectionEnabledAtTime:time] &&
[self objectPointFromCanvasX:x y:y objectX:&nx objectY:&ny]) [self setSelectionX:nx y:ny 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 {
if(update) *update=NO; if(update) *update=NO;
if(_dragging && part==1 && [self selectionEnabledAtTime:time]) [self setSelectionX:x y:y time:time update:update]; double nx=NAN,ny=NAN;
if(_dragging && part==1 && [self selectionEnabledAtTime:time] &&
[self objectPointFromCanvasX:x y:y objectX:&nx objectY:&ny]) [self setSelectionX:nx y:ny time:time update:update];
_dragging=NO; _dragging=NO;
if(_undoOpen) {id<FxUndoAPI> undo=[_apiManager apiForProtocol:@protocol(FxUndoAPI)];[undo endUndoGroup];_undoOpen=NO;} if(_undoOpen) {id<FxUndoAPI> undo=[_apiManager apiForProtocol:@protocol(FxUndoAPI)];[undo endUndoGroup];_undoOpen=NO;}
} }
+101 -16
查看文件
@@ -14,15 +14,18 @@
@property BOOL enabled, rejectRead, rejectPoint, rejectWrite, rejectUndo; @property BOOL enabled, rejectRead, rejectPoint, rejectWrite, rejectUndo;
@property BOOL disableOSC, disableParams, disableSetting, disableUndo,pixelSetter; @property BOOL disableOSC, disableParams, disableSetting, disableUndo,pixelSetter;
@property BOOL corruptReadback; @property BOOL corruptReadback;
@property BOOL rejectConversion,nonfiniteConversion,partialConversion;
@property CGRect bounds; @property CGRect bounds;
@property CGAffineTransform objectToCanvas;
@property double x,y,backing; @property double x,y,backing;
@property NSUInteger writes,undoStarts,undoEnds; @property NSUInteger writes,undoStarts,undoEnds;
@property double lastX,lastY,lastConvertX,lastConvertY; @property double lastX,lastY,lastConvertX,lastConvertY;
@property NSUInteger objectBoundsReads; @property NSUInteger objectBoundsReads,canvasToObjectCalls,objectToCanvasCalls;
@property FxDrawingCoordinates lastConvertFrom,lastConvertTo;
@property CMTime lastTime; @property CMTime lastTime;
@end @end
@implementation PGOSCAPI @implementation PGOSCAPI
- (instancetype)init {if((self=[super init])){_enabled=YES;_bounds=CGRectMake(-100,-50,200,100);_x=.25;_y=.75;_backing=1;}return self;} - (instancetype)init {if((self=[super init])){_enabled=YES;_bounds=CGRectMake(-100,-50,200,100);_x=.25;_y=.75;_backing=1;_objectToCanvas=CGAffineTransformMakeScale(320,240);}return self;}
- (id)apiForProtocol:(Protocol *)protocol { - (id)apiForProtocol:(Protocol *)protocol {
NSString *name=NSStringFromProtocol(protocol); NSString *name=NSStringFromProtocol(protocol);
if([name isEqual:@"FxOnScreenControlAPI_v4"])return _disableOSC?nil:self; if([name isEqual:@"FxOnScreenControlAPI_v4"])return _disableOSC?nil:self;
@@ -48,8 +51,16 @@
- (CGRect)objectBounds {_objectBoundsReads++;return _bounds;} - (CGRect)objectBounds {_objectBoundsReads++;return _bounds;}
- (double)backingScaleFactor {return _backing;} - (double)backingScaleFactor {return _backing;}
- (void)convertPointFromSpace:(FxDrawingCoordinates)from fromX:(double)x fromY:(double)y toSpace:(FxDrawingCoordinates)to toX:(double *)tx toY:(double *)ty { - (void)convertPointFromSpace:(FxDrawingCoordinates)from fromX:(double)x fromY:(double)y toSpace:(FxDrawingCoordinates)to toX:(double *)tx toY:(double *)ty {
// Match the SDK contract: OBJECT inputs are normalized; CANVAS is pixels. _lastConvertFrom=from;_lastConvertTo=to;_lastConvertX=x;_lastConvertY=y;
_lastConvertX=x;_lastConvertY=y;*tx=x*320;*ty=y*240; CGPoint point=CGPointMake(NAN,NAN);
if(from==kFxDrawingCoordinates_OBJECT && to==kFxDrawingCoordinates_CANVAS) {
_objectToCanvasCalls++;point=CGPointApplyAffineTransform(CGPointMake(x,y),_objectToCanvas);
} else if(from==kFxDrawingCoordinates_CANVAS && to==kFxDrawingCoordinates_OBJECT) {
_canvasToObjectCalls++;point=CGPointApplyAffineTransform(CGPointMake(x,y),CGAffineTransformInvert(_objectToCanvas));
}
if(_rejectConversion)return; // A void host API can leave outputs untouched.
if(_nonfiniteConversion)point=CGPointMake(INFINITY,NAN);
*tx=point.x;if(!_partialConversion)*ty=point.y;
} }
- (BOOL)startUndoGroup:(NSString *)name {_undoStarts++;return !_rejectUndo;} - (BOOL)startUndoGroup:(NSString *)name {_undoStarts++;return !_rejectUndo;}
- (BOOL)endUndoGroup {_undoEnds++;return YES;} - (BOOL)endUndoGroup {_undoEnds++;return YES;}
@@ -81,7 +92,7 @@ int main(int argc,const char **argv) {@autoreleasepool {
Check([osc conformsToProtocol:@protocol(FxOnScreenControl_v4)],@"OSC class conforms to the real FxPlug v4 protocol"); 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:"]; 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]); 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"); Check(osc.drawingCoordinates==kFxDrawingCoordinates_CANVAS,@"Mouse callbacks explicitly request CANVAS coordinates");
NSDictionary *info=argc>1 ? [NSDictionary dictionaryWithContentsOfFile:[NSString stringWithUTF8String:argv[1]]] : nil; 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; 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];} 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];}
@@ -90,18 +101,18 @@ int main(int argc,const char **argv) {@autoreleasepool {
Check([control[@"supportedPlugins"] isEqual:@[filters.firstObject[@"uuid"]]],@"Dedicated OSC links to the released filter UUID"); 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"); 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"); [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 normalized object point"); api.enabled=YES;[osc hitTestOSCAtMousePositionX:80 mousePositionY:180 activePart:&part atTime:time];Check(part==1,@"Enabled selection accepts a canvas point inside the converted source");
[osc hitTestOSCAtMousePositionX:1000 mousePositionY:0 activePart:&part atTime:time];Check(part==0,@"Outside-object hit test is empty"); [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: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 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"); [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"); 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:.25 positionY:.75 activePart:1 modifiers:0 forceUpdate:&update atTime:time];Check(update && fabs(api.lastX-.25)<1e-8 && fabs(api.lastY-.75)<1e-8,@"OBJECT mouse-down writes normalized point coordinates directly"); [osc mouseDownAtPositionX:80 positionY:180 activePart:1 modifiers:0 forceUpdate:&update atTime:time];Check(update && fabs(api.lastX-.25)<1e-8 && fabs(api.lastY-.75)<1e-8,@"CANVAS mouse-down converts to normalized point parameter coordinates");
Check(CMTimeCompare(api.lastTime,time)==0 && api.undoStarts==1,@"Selection writes use callback time and open one undo group"); 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 mouseDraggedAtPositionX:500 positionY:-500 activePart:1 modifiers:0 forceUpdate:&update atTime:time];Check(update && api.lastX==1 && api.lastY==0,@"Dragging clamps the converted normalized point 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"); [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"); 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:.5 mousePositionY:.5 activePart:&part atTime:time];Check(part==1,@"Normalized hit testing does not depend on document pixel bounds"); api.bounds=CGRectMake(0,0,NAN,100);[osc hitTestOSCAtMousePositionX:160 mousePositionY:120 activePart:&part atTime:time];Check(part==1,@"Converted hit testing does not depend on document pixel bounds");
update=YES;[osc mouseDownAtPositionX:NAN positionY:0 activePart:1 modifiers:0 forceUpdate:&update atTime:time];Check(!update && api.writes==writes,@"Nonfinite event initializes forceUpdate and avoids parameter writes");api.bounds=CGRectMake(-100,-50,200,100); update=YES;[osc mouseDownAtPositionX:NAN positionY:0 activePart:1 modifiers:0 forceUpdate:&update atTime:time];Check(!update && api.writes==writes,@"Nonfinite event initializes forceUpdate and avoids 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.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; 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;
@@ -125,25 +136,99 @@ int main(int argc,const char **argv) {@autoreleasepool {
Check(api.objectBoundsReads==1,@"Drawing reads object bounds once for diagnostics and never uses them to scale the normalized point"); Check(api.objectBoundsReads==1,@"Drawing reads object bounds once for diagnostics and never uses them to scale the normalized point");
api.x=NAN;[osc drawOSCWithWidth:1920 height:1080 activePart:0 destinationImage:tile atTime:time];Check(AlphaAt(tile,80,180)<.01,@"Nonfinite stored selection does not create invalid drawing filters"); api.x=NAN;[osc drawOSCWithWidth:1920 height:1080 activePart:0 destinationImage:tile atTime:time];Check(AlphaAt(tile,80,180)<.01,@"Nonfinite stored selection does not create invalid drawing filters");
api.x=.5;api.y=.5; api.x=.5;api.y=.5;
[osc mouseDownAtPositionX:.8 positionY:.3 activePart:1 modifiers:0 forceUpdate:&update atTime:time]; [osc mouseDownAtPositionX:256 positionY:72 activePart:1 modifiers:0 forceUpdate:&update atTime:time];
[osc mouseUpAtPositionX:.8 positionY:.3 activePart:1 modifiers:0 forceUpdate:&update atTime:time]; [osc mouseUpAtPositionX:256 positionY:72 activePart:1 modifiers:0 forceUpdate:&update atTime:time];
[osc drawOSCWithWidth:1921 height:1080 activePart:0 destinationImage:tile atTime:time]; [osc drawOSCWithWidth:1921 height:1080 activePart:0 destinationImage:tile atTime:time];
Check(fabs(api.x-.8)<1e-8 && fabs(api.y-.3)<1e-8 && AlphaAt(tile,256,72)>.9,@"Click writes normalized selection and redraws the handle at that point"); Check(fabs(api.x-.8)<1e-8 && fabs(api.y-.3)<1e-8 && AlphaAt(tile,256,72)>.9,@"Click writes normalized selection and redraws the handle at that point");
PGOSCAPI *motion=[PGOSCAPI new];motion.pixelSetter=YES;motion.bounds=CGRectMake(-1920,-1080,3840,2160); PGOSCAPI *motion=[PGOSCAPI new];motion.pixelSetter=YES;motion.bounds=CGRectMake(-1920,-1080,3840,2160);
motion.objectToCanvas=CGAffineTransformMake(160,0,0,120,40,30);
PGFaceSelectOSC *motionOSC=[[PGFaceSelectOSC alloc] initWithAPIManager:motion]; PGFaceSelectOSC *motionOSC=[[PGFaceSelectOSC alloc] initWithAPIManager:motion];
[motionOSC mouseDownAtPositionX:.8 positionY:.3 activePart:1 modifiers:0 forceUpdate:&update atTime:time]; [motionOSC mouseDownAtPositionX:168 positionY:66 activePart:1 modifiers:0 forceUpdate:&update atTime:time];
Check(update && fabs(motion.x-.8)<1e-5 && fabs(motion.y-.3)<1e-5,@"Motion pixel-setter bridge is detected by verified parameter readback"); Check(update && fabs(motion.x-.8)<1e-5 && fabs(motion.y-.3)<1e-5,@"Motion pixel-setter bridge is detected by verified parameter readback");
Check(motion.writes==2 && fabs(motion.lastX-1152)<1e-5 && fabs(motion.lastY+432)<1e-5,@"Detected bridge receives the centered pixel inverse transform"); Check(motion.writes==2 && fabs(motion.lastX-1152)<1e-5 && fabs(motion.lastY+432)<1e-5,@"Detected bridge receives the centered pixel inverse transform");
NSUInteger beforeCached=motion.writes; NSUInteger beforeCached=motion.writes;
[motionOSC mouseDraggedAtPositionX:.7 positionY:.4 activePart:1 modifiers:0 forceUpdate:&update atTime:time]; [motionOSC mouseDraggedAtPositionX:152 positionY:78 activePart:1 modifiers:0 forceUpdate:&update atTime:time];
Check(update && motion.writes==beforeCached+1 && fabs(motion.x-.7)<1e-5 && fabs(motion.y-.4)<1e-5,@"Pixel-setter mode is cached only on the same OSC instance"); Check(update && motion.writes==beforeCached+1 && fabs(motion.x-.7)<1e-5 && fabs(motion.y-.4)<1e-5,@"Pixel-setter mode is cached only on the same OSC instance");
[motionOSC mouseUpAtPositionX:.7 positionY:.4 activePart:1 modifiers:0 forceUpdate:&update atTime:time]; [motionOSC mouseUpAtPositionX:152 positionY:78 activePart:1 modifiers:0 forceUpdate:&update atTime:time];
[motionOSC drawOSCWithWidth:3840 height:2160 activePart:0 destinationImage:tile atTime:time]; [motionOSC drawOSCWithWidth:3840 height:2160 activePart:0 destinationImage:tile atTime:time];
Check(AlphaAt(tile,224,96)>.9,@"Motion-compatible setter and normalized drawing round-trip to the same visible point"); Check(AlphaAt(tile,152,78)>.9,@"Motion-compatible setter and drawing round-trip through zoom and nonzero canvas translation");
Check(motion.undoStarts==1 && motion.undoEnds==1,@"Calibration writes remain in one balanced undo gesture"); Check(motion.undoStarts==1 && motion.undoEnds==1,@"Calibration writes remain in one balanced undo gesture");
PGOSCAPI *bad=[PGOSCAPI new];bad.corruptReadback=YES; PGOSCAPI *bad=[PGOSCAPI new];bad.corruptReadback=YES;
PGFaceSelectOSC *badOSC=[[PGFaceSelectOSC alloc] initWithAPIManager:bad]; PGFaceSelectOSC *badOSC=[[PGFaceSelectOSC alloc] initWithAPIManager:bad];
[badOSC mouseDownAtPositionX:.8 positionY:.3 activePart:1 modifiers:0 forceUpdate:&update atTime:time]; [badOSC mouseDownAtPositionX:256 positionY:72 activePart:1 modifiers:0 forceUpdate:&update atTime:time];
Check(!update && bad.undoStarts==bad.undoEnds,@"Unrecognized readback response is reported as failure with balanced undo"); Check(!update && bad.undoStarts==bad.undoEnds,@"Unrecognized readback response is reported as failure with balanced undo");
// Each fixture describes a host projection independent of effect dimensions:
// reduced view with padding, magnified/panned view, and a shifted canvas.
const CGAffineTransform transforms[]={
CGAffineTransformMake(160,0,0,120,40,30),
CGAffineTransformMake(640,0,0,480,-160,-120),
CGAffineTransformMake(192,0,0,144,96,72)};
const CGPoint normalizedPoints[]={CGPointMake(.75,.25),CGPointMake(.625,.375),CGPointMake(.25,.75)};
const CGPoint canvasPoints[]={CGPointMake(160,60),CGPointMake(240,60),CGPointMake(144,180)};
for(NSUInteger i=0;i<3;i++) {
PGOSCAPI *projected=[PGOSCAPI new];projected.objectToCanvas=transforms[i];
PGFaceSelectOSC *projectedOSC=[[PGFaceSelectOSC alloc] initWithAPIManager:projected];
CGPoint n=normalizedPoints[i],c=canvasPoints[i];
[projectedOSC hitTestOSCAtMousePositionX:c.x mousePositionY:c.y activePart:&part atTime:time];
Check(part==1,[NSString stringWithFormat:@"Projection %lu claims a visible source point",(unsigned long)i]);
Check(projected.lastConvertFrom==kFxDrawingCoordinates_CANVAS && projected.lastConvertTo==kFxDrawingCoordinates_OBJECT,@"Hit test explicitly requests the inverse host conversion");
[projectedOSC mouseDownAtPositionX:c.x positionY:c.y activePart:part modifiers:0 forceUpdate:&update atTime:time];
Check(update && fabs(projected.x-n.x)<1e-8 && fabs(projected.y-n.y)<1e-8,@"Projected click writes the expected normalized parameter");
[projectedOSC mouseUpAtPositionX:c.x positionY:c.y activePart:part modifiers:0 forceUpdate:&update atTime:time];
Check(update && projected.undoStarts==1 && projected.undoEnds==1,@"Projected mouse-up converts and closes one undo gesture");
[projectedOSC drawOSCWithWidth:3840 height:2160 activePart:0 destinationImage:tile atTime:time];
Check(AlphaAt(tile,(int)c.x,(int)c.y)>.9,@"Projected click, parameter and rendered handle share the same canvas position");
Check(projected.lastConvertFrom==kFxDrawingCoordinates_OBJECT && projected.lastConvertTo==kFxDrawingCoordinates_CANVAS,@"Draw explicitly requests the forward host conversion");
Check(projected.canvasToObjectCalls==3 && projected.objectToCanvasCalls==1,@"Hit, down, up and draw each use the live host conversion");
}
PGOSCAPI *panned=[PGOSCAPI new];panned.objectToCanvas=transforms[0];
PGFaceSelectOSC *pannedOSC=[[PGFaceSelectOSC alloc] initWithAPIManager:panned];
[pannedOSC hitTestOSCAtMousePositionX:20 mousePositionY:90 activePart:&part atTime:time];
Check(part==0,@"Canvas pixels inside the surface but outside a shifted source do not hit");
[pannedOSC mouseDownAtPositionX:20 positionY:90 activePart:1 modifiers:0 forceUpdate:&update atTime:time];
Check(!update && panned.writes==0 && panned.undoStarts==0,@"Outside-source mouse-down is rejected even with a stale active part");
[pannedOSC mouseDownAtPositionX:120 positionY:90 activePart:1 modifiers:0 forceUpdate:&update atTime:time];
Check(update && fabs(panned.x-.5)<1e-8 && fabs(panned.y-.5)<1e-8,@"Gesture starts at the shifted source center");
panned.objectToCanvas=CGAffineTransformMake(80,0,0,60,100,80);
[pannedOSC mouseDraggedAtPositionX:160 positionY:95 activePart:1 modifiers:0 forceUpdate:&update atTime:time];
Check(update && fabs(panned.x-.75)<1e-8 && fabs(panned.y-.25)<1e-8,@"Drag follows host zoom and pan changes during the gesture");
[pannedOSC mouseDraggedAtPositionX:240 positionY:20 activePart:1 modifiers:0 forceUpdate:&update atTime:time];
Check(update && panned.x==1 && panned.y==0,@"Drag outside shifted source clamps after inverse conversion");
[pannedOSC mouseUpAtPositionX:80 positionY:200 activePart:1 modifiers:0 forceUpdate:&update atTime:time];
Check(update && panned.x==0 && panned.y==1 && panned.undoStarts==panned.undoEnds,@"Release outside shifted source clamps and balances undo");
[pannedOSC drawOSCWithWidth:1920 height:1080 activePart:0 destinationImage:tile atTime:time];
Check(AlphaAt(tile,100,140)>.9,@"Clamped release draws at the corresponding transformed source corner");
for(NSUInteger failure=0;failure<3;failure++) {
PGOSCAPI *failed=[PGOSCAPI new];
failed.rejectConversion=failure==0;failed.nonfiniteConversion=failure==1;failed.partialConversion=failure==2;
PGFaceSelectOSC *failedOSC=[[PGFaceSelectOSC alloc] initWithAPIManager:failed];
[failedOSC hitTestOSCAtMousePositionX:160 mousePositionY:120 activePart:&part atTime:time];
Check(part==0,@"Untouched, nonfinite or partial conversion cannot claim a hit");
[failedOSC mouseDownAtPositionX:160 positionY:120 activePart:1 modifiers:0 forceUpdate:&update atTime:time];
Check(!update && failed.writes==0 && failed.undoStarts==0,@"Failed conversion cannot write a point or open undo");
[failedOSC drawOSCWithWidth:1920 height:1080 activePart:0 destinationImage:tile atTime:time];
Check(AlphaAt(tile,80,180)<.01,@"Failed forward conversion clears the prior OSC handle");
failed.rejectConversion=failed.nonfiniteConversion=failed.partialConversion=NO;
[failedOSC mouseDownAtPositionX:160 positionY:120 activePart:1 modifiers:0 forceUpdate:&update atTime:time];
NSUInteger written=failed.writes;
failed.rejectConversion=failure==0;failed.nonfiniteConversion=failure==1;failed.partialConversion=failure==2;
[failedOSC mouseDraggedAtPositionX:200 positionY:100 activePart:1 modifiers:0 forceUpdate:&update atTime:time];
Check(!update && failed.writes==written,@"Conversion failure during drag preserves the previous selection");
[failedOSC mouseUpAtPositionX:200 positionY:100 activePart:1 modifiers:0 forceUpdate:&update atTime:time];
Check(!update && failed.writes==written && failed.undoStarts==failed.undoEnds,@"Conversion failure on release still closes undo without a write");
failed.rejectConversion=failed.nonfiniteConversion=failed.partialConversion=NO;
[failedOSC mouseDraggedAtPositionX:200 positionY:100 activePart:1 modifiers:0 forceUpdate:&update atTime:time];
Check(!update && failed.writes==written,@"Failed release ends dragging so later stray drag cannot write");
}
PGOSCAPI *invalidEvent=[PGOSCAPI new];
PGFaceSelectOSC *invalidEventOSC=[[PGFaceSelectOSC alloc] initWithAPIManager:invalidEvent];
[invalidEventOSC mouseDownAtPositionX:160 positionY:120 activePart:1 modifiers:0 forceUpdate:&update atTime:time];
writes=invalidEvent.writes;NSUInteger conversions=invalidEvent.canvasToObjectCalls;
[invalidEventOSC mouseDraggedAtPositionX:INFINITY positionY:120 activePart:1 modifiers:0 forceUpdate:&update atTime:time];
Check(!update && invalidEvent.writes==writes && invalidEvent.canvasToObjectCalls==conversions,@"Nonfinite canvas drag is rejected before calling the host conversion");
[invalidEventOSC mouseUpAtPositionX:160 positionY:NAN activePart:1 modifiers:0 forceUpdate:&update atTime:time];
Check(!update && invalidEvent.writes==writes && invalidEvent.undoStarts==invalidEvent.undoEnds,@"Nonfinite canvas release balances undo without changing selection");
printf("OSC adapter: %lu checks, %lu failures\n",(unsigned long)Checks,(unsigned long)Failures);return Failures?1:0; printf("OSC adapter: %lu checks, %lu failures\n",(unsigned long)Checks,(unsigned long)Failures);return Failures?1:0;
}} }}