@pilio/gemini-watermark-remover 1.0.17 → 1.0.18

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/README.md CHANGED
@@ -209,10 +209,9 @@ import {
209
209
  Use the pure-data API when you already have decoded `ImageData`:
210
210
 
211
211
  ```javascript
212
- const result = await removeWatermarkFromImageData(imageData, {
213
- adaptiveMode: 'auto',
214
- maxPasses: 4,
215
- });
212
+ const result = await removeWatermarkFromImageData(imageData, {
213
+ adaptiveMode: 'auto',
214
+ });
216
215
 
217
216
  console.log(result.meta.decisionTier);
218
217
  ```
package/README_zh.md CHANGED
@@ -211,7 +211,6 @@ import {
211
211
  ```javascript
212
212
  const result = await removeWatermarkFromImageData(imageData, {
213
213
  adaptiveMode: 'auto',
214
- maxPasses: 4,
215
214
  });
216
215
 
217
216
  console.log(result.meta.decisionTier);
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.17",
4
+ "version": "1.0.18",
5
5
  "author": "GargantuaX",
6
6
  "sideEffects": false,
7
7
  "files": [
@@ -31,6 +31,11 @@ const VALIDATION_MAX_GRADIENT_INCREASE = 0.04;
31
31
  const VALIDATION_MIN_CONFIDENCE_FOR_ADAPTIVE_TRIAL = 0.25;
32
32
  const STANDARD_FAST_PATH_RESIDUAL_THRESHOLD = 0.22;
33
33
  const STANDARD_FAST_PATH_GRADIENT_THRESHOLD = 0.08;
34
+ const FIXED_CORE_MAX_ACCEPTED_SPATIAL_RESIDUAL = 0.36;
35
+ const STANDARD_EXPAND_CATALOG_MIN_ORIGINAL_GRADIENT = 0.12;
36
+ const WEAK_ALPHA_PRIORITY_CLEAN_GRADIENT_THRESHOLD = 0.12;
37
+ const STRONG_ORIGINAL_SIGNAL_SPATIAL_ADVANTAGE = 0.2;
38
+ const STRONG_ORIGINAL_SIGNAL_GRADIENT_ADVANTAGE = 0.25;
34
39
  const STANDARD_NEARBY_SEARCH_RESIDUAL_THRESHOLD = 0.18;
35
40
  const STANDARD_NEARBY_SEARCH_GRADIENT_THRESHOLD = 0.05;
36
41
  const STANDARD_LOCAL_SHIFT_STRONG_BASE_GRADIENT_SCORE = 0.35;
@@ -57,6 +62,13 @@ const STANDARD_HARD_REJECT_OVERRIDE_MAX_RESIDUAL = 0.08;
57
62
  const STANDARD_HARD_REJECT_OVERRIDE_MAX_GRADIENT = 0.1;
58
63
  const STANDARD_HARD_REJECT_OVERRIDE_MIN_IMPROVEMENT = 0.7;
59
64
  const STANDARD_HARD_REJECT_OVERRIDE_MAX_NEAR_BLACK_INCREASE = 0.01;
65
+ const STANDARD_STRONG_SIGNAL_NEAR_BLACK_OVERRIDE_MIN_SPATIAL_SCORE = 0.9;
66
+ const STANDARD_STRONG_SIGNAL_NEAR_BLACK_OVERRIDE_MIN_GRADIENT_SCORE = 0.7;
67
+ const STANDARD_STRONG_SIGNAL_NEAR_BLACK_OVERRIDE_MAX_RESIDUAL = 0.22;
68
+ const STANDARD_STRONG_SIGNAL_NEAR_BLACK_OVERRIDE_MAX_GRADIENT = 0.32;
69
+ const STANDARD_STRONG_SIGNAL_NEAR_BLACK_OVERRIDE_MIN_IMPROVEMENT = 0.7;
70
+ const STANDARD_STRONG_SIGNAL_NEAR_BLACK_OVERRIDE_MIN_GRADIENT_DROP = 0.6;
71
+ const STANDARD_STRONG_SIGNAL_NEAR_BLACK_OVERRIDE_MAX_NEAR_BLACK_INCREASE = 0.4;
60
72
  const TEMPLATE_ALIGN_SHIFTS = [-0.5, -0.25, 0, 0.25, 0.5];
61
73
  const TEMPLATE_ALIGN_SCALES = [0.99, 1, 1.01];
62
74
  const STANDARD_NEARBY_SHIFTS = [-12, -8, -4, 0, 4, 8, 12];
@@ -93,6 +105,7 @@ function mergeCandidateProvenance(...provenanceParts) {
93
105
 
94
106
  function isProjectedPreviewCatalogConfig(originalImageData, candidateConfig, baseConfig) {
95
107
  if (!originalImageData || !candidateConfig) return false;
108
+ if (candidateConfig.fixedVariant === true) return false;
96
109
  if (matchOfficialGeminiImageSize(originalImageData.width, originalImageData.height)) return false;
97
110
 
98
111
  return candidateConfig.logoSize < 48 ||
@@ -165,6 +178,7 @@ function buildStandardCandidateSeeds({
165
178
  : (candidateConfig === config ? 'standard' : 'standard+catalog'),
166
179
  provenance: mergeCandidateProvenance(
167
180
  candidateConfig === config ? null : { catalogVariant: true },
181
+ candidateConfig.fixedVariant === true ? { fixedVariant: true } : null,
168
182
  projectedPreviewCatalog ? { previewAnchor: true } : null,
169
183
  candidateConfig.alphaVariant ? { alphaVariant: candidateConfig.alphaVariant } : null
170
184
  )
@@ -182,15 +196,26 @@ function inferDecisionTier(candidate, { directMatch = false } = {}) {
182
196
  return 'safe-removal';
183
197
  }
184
198
 
185
- function shouldEscalateSearch(candidate) {
186
- if (!candidate) return true;
187
-
188
- return Math.abs(candidate.processedSpatialScore) > STANDARD_FAST_PATH_RESIDUAL_THRESHOLD ||
189
- Math.max(0, candidate.processedGradientScore) > STANDARD_FAST_PATH_GRADIENT_THRESHOLD;
190
- }
191
-
192
- function shouldSearchNearbyStandardCandidate(candidate, originalImageData) {
193
- if (!candidate) return true;
199
+ function shouldEscalateSearch(candidate) {
200
+ if (!candidate) return true;
201
+
202
+ return Math.abs(candidate.processedSpatialScore) > STANDARD_FAST_PATH_RESIDUAL_THRESHOLD ||
203
+ Math.max(0, candidate.processedGradientScore) > STANDARD_FAST_PATH_GRADIENT_THRESHOLD;
204
+ }
205
+
206
+ function shouldExpandCatalogForWeakOriginalStandardEvidence(candidate) {
207
+ if (!candidate) return true;
208
+ if (!isStandardCandidateSource(candidate)) return false;
209
+ if (candidate?.provenance?.catalogVariant === true) return false;
210
+
211
+ const originalGradient = Number(candidate.originalGradientScore);
212
+ if (!Number.isFinite(originalGradient)) return false;
213
+
214
+ return originalGradient < STANDARD_EXPAND_CATALOG_MIN_ORIGINAL_GRADIENT;
215
+ }
216
+
217
+ function shouldSearchNearbyStandardCandidate(candidate, originalImageData) {
218
+ if (!candidate) return true;
194
219
 
195
220
  return Number(candidate.position?.width) >= 72 &&
196
221
  Number(originalImageData?.height) > Number(originalImageData?.width) * 1.25 &&
@@ -262,6 +287,22 @@ function isCleanStandardAlphaCandidate(candidate) {
262
287
  Math.max(0, candidate.processedGradientScore) <= STANDARD_FAST_PATH_GRADIENT_THRESHOLD;
263
288
  }
264
289
 
290
+ function isStrictFixedCoreCandidate(candidate) {
291
+ if (!candidate?.accepted) return false;
292
+
293
+ const processedSpatial = Number(candidate.processedSpatialScore);
294
+ if (!Number.isFinite(processedSpatial)) return false;
295
+
296
+ return Math.abs(processedSpatial) <= FIXED_CORE_MAX_ACCEPTED_SPATIAL_RESIDUAL;
297
+ }
298
+
299
+ function isCleanWeakAlphaPriorityCandidate(candidate) {
300
+ if (!candidate?.accepted) return false;
301
+
302
+ return Math.abs(candidate.processedSpatialScore) <= STANDARD_FAST_PATH_RESIDUAL_THRESHOLD &&
303
+ Math.max(0, candidate.processedGradientScore) <= WEAK_ALPHA_PRIORITY_CLEAN_GRADIENT_THRESHOLD;
304
+ }
305
+
265
306
  function normalizeAlphaPriorityGains(alphaPriorityGains) {
266
307
  const gains = Array.isArray(alphaPriorityGains) && alphaPriorityGains.length > 0
267
308
  ? alphaPriorityGains
@@ -316,7 +357,7 @@ function evaluateStandardTrialForSeed({
316
357
  fallbackTrial = trial;
317
358
  }
318
359
  if (alphaGain < 1) {
319
- if (isWeakAlphaPrioritySeed(seed) && isCleanStandardAlphaCandidate(trial)) {
360
+ if (isWeakAlphaPrioritySeed(seed) && isCleanWeakAlphaPriorityCandidate(trial)) {
320
361
  return trial;
321
362
  }
322
363
  continue;
@@ -370,8 +411,19 @@ export function evaluateRestorationCandidate({
370
411
  });
371
412
  const texturePenalty = textureAssessment.texturePenalty;
372
413
  const gradientDrop = originalScores.gradientScore - processedScores.gradientScore;
414
+ const strongStandardSignalNearBlackOverride =
415
+ isStandardCandidateSource({ source }) &&
416
+ alphaGain === 1 &&
417
+ originalScores.spatialScore >= STANDARD_STRONG_SIGNAL_NEAR_BLACK_OVERRIDE_MIN_SPATIAL_SCORE &&
418
+ originalScores.gradientScore >= STANDARD_STRONG_SIGNAL_NEAR_BLACK_OVERRIDE_MIN_GRADIENT_SCORE &&
419
+ Math.abs(processedScores.spatialScore) <= STANDARD_STRONG_SIGNAL_NEAR_BLACK_OVERRIDE_MAX_RESIDUAL &&
420
+ processedScores.gradientScore <= STANDARD_STRONG_SIGNAL_NEAR_BLACK_OVERRIDE_MAX_GRADIENT &&
421
+ improvement >= STANDARD_STRONG_SIGNAL_NEAR_BLACK_OVERRIDE_MIN_IMPROVEMENT &&
422
+ gradientDrop >= STANDARD_STRONG_SIGNAL_NEAR_BLACK_OVERRIDE_MIN_GRADIENT_DROP &&
423
+ nearBlackIncrease <= STANDARD_STRONG_SIGNAL_NEAR_BLACK_OVERRIDE_MAX_NEAR_BLACK_INCREASE;
373
424
  const nearBlackIncreaseAllowed =
374
425
  nearBlackIncrease <= MAX_NEAR_BLACK_RATIO_INCREASE ||
426
+ strongStandardSignalNearBlackOverride ||
375
427
  (
376
428
  source === 'standard' &&
377
429
  originalScores.spatialScore >= STANDARD_TEXT_OVERLAP_MIN_SPATIAL_SCORE &&
@@ -500,6 +552,12 @@ function ensureCandidateImageData(candidate, originalImageData) {
500
552
  export function pickBetterCandidate(currentBest, candidate, minCostDelta = 0.005) {
501
553
  if (!candidate?.accepted) return currentBest;
502
554
  if (!currentBest) return candidate;
555
+ if (shouldPreserveCatalogOriginalSignal(currentBest, candidate)) {
556
+ return currentBest;
557
+ }
558
+ if (shouldPreferCatalogOriginalSignal(candidate, currentBest)) {
559
+ return candidate;
560
+ }
503
561
  if (shouldPreserveStrongStandardAnchor(currentBest, candidate)) {
504
562
  return currentBest;
505
563
  }
@@ -554,6 +612,75 @@ function hasStrongCanonicalAnchorSignal(candidate) {
554
612
  baseSpatial >= STANDARD_LOCAL_SHIFT_STRONG_BASE_SPATIAL_SCORE;
555
613
  }
556
614
 
615
+ function hasReliableCandidateOriginalSignal(candidate) {
616
+ return hasReliableStandardWatermarkSignal({
617
+ spatialScore: candidate?.originalSpatialScore,
618
+ gradientScore: candidate?.originalGradientScore
619
+ });
620
+ }
621
+
622
+ function hasMuchStrongerOriginalSignal(candidate, otherCandidate) {
623
+ const spatial = Number(candidate?.originalSpatialScore);
624
+ const gradient = Number(candidate?.originalGradientScore);
625
+ const otherSpatial = Number(otherCandidate?.originalSpatialScore);
626
+ const otherGradient = Number(otherCandidate?.originalGradientScore);
627
+ if (
628
+ !Number.isFinite(spatial) ||
629
+ !Number.isFinite(gradient) ||
630
+ !Number.isFinite(otherSpatial) ||
631
+ !Number.isFinite(otherGradient)
632
+ ) {
633
+ return false;
634
+ }
635
+
636
+ return spatial >= otherSpatial + STRONG_ORIGINAL_SIGNAL_SPATIAL_ADVANTAGE &&
637
+ gradient >= otherGradient + STRONG_ORIGINAL_SIGNAL_GRADIENT_ADVANTAGE;
638
+ }
639
+
640
+ function isCatalogVariantCandidate(candidate) {
641
+ return candidate?.provenance?.catalogVariant === true;
642
+ }
643
+
644
+ function isCurrentLargeMarginCatalogCandidate(candidate) {
645
+ return isCatalogVariantCandidate(candidate) &&
646
+ candidate?.config?.logoSize === 48 &&
647
+ candidate.config.marginRight === 96 &&
648
+ candidate.config.marginBottom === 96;
649
+ }
650
+
651
+ function isPreviewAnchorCandidate(candidate) {
652
+ return candidate?.provenance?.previewAnchor === true;
653
+ }
654
+
655
+ function shouldPreferCatalogOriginalSignal(candidate, currentBest) {
656
+ if (!isCurrentLargeMarginCatalogCandidate(candidate)) return false;
657
+ if (currentBest?.provenance?.localShift === true || currentBest?.provenance?.sizeJitter === true) {
658
+ return false;
659
+ }
660
+
661
+ const candidateReliable = hasReliableCandidateOriginalSignal(candidate);
662
+ const currentReliable = hasReliableCandidateOriginalSignal(currentBest);
663
+ if (candidateReliable && !currentReliable) return true;
664
+
665
+ return candidateReliable &&
666
+ currentReliable &&
667
+ isPreviewAnchorCandidate(currentBest) &&
668
+ hasMuchStrongerOriginalSignal(candidate, currentBest);
669
+ }
670
+
671
+ function shouldPreserveCatalogOriginalSignal(currentBest, candidate) {
672
+ if (!isCurrentLargeMarginCatalogCandidate(currentBest)) return false;
673
+ if (!isPreviewAnchorCandidate(candidate)) return false;
674
+
675
+ const currentReliable = hasReliableCandidateOriginalSignal(currentBest);
676
+ const candidateReliable = hasReliableCandidateOriginalSignal(candidate);
677
+ if (currentReliable && !candidateReliable) return true;
678
+
679
+ return currentReliable &&
680
+ candidateReliable &&
681
+ hasMuchStrongerOriginalSignal(currentBest, candidate);
682
+ }
683
+
557
684
  function hasWeakDriftEvidence(candidate) {
558
685
  const candidateSpatial = Number(candidate?.originalSpatialScore);
559
686
  const candidateGradient = Number(candidate?.originalGradientScore);
@@ -1158,7 +1285,8 @@ function resolveStandardAnchorSelection({
1158
1285
  alpha96Variants,
1159
1286
  getAlphaMap,
1160
1287
  resolveAlphaMap,
1161
- alphaPriorityGains
1288
+ alphaPriorityGains,
1289
+ forceCatalogVariants = false
1162
1290
  }) {
1163
1291
  let standardCandidateSeeds = buildStandardCandidateSeeds({
1164
1292
  originalImageData,
@@ -1178,8 +1306,16 @@ function resolveStandardAnchorSelection({
1178
1306
  });
1179
1307
 
1180
1308
  const shouldExpandStandardCatalog =
1309
+ forceCatalogVariants ||
1181
1310
  !standardSelection.hasReliableStandardMatch &&
1182
- (!standardSelection.standardTrial || shouldEscalateSearch(standardSelection.standardTrial));
1311
+ (
1312
+ !standardSelection.standardTrial ||
1313
+ shouldEscalateSearch(standardSelection.standardTrial) ||
1314
+ (
1315
+ matchOfficialGeminiImageSize(originalImageData.width, originalImageData.height) === null &&
1316
+ shouldExpandCatalogForWeakOriginalStandardEvidence(standardSelection.standardTrial)
1317
+ )
1318
+ );
1183
1319
 
1184
1320
  if (shouldExpandStandardCatalog) {
1185
1321
  standardCandidateSeeds = buildStandardCandidateSeeds({
@@ -1252,6 +1388,16 @@ function promoteBaseCandidate(baseCandidate, baseDecisionTier, candidate, {
1252
1388
  };
1253
1389
  }
1254
1390
 
1391
+ if (
1392
+ reliableMatch &&
1393
+ shouldPreferCatalogOriginalSignal(promotion.candidate, baseCandidate)
1394
+ ) {
1395
+ return {
1396
+ baseCandidate: promotion.candidate,
1397
+ baseDecisionTier: promotion.decisionTier
1398
+ };
1399
+ }
1400
+
1255
1401
  const previousCandidate = baseCandidate;
1256
1402
  const nextCandidate = pickBetterCandidate(baseCandidate, promotion.candidate, minCostDelta);
1257
1403
  return {
@@ -1334,7 +1480,8 @@ function refineSelectedAnchorCandidate({
1334
1480
  baseCandidate,
1335
1481
  baseDecisionTier,
1336
1482
  adaptiveConfidence,
1337
- alphaGainCandidates
1483
+ alphaGainCandidates,
1484
+ allowTemplateWarp = true
1338
1485
  }) {
1339
1486
  let selectedTrial = ensureCandidateImageData(baseCandidate, originalImageData);
1340
1487
  let alphaMap = baseCandidate.alphaMap;
@@ -1345,19 +1492,21 @@ function refineSelectedAnchorCandidate({
1345
1492
  let templateWarp = null;
1346
1493
  let selectedAlphaGain = baseCandidate.alphaGain ?? 1;
1347
1494
 
1348
- const warpCandidate = findBestTemplateWarp({
1349
- originalImageData,
1350
- alphaMap,
1351
- position,
1352
- baselineSpatialScore: selectedTrial.originalSpatialScore,
1353
- baselineGradientScore: selectedTrial.originalGradientScore,
1354
- shiftCandidates: selectedTrial.provenance?.previewAnchor === true
1355
- ? PREVIEW_TEMPLATE_ALIGN_SHIFTS
1356
- : TEMPLATE_ALIGN_SHIFTS,
1357
- scaleCandidates: selectedTrial.provenance?.previewAnchor === true
1358
- ? PREVIEW_TEMPLATE_ALIGN_SCALES
1359
- : TEMPLATE_ALIGN_SCALES
1360
- });
1495
+ const warpCandidate = allowTemplateWarp
1496
+ ? findBestTemplateWarp({
1497
+ originalImageData,
1498
+ alphaMap,
1499
+ position,
1500
+ baselineSpatialScore: selectedTrial.originalSpatialScore,
1501
+ baselineGradientScore: selectedTrial.originalGradientScore,
1502
+ shiftCandidates: selectedTrial.provenance?.previewAnchor === true
1503
+ ? PREVIEW_TEMPLATE_ALIGN_SHIFTS
1504
+ : TEMPLATE_ALIGN_SHIFTS,
1505
+ scaleCandidates: selectedTrial.provenance?.previewAnchor === true
1506
+ ? PREVIEW_TEMPLATE_ALIGN_SCALES
1507
+ : TEMPLATE_ALIGN_SCALES
1508
+ })
1509
+ : null;
1361
1510
  if (warpCandidate) {
1362
1511
  const warpedTrial = evaluateRestorationCandidate({
1363
1512
  originalImageData,
@@ -1433,6 +1582,7 @@ export function selectInitialCandidate({
1433
1582
  alpha96,
1434
1583
  getAlphaMap,
1435
1584
  allowAdaptiveSearch,
1585
+ allowAutomaticSearch = true,
1436
1586
  alphaGainCandidates,
1437
1587
  alphaPriorityGains = [1],
1438
1588
  alpha96Variants = null
@@ -1455,7 +1605,8 @@ export function selectInitialCandidate({
1455
1605
  alpha96Variants,
1456
1606
  getAlphaMap,
1457
1607
  resolveAlphaMap,
1458
- alphaPriorityGains
1608
+ alphaPriorityGains,
1609
+ forceCatalogVariants: !allowAutomaticSearch
1459
1610
  });
1460
1611
  let baseCandidate = null;
1461
1612
  let baseDecisionTier = 'insufficient';
@@ -1498,23 +1649,26 @@ export function selectInitialCandidate({
1498
1649
  alphaGainCandidates
1499
1650
  }));
1500
1651
 
1501
- const previewAnchorCandidate = searchBottomRightPreviewCandidate({
1502
- originalImageData,
1503
- config,
1504
- alpha48,
1505
- alpha96,
1506
- getAlphaMap,
1507
- resolveAlphaMap,
1508
- adaptiveConfidence
1509
- });
1510
- if (previewAnchorCandidate) {
1511
- ({
1512
- baseCandidate,
1513
- baseDecisionTier
1514
- } = promoteBaseCandidate(baseCandidate, baseDecisionTier, previewAnchorCandidate));
1652
+ if (allowAutomaticSearch) {
1653
+ const previewAnchorCandidate = searchBottomRightPreviewCandidate({
1654
+ originalImageData,
1655
+ config,
1656
+ alpha48,
1657
+ alpha96,
1658
+ getAlphaMap,
1659
+ resolveAlphaMap,
1660
+ adaptiveConfidence
1661
+ });
1662
+ if (previewAnchorCandidate) {
1663
+ ({
1664
+ baseCandidate,
1665
+ baseDecisionTier
1666
+ } = promoteBaseCandidate(baseCandidate, baseDecisionTier, previewAnchorCandidate));
1667
+ }
1515
1668
  }
1516
1669
 
1517
1670
  if (
1671
+ allowAutomaticSearch &&
1518
1672
  baseDecisionTier !== 'direct-match' &&
1519
1673
  !baseCandidate?.provenance?.previewAnchor &&
1520
1674
  shouldEscalateSearch(baseCandidate)
@@ -1536,6 +1690,7 @@ export function selectInitialCandidate({
1536
1690
  }
1537
1691
 
1538
1692
  if (
1693
+ allowAutomaticSearch &&
1539
1694
  baseDecisionTier !== 'direct-match' &&
1540
1695
  baseCandidate?.provenance?.sizeJitter === true &&
1541
1696
  !baseCandidate?.provenance?.previewAnchor &&
@@ -1571,7 +1726,7 @@ export function selectInitialCandidate({
1571
1726
  });
1572
1727
  };
1573
1728
 
1574
- if (shouldEvaluateAdaptive()) {
1729
+ if (allowAutomaticSearch && shouldEvaluateAdaptive()) {
1575
1730
  ({
1576
1731
  adaptive,
1577
1732
  adaptiveConfidence,
@@ -1593,8 +1748,9 @@ export function selectInitialCandidate({
1593
1748
  reliableMatch: hasReliableAdaptiveWatermarkSignal(adaptive)
1594
1749
  }));
1595
1750
  }
1596
-
1751
+
1597
1752
  if (
1753
+ allowAutomaticSearch &&
1598
1754
  !baseCandidate?.provenance?.previewAnchor &&
1599
1755
  !hasReliableAdaptiveWatermarkSignal(adaptive) &&
1600
1756
  shouldSearchNearbyStandardCandidate(baseCandidate, originalImageData)
@@ -1613,7 +1769,7 @@ export function selectInitialCandidate({
1613
1769
  }
1614
1770
 
1615
1771
  if (!baseCandidate) {
1616
- if (hasReliableStandardMatch && standardTrial) {
1772
+ if (hasReliableStandardMatch && standardTrial?.accepted) {
1617
1773
  baseCandidate = standardTrial;
1618
1774
  baseDecisionTier = 'direct-match';
1619
1775
  } else if (hasReliableAdaptiveWatermarkSignal(adaptive) && adaptiveTrial) {
@@ -1651,6 +1807,37 @@ export function selectInitialCandidate({
1651
1807
  baseDecisionTier = hasReliableStandardMatch ? 'direct-match' : 'validated-match';
1652
1808
  }
1653
1809
 
1810
+ if (!allowAutomaticSearch && !isStrictFixedCoreCandidate(baseCandidate)) {
1811
+ baseCandidate = null;
1812
+ baseDecisionTier = 'insufficient';
1813
+ }
1814
+
1815
+ if (!baseCandidate) {
1816
+ const validatedCandidate = allowAutomaticSearch
1817
+ ? pickBestValidatedCandidate([standardTrial, adaptiveTrial])
1818
+ : null;
1819
+ if (!validatedCandidate) {
1820
+ return {
1821
+ selectedTrial: null,
1822
+ source: 'skipped',
1823
+ alphaMap: fallbackAlphaMap,
1824
+ position,
1825
+ config,
1826
+ adaptiveConfidence,
1827
+ standardSpatialScore,
1828
+ standardGradientScore,
1829
+ templateWarp: null,
1830
+ alphaGain: 1,
1831
+ decisionTier: 'insufficient'
1832
+ };
1833
+ }
1834
+ baseCandidate = {
1835
+ ...validatedCandidate,
1836
+ source: `${validatedCandidate.source}+validated`
1837
+ };
1838
+ baseDecisionTier = 'validated-match';
1839
+ }
1840
+
1654
1841
  const {
1655
1842
  selectedTrial,
1656
1843
  source,
@@ -1665,7 +1852,8 @@ export function selectInitialCandidate({
1665
1852
  baseCandidate,
1666
1853
  baseDecisionTier,
1667
1854
  adaptiveConfidence,
1668
- alphaGainCandidates
1855
+ alphaGainCandidates,
1856
+ allowTemplateWarp: allowAutomaticSearch
1669
1857
  });
1670
1858
 
1671
1859
  return {
@@ -25,6 +25,11 @@ const GEMINI_3X_CURRENT_1K_LARGE_MARGIN_WATERMARK_CONFIG = Object.freeze({
25
25
  marginRight: 96,
26
26
  marginBottom: 96
27
27
  });
28
+ const KNOWN_FIXED_GEMINI_WATERMARK_CONFIGS_BY_SIZE = Object.freeze({
29
+ '1408x768': Object.freeze([
30
+ Object.freeze({ logoSize: 46, marginRight: 32, marginBottom: 32, fixedVariant: true })
31
+ ])
32
+ });
28
33
 
29
34
  // Gemini image generation does not emit arbitrary dimensions.
30
35
  // The models use a discrete set of official sizes, so the catalog is a better
@@ -204,6 +209,15 @@ function isOfficialOrKnownGeminiDimensions(width, height) {
204
209
  return matchOfficialGeminiImageSize(width, height) !== null;
205
210
  }
206
211
 
212
+ function resolveKnownFixedGeminiWatermarkConfigs(width, height) {
213
+ const normalizedWidth = normalizeDimension(width);
214
+ const normalizedHeight = normalizeDimension(height);
215
+ if (!normalizedWidth || !normalizedHeight) return [];
216
+
217
+ const configs = KNOWN_FIXED_GEMINI_WATERMARK_CONFIGS_BY_SIZE[`${normalizedWidth}x${normalizedHeight}`];
218
+ return Array.isArray(configs) ? configs.map((config) => ({ ...config })) : [];
219
+ }
220
+
207
221
  export function resolveOfficialGeminiSearchConfigs(
208
222
  width,
209
223
  height,
@@ -311,6 +325,7 @@ export function resolveGeminiWatermarkSearchConfigs(width, height, defaultConfig
311
325
  if (defaultConfig) {
312
326
  configs.push(defaultConfig);
313
327
  }
328
+ configs.push(...resolveKnownFixedGeminiWatermarkConfigs(width, height));
314
329
  configs.push(...resolveOfficialGeminiSearchConfigs(width, height));
315
330
  const currentLargeMarginVariant = createCurrentLargeMarginVariantConfig(defaultConfig, width, height);
316
331
  if (currentLargeMarginVariant) {