@pilio/gemini-watermark-remover 1.0.30 → 1.0.32

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/dist/video-app.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * @pilio/gemini-watermark-remover v1.0.30+d4717dc
2
+ * @pilio/gemini-watermark-remover v1.0.32+6fab72e
3
3
  * Automatically removes watermarks from Gemini AI generated images
4
4
  * (c) 2026 GargantuaX
5
5
  * https://github.com/GargantuaX/gemini-watermark-remover
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.30",
4
+ "version": "1.0.32",
5
5
  "author": "GargantuaX",
6
6
  "sideEffects": false,
7
7
  "files": [
@@ -102,6 +102,7 @@
102
102
  "probe:tm:profile": "node scripts/open-tampermonkey-profile.js",
103
103
  "probe:tm:setup": "node scripts/tampermonkey-smoke.js setup",
104
104
  "probe:real-page:compare": "node scripts/real-page-pixel-compare.js",
105
+ "probe:real-page:copy-download": "node scripts/real-page-copy-download-probe.js",
105
106
  "benchmark:samples": "node scripts/sample-benchmark.js",
106
107
  "benchmark:userscript": "node scripts/userscript-benchmark.js",
107
108
  "benchmark:online-sample:gate": "node scripts/gate-online-gemini-watermark-sample-benchmark.js",
@@ -196,6 +196,52 @@ export function computeSizeAdjustedConfidence(confidence, size, referenceSize =
196
196
  return confidence * sizeWeight;
197
197
  }
198
198
 
199
+ function compareAdaptiveCandidatePriority(left, right) {
200
+ const leftScore = Number.isFinite(left?.adjustedScore)
201
+ ? left.adjustedScore
202
+ : computeSizeAdjustedConfidence(left?.confidence, left?.size);
203
+ const rightScore = Number.isFinite(right?.adjustedScore)
204
+ ? right.adjustedScore
205
+ : computeSizeAdjustedConfidence(right?.confidence, right?.size);
206
+ if (Math.abs(leftScore - rightScore) > 1e-12) {
207
+ return leftScore - rightScore;
208
+ }
209
+
210
+ const leftSize = Number.isFinite(left?.size) ? left.size : 0;
211
+ const rightSize = Number.isFinite(right?.size) ? right.size : 0;
212
+ const leftDistance = Math.abs(leftSize - REFERENCE_WATERMARK_SIZE);
213
+ const rightDistance = Math.abs(rightSize - REFERENCE_WATERMARK_SIZE);
214
+ if (leftDistance !== rightDistance) {
215
+ return rightDistance - leftDistance;
216
+ }
217
+
218
+ const leftConfidence = Number.isFinite(left?.confidence) ? left.confidence : 0;
219
+ const rightConfidence = Number.isFinite(right?.confidence) ? right.confidence : 0;
220
+ if (leftConfidence !== rightConfidence) {
221
+ return leftConfidence - rightConfidence;
222
+ }
223
+ if (leftSize !== rightSize) {
224
+ return leftSize - rightSize;
225
+ }
226
+
227
+ const leftX = Number.isFinite(left?.x) ? left.x : Infinity;
228
+ const rightX = Number.isFinite(right?.x) ? right.x : Infinity;
229
+ if (leftX !== rightX) {
230
+ return rightX - leftX;
231
+ }
232
+ const leftY = Number.isFinite(left?.y) ? left.y : Infinity;
233
+ const rightY = Number.isFinite(right?.y) ? right.y : Infinity;
234
+ return rightY - leftY;
235
+ }
236
+
237
+ export function selectAdaptiveFineCandidate(currentBest, candidate) {
238
+ if (!currentBest) return candidate;
239
+ if (!candidate) return currentBest;
240
+ return compareAdaptiveCandidatePriority(candidate, currentBest) > 0
241
+ ? candidate
242
+ : currentBest;
243
+ }
244
+
199
245
  function buildSeedConfigs(width, height, defaultConfig) {
200
246
  // Start adaptive search from both the coarse default anchor and any
201
247
  // catalog-projected anchors for official or near-official Gemini sizes.
@@ -445,10 +491,10 @@ export function detectAdaptiveWatermarkRegion({
445
491
  })
446
492
  .filter(Boolean);
447
493
 
448
- const bestSeed = seedCandidates.reduce((best, candidate) => {
449
- if (!best || candidate.confidence > best.confidence) return candidate;
450
- return best;
451
- }, null);
494
+ const bestSeed = seedCandidates.reduce(
495
+ (best, candidate) => selectAdaptiveFineCandidate(best, candidate),
496
+ null
497
+ );
452
498
  if (bestSeed && bestSeed.confidence >= threshold + 0.08) {
453
499
  return {
454
500
  found: true,
@@ -483,7 +529,7 @@ export function detectAdaptiveWatermarkRegion({
483
529
  const topK = [];
484
530
  const pushTopK = (candidate) => {
485
531
  topK.push(candidate);
486
- topK.sort((a, b) => b.adjustedScore - a.adjustedScore);
532
+ topK.sort((left, right) => -compareAdaptiveCandidatePriority(left, right));
487
533
  if (topK.length > 5) topK.length = 5;
488
534
  };
489
535
 
@@ -545,14 +591,12 @@ export function detectAdaptiveWatermarkRegion({
545
591
  const score = scoreCandidate(context, tpl.alpha, tpl.grad, { x, y, size });
546
592
  if (!score) continue;
547
593
 
548
- if (score.confidence > best.confidence) {
549
- best = {
550
- x,
551
- y,
552
- size,
553
- ...score
554
- };
555
- }
594
+ best = selectAdaptiveFineCandidate(best, {
595
+ x,
596
+ y,
597
+ size,
598
+ ...score
599
+ });
556
600
  }
557
601
  }
558
602
  }
@@ -5,8 +5,13 @@ import { createRepairTrialFromStages } from './pipelineRepairTrial.js';
5
5
 
6
6
  const NEW_MARGIN_96_SIZE = 96;
7
7
  const NEW_MARGIN_96_MARGIN = 192;
8
- const HIGH_RISK_NEW_MARGIN_MIN_SPATIAL = 0.4;
9
- const HIGH_RISK_NEW_MARGIN_MIN_GRADIENT = 0.08;
8
+ const HIGH_RISK_NEW_MARGIN_MIN_SPATIAL = 0.4;
9
+ const HIGH_RISK_NEW_MARGIN_MIN_GRADIENT = 0.08;
10
+ const SAFE_EXACT_NEW_MARGIN_MIN_SPATIAL = 0.18;
11
+ const SAFE_EXACT_NEW_MARGIN_MIN_GRADIENT = 0.05;
12
+ const SAFE_EXACT_NEW_MARGIN_MIN_IMPROVEMENT = 0.1;
13
+ const SAFE_EXACT_NEW_MARGIN_MAX_PROCESSED_SPATIAL = 0.32;
14
+ const SAFE_EXACT_NEW_MARGIN_MAX_PROCESSED_GRADIENT = 0.05;
10
15
  const DEFAULT_ALPHA_NEW_MARGIN_MAX_SPATIAL_RESIDUAL = 0.18;
11
16
  const DEFAULT_ALPHA_NEW_MARGIN_MAX_GRADIENT_RESIDUAL = 0.08;
12
17
  const DEFAULT_ALPHA_NEW_MARGIN_MIN_IMPROVEMENT = 0.12;
@@ -40,11 +45,30 @@ export function isNewMarginAlphaVariantTrial(candidate) {
40
45
  config.alphaVariant === '20260520';
41
46
  }
42
47
 
43
- export function isDefaultAlphaNewMarginTrial(candidate) {
44
- const config = getConfig(candidate);
45
- return isNewMargin96Candidate(candidate) &&
46
- !config.alphaVariant;
47
- }
48
+ export function isDefaultAlphaNewMarginTrial(candidate) {
49
+ const config = getConfig(candidate);
50
+ return isNewMargin96Candidate(candidate) &&
51
+ !config.alphaVariant;
52
+ }
53
+
54
+ function isNewMarginVariantFamilyCandidate(candidate) {
55
+ const config = getConfig(candidate);
56
+ const provenance = getProvenance(candidate);
57
+ return config.marginRight === NEW_MARGIN_96_MARGIN &&
58
+ config.marginBottom === NEW_MARGIN_96_MARGIN &&
59
+ (config.alphaVariant === '20260520' || provenance.alphaVariant === '20260520');
60
+ }
61
+
62
+ function hasSafeExactNewMarginVariantRecovery(candidate) {
63
+ if (!isNewMarginAlphaVariantTrial(candidate)) return false;
64
+ if (candidate?.damage?.safe !== true) return false;
65
+
66
+ return numberOr(candidate?.originalSpatialScore) >= SAFE_EXACT_NEW_MARGIN_MIN_SPATIAL &&
67
+ numberOr(candidate?.originalGradientScore) >= SAFE_EXACT_NEW_MARGIN_MIN_GRADIENT &&
68
+ numberOr(candidate?.improvement, -Infinity) >= SAFE_EXACT_NEW_MARGIN_MIN_IMPROVEMENT &&
69
+ Math.abs(numberOr(candidate?.processedSpatialScore, Infinity)) <= SAFE_EXACT_NEW_MARGIN_MAX_PROCESSED_SPATIAL &&
70
+ Math.max(0, numberOr(candidate?.processedGradientScore, Infinity)) <= SAFE_EXACT_NEW_MARGIN_MAX_PROCESSED_GRADIENT;
71
+ }
48
72
 
49
73
  export function hasClearedResidual(candidate) {
50
74
  return candidate?.residual?.cleared === true ||
@@ -61,13 +85,14 @@ export function hasSafeDefaultAlphaNewMarginResidual(candidate) {
61
85
  numberOr(candidate?.improvement, -Infinity) >= DEFAULT_ALPHA_NEW_MARGIN_MIN_IMPROVEMENT;
62
86
  }
63
87
 
64
- export function hasHighRiskNewMarginPositiveEvidence(candidate) {
65
- if (getProvenance(candidate).darkPolarity === true) return true;
66
- if (!isNewMarginAlphaVariantTrial(candidate) && !isDefaultAlphaNewMarginTrial(candidate)) return true;
67
-
68
- return numberOr(candidate?.originalGradientScore) >= HIGH_RISK_NEW_MARGIN_MIN_GRADIENT ||
69
- numberOr(candidate?.originalSpatialScore) >= HIGH_RISK_NEW_MARGIN_MIN_SPATIAL;
70
- }
88
+ export function hasHighRiskNewMarginPositiveEvidence(candidate) {
89
+ if (getProvenance(candidate).darkPolarity === true) return true;
90
+ if (!isNewMarginVariantFamilyCandidate(candidate) && !isDefaultAlphaNewMarginTrial(candidate)) return true;
91
+
92
+ const hasStrongEvidence = numberOr(candidate?.originalGradientScore) >= HIGH_RISK_NEW_MARGIN_MIN_GRADIENT ||
93
+ numberOr(candidate?.originalSpatialScore) >= HIGH_RISK_NEW_MARGIN_MIN_SPATIAL;
94
+ return hasStrongEvidence || hasSafeExactNewMarginVariantRecovery(candidate);
95
+ }
71
96
 
72
97
  export function shouldFailClosedForVisibleResidualUnsafeDamage({
73
98
  selectedTrial = null,
@@ -124,10 +149,11 @@ export function createCandidateEvaluation({
124
149
  provenance,
125
150
  originalSpatialScore: originalScores?.spatialScore,
126
151
  originalGradientScore: originalScores?.gradientScore,
127
- processedSpatialScore: processedScores?.spatialScore,
128
- processedGradientScore: processedScores?.gradientScore,
129
- improvement
130
- };
152
+ processedSpatialScore: processedScores?.spatialScore,
153
+ processedGradientScore: processedScores?.gradientScore,
154
+ improvement,
155
+ damage
156
+ };
131
157
  const originalSpatial = numberOr(originalScores?.spatialScore);
132
158
  const originalGradient = numberOr(originalScores?.gradientScore);
133
159
  const processedSpatial = numberOr(processedScores?.spatialScore);
@@ -137,6 +137,8 @@ const BOTTOM_RIGHT_48_EVIDENCE_DOMINANCE_GRADIENT_ADVANTAGE = 0.12;
137
137
  const BOTTOM_RIGHT_48_EVIDENCE_DOMINANCE_MAX_RESIDUAL_DELTA = 0.08;
138
138
  const DARK_POLARITY_CATALOG_MIN_ORIGINAL_SPATIAL = 0.12;
139
139
  const DARK_POLARITY_CATALOG_MIN_ORIGINAL_GRADIENT = 0.08;
140
+ const DARK_POLARITY_48_MIN_ORIGINAL_SPATIAL = 0.6;
141
+ const DARK_POLARITY_48_MIN_ORIGINAL_GRADIENT = 0.3;
140
142
  const DARK_POLARITY_CATALOG_MAX_TEXTURE_FOR_WEAK_EVIDENCE = 0.25;
141
143
  const DARK_POLARITY_MAX_NEAR_WHITE_RATIO_INCREASE_FOR_WEAK_EVIDENCE = 0.05;
142
144
  const DARK_POLARITY_NEAR_WHITE_OVERRIDE_MIN_ORIGINAL_SPATIAL = 0.4;
@@ -183,6 +185,7 @@ const PREVIEW_ANCHOR_GAIN_SKIP_RESIDUAL_THRESHOLD = 0.24;
183
185
  const PREVIEW_ANCHOR_GAIN_SKIP_GRADIENT_THRESHOLD = 0.24;
184
186
  const CORE_ALPHA_PRIORITY_GAINS = Object.freeze([0.6, 1, 1.1, 1.15, 1.3, 0.45, 0.7, 0.85, 0.55]);
185
187
  const CURRENT_LARGE_MARGIN_ULTRA_WEAK_ALPHA_GAINS = Object.freeze([0.25, 0.3, 0.35, 0.4]);
188
+ const DARK_POLARITY_48_ULTRA_WEAK_ALPHA_GAINS = Object.freeze([0.08, 0.1, 0.12, 0.15, 0.2]);
186
189
  const STANDARD_ANCHOR_WEAK_ALPHA_RESCUE_GAINS = Object.freeze([0.55, 0.7, 0.85]);
187
190
  const STANDARD_ANCHOR_WEAK_RESCUE_MAX_SPATIAL = 0.35;
188
191
  const STANDARD_ANCHOR_WEAK_RESCUE_MAX_GRADIENT = 0.24;
@@ -393,13 +396,17 @@ function buildStandardCandidateSeeds({
393
396
  }
394
397
 
395
398
  if (shouldAddDarkPolaritySeed(candidateConfig)) {
399
+ const weakDarkPolarity48 = candidateConfig.logoSize === 48;
396
400
  seeds.push({
397
401
  ...baseSeed,
398
402
  alphaMap: createNegativeAlphaMap(alphaMap),
399
403
  source: `${baseSeed.source}+dark-polarity`,
400
404
  provenance: mergeCandidateProvenance(
401
405
  baseSeed.provenance,
402
- { darkPolarity: true }
406
+ {
407
+ darkPolarity: true,
408
+ ...(weakDarkPolarity48 ? { weakDarkPolarity48: true } : {})
409
+ }
403
410
  )
404
411
  });
405
412
  }
@@ -409,9 +416,19 @@ function buildStandardCandidateSeeds({
409
416
  }
410
417
 
411
418
  function shouldAddDarkPolaritySeed(config) {
412
- return config?.logoSize === 96 &&
419
+ if (
420
+ config?.logoSize === 96 &&
413
421
  config.marginRight === 192 &&
414
- config.marginBottom === 192;
422
+ config.marginBottom === 192
423
+ ) {
424
+ return true;
425
+ }
426
+
427
+ return config?.logoSize === 48 &&
428
+ config.marginRight >= 90 &&
429
+ config.marginRight <= 100 &&
430
+ config.marginBottom >= 90 &&
431
+ config.marginBottom <= 100;
415
432
  }
416
433
 
417
434
  const negativeAlphaMapCache = new WeakMap();
@@ -496,6 +513,23 @@ function resolveAlphaMapForSize(size, { alpha48, alpha96, getAlphaMap } = {}) {
496
513
  return alpha96 ? interpolateAlphaMap(alpha96, 96, size) : null;
497
514
  }
498
515
 
516
+ export function resolveSizeJitterAlphaMap(seed, size, {
517
+ alpha48,
518
+ alpha96,
519
+ getAlphaMap,
520
+ resolveAlphaMap = null
521
+ } = {}) {
522
+ const seedSize = Number(seed?.position?.width);
523
+ if (seed?.alphaMap && Number.isFinite(seedSize) && seedSize > 0) {
524
+ if (size === seedSize) return seed.alphaMap;
525
+ return interpolateAlphaMap(seed.alphaMap, seedSize, size);
526
+ }
527
+
528
+ return typeof resolveAlphaMap === 'function'
529
+ ? resolveAlphaMap(size)
530
+ : resolveAlphaMapForSize(size, { alpha48, alpha96, getAlphaMap });
531
+ }
532
+
499
533
  function resolveAlphaMapForConfig(config, {
500
534
  alpha48,
501
535
  alpha96,
@@ -642,7 +676,15 @@ function isWeakAlphaPrioritySeed(seed) {
642
676
  seed.config.marginBottom === 96;
643
677
  }
644
678
 
679
+ function isWeakDarkPolarity48Seed(seed) {
680
+ return seed?.provenance?.weakDarkPolarity48 === true;
681
+ }
682
+
645
683
  function resolveStandardSeedAlphaPriorityGains(seed, alphaPriorityGains) {
684
+ if (isWeakDarkPolarity48Seed(seed)) {
685
+ return DARK_POLARITY_48_ULTRA_WEAK_ALPHA_GAINS;
686
+ }
687
+
646
688
  const extras = isWeakAlphaPrioritySeed(seed)
647
689
  ? [
648
690
  ...CURRENT_LARGE_MARGIN_ULTRA_WEAK_ALPHA_GAINS,
@@ -857,7 +899,7 @@ function evaluateStandardTrialForSeed({
857
899
  }
858
900
  }
859
901
 
860
- return isWeakAlphaPrioritySeed(seed)
902
+ const selected = isWeakAlphaPrioritySeed(seed)
861
903
  ? bestWeakAlphaPriorityTrial ?? bestWeakAlphaRescueTrial ?? bestAcceptedTrial ?? fallbackTrial
862
904
  : bestWeakAlphaPriorityTrial ??
863
905
  (
@@ -867,6 +909,10 @@ function evaluateStandardTrialForSeed({
867
909
  ) ??
868
910
  bestWeakAlphaRescueTrial ??
869
911
  fallbackTrial;
912
+
913
+ return isWeakDarkPolarity48Seed(seed) && selected?.accepted !== true
914
+ ? null
915
+ : selected;
870
916
  }
871
917
 
872
918
  export function evaluateRestorationCandidate({
@@ -994,11 +1040,40 @@ export function evaluateRestorationCandidate({
994
1040
  const strongDarkPolarityOriginalEvidence =
995
1041
  originalScores.spatialScore >= DARK_POLARITY_CATALOG_MIN_ORIGINAL_SPATIAL ||
996
1042
  originalScores.gradientScore >= DARK_POLARITY_CATALOG_MIN_ORIGINAL_GRADIENT;
1043
+ const isLargeMargin48DarkPolarity =
1044
+ provenance?.darkPolarity === true &&
1045
+ config?.logoSize === 48 &&
1046
+ config.marginRight >= 90 &&
1047
+ config.marginRight <= 100 &&
1048
+ config.marginBottom >= 90 &&
1049
+ config.marginBottom <= 100;
1050
+ const largeMargin48DarkPolarityEvidenceAllowed =
1051
+ !isLargeMargin48DarkPolarity ||
1052
+ (
1053
+ originalScores.spatialScore >= DARK_POLARITY_48_MIN_ORIGINAL_SPATIAL &&
1054
+ originalScores.gradientScore >= DARK_POLARITY_48_MIN_ORIGINAL_GRADIENT
1055
+ );
1056
+ const weakDarkPolarity48ScopeAllowed =
1057
+ provenance?.weakDarkPolarity48 !== true ||
1058
+ (
1059
+ config?.logoSize === 48 &&
1060
+ config.marginRight >= 90 &&
1061
+ config.marginRight <= 100 &&
1062
+ config.marginBottom >= 90 &&
1063
+ config.marginBottom <= 100 &&
1064
+ alphaGain <= 0.2
1065
+ );
997
1066
  const darkPolarityCatalogEvidenceAllowed =
998
1067
  provenance?.darkPolarity !== true ||
999
1068
  provenance?.catalogVariant !== true ||
1000
- strongDarkPolarityOriginalEvidence ||
1001
- texturePenalty <= DARK_POLARITY_CATALOG_MAX_TEXTURE_FOR_WEAK_EVIDENCE;
1069
+ (
1070
+ weakDarkPolarity48ScopeAllowed &&
1071
+ largeMargin48DarkPolarityEvidenceAllowed &&
1072
+ (
1073
+ strongDarkPolarityOriginalEvidence ||
1074
+ texturePenalty <= DARK_POLARITY_CATALOG_MAX_TEXTURE_FOR_WEAK_EVIDENCE
1075
+ )
1076
+ );
1002
1077
  const strongDarkPolarityNearWhiteOverrideEvidence =
1003
1078
  originalScores.spatialScore >= DARK_POLARITY_NEAR_WHITE_OVERRIDE_MIN_ORIGINAL_SPATIAL ||
1004
1079
  originalScores.gradientScore >= DARK_POLARITY_NEAR_WHITE_OVERRIDE_MIN_ORIGINAL_GRADIENT;
@@ -1320,11 +1395,35 @@ function compareSameAnchorCandidateRanking(currentBest, candidate) {
1320
1395
  return compareRankingKey(candidate.rankingKey, currentBest.rankingKey);
1321
1396
  }
1322
1397
 
1398
+ function shouldPreserveExactNewMarginVariant(exactCandidate, competingCandidate) {
1399
+ const exactConfig = exactCandidate?.config ?? {};
1400
+ const competingConfig = competingCandidate?.config ?? {};
1401
+ const exactVariant = exactConfig.alphaVariant ?? exactCandidate?.provenance?.alphaVariant;
1402
+ const competingVariant = competingConfig.alphaVariant ?? competingCandidate?.provenance?.alphaVariant;
1403
+
1404
+ if (exactCandidate?.accepted !== true || exactCandidate?.damage?.safe !== true) return false;
1405
+ if (exactConfig.logoSize !== 96 || exactConfig.marginRight !== 192 || exactConfig.marginBottom !== 192) {
1406
+ return false;
1407
+ }
1408
+ if (exactVariant !== '20260520') return false;
1409
+ if (competingCandidate?.provenance?.sizeJitter !== true) return false;
1410
+ if (competingConfig.marginRight !== 192 || competingConfig.marginBottom !== 192) return false;
1411
+ if (competingVariant !== exactVariant) return false;
1412
+
1413
+ return !hasMuchStrongerOriginalSignal(competingCandidate, exactCandidate);
1414
+ }
1415
+
1323
1416
  export function pickBetterCandidate(currentBest, candidate, minCostDelta = 0.005) {
1324
1417
  if (!candidate?.accepted) return currentBest;
1325
1418
  if (!currentBest) return candidate;
1326
1419
  const evaluationDecision = arbitrateCandidateByEvaluation(currentBest, candidate);
1327
1420
  if (evaluationDecision) return evaluationDecision;
1421
+ if (shouldPreserveExactNewMarginVariant(currentBest, candidate)) {
1422
+ return currentBest;
1423
+ }
1424
+ if (shouldPreserveExactNewMarginVariant(candidate, currentBest)) {
1425
+ return candidate;
1426
+ }
1328
1427
  if (shouldPreserveCatalogOriginalSignal(currentBest, candidate)) {
1329
1428
  return currentBest;
1330
1429
  }
@@ -1975,13 +2074,12 @@ function searchStandardSizeJitterCandidate({
1975
2074
  if (candidatePosition.x + candidatePosition.width > originalImageData.width) continue;
1976
2075
  if (candidatePosition.y + candidatePosition.height > originalImageData.height) continue;
1977
2076
 
1978
- const candidateAlphaMap = typeof resolveAlphaMap === 'function'
1979
- ? resolveAlphaMap(size)
1980
- : resolveAlphaMapForSize(size, {
1981
- alpha48,
1982
- alpha96,
1983
- getAlphaMap
1984
- });
2077
+ const candidateAlphaMap = resolveSizeJitterAlphaMap(seed, size, {
2078
+ alpha48,
2079
+ alpha96,
2080
+ getAlphaMap,
2081
+ resolveAlphaMap
2082
+ });
1985
2083
  if (!candidateAlphaMap) continue;
1986
2084
 
1987
2085
  const candidate = evaluateRestorationCandidate({
@@ -1989,11 +2087,10 @@ function searchStandardSizeJitterCandidate({
1989
2087
  alphaMap: candidateAlphaMap,
1990
2088
  position: candidatePosition,
1991
2089
  source: `${seed.source}+size`,
1992
- config: {
1993
- logoSize: size,
1994
- marginRight: seed.config.marginRight,
1995
- marginBottom: seed.config.marginBottom
1996
- },
2090
+ config: {
2091
+ ...seed.config,
2092
+ logoSize: size
2093
+ },
1997
2094
  baselineNearBlackRatio: calculateNearBlackRatio(originalImageData, candidatePosition),
1998
2095
  adaptiveConfidence,
1999
2096
  provenance: mergeCandidateProvenance(seed.provenance, { sizeJitter: true }),
@@ -465,7 +465,6 @@ export function resolveOfficialGeminiSearchConfigEntries(
465
465
  })
466
466
  .filter(Boolean)
467
467
  .sort((a, b) => a.score - b.score);
468
-
469
468
  const deduped = [];
470
469
  const seen = new Set();
471
470
  for (const candidate of candidates) {
@@ -27,6 +27,14 @@ function createSelectedCandidate(best) {
27
27
  };
28
28
  }
29
29
 
30
+ function findDarkBackgroundSupportConvergence(meta = {}) {
31
+ const stages = Array.isArray(meta.alphaAdjustmentStages)
32
+ ? meta.alphaAdjustmentStages
33
+ : [];
34
+ const convergence = stages.at(-1)?.darkBackgroundSupportConvergence;
35
+ return convergence?.accepted === true ? convergence : null;
36
+ }
37
+
30
38
  function createRuntimeFailureResult({
31
39
  createRejectedResult,
32
40
  originalImageData,
@@ -42,6 +50,26 @@ function createRuntimeFailureResult({
42
50
  });
43
51
  }
44
52
 
53
+ function createNoWatermarkResult({
54
+ createRejectedResult,
55
+ originalImageData,
56
+ collection,
57
+ debugTimings
58
+ }) {
59
+ const selection = collection?.automaticSelection ?? collection?.fixedSelection ?? {};
60
+ return createRejectedResult({
61
+ imageData: originalImageData,
62
+ debugTimings,
63
+ reason: 'no-watermark-detected',
64
+ adaptiveConfidence: selection.adaptiveConfidence ?? null,
65
+ originalSpatialScore: selection.standardSpatialScore ?? null,
66
+ originalGradientScore: selection.standardGradientScore ?? null,
67
+ source: 'skipped',
68
+ decisionTier: selection.decisionTier ?? 'insufficient',
69
+ selectionDebug: null
70
+ });
71
+ }
72
+
45
73
  function notifyCandidateCompleted({ options, candidate, debugTimings }) {
46
74
  if (typeof options?.onCandidateCompleted !== 'function') return;
47
75
 
@@ -55,6 +83,41 @@ function notifyCandidateCompleted({ options, candidate, debugTimings }) {
55
83
  }
56
84
  }
57
85
 
86
+ function appendUniqueRiskFlag(flags, flag) {
87
+ const normalized = Array.isArray(flags) ? flags : [];
88
+ return normalized.includes(flag) ? normalized : [...normalized, flag];
89
+ }
90
+
91
+ function attachPresenceBestEffortMeta(meta, collection) {
92
+ if (collection?.bestEffortFallback !== true) return meta;
93
+
94
+ const riskFlag = 'unconfirmed-watermark-presence';
95
+ const decisionPath = meta?.decisionPath && typeof meta.decisionPath === 'object'
96
+ ? {
97
+ ...meta.decisionPath,
98
+ riskFlags: appendUniqueRiskFlag(meta.decisionPath.riskFlags, riskFlag),
99
+ evaluation: meta.decisionPath.evaluation &&
100
+ typeof meta.decisionPath.evaluation === 'object'
101
+ ? {
102
+ ...meta.decisionPath.evaluation,
103
+ riskFlags: appendUniqueRiskFlag(
104
+ meta.decisionPath.evaluation.riskFlags,
105
+ riskFlag
106
+ )
107
+ }
108
+ : meta.decisionPath.evaluation
109
+ }
110
+ : meta?.decisionPath;
111
+
112
+ return {
113
+ ...meta,
114
+ presenceConfirmed: false,
115
+ bestEffortReason:
116
+ collection.bestEffortReason ?? 'presence-witness-unconfirmed',
117
+ decisionPath
118
+ };
119
+ }
120
+
58
121
  export function runImageWatermarkPipeline({
59
122
  imageData,
60
123
  options = {},
@@ -119,6 +182,26 @@ export function runImageWatermarkPipeline({
119
182
  debugTimings.generatedCandidateCount = hypotheses.length;
120
183
  debugTimings.earlyExitReason = null;
121
184
  }
185
+ if (
186
+ collection?.presenceConfirmed === false &&
187
+ collection?.bestEffortFallback !== true
188
+ ) {
189
+ if (debugTimingsEnabled) {
190
+ debugTimings.candidateExecutionMs = 0;
191
+ debugTimings.executedCandidateCount = 0;
192
+ debugTimings.completedCandidateCount = 0;
193
+ debugTimings.failedCandidateCount = 0;
194
+ debugTimings.candidateRankingMs = 0;
195
+ debugTimings.earlyExitReason = 'no-watermark-detected';
196
+ debugTimings.totalMs = nowMs() - totalStartedAt;
197
+ }
198
+ return createNoWatermarkResult({
199
+ createRejectedResult,
200
+ originalImageData,
201
+ collection,
202
+ debugTimings
203
+ });
204
+ }
122
205
 
123
206
  const completed = [];
124
207
  const failures = [];
@@ -135,7 +218,9 @@ export function runImageWatermarkPipeline({
135
218
  debugTimingsEnabled,
136
219
  visualPostProcessingEnabled,
137
220
  cleanupConfig,
138
- createAcceptedPipelineDependencies,
221
+ createAcceptedPipelineDependencies: () => (
222
+ createAcceptedPipelineDependencies(hypothesis)
223
+ ),
139
224
  runAcceptedPipeline,
140
225
  createAcceptedFinalResult
141
226
  });
@@ -148,7 +233,16 @@ export function runImageWatermarkPipeline({
148
233
  qualitySignals: measureCandidate({
149
234
  originalImageData,
150
235
  candidateImageData: completedCandidate.result.imageData,
151
- hypothesis
236
+ hypothesis,
237
+ finalCandidate: {
238
+ position: completedCandidate.pipelineState?.position,
239
+ alphaMap: completedCandidate.pipelineState?.alphaMap,
240
+ alphaGain: completedCandidate.pipelineState?.alphaGain,
241
+ darkBackgroundSupportConvergence:
242
+ findDarkBackgroundSupportConvergence(
243
+ completedCandidate.result.meta
244
+ )
245
+ }
152
246
  })
153
247
  };
154
248
  completed.push(candidate);
@@ -197,13 +291,14 @@ export function runImageWatermarkPipeline({
197
291
  }
198
292
 
199
293
  const candidateSummaries = createSummaries(ranked, failures);
200
- const meta = attachSelectionMeta(best.result.meta, {
294
+ const selectionMeta = attachSelectionMeta(best.result.meta, {
201
295
  qualityStatus: best.qualitySignals?.qualityStatus,
202
296
  selectionConfidence: best.selectionConfidence,
203
297
  selectedCandidate: createSelectedCandidate(best),
204
298
  qualitySignals: best.qualitySignals,
205
299
  candidateSummaries
206
300
  });
301
+ const meta = attachPresenceBestEffortMeta(selectionMeta, collection);
207
302
  if (debugTimingsEnabled) {
208
303
  debugTimings.candidateRankingMs = nowMs() - rankingStartedAt;
209
304
  debugTimings.totalMs = nowMs() - totalStartedAt;
@@ -96,11 +96,15 @@ export function runAcceptedAlphaRepairPipeline({
96
96
  originalGradientScore: current.originalGradientScore,
97
97
  calculateNearBlackRatio: metrics.calculateNearBlackRatio,
98
98
  acceptCurrentAlphaTrialResult,
99
- debugTimings,
100
- debugTimingsEnabled,
101
- refiners: {
102
- recalibrateOverSubtractedAlpha: refiners.recalibrateOverSubtractedAlpha,
103
- fineTuneDarkCatalogAlpha: refiners.fineTuneDarkCatalogAlpha,
99
+ debugTimings,
100
+ debugTimingsEnabled,
101
+ refiners: {
102
+ refineLargeMargin48ProfileAlphaRescue:
103
+ refiners.refineLargeMargin48ProfileAlphaRescue,
104
+ fineTuneEvidenceGatedLocalAlpha:
105
+ refiners.fineTuneEvidenceGatedLocalAlpha,
106
+ recalibrateOverSubtractedAlpha: refiners.recalibrateOverSubtractedAlpha,
107
+ fineTuneDarkCatalogAlpha: refiners.fineTuneDarkCatalogAlpha,
104
108
  fineTuneWeakPositiveResidualAlpha: refiners.fineTuneWeakPositiveResidualAlpha
105
109
  }
106
110
  });