Migrate new beauty controls with continuous protection, local detail recovery and legacy state compatibility; 1636 checks passed
这个提交包含在:
1 父节点
2e79602761
当前提交
2f2c081980
16 个文件已更改
+350
-44
没有与搜索条件相匹配的文件
@@ -103,6 +103,46 @@ 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);
|
||||
// Independent protection controls use fractional coverage; neither changes
|
||||
// exposure coverage nor invents smoothing when the smoothing amount is zero.
|
||||
s=PGBeautyDefaults();s.midtoneDetail=1;s.maskSoftness=0;s.maskEdgeProtect=0;
|
||||
float *featureFull=Render(context,[renderer processImage:source settings:s skinMask:white teethMask:nil eyeMask:half error:&error],extent);
|
||||
s.featureProtectionAmount=.5;
|
||||
float *featureHalf=Render(context,[renderer processImage:source settings:s skinMask:white teethMask:nil eyeMask:half error:&error],extent);
|
||||
s.featureProtectionAmount=0;
|
||||
float *featureNone=Render(context,[renderer processImage:source settings:s skinMask:white teethMask:nil eyeMask:half error:&error],extent);
|
||||
float fullDelta=Difference(base,featureFull,6,40),halfDelta=Difference(base,featureHalf,6,40),noneDelta=Difference(base,featureNone,6,40);
|
||||
Check(fullDelta<.00001 && halfDelta>fullDelta+.001 && noneDelta>halfDelta+.001,@"Five-feature protection has distinct ordered 0/50/100 percent pixel effects");
|
||||
Check(Difference(featureFull,featureNone,56,88)<noneDelta*.25,@"Feature slider primarily changes marked features rather than nearby skin texture");
|
||||
free(featureFull);free(featureHalf);free(featureNone);
|
||||
s=PGBeautyDefaults();s.midtoneDetail=1;s.maskSoftness=0;s.maskEdgeProtect=0;s.featureProtection=0;
|
||||
float *skinFull=Render(context,[renderer processImage:source settings:s skinMask:half teethMask:nil eyeMask:nil error:&error],extent);
|
||||
s.nonSkinProtection=.5;
|
||||
float *skinHalf=Render(context,[renderer processImage:source settings:s skinMask:half teethMask:nil eyeMask:nil error:&error],extent);
|
||||
s.nonSkinProtection=0;
|
||||
float *skinNone=Render(context,[renderer processImage:source settings:s skinMask:half teethMask:nil eyeMask:nil error:&error],extent);
|
||||
Check(Difference(base,skinFull,56,88)<.00001 && Difference(base,skinHalf,56,88)>.001 && Difference(base,skinNone,56,88)>Difference(base,skinHalf,56,88)+.001,@"Non-skin protection progressively admits background into the local smoothing mask");
|
||||
Check(Difference(skinFull,skinNone,6,40)<.00001,@"Non-skin protection leaves already-covered skin strength unchanged");
|
||||
free(skinFull);free(skinHalf);free(skinNone);
|
||||
s.midtoneDetail=0;
|
||||
float *noSmoothing=Render(context,[renderer processImage:source settings:s skinMask:half teethMask:nil eyeMask:nil error:&error],extent);
|
||||
Check(Difference(base,noSmoothing,4,W-4)<.00001,@"Releasing non-skin protection alone does not enable an independent global smoothing effect");free(noSmoothing);
|
||||
s=PGBeautyDefaults();s.microDetail=1;s.maskSoftness=0;s.maskEdgeProtect=0;
|
||||
float *micro=Render(context,[renderer processImage:source settings:s skinMask:half teethMask:nil eyeMask:nil error:&error],extent);
|
||||
Check(Texture(micro,6,40)>Texture(base,6,40)*1.05 && Difference(base,micro,56,88)<.00001,@"Micro detail recovers texture within smoothing support, including without smoothing");
|
||||
free(micro);
|
||||
s=PGBeautyDefaults();s.skinBrightness=.7;s.maskSoftness=0;s.maskEdgeProtect=0;
|
||||
float *colorProtected=Render(context,[renderer processImage:source settings:s skinMask:half colorMask:half teethMask:nil eyeMask:nil error:&error],extent);
|
||||
s.nonSkinProtection=0;s.featureProtectionAmount=0;
|
||||
float *colorReleased=Render(context,[renderer processImage:source settings:s skinMask:half colorMask:half teethMask:nil eyeMask:nil error:&error],extent);
|
||||
Check(Difference(colorProtected,colorReleased,4,W-4)<.00001,@"Both new protection sliders leave skin exposure coverage unchanged");
|
||||
free(colorProtected);free(colorReleased);
|
||||
s=PGBeautyDefaults();s.skinBrightness=.7;s.maskSoftness=0;s.maskEdgeProtect=0;
|
||||
colorProtected=Render(context,[renderer processImage:source settings:s skinMask:white teethMask:half eyeMask:half error:&error],extent);
|
||||
s.featureProtectionAmount=0;s.featureProtection=0;s.nonSkinProtection=0;
|
||||
colorReleased=Render(context,[renderer processImage:source settings:s skinMask:white teethMask:half eyeMask:half error:&error],extent);
|
||||
Check(Difference(colorProtected,colorReleased,4,W-4)<.00001,@"Standalone renderer also keeps eye/teeth protection independent from exposure when the legacy bool changes");
|
||||
free(colorProtected);free(colorReleased);
|
||||
// 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;
|
||||
@@ -192,6 +232,7 @@ int main(void) { @autoreleasepool {
|
||||
// Every operation must preserve transparency rather than creating halos/opaque pixels.
|
||||
for(int i=0;i<W*H;i++){p[i*4]*=0.4;p[i*4+1]*=0.4;p[i*4+2]*=0.4;p[i*4+3]=0.4;}
|
||||
CIImage *transparent=Image(p,extent);s=PGBeautyDefaults();s.midtoneDetail=1;s.skinBrightness=1;s.teethWhitening=1;s.eyeBrightening=1;s.skinTonePreset=3;s.highPassDetail=1;
|
||||
s.microDetail=.7;s.nonSkinProtection=.3;s.featureProtectionAmount=.4;
|
||||
float *alpha=Render(context,[renderer processImage:transparent settings:s skinMask:white teethMask:half eyeMask:half error:&error],extent);
|
||||
BOOL alphaOK=YES,finite=YES;
|
||||
for(int i=0;i<W*H;i++){if(fabsf(alpha[i*4+3]-0.4f)>0.0002)alphaOK=NO;for(int c=0;c<4;c++)if(!isfinite(alpha[i*4+c]))finite=NO;}
|
||||
|
||||
+11
-3
@@ -469,7 +469,9 @@ int main(int argc, const char *argv[]) {
|
||||
Check([plugin addParametersWithError:&error] && !error, @"host creates complete parameter set");
|
||||
UInt32 identifiers[] = {PGAmount,PGBrightness,PGRadius,PGSharpness,PGMix,PGThreshold,PGSoftness,PGEdgeProtect,PGShowMask,PGMidtone,PGHue,PGSaturation,PGHighPass,PGThin,PGFaceBoxes,PGSingleFace,PGSelection,PGShort,PGNarrow,PGChin,PGVFace,PGJaw,PGCheekbone,PGSmallHead,PGSmallHeadRange,PGFeatureProtect,PGTeeth,PGEyeBright,PGBigEye,PGTrackBoxes,PGFastMulti,PGJumpGuard,PGEvenTone,PGTonePreset,PGInvertMask,PGDebugChannel,PGTrackEnabled,PGTarget,PGAnalyzeForward,PGAnalyzeBackward,PGClearTrack,PGStatus,PGTrackData1,PGTrackData2,PGTrackData3,PGSmoothingEnabled,PGColorEnabled,PGShapeEnabled,PGDetailEnabled,PGInspectorPanel};
|
||||
for (NSUInteger i = 0; i < sizeof(identifiers) / sizeof(UInt32); i++) Check(api.definitions[@(identifiers[i])] != nil, [NSString stringWithFormat:@"required parameter %u exists", identifiers[i]]);
|
||||
Check(api.depth == 0 && !api.duplicate && api.definitions.count == sizeof(identifiers) / sizeof(UInt32) + 6, @"parameter groups close and IDs are unique");
|
||||
for(NSNumber *identifier in @[@(PGFeatureProtectionAmount),@(PGNonSkinProtection),@(PGMicroDetail)])
|
||||
Check(api.definitions[identifier]!=nil,@"new protection/detail float parameter is registered");
|
||||
Check(api.depth == 0 && !api.duplicate && api.definitions.count == sizeof(identifiers) / sizeof(UInt32) + 10, @"parameter groups close and IDs are unique");
|
||||
Check([api.values[@(PGMidtone)] doubleValue] == 0 && [api.values[@(PGMix)] doubleValue] == 1, @"default detail and mix form identity filter");
|
||||
Check([api.definitions[@(PGShort)][@"min"] doubleValue] == -1 && [api.definitions[@(PGShort)][@"max"] doubleValue] == 1, @"signed face control exposes both directions");
|
||||
Check([api.definitions[@(PGTrackData1)][@"flags"] unsignedLongLongValue] & kFxParameterFlag_HIDDEN, @"persistent tracking data stays hidden in host UI");
|
||||
@@ -480,7 +482,7 @@ int main(int argc, const char *argv[]) {
|
||||
NSDictionary *decoded = [NSPropertyListSerialization propertyListWithData:state options:0 format:nil error:nil];
|
||||
Check([decoded[@"tracks"] count] == 3 && [decoded[@"settings"] length] == sizeof(PGBeautySettings), @"snapshot contains fixed settings schema and three target slots");
|
||||
PGBeautySettings settings; [decoded[@"settings"] getBytes:&settings length:sizeof(settings)];
|
||||
Check(settings.version == 1 && settings.mix == 1 && settings.midtoneDetail == 0, @"snapshot round-trips default numeric state");
|
||||
Check(settings.version == PGBeautySettingsVersion && settings.mix == 1 && settings.midtoneDetail == 0 && settings.featureProtectionAmount==1 && settings.nonSkinProtection==1 && settings.microDetail==0, @"snapshot round-trips v2 with neutral defaults for the new controls");
|
||||
api.values[@(PGThin)] = @5; api.values[@(PGRadius)] = @(-9); api.values[@(PGSelection)] = @[@(-1), @2];
|
||||
settings = [plugin settingsAtTime:api.hostTime];
|
||||
Check(settings.faceThin == 1 && settings.radius == 1 && settings.selectionX == 0 && settings.selectionY == 1, @"host parameter limits and normalized selection are enforced");
|
||||
@@ -493,6 +495,12 @@ int main(int argc, const char *argv[]) {
|
||||
NSUInteger difference = MaximumDifference(SurfaceBytes(source), SurfaceBytes(destination));
|
||||
printf("Default IOSurface maximum byte difference: %lu\n", (unsigned long)difference);
|
||||
Check(difference <= 1, @"default adapter output preserves gamma-video numeric samples");
|
||||
PGBeautySettings old=PGBeautyDefaults();old.version=1;
|
||||
NSData *legacy=PlistState(@{@"settings":[NSData dataWithBytes:&old length:PGBeautySettingsV1Size],@"tracks":@[@"",@"",@""]});
|
||||
Check(Render(plugin,destination,@[source],legacy,kCMTimeZero,&error,NULL) && MaximumDifference(SurfaceBytes(source),SurfaceBytes(destination))<=1,@"160-byte v1 snapshots migrate with neutral new controls and preserve original pixels");
|
||||
RejectState(plugin,destination,source,PlistState(@{@"settings":[NSData dataWithBytes:&old length:sizeof(old)],@"tracks":@[@"",@"",@""]}),@"v1 with the new structure length is rejected instead of guessing its layout");
|
||||
old.version=PGBeautySettingsVersion;
|
||||
RejectState(plugin,destination,source,PlistState(@{@"settings":[NSData dataWithBytes:&old length:PGBeautySettingsV1Size],@"tracks":@[@"",@"",@""]}),@"v2 with a truncated legacy-sized payload is rejected");
|
||||
CGColorSpaceRelease(destination.space); destination.space = CGColorSpaceCreateWithName(kCGColorSpaceLinearSRGB);
|
||||
Check(Render(plugin, destination, @[source], state, kCMTimeZero, &error, NULL), @"numeric host render accepts destination color-space metadata");
|
||||
difference = MaximumDifference(SurfaceBytes(source), SurfaceBytes(destination));
|
||||
@@ -510,7 +518,7 @@ int main(int argc, const char *argv[]) {
|
||||
RejectState(plugin, destination, source, PlistState(@{@"settings":[NSData data], @"tracks":@[@"",@"",@""]}), @"wrong settings size is rejected safely");
|
||||
settings = PGBeautyDefaults(); settings.version = 99;
|
||||
RejectState(plugin, destination, source, PlistState(@{@"settings":[NSData dataWithBytes:&settings length:sizeof(settings)], @"tracks":@[@"",@"",@""]}), @"unknown saved settings version is rejected safely");
|
||||
settings.version = 1;
|
||||
settings.version = PGBeautySettingsVersion;
|
||||
RejectState(plugin, destination, source, PlistState(@{@"settings":[NSData dataWithBytes:&settings length:sizeof(settings)], @"tracks":@[@"",@""]}), @"wrong target slot count is rejected safely");
|
||||
RejectState(plugin, destination, source, PlistState(@{@"settings":[NSData dataWithBytes:&settings length:sizeof(settings)], @"tracks":@[@"",@7,@""]}), @"non-string target slot is rejected safely");
|
||||
for (NSUInteger top = 0; top < 2; top++) {
|
||||
|
||||
+112
-14
@@ -52,6 +52,9 @@ static NSControl *FindControl(NSView *view,Class type,NSString *label) {
|
||||
}
|
||||
static void Fire(NSControl *control) {[control sendAction:control.action to:control.target];}
|
||||
static BOOL Near(double a,double b) {return fabs(a-b)<.00001;}
|
||||
static PGInspectorState *State(NSDictionary *values,NSSet *disabled) {
|
||||
return [PGInspectorState stateWithValues:values disabledParameters:disabled?:[NSSet set] hasEdits:YES];
|
||||
}
|
||||
static void RegistrationTests(void) {
|
||||
PGHostAPI *api=[PGHostAPI new];beatygrilPlugIn *plugin=[[beatygrilPlugIn alloc]initWithAPIManager:api];[plugin addParametersWithError:nil];
|
||||
NSUInteger visible=0;for(NSNumber *key in api.definitions){NSDictionary *d=api.definitions[key];if(!([d[@"flags"]unsignedIntValue]&kFxParameterFlag_HIDDEN)){visible++;Check(key.unsignedIntValue==PGInspectorPanel,@"only the custom panel is visible in the native parameter list");}}
|
||||
@@ -65,11 +68,47 @@ static void RegistrationTests(void) {
|
||||
NSNumber *key=d[@"id"];Check(api.definitions[key]!=nil,@"every inspector control maps to an existing stable parameter ID");
|
||||
if([d[@"kind"]isEqual:@"slider"])Check(Near([d[@"min"]doubleValue],[api.definitions[key][@"min"]doubleValue]) && Near([d[@"max"]doubleValue],[api.definitions[key][@"max"]doubleValue]),@"UI and renderer enforce the same slider range");
|
||||
}
|
||||
NSMutableArray *smoothIDs=[NSMutableArray new];
|
||||
for(NSDictionary *d in PGInspectorControls())if([d[@"section"]isEqual:@"smooth"] && ![d[@"kind"]isEqual:@"master"])[smoothIDs addObject:d[@"id"]];
|
||||
Check([smoothIDs isEqual:@[@(PGMidtone),@(PGMicroDetail),@(PGFeatureProtectionAmount),@(PGNonSkinProtection)]],@"primary smoothing card exposes the four new controls in reference order");
|
||||
Check(PGInspectorDescriptor(PGFeatureProtect)==nil && [PGInspectorDescriptor(PGHighPass)[@"section"]isEqual:@"more"] && [PGInspectorDescriptor(PGSharpness)[@"section"]isEqual:@"more"],@"legacy boolean stays hidden while legacy global smoothing and sharpening remain available in more settings");
|
||||
api.values[@(PGTrackEnabled)]=@YES;api.values[@(PGSingleFace)]=@YES;api.values[@(PGShowMask)]=@YES;
|
||||
PGBeautySettings s=[plugin settingsAtTime:kCMTimeZero];Check(!s.trackingEnabled && !s.singleFace && !s.showMask,@"old manual analysis, selection and debug flags cannot override automatic output");
|
||||
NSData *snapshot=Snapshot(plugin,kCMTimeZero);NSDictionary *plist=[NSPropertyListSerialization propertyListWithData:snapshot options:0 format:nil error:nil];
|
||||
Check([plist[@"tracks"]isEqual:@[@"",@"",@""]],@"automatic render snapshots do not transfer persisted manual-analysis payloads");
|
||||
}
|
||||
static void BeautyControlModelTests(void) {
|
||||
PGInspectorState *defaults=State(@{},nil);
|
||||
Check([defaults valueForParameter:PGFeatureProtectionAmount]==1 && [defaults valueForParameter:PGNonSkinProtection]==1 && [defaults valueForParameter:PGMicroDetail]==0,@"new controls default to full five-feature/non-skin protection and zero micro detail");
|
||||
for(NSNumber *legacy in @[@NO,@YES]) {
|
||||
PGInspectorState *migrated=State(@{@(PGFeatureProtect):legacy,@(PGHighPass):@.63},nil);
|
||||
PGBeautySettings s=PGSettingsWithInspectorState(PGBeautyDefaults(),migrated);
|
||||
Check([migrated valueForParameter:PGFeatureProtectionAmount]==legacy.boolValue && s.featureProtection==legacy.boolValue && Near(s.highPassDetail,.63) && s.nonSkinProtection==1,@"old schema dictionaries migrate five-feature bool without reinterpreting legacy global smoothing");
|
||||
}
|
||||
PGInspectorState *explicit=State(@{@(PGFeatureProtect):@NO,@(PGFeatureProtectionAmount):@.37},nil);
|
||||
Check(Near([explicit valueForParameter:PGFeatureProtectionAmount],.37),@"an explicit numeric protection setting takes precedence over the old boolean");
|
||||
NSDictionary *values=@{@(PGFeatureProtectionAmount):@.25,@(PGNonSkinProtection):@.6,@(PGMicroDetail):@.8,@(PGMidtone):@.7,@(PGHighPass):@.4,@(PGSharpness):@.5,@(PGHue):@12,@(PGBrightness):@.3,@(PGTonePreset):@2};
|
||||
PGInspectorState *a=State(values,nil);
|
||||
NSData *archive=[NSKeyedArchiver archivedDataWithRootObject:a requiringSecureCoding:YES error:nil];
|
||||
PGInspectorState *restored=[NSKeyedUnarchiver unarchivedObjectOfClass:PGInspectorState.class fromData:archive error:nil];
|
||||
Check([restored isEqual:a] && Near([restored valueForParameter:PGFeatureProtectionAmount],.25) && Near([restored valueForParameter:PGNonSkinProtection],.6) && Near([restored valueForParameter:PGMicroDetail],.8),@"secure document archive retains all three independent numeric controls");
|
||||
NSMutableDictionary *right=[values mutableCopy];right[@(PGFeatureProtectionAmount)]=@.75;right[@(PGNonSkinProtection)]=@.2;right[@(PGMicroDetail)]=@.4;
|
||||
PGInspectorState *mid=(PGInspectorState *)[a interpolateBetween:State(right,nil) withWeight:.5];
|
||||
Check(Near([mid valueForParameter:PGFeatureProtectionAmount],.5) && Near([mid valueForParameter:PGNonSkinProtection],.4) && Near([mid valueForParameter:PGMicroDetail],.6),@"new floating-point protection and micro-detail keyframes interpolate independently");
|
||||
for(NSNumber *key in @[@(PGFeatureProtectionAmount),@(PGNonSkinProtection),@(PGMicroDetail)]) {
|
||||
PGInspectorState *bypassed=State(values,[NSSet setWithObject:key]);
|
||||
PGBeautySettings s=PGSettingsWithInspectorState(PGBeautyDefaults(),bypassed);
|
||||
Check(Near(s.featureProtectionAmount,key.unsignedIntValue==PGFeatureProtectionAmount?0:.25) && Near(s.nonSkinProtection,key.unsignedIntValue==PGNonSkinProtection?0:.6) && Near(s.microDetail,key.unsignedIntValue==PGMicroDetail?0:.8),@"each new bypass affects only its own render setting");
|
||||
Check(s.featureProtection==(s.featureProtectionAmount>0) && Near(s.skinHue,12) && Near(s.skinBrightness,.3) && s.skinTonePreset==2 && Near(s.highPassDetail,.4) && Near(s.sharpness,.5),@"protection bypass preserves color and legacy detail settings and derives the old feature bool");
|
||||
}
|
||||
NSMutableDictionary *off=[values mutableCopy];off[@(PGSmoothingEnabled)]=@0;
|
||||
PGBeautySettings s=PGSettingsWithInspectorState(PGBeautyDefaults(),State(off,nil));
|
||||
Check(s.amount==0 && s.midtoneDetail==0 && s.highPassDetail==0 && s.microDetail==0 && Near(s.skinHue,12) && Near(s.skinBrightness,.3),@"smoothing master bypass also disables micro detail while preserving color adjustments");
|
||||
PGInspectorState *untouched=[PGInspectorState stateWithValues:@{@(PGFeatureProtect):@NO} disabledParameters:[NSSet set] hasEdits:NO];
|
||||
PGBeautySettings original=PGBeautyDefaults();original.featureProtectionAmount=.8;original.nonSkinProtection=.2;original.microDetail=.7;
|
||||
PGBeautySettings result=PGSettingsWithInspectorState(original,untouched);
|
||||
Check(memcmp(&original,&result,sizeof(result))==0,@"an untouched migrated inspector still leaves existing document settings unchanged");
|
||||
}
|
||||
static void ModelTests(void) {
|
||||
PGInspectorState *plain=[PGInspectorState stateWithValues:@{} disabledParameters:[NSSet set] hasEdits:NO];
|
||||
PGBeautySettings original=PGBeautyDefaults();original.midtoneDetail=.5;original.faceChin=-.3;
|
||||
@@ -191,13 +230,51 @@ static void ViewInteractionTests(void) {
|
||||
Fire(number);
|
||||
Check(number.currentEditor==nil && Near([(PGInspectorState *)api.values[@(PGInspectorPanel)] valueForParameter:PGChin],.25),@"Return commits once and releases the actual AppKit editor before host commands");
|
||||
[window close];
|
||||
NSButton *preview=(NSButton *)FindControl(view,NSButton.class,@"人脸框预览");preview.state=NSControlStateValueOn;Fire(preview);s=api.values[@(PGInspectorPanel)];Check([s valueForParameter:PGTrackBoxes]==1,@"preview switch events change the automatic face box setting");
|
||||
NSButton *preview=(NSButton *)FindControl(view,NSButton.class,@"检测框预览");preview.state=NSControlStateValueOn;Fire(preview);s=api.values[@(PGInspectorPanel)];Check([s valueForParameter:PGTrackBoxes]==1,@"preview switch events change the automatic face box setting");
|
||||
for(NSNumber *width in @[@280,@320,@380,@520]) {
|
||||
[view setFrameSize:NSMakeSize(width.doubleValue,20)];[view layoutSubtreeIfNeeded];
|
||||
Check(view.frame.size.height>500 && Near(view.frame.size.height,view.fittingSize.height) && Near(view.frame.size.height,view.intrinsicContentSize.height),@"width changes produce a complete consistent intrinsic/fitting height");
|
||||
for(NSView *card in view.subviews)if(!card.hidden)Check(NSMaxY(card.frame)<=NSHeight(view.bounds)+1 && NSMaxX(card.frame)<=NSWidth(view.bounds)+1,@"every visible card and footer remains inside the panel at all inspector widths");
|
||||
}
|
||||
}
|
||||
static void BeautyControlInteractionTests(void) {
|
||||
PGInspectorTestAPI *api=[PGInspectorTestAPI new];beatygrilPlugIn *plugin=[[beatygrilPlugIn alloc]initWithAPIManager:api];[plugin addParametersWithError:nil];
|
||||
api.values[@(PGFeatureProtectionAmount)]=@.37;api.values[@(PGHighPass)]=@.63;api.values[@(PGHue)]=@12;api.values[@(PGBrightness)]=@.3;
|
||||
PGInspectorView *view=(PGInspectorView *)[plugin createViewForParameterID:PGInspectorPanel];[view refreshFromHost];
|
||||
Check(Near([view.controller.state valueForParameter:PGFeatureProtectionAmount],.37) && !view.controller.state.hasEdits,@"an untouched inspector preserves native fractional protection when the legacy boolean is enabled");
|
||||
[view.controller setValue:.35 forParameter:PGBrightness];
|
||||
Check(Near([(PGInspectorState *)api.values[@(PGInspectorPanel)] valueForParameter:PGFeatureProtectionAmount],.37) && Near([api.values[@(PGFeatureProtectionAmount)]doubleValue],.37),@"first unrelated edit captures native fractional protection without replacing it with the legacy true value");
|
||||
api.values[@(PGInspectorPanel)]=[PGInspectorState stateWithValues:@{} disabledParameters:[NSSet set] hasEdits:NO];
|
||||
api.values[@(PGFeatureProtect)]=@NO;[view hostParameterChanged];
|
||||
Check([view.controller.state valueForParameter:PGFeatureProtectionAmount]==0 && !view.controller.state.hasEdits,@"untouched inspector imports the old host five-feature boolean into the new numeric control");
|
||||
[view.controller setValue:.35 forParameter:PGBrightness];
|
||||
Check(![api.values[@(PGFeatureProtect)]boolValue] && Near([view.controller.state valueForParameter:PGFeatureProtectionAmount],0),@"first unrelated edit preserves disabled legacy feature protection");
|
||||
NSArray *ids=@[@(PGFeatureProtectionAmount),@(PGNonSkinProtection),@(PGMicroDetail)],*names=@[@"五官保护",@"非肤色区保护",@"微细锐化"];
|
||||
for(NSUInteger i=0;i<ids.count;i++) {
|
||||
UInt32 key=[ids[i]unsignedIntValue];NSString *name=names[i];
|
||||
NSSlider *slider=(NSSlider *)FindControl(view,NSSlider.class,name);
|
||||
NSTextField *number=(NSTextField *)FindControl(view,NSTextField.class,[name stringByAppendingString:@"数值"]);
|
||||
Check(slider && number && slider.enabled && number.enabled,@"each new control exposes an enabled native slider and numeric field");
|
||||
number.stringValue=@"27%";Fire(number);
|
||||
Check(Near([view.controller.state valueForParameter:key],.27) && [number.stringValue isEqual:@"27%"],@"new numeric controls accept direct percentages without inverse interpretation");
|
||||
slider.doubleValue=.42;Fire(slider);
|
||||
Check(Near([view.controller.state valueForParameter:key],.42) && [number.stringValue isEqual:@"42%"],@"actual new slider events publish their direct amount and update the percentage display");
|
||||
NSButton *eye=(NSButton *)FindControl(view,NSButton.class,[@"启用 / 停用 · "stringByAppendingString:name]);Fire(eye);
|
||||
Check(![view.controller.state isParameterEnabled:key] && Near([view.controller.state valueForParameter:key],.42) && !slider.enabled,@"new individual bypass preserves its value and disables its native controls");
|
||||
if(key==PGFeatureProtectionAmount)Check(![api.values[@(PGFeatureProtect)]boolValue],@"bypassed numeric feature protection mirrors false into the legacy hidden boolean");
|
||||
NSButton *reset=(NSButton *)FindControl(view,NSButton.class,[@"重置 · "stringByAppendingString:name]);Fire(reset);
|
||||
Check([view.controller.state isParameterEnabled:key] && [view.controller.state valueForParameter:key]==(key==PGMicroDetail?0:1) && slider.enabled,@"new individual reset restores the proper default and clears bypass");
|
||||
}
|
||||
Check([api.values[@(PGFeatureProtect)]boolValue] && Near([view.controller.state valueForParameter:PGHighPass],.63) && Near([view.controller.state valueForParameter:PGHue],12) && Near([view.controller.state valueForParameter:PGBrightness],.35),@"new resets restore the legacy feature bool without modifying global smoothing, hue or brightness");
|
||||
NSButton *multi=(NSButton *)FindControl(view,NSButton.class,@"多人模式");Check([multi.toolTip containsString:@"易抖动"],@"multi-face mode exposes its instability notice as a tooltip");
|
||||
[view.controller setValue:.6 forParameter:PGMicroDetail];
|
||||
NSButton *master=(NSButton *)FindControl(view,NSButton.class,@"磨皮");master.state=NSControlStateValueOff;Fire(master);
|
||||
PGBeautySettings s=PGSettingsWithInspectorState(PGBeautyDefaults(),view.controller.state);
|
||||
Check(s.microDetail==0 && s.midtoneDetail==0 && s.highPassDetail==0 && Near(s.skinHue,12) && Near(s.skinBrightness,.35),@"actual smoothing master event disables new detail processing independently from whitening");
|
||||
master.state=NSControlStateValueOn;Fire(master);
|
||||
Check(Near(PGSettingsWithInspectorState(PGBeautyDefaults(),view.controller.state).microDetail,.6),@"reenabling the master restores the stored micro-detail amount");
|
||||
Check(api.actionDepth==0 && api.actionStarts==api.actionEnds && api.accessViolations==0,@"all new numeric, bypass, reset and master interactions use balanced host actions");
|
||||
}
|
||||
static void HostHandshakeTests(void) {
|
||||
PGInspectorTestAPI *api=[PGInspectorTestAPI new];beatygrilPlugIn *plugin=[[beatygrilPlugIn alloc]initWithAPIManager:api];[plugin addParametersWithError:nil];
|
||||
api.values[@(PGMidtone)]=@.5;
|
||||
@@ -219,24 +296,45 @@ static void ContinuousPreviewTests(void) {
|
||||
PGInspectorTestAPI *api=[PGInspectorTestAPI new];beatygrilPlugIn *plugin=[[beatygrilPlugIn alloc]initWithAPIManager:api];[plugin addParametersWithError:nil];
|
||||
api.parameterAPIsRequireAction=YES;api.actionRequiresView=YES;
|
||||
PGInspectorView *view=(PGInspectorView *)[plugin createViewForParameterID:PGInspectorPanel];[view refreshFromHost];
|
||||
NSSlider *slider=(NSSlider *)FindControl(view,NSSlider.class,@"短脸");
|
||||
void (^began)(void)=[slider valueForKey:@"interactionBegan"], (^ended)(void)=[slider valueForKey:@"interactionEnded"];
|
||||
Check(slider.continuous && began && ended,@"native slider emits continuous events and exposes balanced gesture callbacks");
|
||||
began();
|
||||
for(NSNumber *value in @[@.2,@.5,@1]) {
|
||||
NSUInteger publications=api.previewPublications;slider.doubleValue=value.doubleValue;Fire(slider);
|
||||
PGInspectorState *visible=api.previewValues[@(PGInspectorPanel)];
|
||||
Check(api.actionDepth==0 && api.previewPublications==publications+1 && Near([visible valueForParameter:PGShort],value.doubleValue),@"every drag step publishes a completed host snapshot before mouse-up");
|
||||
Check(visible && Near(PGSettingsWithInspectorState(PGBeautyDefaults(),visible).faceShort,value.doubleValue),@"published frame settings contain each intermediate short-face value while gesture is still open");
|
||||
NSArray *names=@[@"短脸",@"五官保护",@"非肤色区保护",@"微细锐化"],*ids=@[@(PGShort),@(PGFeatureProtectionAmount),@(PGNonSkinProtection),@(PGMicroDetail)];
|
||||
for(NSUInteger i=0;i<names.count;i++) {
|
||||
UInt32 key=[ids[i]unsignedIntValue];NSSlider *slider=(NSSlider *)FindControl(view,NSSlider.class,names[i]);
|
||||
void (^began)(void)=[slider valueForKey:@"interactionBegan"], (^ended)(void)=[slider valueForKey:@"interactionEnded"];
|
||||
Check(slider.continuous && began && ended,@"native slider emits continuous events and exposes balanced gesture callbacks");
|
||||
began();
|
||||
for(NSNumber *value in @[@.2,@.5,@1]) {
|
||||
NSUInteger publications=api.previewPublications;slider.doubleValue=value.doubleValue;Fire(slider);
|
||||
PGInspectorState *visible=api.previewValues[@(PGInspectorPanel)];
|
||||
Check(api.actionDepth==0 && api.previewPublications==publications+1 && Near([visible valueForParameter:key],value.doubleValue),@"every old and new slider step publishes a completed host snapshot before mouse-up");
|
||||
PGBeautySettings s=PGSettingsWithInspectorState(PGBeautyDefaults(),visible);
|
||||
double actual=key==PGShort?s.faceShort:key==PGFeatureProtectionAmount?s.featureProtectionAmount:key==PGNonSkinProtection?s.nonSkinProtection:s.microDetail;
|
||||
Check(visible && Near(actual,value.doubleValue),@"published frame settings contain each intermediate new protection/detail value while gesture is still open");
|
||||
}
|
||||
NSUInteger publications=api.previewPublications;Fire(slider);
|
||||
Check(api.previewPublications==publications,@"unchanged drag events do not enqueue redundant previews");
|
||||
ended();
|
||||
}
|
||||
NSUInteger publications=api.previewPublications;Fire(slider);
|
||||
Check(api.previewPublications==publications,@"unchanged drag events do not enqueue redundant previews");
|
||||
ended();
|
||||
Check(api.actionDepth==0 && api.actionStarts==api.actionEnds && api.accessViolations==0 && api.invalidActionSenders==0,@"step-wise publication preserves action-scoped APIs and the actual view sender");
|
||||
}
|
||||
static void OptionalInspectorPreview(void) {
|
||||
NSString *path=NSProcessInfo.processInfo.environment[@"PAINTFCP_INSPECTOR_PNG"];
|
||||
if(!path.length)return;
|
||||
PGHostAPI *api=[PGHostAPI new];beatygrilPlugIn *plugin=[[beatygrilPlugIn alloc]initWithAPIManager:api];[plugin addParametersWithError:nil];
|
||||
api.values[@(PGMidtone)]=@.4;
|
||||
PGInspectorView *view=(PGInspectorView *)[plugin createViewForParameterID:PGInspectorPanel];[view refreshFromHost];
|
||||
[view setFrameSize:NSMakeSize(380,20)];
|
||||
NSWindow *window=[[NSWindow alloc]initWithContentRect:view.bounds styleMask:NSWindowStyleMaskBorderless backing:NSBackingStoreBuffered defer:NO];
|
||||
window.releasedWhenClosed=NO;window.appearance=[NSAppearance appearanceNamed:NSAppearanceNameDarkAqua];window.contentView=view;
|
||||
[window setContentSize:view.frame.size];[view layoutSubtreeIfNeeded];[window displayIfNeeded];
|
||||
NSBitmapImageRep *bitmap=[view bitmapImageRepForCachingDisplayInRect:view.bounds];
|
||||
[view cacheDisplayInRect:view.bounds toBitmapImageRep:bitmap];
|
||||
NSData *png=[bitmap representationUsingType:NSBitmapImageFileTypePNG properties:@{}];
|
||||
Check(png.length>0 && [png writeToFile:path atomically:YES],@"optional offscreen inspector PNG is written to the requested review path");
|
||||
[window close];
|
||||
}
|
||||
int main(int argc,const char **argv) {@autoreleasepool {
|
||||
[NSApplication sharedApplication];[NSApp setActivationPolicy:NSApplicationActivationPolicyProhibited];
|
||||
RegistrationTests();ModelTests();ControllerTests();ViewInteractionTests();HostHandshakeTests();ContinuousPreviewTests();
|
||||
RegistrationTests();ModelTests();BeautyControlModelTests();ControllerTests();ViewInteractionTests();BeautyControlInteractionTests();HostHandshakeTests();ContinuousPreviewTests();OptionalInspectorPreview();
|
||||
if(argc>2) {
|
||||
NSData *inner=[NSData dataWithContentsOfFile:@(argv[1])],*outer=[NSData dataWithContentsOfFile:@(argv[2])];NSError *error=nil;
|
||||
NSKeyedUnarchiver *reader=[[NSKeyedUnarchiver alloc]initForReadingFromData:inner error:&error];reader.requiresSecureCoding=YES;
|
||||
|
||||
在新工单中引用
屏蔽一个用户