@pilio/gemini-watermark-remover 1.0.24 → 1.0.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@pilio/gemini-watermark-remover",
3
3
  "description": "Automatically removes watermarks from Gemini AI generated images",
4
- "version": "1.0.24",
4
+ "version": "1.0.26",
5
5
  "author": "GargantuaX",
6
6
  "sideEffects": false,
7
7
  "files": [
@@ -96,7 +96,8 @@
96
96
  "release:readiness": "node scripts/create-release-readiness-report.js",
97
97
  "release:quality-gate": "pnpm compare:allenk-v2 -- --fail-on-incomplete && pnpm release:readiness -- --fail-on-not-ready",
98
98
  "release:goal-audit": "node scripts/create-release-goal-audit-report.js",
99
- "release:preflight": "pnpm test && pnpm build && pnpm package:extension && pnpm release:quality-gate && pnpm release:goal-audit -- --fail-on-incomplete",
99
+ "release:ci-check": "node scripts/check-github-ci.js --workflow ci.yml --commit HEAD --fail-closed",
100
+ "release:preflight": "pnpm test && pnpm build && pnpm package:extension && pnpm release:quality-gate && pnpm release:goal-audit -- --fail-on-incomplete && pnpm release:ci-check",
100
101
  "visible-residual:loop": "node scripts/run-visible-residual-loop.js",
101
102
  "visible-residual:verify": "node scripts/verify-visible-residual-loop.js",
102
103
  "visible-residual:validate-human-review": "node scripts/validate-visible-residual-human-review.js",
@@ -108,6 +109,10 @@
108
109
  "visible-residual:goal-audit": "node scripts/create-visible-residual-goal-audit-report.js",
109
110
  "visible-residual:cluster-report": "node scripts/create-visible-residual-cluster-report.js",
110
111
  "visible-residual:geometry-audit": "node scripts/audit-visible-residual-geometry.js",
112
+ "review:metric-mismatch": "node scripts/create-metric-mismatch-review-pack.js",
113
+ "probe:metric-48-alpha-profile": "node scripts/probe-metric-48-96-96-alpha-profile.js",
114
+ "probe:metric-48-localization": "node scripts/probe-metric-48-96-96-localization.js",
115
+ "review:metric-48-taxonomy": "node scripts/create-metric-48-96-96-failure-taxonomy.js",
111
116
  "scan:sample-watermarks": "node scripts/scan-sample-watermarks.js",
112
117
  "render:skipped-crops": "node scripts/render-skipped-candidate-crops.js",
113
118
  "render:video-crops": "node scripts/render-video-crop-sheet.js",
@@ -22,6 +22,7 @@ import {
22
22
  import {
23
23
  buildRankingKey,
24
24
  compareRankingKey,
25
+ scoreBalancedVisualCandidate,
25
26
  scoreDamage,
26
27
  scoreOriginalEvidence,
27
28
  scoreResidual,
@@ -76,6 +77,13 @@ const STANDARD_TEXT_OVERLAP_MIN_GRADIENT_SCORE = 0.18;
76
77
  const STANDARD_TEXT_OVERLAP_MIN_IMPROVEMENT = 0.25;
77
78
  const STANDARD_TEXT_OVERLAP_MAX_RESIDUAL = 0.1;
78
79
  const STANDARD_TEXT_OVERLAP_MIN_GRADIENT_DROP = 0.1;
80
+ const STANDARD_TEXT_OVERLAP_WEAK_ALPHA_MIN_ORIGINAL_SPATIAL_SCORE = 0.12;
81
+ const STANDARD_TEXT_OVERLAP_WEAK_ALPHA_MIN_ORIGINAL_GRADIENT_SCORE = 0.06;
82
+ const STANDARD_TEXT_OVERLAP_WEAK_ALPHA_MIN_IMPROVEMENT = 0.08;
83
+ const STANDARD_TEXT_OVERLAP_WEAK_ALPHA_MAX_SPATIAL_RESIDUAL = 0.08;
84
+ const STANDARD_TEXT_OVERLAP_WEAK_ALPHA_MAX_GRADIENT_RESIDUAL = 0.08;
85
+ const STANDARD_TEXT_OVERLAP_WEAK_ALPHA_MAX_NEAR_BLACK_INCREASE = 0.005;
86
+ const STANDARD_TEXT_OVERLAP_WEAK_ALPHA_MAX_TEXTURE = 0.05;
79
87
  const STANDARD_VALIDATION_MIN_ORIGINAL_SPATIAL_SCORE = 0.05;
80
88
  const STANDARD_VALIDATION_MIN_ORIGINAL_GRADIENT_SCORE = 0.12;
81
89
  const STANDARD_CONSERVATIVE_CATALOG_PREFERRED_ALPHA_GAIN = 0.55;
@@ -114,6 +122,14 @@ const STANDARD_STRONG_SIGNAL_NEAR_BLACK_OVERRIDE_MAX_GRADIENT = 0.32;
114
122
  const STANDARD_STRONG_SIGNAL_NEAR_BLACK_OVERRIDE_MIN_IMPROVEMENT = 0.7;
115
123
  const STANDARD_STRONG_SIGNAL_NEAR_BLACK_OVERRIDE_MIN_GRADIENT_DROP = 0.6;
116
124
  const STANDARD_STRONG_SIGNAL_NEAR_BLACK_OVERRIDE_MAX_NEAR_BLACK_INCREASE = 0.4;
125
+ const BOTTOM_RIGHT_48_EVIDENCE_DOMINANCE_MIN_SPATIAL = 0.35;
126
+ const BOTTOM_RIGHT_48_EVIDENCE_DOMINANCE_MIN_GRADIENT = 0.16;
127
+ const BOTTOM_RIGHT_48_EVIDENCE_DOMINANCE_SPATIAL_ADVANTAGE = 0.18;
128
+ const BOTTOM_RIGHT_48_EVIDENCE_DOMINANCE_GRADIENT_ADVANTAGE = 0.12;
129
+ const BOTTOM_RIGHT_48_EVIDENCE_DOMINANCE_MAX_RESIDUAL_DELTA = 0.08;
130
+ const DARK_POLARITY_CATALOG_MIN_ORIGINAL_SPATIAL = 0.12;
131
+ const DARK_POLARITY_CATALOG_MIN_ORIGINAL_GRADIENT = 0.08;
132
+ const DARK_POLARITY_CATALOG_MAX_TEXTURE_FOR_WEAK_EVIDENCE = 0.25;
117
133
  const TEMPLATE_ALIGN_SHIFTS = [-0.5, -0.25, 0, 0.25, 0.5];
118
134
  const TEMPLATE_ALIGN_SCALES = [0.99, 1, 1.01];
119
135
  const STANDARD_NEARBY_SHIFTS = [-12, -8, -4, 0, 4, 8, 12];
@@ -137,15 +153,16 @@ const PREVIEW_ANCHOR_MIN_SCORE = 0.2;
137
153
  const PREVIEW_ANCHOR_LOCAL_DELTAS = [-1, 0, 1];
138
154
  const PREVIEW_TEMPLATE_ALIGN_SHIFTS = [-1, -0.5, 0, 0.5, 1];
139
155
  const PREVIEW_TEMPLATE_ALIGN_SCALES = [0.985, 1, 1.015];
140
- const PREVIEW_ANCHOR_GAIN_SKIP_RESIDUAL_THRESHOLD = 0.22;
156
+ const PREVIEW_ANCHOR_GAIN_SKIP_RESIDUAL_THRESHOLD = 0.24;
141
157
  const PREVIEW_ANCHOR_GAIN_SKIP_GRADIENT_THRESHOLD = 0.24;
142
- const CORE_ALPHA_PRIORITY_GAINS = Object.freeze([0.6, 1, 1.15, 1.3, 0.45, 0.7, 0.85, 0.55]);
158
+ const CORE_ALPHA_PRIORITY_GAINS = Object.freeze([0.6, 1, 1.1, 1.15, 1.3, 0.45, 0.7, 0.85, 0.55]);
143
159
  const CURRENT_LARGE_MARGIN_ULTRA_WEAK_ALPHA_GAINS = Object.freeze([0.25, 0.3, 0.35, 0.4]);
144
160
  const STANDARD_ANCHOR_WEAK_ALPHA_RESCUE_GAINS = Object.freeze([0.55, 0.7, 0.85]);
145
161
  const STANDARD_ANCHOR_WEAK_RESCUE_MAX_SPATIAL = 0.35;
146
- const STANDARD_ANCHOR_WEAK_RESCUE_MAX_GRADIENT = 0.18;
162
+ const STANDARD_ANCHOR_WEAK_RESCUE_MAX_GRADIENT = 0.24;
147
163
  const STANDARD_ANCHOR_WEAK_RESCUE_MIN_IMPROVEMENT = 0.08;
148
164
  const STANDARD_ANCHOR_WEAK_RESCUE_MAX_NEAR_BLACK_INCREASE = 0.02;
165
+ const STANDARD_ANCHOR_WEAK_RESCUE_MIN_BALANCED_ADVANTAGE = 0.03;
149
166
  const CURRENT_LARGE_MARGIN_WEAK_RESCUE_MAX_SPATIAL = 0.22;
150
167
  const CURRENT_LARGE_MARGIN_WEAK_RESCUE_MAX_GRADIENT = 0.36;
151
168
  const CURRENT_LARGE_MARGIN_WEAK_RESCUE_MIN_ORIGINAL_SPATIAL = 0.17;
@@ -595,6 +612,35 @@ function isWeakAlphaRescueCandidate(seed, trial) {
595
612
  trial.texturePenalty <= 0.25;
596
613
  }
597
614
 
615
+ function isStandardTextOverlapWeakAlphaCandidate(seed, trial) {
616
+ if (!trial?.accepted || trial.alphaGain >= 1 || trial.hardReject === true) {
617
+ return false;
618
+ }
619
+ if (
620
+ seed?.config?.logoSize !== 96 ||
621
+ seed.config.marginRight !== 64 ||
622
+ seed.config.marginBottom !== 64
623
+ ) {
624
+ return false;
625
+ }
626
+
627
+ return trial.originalSpatialScore >= STANDARD_TEXT_OVERLAP_WEAK_ALPHA_MIN_ORIGINAL_SPATIAL_SCORE &&
628
+ trial.originalGradientScore >= STANDARD_TEXT_OVERLAP_WEAK_ALPHA_MIN_ORIGINAL_GRADIENT_SCORE &&
629
+ trial.improvement >= STANDARD_TEXT_OVERLAP_WEAK_ALPHA_MIN_IMPROVEMENT &&
630
+ Math.abs(trial.processedSpatialScore) <= STANDARD_TEXT_OVERLAP_WEAK_ALPHA_MAX_SPATIAL_RESIDUAL &&
631
+ Math.max(0, trial.processedGradientScore) <= STANDARD_TEXT_OVERLAP_WEAK_ALPHA_MAX_GRADIENT_RESIDUAL &&
632
+ trial.nearBlackIncrease <= STANDARD_TEXT_OVERLAP_WEAK_ALPHA_MAX_NEAR_BLACK_INCREASE &&
633
+ trial.texturePenalty <= STANDARD_TEXT_OVERLAP_WEAK_ALPHA_MAX_TEXTURE;
634
+ }
635
+
636
+ function tagCandidateSource(candidate, tag) {
637
+ if (!candidate || String(candidate.source).includes(tag)) return candidate;
638
+ return {
639
+ ...candidate,
640
+ source: `${candidate.source}+${tag}`
641
+ };
642
+ }
643
+
598
644
  function shouldPreferLargeMarginGradientClearance(currentBest, candidate) {
599
645
  if (!isCurrentLargeMarginCatalogCandidate(currentBest) || !isCurrentLargeMarginCatalogCandidate(candidate)) {
600
646
  return false;
@@ -640,6 +686,20 @@ function pickBetterWeakAlphaRescueCandidate(seed, currentBest, candidate) {
640
686
  return pickBetterCandidate(currentBest, candidate, 0.002);
641
687
  }
642
688
 
689
+ function shouldPreferWeakAlphaRescueOverAccepted(acceptedTrial, rescueTrial) {
690
+ if (!acceptedTrial?.accepted || !rescueTrial?.accepted) return false;
691
+ if (rescueTrial.alphaGain >= 1) return false;
692
+
693
+ const acceptedCost = Number(acceptedTrial.validationCost);
694
+ const rescueCost = Number(rescueTrial.validationCost);
695
+ if (!Number.isFinite(acceptedCost) || !Number.isFinite(rescueCost)) return false;
696
+
697
+ return rescueCost <= acceptedCost - STANDARD_ANCHOR_WEAK_RESCUE_MIN_BALANCED_ADVANTAGE &&
698
+ Math.abs(rescueTrial.processedSpatialScore) <= Math.abs(acceptedTrial.processedSpatialScore) &&
699
+ Math.max(0, rescueTrial.processedGradientScore) <= STANDARD_ANCHOR_WEAK_RESCUE_MAX_GRADIENT &&
700
+ rescueTrial.nearBlackIncrease <= STANDARD_ANCHOR_WEAK_RESCUE_MAX_NEAR_BLACK_INCREASE;
701
+ }
702
+
643
703
  function evaluateStandardTrialForSeed({
644
704
  originalImageData,
645
705
  seed,
@@ -669,6 +729,13 @@ function evaluateStandardTrialForSeed({
669
729
  fallbackTrial = trial;
670
730
  }
671
731
  if (alphaGain < 1) {
732
+ if (isStandardTextOverlapWeakAlphaCandidate(seed, trial)) {
733
+ bestWeakAlphaPriorityTrial = pickBetterCandidate(
734
+ bestWeakAlphaPriorityTrial,
735
+ tagCandidateSource(trial, 'text-overlap'),
736
+ 0.002
737
+ );
738
+ }
672
739
  if (
673
740
  isWeakAlphaPrioritySeed(seed) &&
674
741
  isCleanWeakAlphaPriorityCandidate(trial) &&
@@ -688,7 +755,14 @@ function evaluateStandardTrialForSeed({
688
755
 
689
756
  return isWeakAlphaPrioritySeed(seed)
690
757
  ? bestWeakAlphaPriorityTrial ?? bestWeakAlphaRescueTrial ?? bestAcceptedTrial ?? fallbackTrial
691
- : bestWeakAlphaPriorityTrial ?? bestAcceptedTrial ?? bestWeakAlphaRescueTrial ?? fallbackTrial;
758
+ : bestWeakAlphaPriorityTrial ??
759
+ (
760
+ shouldPreferWeakAlphaRescueOverAccepted(bestAcceptedTrial, bestWeakAlphaRescueTrial)
761
+ ? bestWeakAlphaRescueTrial
762
+ : bestAcceptedTrial
763
+ ) ??
764
+ bestWeakAlphaRescueTrial ??
765
+ fallbackTrial;
692
766
  }
693
767
 
694
768
  export function evaluateRestorationCandidate({
@@ -807,9 +881,16 @@ export function evaluateRestorationCandidate({
807
881
  catalogEvidenceGate !== 'medium' ||
808
882
  originalScores.spatialScore >= 0.15 ||
809
883
  originalScores.gradientScore >= 0.08;
884
+ const darkPolarityCatalogEvidenceAllowed =
885
+ provenance?.darkPolarity !== true ||
886
+ provenance?.catalogVariant !== true ||
887
+ originalScores.spatialScore >= DARK_POLARITY_CATALOG_MIN_ORIGINAL_SPATIAL ||
888
+ originalScores.gradientScore >= DARK_POLARITY_CATALOG_MIN_ORIGINAL_GRADIENT ||
889
+ texturePenalty <= DARK_POLARITY_CATALOG_MAX_TEXTURE_FOR_WEAK_EVIDENCE;
810
890
  const accepted =
811
891
  originalEvidenceAllowed &&
812
892
  catalogEvidenceAllowed &&
893
+ darkPolarityCatalogEvidenceAllowed &&
813
894
  (
814
895
  hardRejectAllowed ||
815
896
  conservativeCatalogHardRejectAllowed ||
@@ -838,6 +919,13 @@ export function evaluateRestorationCandidate({
838
919
  processedGradient: processedScores.gradientScore,
839
920
  suppressionGain: improvement
840
921
  });
922
+ const balancedVisual = scoreBalancedVisualCandidate({
923
+ processedSpatial: processedScores.spatialScore,
924
+ processedGradient: processedScores.gradientScore,
925
+ nearBlackIncrease,
926
+ texturePenalty,
927
+ gradientIncrease
928
+ });
841
929
  const hardRejectBypassed =
842
930
  conservativeCatalogHardRejectAllowed ||
843
931
  visibleCatalogHardRejectAllowed ||
@@ -893,13 +981,10 @@ export function evaluateRestorationCandidate({
893
981
  originalEvidence,
894
982
  residual,
895
983
  damage,
896
- validationCost:
897
- Math.abs(processedScores.spatialScore) +
898
- Math.max(0, processedScores.gradientScore) * 0.6 +
899
- Math.max(0, nearBlackIncrease) * 3 +
900
- texturePenalty
901
- };
902
- }
984
+ balancedVisual,
985
+ validationCost: balancedVisual.score
986
+ };
987
+ }
903
988
 
904
989
  function pickBestValidatedCandidate(candidates) {
905
990
  const accepted = candidates.filter((candidate) => candidate?.accepted);
@@ -1038,12 +1123,36 @@ export function pickBetterCandidate(currentBest, candidate, minCostDelta = 0.005
1038
1123
  if (shouldPreserveCatalogOriginalSignal(currentBest, candidate)) {
1039
1124
  return currentBest;
1040
1125
  }
1126
+ if (shouldPreserveDominantBottomRight48AgainstWeakStandard(currentBest, candidate)) {
1127
+ return currentBest;
1128
+ }
1129
+ if (shouldPreserveDominantBottomRight48AgainstWeakStandard(candidate, currentBest)) {
1130
+ return candidate;
1131
+ }
1041
1132
  if (shouldPreserveStrongCanonical96AgainstWeakCurrentLargeMargin(currentBest, candidate)) {
1042
1133
  return currentBest;
1043
1134
  }
1044
1135
  if (shouldPreserveStrongCanonical96AgainstWeakCurrentLargeMargin(candidate, currentBest)) {
1045
1136
  return candidate;
1046
1137
  }
1138
+ if (shouldPreserveStrongCanonical96AgainstWeakNewMargin(currentBest, candidate)) {
1139
+ return currentBest;
1140
+ }
1141
+ if (shouldPreserveStrongCanonical96AgainstWeakNewMargin(candidate, currentBest)) {
1142
+ return candidate;
1143
+ }
1144
+ if (shouldPreserveStandardAlphaCanonical96AgainstDarkGain(currentBest, candidate)) {
1145
+ return currentBest;
1146
+ }
1147
+ if (shouldPreserveStandardAlphaCanonical96AgainstDarkGain(candidate, currentBest)) {
1148
+ return candidate;
1149
+ }
1150
+ if (shouldPreserveBalancedAlphaCanonical96(currentBest, candidate)) {
1151
+ return currentBest;
1152
+ }
1153
+ if (shouldPreserveBalancedAlphaCanonical96(candidate, currentBest)) {
1154
+ return candidate;
1155
+ }
1047
1156
  if (shouldPreferCatalogOriginalSignal(candidate, currentBest)) {
1048
1157
  return candidate;
1049
1158
  }
@@ -1146,6 +1255,51 @@ function isCurrentLargeMarginCandidate(candidate) {
1146
1255
  candidate.config.marginBottom === 96;
1147
1256
  }
1148
1257
 
1258
+ function isBottomRight48Candidate(candidate) {
1259
+ return candidate?.config?.logoSize === 48 &&
1260
+ candidate.config.marginRight === 32 &&
1261
+ candidate.config.marginBottom === 32 &&
1262
+ candidate?.provenance?.localShift !== true &&
1263
+ candidate?.provenance?.sizeJitter !== true &&
1264
+ candidate?.provenance?.previewAnchor !== true;
1265
+ }
1266
+
1267
+ function isWeakCompetingStandardAnchor(candidate) {
1268
+ return isStandardCandidateSource(candidate) &&
1269
+ candidate?.provenance?.localShift !== true &&
1270
+ candidate?.provenance?.sizeJitter !== true &&
1271
+ candidate?.provenance?.previewAnchor !== true &&
1272
+ !isBottomRight48Candidate(candidate);
1273
+ }
1274
+
1275
+ function shouldPreserveDominantBottomRight48AgainstWeakStandard(bottomRightCandidate, competingCandidate) {
1276
+ if (!isBottomRight48Candidate(bottomRightCandidate)) return false;
1277
+ if (!isWeakCompetingStandardAnchor(competingCandidate)) return false;
1278
+
1279
+ const bottomSpatial = Number(bottomRightCandidate.originalSpatialScore);
1280
+ const bottomGradient = Number(bottomRightCandidate.originalGradientScore);
1281
+ const largeSpatial = Number(competingCandidate.originalSpatialScore);
1282
+ const largeGradient = Number(competingCandidate.originalGradientScore);
1283
+ const bottomResidual = Number(bottomRightCandidate.residual?.score);
1284
+ const largeResidual = Number(competingCandidate.residual?.score);
1285
+ if (
1286
+ !Number.isFinite(bottomSpatial) ||
1287
+ !Number.isFinite(bottomGradient) ||
1288
+ !Number.isFinite(largeSpatial) ||
1289
+ !Number.isFinite(largeGradient) ||
1290
+ !Number.isFinite(bottomResidual) ||
1291
+ !Number.isFinite(largeResidual)
1292
+ ) {
1293
+ return false;
1294
+ }
1295
+
1296
+ return bottomSpatial >= BOTTOM_RIGHT_48_EVIDENCE_DOMINANCE_MIN_SPATIAL &&
1297
+ bottomGradient >= BOTTOM_RIGHT_48_EVIDENCE_DOMINANCE_MIN_GRADIENT &&
1298
+ bottomSpatial >= largeSpatial + BOTTOM_RIGHT_48_EVIDENCE_DOMINANCE_SPATIAL_ADVANTAGE &&
1299
+ bottomGradient >= largeGradient + BOTTOM_RIGHT_48_EVIDENCE_DOMINANCE_GRADIENT_ADVANTAGE &&
1300
+ bottomResidual <= largeResidual + BOTTOM_RIGHT_48_EVIDENCE_DOMINANCE_MAX_RESIDUAL_DELTA;
1301
+ }
1302
+
1149
1303
  function isPreviewAnchorCandidate(candidate) {
1150
1304
  return candidate?.provenance?.previewAnchor === true;
1151
1305
  }
@@ -1198,6 +1352,16 @@ function isDefault96GeometryCandidate(candidate) {
1198
1352
  candidate.config.marginBottom === 64;
1199
1353
  }
1200
1354
 
1355
+ function isNewMargin96Candidate(candidate) {
1356
+ return isStandardCandidateSource(candidate) &&
1357
+ candidate?.provenance?.localShift !== true &&
1358
+ candidate?.provenance?.sizeJitter !== true &&
1359
+ candidate?.provenance?.previewAnchor !== true &&
1360
+ candidate?.config?.logoSize === 96 &&
1361
+ candidate.config.marginRight === 192 &&
1362
+ candidate.config.marginBottom === 192;
1363
+ }
1364
+
1201
1365
  function shouldPreserveStrongCanonical96AgainstWeakCurrentLargeMargin(currentBest, candidate) {
1202
1366
  if (!isCanonicalDefault96Candidate(currentBest)) return false;
1203
1367
  if (!isCurrentLargeMarginCatalogCandidate(candidate)) return false;
@@ -1238,6 +1402,111 @@ function shouldPreserveStrongCanonical96AgainstWeakCurrentLargeMargin(currentBes
1238
1402
  candidateHasWeakOriginalSignal;
1239
1403
  }
1240
1404
 
1405
+ function shouldPreserveStrongCanonical96AgainstWeakNewMargin(currentBest, candidate) {
1406
+ if (!isCanonicalDefault96Candidate(currentBest)) return false;
1407
+ if (!isNewMargin96Candidate(candidate)) return false;
1408
+
1409
+ const currentSpatial = Number(currentBest.originalSpatialScore);
1410
+ const currentGradient = Number(currentBest.originalGradientScore);
1411
+ const candidateSpatial = Number(candidate.originalSpatialScore);
1412
+ const candidateGradient = Number(candidate.originalGradientScore);
1413
+ if (
1414
+ !Number.isFinite(currentSpatial) ||
1415
+ !Number.isFinite(currentGradient) ||
1416
+ !Number.isFinite(candidateSpatial) ||
1417
+ !Number.isFinite(candidateGradient)
1418
+ ) {
1419
+ return false;
1420
+ }
1421
+
1422
+ const currentHasStrongCanonicalSignal =
1423
+ currentSpatial >= 0.55 &&
1424
+ currentGradient >= 0.2;
1425
+ const candidateHasStrongNewMarginSignal =
1426
+ candidateSpatial >= 0.55 &&
1427
+ candidateGradient >= 0.3;
1428
+ const candidateHasWeakOrMuchWeakerSignal =
1429
+ candidateSpatial < STANDARD_VALIDATION_MIN_ORIGINAL_SPATIAL_SCORE ||
1430
+ candidateGradient < STANDARD_VALIDATION_MIN_ORIGINAL_GRADIENT_SCORE ||
1431
+ (
1432
+ candidateSpatial <= currentSpatial - STRONG_ORIGINAL_SIGNAL_SPATIAL_ADVANTAGE &&
1433
+ candidateGradient <= currentGradient - STRONG_ORIGINAL_SIGNAL_GRADIENT_ADVANTAGE
1434
+ );
1435
+
1436
+ return currentHasStrongCanonicalSignal &&
1437
+ !candidateHasStrongNewMarginSignal &&
1438
+ candidateHasWeakOrMuchWeakerSignal;
1439
+ }
1440
+
1441
+ function isBalancedCanonical96AlphaGain(candidate) {
1442
+ const alphaGain = Number(candidate?.alphaGain);
1443
+ return Number.isFinite(alphaGain) && alphaGain >= 1 && alphaGain <= 1.1;
1444
+ }
1445
+
1446
+ function shouldPreserveStandardAlphaCanonical96AgainstDarkGain(currentBest, candidate) {
1447
+ if (!sameCandidateAnchor(currentBest, candidate)) return false;
1448
+ if (!isCanonicalDefault96Candidate(currentBest)) return false;
1449
+
1450
+ const currentAlphaGain = Number(currentBest?.alphaGain);
1451
+ const candidateAlphaGain = Number(candidate?.alphaGain);
1452
+ if (
1453
+ !Number.isFinite(currentAlphaGain) ||
1454
+ !Number.isFinite(candidateAlphaGain) ||
1455
+ currentAlphaGain !== 1 ||
1456
+ candidateAlphaGain <= 1 ||
1457
+ candidate?.tooDark !== true
1458
+ ) {
1459
+ return false;
1460
+ }
1461
+
1462
+ const originalSpatial = Number(currentBest.originalSpatialScore);
1463
+ const originalGradient = Number(currentBest.originalGradientScore);
1464
+ const currentSpatial = Number(currentBest.processedSpatialScore);
1465
+ const currentGradient = Number(currentBest.processedGradientScore);
1466
+ if (
1467
+ !Number.isFinite(originalSpatial) ||
1468
+ !Number.isFinite(originalGradient) ||
1469
+ !Number.isFinite(currentSpatial) ||
1470
+ !Number.isFinite(currentGradient)
1471
+ ) {
1472
+ return false;
1473
+ }
1474
+
1475
+ return originalSpatial >= 0.55 &&
1476
+ originalGradient >= 0.2 &&
1477
+ currentSpatial >= 0 &&
1478
+ currentSpatial <= 0.35 &&
1479
+ Math.max(0, currentGradient) <= 0.08;
1480
+ }
1481
+
1482
+ function shouldPreserveBalancedAlphaCanonical96(currentBest, candidate) {
1483
+ if (!sameCandidateAnchor(currentBest, candidate)) return false;
1484
+ if (!isCanonicalDefault96Candidate(currentBest)) return false;
1485
+ if (!isBalancedCanonical96AlphaGain(currentBest)) return false;
1486
+
1487
+ const candidateAlphaGain = Number(candidate?.alphaGain);
1488
+ if (!Number.isFinite(candidateAlphaGain) || candidateAlphaGain <= 1.1) return false;
1489
+
1490
+ const originalSpatial = Number(currentBest.originalSpatialScore);
1491
+ const originalGradient = Number(currentBest.originalGradientScore);
1492
+ const currentSpatial = Number(currentBest.processedSpatialScore);
1493
+ const currentGradient = Number(currentBest.processedGradientScore);
1494
+ if (
1495
+ !Number.isFinite(originalSpatial) ||
1496
+ !Number.isFinite(originalGradient) ||
1497
+ !Number.isFinite(currentSpatial) ||
1498
+ !Number.isFinite(currentGradient)
1499
+ ) {
1500
+ return false;
1501
+ }
1502
+
1503
+ return originalSpatial >= 0.55 &&
1504
+ originalGradient >= 0.2 &&
1505
+ currentSpatial >= 0 &&
1506
+ currentSpatial <= 0.22 &&
1507
+ Math.max(0, currentGradient) <= 0.1;
1508
+ }
1509
+
1241
1510
  function hasWeakDriftEvidence(candidate) {
1242
1511
  const candidateSpatial = Number(candidate?.originalSpatialScore);
1243
1512
  const candidateGradient = Number(candidate?.originalGradientScore);
@@ -26,6 +26,24 @@ const RESIDUAL_VISIBILITY_OUTER_MARGIN = 4;
26
26
  const RESIDUAL_VISIBILITY_POSITIVE_HALO_LUM_THRESHOLD = 6;
27
27
  const RESIDUAL_VISIBILITY_GRADIENT_THRESHOLD = 0.22;
28
28
  const RESIDUAL_VISIBILITY_SPATIAL_THRESHOLD = 0.18;
29
+ const FLAT_CLIPPED_METRIC_RISK_NEAR_BLACK_RATIO = 0.92;
30
+ const FLAT_CLIPPED_METRIC_RISK_NEWLY_CLIPPED_RATIO = 0.18;
31
+ const FLAT_CLIPPED_METRIC_RISK_MAX_POSITIVE_HALO_LUM = 2;
32
+ const FLAT_CLIPPED_METRIC_RISK_MIN_NEGATIVE_SPATIAL = 0.18;
33
+ const POSITIVE_SPATIAL_BACKGROUND_COLLISION_MIN_SPATIAL = 0.14;
34
+ const POSITIVE_SPATIAL_BACKGROUND_COLLISION_MAX_GRADIENT = 0.12;
35
+ const POSITIVE_SPATIAL_BACKGROUND_COLLISION_MAX_POSITIVE_HALO_LUM = 32;
36
+ const POSITIVE_SPATIAL_BACKGROUND_COLLISION_MAX_ARTIFACT_COST = 0.12;
37
+ const WEAK_HALO_BACKGROUND_COLLISION_MAX_ABS_SPATIAL = 0.14;
38
+ const WEAK_HALO_BACKGROUND_COLLISION_MAX_GRADIENT = 0.12;
39
+ const WEAK_HALO_BACKGROUND_COLLISION_MAX_POSITIVE_HALO_LUM = 16;
40
+ const WEAK_HALO_BACKGROUND_COLLISION_MAX_ARTIFACT_COST = 0.12;
41
+ const WEAK_HALO_BACKGROUND_COLLISION_MAX_NEAR_BLACK_RATIO = 0.25;
42
+ const WEAK_HALO_BACKGROUND_COLLISION_MAX_NEWLY_CLIPPED_RATIO = 0.02;
43
+ const STRUCTURED_EDGE_BACKGROUND_COLLISION_MIN_GRADIENT = 0.2;
44
+ const STRUCTURED_EDGE_BACKGROUND_COLLISION_MIN_ARTIFACT_COST = 0.2;
45
+ const STRUCTURED_EDGE_BACKGROUND_COLLISION_MAX_NEAR_BLACK_RATIO = 0.1;
46
+ const STRUCTURED_EDGE_BACKGROUND_COLLISION_MAX_NEWLY_CLIPPED_RATIO = 0.01;
29
47
 
30
48
  function meanAndVariance(values) {
31
49
  let sum = 0;
@@ -359,6 +377,123 @@ export function assessWatermarkResidualVisibility({
359
377
  };
360
378
  }
361
379
 
380
+ export function assessCalibratedWatermarkResidualVisibility({
381
+ imageData,
382
+ originalImageData = null,
383
+ position,
384
+ alphaMap,
385
+ alphaGain = 1
386
+ }) {
387
+ const visibility = assessWatermarkResidualVisibility({
388
+ imageData,
389
+ position,
390
+ alphaMap
391
+ });
392
+ if (!visibility) return null;
393
+
394
+ const scores = scoreRegion(imageData, alphaMap, position);
395
+ const nearBlackRatio = calculateNearBlackRatio(imageData, position);
396
+ const artifacts = originalImageData
397
+ ? assessRemovalDiffArtifacts({
398
+ originalImageData,
399
+ candidateImageData: imageData,
400
+ alphaMap,
401
+ position,
402
+ alphaGain
403
+ })
404
+ : null;
405
+ const newlyClippedRatio = artifacts?.newlyClippedRatio ?? 0;
406
+ const metricRisk = classifyCalibratedResidualMetricRisk({
407
+ visibility,
408
+ spatialScore: scores.spatialScore,
409
+ gradientScore: scores.gradientScore,
410
+ nearBlackRatio,
411
+ newlyClippedRatio,
412
+ visualArtifactCost: artifacts?.visualArtifactCost ?? null,
413
+ hasOriginalImageData: Boolean(originalImageData)
414
+ });
415
+
416
+ return {
417
+ ...visibility,
418
+ rawVisible: visibility.visible,
419
+ visible: visibility.visible && !metricRisk,
420
+ calibratedVisible: visibility.visible && !metricRisk,
421
+ metricRisk,
422
+ nearBlackRatio,
423
+ newlyClippedRatio,
424
+ rawSpatialScore: scores.spatialScore,
425
+ rawGradientScore: scores.gradientScore
426
+ };
427
+ }
428
+
429
+ export function classifyCalibratedResidualMetricRisk({
430
+ visibility,
431
+ spatialScore,
432
+ gradientScore,
433
+ nearBlackRatio = 0,
434
+ newlyClippedRatio = 0,
435
+ visualArtifactCost = null,
436
+ hasOriginalImageData = false
437
+ }) {
438
+ if (visibility?.visible !== true) return null;
439
+
440
+ if (
441
+ visibility.visibleSpatialResidual === true &&
442
+ visibility.visiblePositiveHalo !== true &&
443
+ spatialScore <= -FLAT_CLIPPED_METRIC_RISK_MIN_NEGATIVE_SPATIAL &&
444
+ visibility.positiveHaloLum <= FLAT_CLIPPED_METRIC_RISK_MAX_POSITIVE_HALO_LUM &&
445
+ nearBlackRatio >= FLAT_CLIPPED_METRIC_RISK_NEAR_BLACK_RATIO &&
446
+ (
447
+ !hasOriginalImageData ||
448
+ newlyClippedRatio >= FLAT_CLIPPED_METRIC_RISK_NEWLY_CLIPPED_RATIO
449
+ )
450
+ ) {
451
+ return 'flat-clipped-low-texture-spatial-correlation';
452
+ }
453
+
454
+ if (
455
+ visibility.visibleGradientResidual !== true &&
456
+ spatialScore >= POSITIVE_SPATIAL_BACKGROUND_COLLISION_MIN_SPATIAL &&
457
+ gradientScore < POSITIVE_SPATIAL_BACKGROUND_COLLISION_MAX_GRADIENT &&
458
+ visibility.positiveHaloLum <= POSITIVE_SPATIAL_BACKGROUND_COLLISION_MAX_POSITIVE_HALO_LUM &&
459
+ visualArtifactCost !== null &&
460
+ visualArtifactCost <= POSITIVE_SPATIAL_BACKGROUND_COLLISION_MAX_ARTIFACT_COST
461
+ ) {
462
+ return visibility.visiblePositiveHalo === true
463
+ ? 'positive-halo-background-collision'
464
+ : 'positive-spatial-background-collision';
465
+ }
466
+
467
+ if (
468
+ visibility.visiblePositiveHalo === true &&
469
+ visibility.visibleSpatialResidual !== true &&
470
+ visibility.visibleGradientResidual !== true &&
471
+ Math.abs(spatialScore) < WEAK_HALO_BACKGROUND_COLLISION_MAX_ABS_SPATIAL &&
472
+ gradientScore < WEAK_HALO_BACKGROUND_COLLISION_MAX_GRADIENT &&
473
+ visibility.positiveHaloLum <= WEAK_HALO_BACKGROUND_COLLISION_MAX_POSITIVE_HALO_LUM &&
474
+ nearBlackRatio < WEAK_HALO_BACKGROUND_COLLISION_MAX_NEAR_BLACK_RATIO &&
475
+ newlyClippedRatio <= WEAK_HALO_BACKGROUND_COLLISION_MAX_NEWLY_CLIPPED_RATIO &&
476
+ visualArtifactCost !== null &&
477
+ visualArtifactCost <= WEAK_HALO_BACKGROUND_COLLISION_MAX_ARTIFACT_COST
478
+ ) {
479
+ return 'weak-halo-background-collision';
480
+ }
481
+
482
+ if (
483
+ visibility.visiblePositiveHalo === true &&
484
+ visibility.visibleGradientResidual !== true &&
485
+ gradientScore >= STRUCTURED_EDGE_BACKGROUND_COLLISION_MIN_GRADIENT &&
486
+ nearBlackRatio < STRUCTURED_EDGE_BACKGROUND_COLLISION_MAX_NEAR_BLACK_RATIO &&
487
+ newlyClippedRatio <= STRUCTURED_EDGE_BACKGROUND_COLLISION_MAX_NEWLY_CLIPPED_RATIO &&
488
+ visualArtifactCost !== null &&
489
+ visualArtifactCost >= STRUCTURED_EDGE_BACKGROUND_COLLISION_MIN_ARTIFACT_COST
490
+ ) {
491
+ return 'structured-edge-background-collision';
492
+ }
493
+
494
+ return null;
495
+ }
496
+
362
497
  function getReferenceRegion(position, imageData) {
363
498
  const referenceY = position.y - position.height;
364
499
  if (referenceY < 0) return null;