@pilio/gemini-watermark-remover 1.0.24 → 1.0.25

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.25",
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",
@@ -139,7 +139,7 @@ const PREVIEW_TEMPLATE_ALIGN_SHIFTS = [-1, -0.5, 0, 0.5, 1];
139
139
  const PREVIEW_TEMPLATE_ALIGN_SCALES = [0.985, 1, 1.015];
140
140
  const PREVIEW_ANCHOR_GAIN_SKIP_RESIDUAL_THRESHOLD = 0.22;
141
141
  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]);
142
+ 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
143
  const CURRENT_LARGE_MARGIN_ULTRA_WEAK_ALPHA_GAINS = Object.freeze([0.25, 0.3, 0.35, 0.4]);
144
144
  const STANDARD_ANCHOR_WEAK_ALPHA_RESCUE_GAINS = Object.freeze([0.55, 0.7, 0.85]);
145
145
  const STANDARD_ANCHOR_WEAK_RESCUE_MAX_SPATIAL = 0.35;
@@ -1044,6 +1044,24 @@ export function pickBetterCandidate(currentBest, candidate, minCostDelta = 0.005
1044
1044
  if (shouldPreserveStrongCanonical96AgainstWeakCurrentLargeMargin(candidate, currentBest)) {
1045
1045
  return candidate;
1046
1046
  }
1047
+ if (shouldPreserveStrongCanonical96AgainstWeakNewMargin(currentBest, candidate)) {
1048
+ return currentBest;
1049
+ }
1050
+ if (shouldPreserveStrongCanonical96AgainstWeakNewMargin(candidate, currentBest)) {
1051
+ return candidate;
1052
+ }
1053
+ if (shouldPreserveStandardAlphaCanonical96AgainstDarkGain(currentBest, candidate)) {
1054
+ return currentBest;
1055
+ }
1056
+ if (shouldPreserveStandardAlphaCanonical96AgainstDarkGain(candidate, currentBest)) {
1057
+ return candidate;
1058
+ }
1059
+ if (shouldPreserveBalancedAlphaCanonical96(currentBest, candidate)) {
1060
+ return currentBest;
1061
+ }
1062
+ if (shouldPreserveBalancedAlphaCanonical96(candidate, currentBest)) {
1063
+ return candidate;
1064
+ }
1047
1065
  if (shouldPreferCatalogOriginalSignal(candidate, currentBest)) {
1048
1066
  return candidate;
1049
1067
  }
@@ -1198,6 +1216,16 @@ function isDefault96GeometryCandidate(candidate) {
1198
1216
  candidate.config.marginBottom === 64;
1199
1217
  }
1200
1218
 
1219
+ function isNewMargin96Candidate(candidate) {
1220
+ return isStandardCandidateSource(candidate) &&
1221
+ candidate?.provenance?.localShift !== true &&
1222
+ candidate?.provenance?.sizeJitter !== true &&
1223
+ candidate?.provenance?.previewAnchor !== true &&
1224
+ candidate?.config?.logoSize === 96 &&
1225
+ candidate.config.marginRight === 192 &&
1226
+ candidate.config.marginBottom === 192;
1227
+ }
1228
+
1201
1229
  function shouldPreserveStrongCanonical96AgainstWeakCurrentLargeMargin(currentBest, candidate) {
1202
1230
  if (!isCanonicalDefault96Candidate(currentBest)) return false;
1203
1231
  if (!isCurrentLargeMarginCatalogCandidate(candidate)) return false;
@@ -1238,6 +1266,111 @@ function shouldPreserveStrongCanonical96AgainstWeakCurrentLargeMargin(currentBes
1238
1266
  candidateHasWeakOriginalSignal;
1239
1267
  }
1240
1268
 
1269
+ function shouldPreserveStrongCanonical96AgainstWeakNewMargin(currentBest, candidate) {
1270
+ if (!isCanonicalDefault96Candidate(currentBest)) return false;
1271
+ if (!isNewMargin96Candidate(candidate)) return false;
1272
+
1273
+ const currentSpatial = Number(currentBest.originalSpatialScore);
1274
+ const currentGradient = Number(currentBest.originalGradientScore);
1275
+ const candidateSpatial = Number(candidate.originalSpatialScore);
1276
+ const candidateGradient = Number(candidate.originalGradientScore);
1277
+ if (
1278
+ !Number.isFinite(currentSpatial) ||
1279
+ !Number.isFinite(currentGradient) ||
1280
+ !Number.isFinite(candidateSpatial) ||
1281
+ !Number.isFinite(candidateGradient)
1282
+ ) {
1283
+ return false;
1284
+ }
1285
+
1286
+ const currentHasStrongCanonicalSignal =
1287
+ currentSpatial >= 0.55 &&
1288
+ currentGradient >= 0.2;
1289
+ const candidateHasStrongNewMarginSignal =
1290
+ candidateSpatial >= 0.55 &&
1291
+ candidateGradient >= 0.3;
1292
+ const candidateHasWeakOrMuchWeakerSignal =
1293
+ candidateSpatial < STANDARD_VALIDATION_MIN_ORIGINAL_SPATIAL_SCORE ||
1294
+ candidateGradient < STANDARD_VALIDATION_MIN_ORIGINAL_GRADIENT_SCORE ||
1295
+ (
1296
+ candidateSpatial <= currentSpatial - STRONG_ORIGINAL_SIGNAL_SPATIAL_ADVANTAGE &&
1297
+ candidateGradient <= currentGradient - STRONG_ORIGINAL_SIGNAL_GRADIENT_ADVANTAGE
1298
+ );
1299
+
1300
+ return currentHasStrongCanonicalSignal &&
1301
+ !candidateHasStrongNewMarginSignal &&
1302
+ candidateHasWeakOrMuchWeakerSignal;
1303
+ }
1304
+
1305
+ function isBalancedCanonical96AlphaGain(candidate) {
1306
+ const alphaGain = Number(candidate?.alphaGain);
1307
+ return Number.isFinite(alphaGain) && alphaGain >= 1 && alphaGain <= 1.1;
1308
+ }
1309
+
1310
+ function shouldPreserveStandardAlphaCanonical96AgainstDarkGain(currentBest, candidate) {
1311
+ if (!sameCandidateAnchor(currentBest, candidate)) return false;
1312
+ if (!isCanonicalDefault96Candidate(currentBest)) return false;
1313
+
1314
+ const currentAlphaGain = Number(currentBest?.alphaGain);
1315
+ const candidateAlphaGain = Number(candidate?.alphaGain);
1316
+ if (
1317
+ !Number.isFinite(currentAlphaGain) ||
1318
+ !Number.isFinite(candidateAlphaGain) ||
1319
+ currentAlphaGain !== 1 ||
1320
+ candidateAlphaGain <= 1 ||
1321
+ candidate?.tooDark !== true
1322
+ ) {
1323
+ return false;
1324
+ }
1325
+
1326
+ const originalSpatial = Number(currentBest.originalSpatialScore);
1327
+ const originalGradient = Number(currentBest.originalGradientScore);
1328
+ const currentSpatial = Number(currentBest.processedSpatialScore);
1329
+ const currentGradient = Number(currentBest.processedGradientScore);
1330
+ if (
1331
+ !Number.isFinite(originalSpatial) ||
1332
+ !Number.isFinite(originalGradient) ||
1333
+ !Number.isFinite(currentSpatial) ||
1334
+ !Number.isFinite(currentGradient)
1335
+ ) {
1336
+ return false;
1337
+ }
1338
+
1339
+ return originalSpatial >= 0.55 &&
1340
+ originalGradient >= 0.2 &&
1341
+ currentSpatial >= 0 &&
1342
+ currentSpatial <= 0.35 &&
1343
+ Math.max(0, currentGradient) <= 0.08;
1344
+ }
1345
+
1346
+ function shouldPreserveBalancedAlphaCanonical96(currentBest, candidate) {
1347
+ if (!sameCandidateAnchor(currentBest, candidate)) return false;
1348
+ if (!isCanonicalDefault96Candidate(currentBest)) return false;
1349
+ if (!isBalancedCanonical96AlphaGain(currentBest)) return false;
1350
+
1351
+ const candidateAlphaGain = Number(candidate?.alphaGain);
1352
+ if (!Number.isFinite(candidateAlphaGain) || candidateAlphaGain <= 1.1) return false;
1353
+
1354
+ const originalSpatial = Number(currentBest.originalSpatialScore);
1355
+ const originalGradient = Number(currentBest.originalGradientScore);
1356
+ const currentSpatial = Number(currentBest.processedSpatialScore);
1357
+ const currentGradient = Number(currentBest.processedGradientScore);
1358
+ if (
1359
+ !Number.isFinite(originalSpatial) ||
1360
+ !Number.isFinite(originalGradient) ||
1361
+ !Number.isFinite(currentSpatial) ||
1362
+ !Number.isFinite(currentGradient)
1363
+ ) {
1364
+ return false;
1365
+ }
1366
+
1367
+ return originalSpatial >= 0.55 &&
1368
+ originalGradient >= 0.2 &&
1369
+ currentSpatial >= 0 &&
1370
+ currentSpatial <= 0.22 &&
1371
+ Math.max(0, currentGradient) <= 0.1;
1372
+ }
1373
+
1241
1374
  function hasWeakDriftEvidence(candidate) {
1242
1375
  const candidateSpatial = Number(candidate?.originalSpatialScore);
1243
1376
  const candidateGradient = Number(candidate?.originalGradientScore);
@@ -22,8 +22,12 @@ import { createSelectionDebugSummary } from './selectionDebug.js';
22
22
  import {
23
23
  calculateWatermarkPosition,
24
24
  detectWatermarkConfig,
25
- resolveInitialStandardConfig
26
- } from './watermarkConfig.js';
25
+ resolveInitialStandardConfig
26
+ } from './watermarkConfig.js';
27
+ import {
28
+ blurAlphaMap,
29
+ buildPreviewNeighborhoodPrior
30
+ } from './previewAlphaCalibration.js';
27
31
 
28
32
  const RESIDUAL_RECALIBRATION_THRESHOLD = 0.5;
29
33
  const MIN_SUPPRESSION_FOR_SKIP_RECALIBRATION = 0.18;
@@ -36,6 +40,7 @@ const SUBPIXEL_REFINE_SCALES = [0.99, 1, 1.01];
36
40
  const ALPHA_PARAMETER_GROUPS = Object.freeze([
37
41
  { name: 'gemini-weak-alpha-202606', alphaGain: 0.6, standardPriority: true },
38
42
  { name: 'gemini-standard-alpha', alphaGain: 1, standardPriority: true },
43
+ { name: 'gemini-balanced-strong-alpha-202606', alphaGain: 1.1 },
39
44
  { name: 'gemini-strong-alpha-202606', alphaGain: 1.15 },
40
45
  { name: 'gemini-strong-alpha-high-202606', alphaGain: 1.3 },
41
46
  { name: 'weak-alpha-extra-conservative', alphaGain: 0.45 },
@@ -136,6 +141,21 @@ const PREVIEW_BACKGROUND_CLEANUP_MIN_RESIDUAL = 0.3;
136
141
  const PREVIEW_BACKGROUND_CLEANUP_MAX_BORDER_STD = 24;
137
142
  const PREVIEW_BACKGROUND_CLEANUP_PAD = 8;
138
143
  const PREVIEW_BACKGROUND_CLEANUP_PRIOR_RADIUS = 10;
144
+ const SMOOTH_PRIOR_LOCATED_MIN_SIZE = 80;
145
+ const SMOOTH_PRIOR_LOCATED_MAX_SIZE = 160;
146
+ const SMOOTH_PRIOR_LOCATED_MIN_BORDER_MEAN = 120;
147
+ const SMOOTH_PRIOR_LOCATED_MIN_SPATIAL = 0.25;
148
+ const SMOOTH_PRIOR_LOCATED_MAX_GRADIENT = 0.22;
149
+ const SMOOTH_PRIOR_LOCATED_MIN_SPATIAL_IMPROVEMENT = 0.16;
150
+ const SMOOTH_PRIOR_LOCATED_MAX_GRADIENT_DRIFT = 0.05;
151
+ const SMOOTH_PRIOR_LOCATED_MIN_ARTIFACT_IMPROVEMENT = 0.025;
152
+ const SMOOTH_PRIOR_LOCATED_MAX_ACCEPTED_GRADIENT = 0.18;
153
+ const SMOOTH_PRIOR_LOCATED_PRESETS = Object.freeze([
154
+ { radius: 24, threshold: 0, blurRadius: 0, strength: 0.75, gamma: 0.45 },
155
+ { radius: 36, threshold: 0, blurRadius: 0, strength: 0.75, gamma: 0.45 },
156
+ { radius: 24, threshold: 0.01, blurRadius: 0, strength: 0.75, gamma: 0.45 },
157
+ { radius: 36, threshold: 0.01, blurRadius: 0, strength: 0.75, gamma: 0.45 }
158
+ ]);
139
159
  const OVER_SUBTRACTION_SPATIAL_THRESHOLD = -0.25;
140
160
  const OVER_SUBTRACTION_GRADIENT_THRESHOLD = 0.35;
141
161
  const OVER_SUBTRACTION_MIN_ABS_SPATIAL_IMPROVEMENT = 0.08;
@@ -1370,7 +1390,7 @@ function expandPosition(position, imageData, pad) {
1370
1390
  };
1371
1391
  }
1372
1392
 
1373
- function measureOuterBorderLuminanceStd(imageData, position, margin = 10) {
1393
+ function measureOuterBorderLuminanceStats(imageData, position, margin = 10) {
1374
1394
  let sum = 0;
1375
1395
  let sq = 0;
1376
1396
  let count = 0;
@@ -1399,16 +1419,219 @@ function measureOuterBorderLuminanceStd(imageData, position, margin = 10) {
1399
1419
  }
1400
1420
  }
1401
1421
 
1402
- if (count <= 0) return Number.POSITIVE_INFINITY;
1422
+ if (count <= 0) {
1423
+ return {
1424
+ mean: Number.POSITIVE_INFINITY,
1425
+ std: Number.POSITIVE_INFINITY,
1426
+ count
1427
+ };
1428
+ }
1403
1429
 
1404
1430
  const mean = sum / count;
1405
- return Math.sqrt(Math.max(0, sq / count - mean * mean));
1431
+ return {
1432
+ mean,
1433
+ std: Math.sqrt(Math.max(0, sq / count - mean * mean)),
1434
+ count
1435
+ };
1436
+ }
1437
+
1438
+ function measureOuterBorderLuminanceStd(imageData, position, margin = 10) {
1439
+ return measureOuterBorderLuminanceStats(imageData, position, margin).std;
1440
+ }
1441
+
1442
+ function clamp01(value) {
1443
+ if (!Number.isFinite(value)) return 0;
1444
+ if (value <= 0) return 0;
1445
+ if (value >= 1) return 1;
1446
+ return value;
1406
1447
  }
1407
1448
 
1408
1449
  function clampChannel(value) {
1409
1450
  return Math.max(0, Math.min(255, Math.round(value)));
1410
1451
  }
1411
1452
 
1453
+ function estimateAlphaMapFromBackgroundPrior({
1454
+ originalImageData,
1455
+ priorImageData,
1456
+ position,
1457
+ threshold,
1458
+ blurRadius
1459
+ }) {
1460
+ const alphaMap = new Float32Array(position.width * position.height);
1461
+
1462
+ for (let row = 0; row < position.height; row++) {
1463
+ for (let col = 0; col < position.width; col++) {
1464
+ const localIndex = row * position.width + col;
1465
+ const pixelIndex = ((position.y + row) * originalImageData.width + position.x + col) * 4;
1466
+ let estimatedAlpha = 0;
1467
+
1468
+ for (let channel = 0; channel < 3; channel++) {
1469
+ const denominator = 255 - priorImageData.data[pixelIndex + channel];
1470
+ if (denominator <= 2) continue;
1471
+
1472
+ estimatedAlpha = Math.max(
1473
+ estimatedAlpha,
1474
+ (originalImageData.data[pixelIndex + channel] - priorImageData.data[pixelIndex + channel]) / denominator
1475
+ );
1476
+ }
1477
+
1478
+ alphaMap[localIndex] = Math.min(0.9, clamp01((estimatedAlpha - threshold) * 1.2));
1479
+ }
1480
+ }
1481
+
1482
+ return blurRadius > 0
1483
+ ? blurAlphaMap(alphaMap, position.width, blurRadius)
1484
+ : alphaMap;
1485
+ }
1486
+
1487
+ function applyEstimatedPriorBlend({
1488
+ sourceImageData,
1489
+ priorImageData,
1490
+ estimatedAlphaMap,
1491
+ position,
1492
+ strength,
1493
+ gamma
1494
+ }) {
1495
+ const candidate = cloneImageData(sourceImageData);
1496
+
1497
+ for (let row = 0; row < position.height; row++) {
1498
+ for (let col = 0; col < position.width; col++) {
1499
+ const localIndex = row * position.width + col;
1500
+ const alpha = estimatedAlphaMap[localIndex];
1501
+ if (alpha <= 0.005) continue;
1502
+
1503
+ const blend = clamp01(Math.pow(alpha, gamma) * strength);
1504
+ if (blend <= 0.005) continue;
1505
+
1506
+ const pixelIndex = ((position.y + row) * sourceImageData.width + position.x + col) * 4;
1507
+ for (let channel = 0; channel < 3; channel++) {
1508
+ candidate.data[pixelIndex + channel] = clampChannel(
1509
+ sourceImageData.data[pixelIndex + channel] * (1 - blend) +
1510
+ priorImageData.data[pixelIndex + channel] * blend
1511
+ );
1512
+ }
1513
+ }
1514
+ }
1515
+
1516
+ return candidate;
1517
+ }
1518
+
1519
+ function refineSmoothLocatedResidualWithEstimatedPrior({
1520
+ originalImageData,
1521
+ currentImageData,
1522
+ alphaMap,
1523
+ position,
1524
+ source,
1525
+ alphaGain,
1526
+ baselineSpatialScore,
1527
+ baselineGradientScore
1528
+ }) {
1529
+ if (
1530
+ typeof source !== 'string' ||
1531
+ !source.includes('located-aggressive') ||
1532
+ position?.width < SMOOTH_PRIOR_LOCATED_MIN_SIZE ||
1533
+ position?.width > SMOOTH_PRIOR_LOCATED_MAX_SIZE ||
1534
+ baselineSpatialScore < SMOOTH_PRIOR_LOCATED_MIN_SPATIAL ||
1535
+ baselineGradientScore > SMOOTH_PRIOR_LOCATED_MAX_GRADIENT
1536
+ ) {
1537
+ return null;
1538
+ }
1539
+
1540
+ const borderStats = measureOuterBorderLuminanceStats(originalImageData, position, 16);
1541
+ if (borderStats.mean < SMOOTH_PRIOR_LOCATED_MIN_BORDER_MEAN) {
1542
+ return null;
1543
+ }
1544
+
1545
+ const baselineArtifacts = assessRemovalDiffArtifacts({
1546
+ originalImageData,
1547
+ candidateImageData: currentImageData,
1548
+ alphaMap,
1549
+ position,
1550
+ alphaGain
1551
+ });
1552
+ const baselineArtifactCost = Number(baselineArtifacts?.visualArtifactCost);
1553
+ if (!Number.isFinite(baselineArtifactCost)) return null;
1554
+
1555
+ let best = null;
1556
+ const priorByRadius = new Map();
1557
+
1558
+ for (const preset of SMOOTH_PRIOR_LOCATED_PRESETS) {
1559
+ let priorImageData = priorByRadius.get(preset.radius);
1560
+ if (!priorImageData) {
1561
+ priorImageData = buildPreviewNeighborhoodPrior({
1562
+ previewImageData: originalImageData,
1563
+ position,
1564
+ radius: preset.radius
1565
+ });
1566
+ priorByRadius.set(preset.radius, priorImageData);
1567
+ }
1568
+
1569
+ const estimatedAlphaMap = estimateAlphaMapFromBackgroundPrior({
1570
+ originalImageData,
1571
+ priorImageData,
1572
+ position,
1573
+ threshold: preset.threshold,
1574
+ blurRadius: preset.blurRadius
1575
+ });
1576
+ const candidateImageData = applyEstimatedPriorBlend({
1577
+ sourceImageData: currentImageData,
1578
+ priorImageData,
1579
+ estimatedAlphaMap,
1580
+ position,
1581
+ strength: preset.strength,
1582
+ gamma: preset.gamma
1583
+ });
1584
+ const spatialScore = computeRegionSpatialCorrelation({
1585
+ imageData: candidateImageData,
1586
+ alphaMap,
1587
+ region: { x: position.x, y: position.y, size: position.width }
1588
+ });
1589
+ const gradientScore = computeRegionGradientCorrelation({
1590
+ imageData: candidateImageData,
1591
+ alphaMap,
1592
+ region: { x: position.x, y: position.y, size: position.width }
1593
+ });
1594
+ const artifacts = assessRemovalDiffArtifacts({
1595
+ originalImageData,
1596
+ candidateImageData,
1597
+ alphaMap,
1598
+ position,
1599
+ alphaGain
1600
+ });
1601
+ const artifactCost = Number(artifacts?.visualArtifactCost);
1602
+ if (!Number.isFinite(artifactCost)) continue;
1603
+
1604
+ const spatialImprovement = Math.abs(baselineSpatialScore) - Math.abs(spatialScore);
1605
+ const gradientDrift = gradientScore - baselineGradientScore;
1606
+ const artifactImprovement = baselineArtifactCost - artifactCost;
1607
+ if (
1608
+ spatialImprovement < SMOOTH_PRIOR_LOCATED_MIN_SPATIAL_IMPROVEMENT ||
1609
+ gradientDrift > SMOOTH_PRIOR_LOCATED_MAX_GRADIENT_DRIFT ||
1610
+ artifactImprovement < SMOOTH_PRIOR_LOCATED_MIN_ARTIFACT_IMPROVEMENT ||
1611
+ gradientScore > SMOOTH_PRIOR_LOCATED_MAX_ACCEPTED_GRADIENT
1612
+ ) {
1613
+ continue;
1614
+ }
1615
+
1616
+ const cost = Math.abs(spatialScore) * 0.75 +
1617
+ Math.max(0, gradientScore) * 0.9 +
1618
+ artifactCost * 0.5;
1619
+ if (!best || cost < best.cost) {
1620
+ best = {
1621
+ imageData: candidateImageData,
1622
+ spatialScore,
1623
+ gradientScore,
1624
+ cost,
1625
+ artifactCost,
1626
+ borderStats,
1627
+ preset
1628
+ };
1629
+ }
1630
+ }
1631
+
1632
+ return best;
1633
+ }
1634
+
1412
1635
  function averageStripColor(imageData, {
1413
1636
  xFrom,
1414
1637
  xTo,
@@ -1873,6 +2096,53 @@ function refineLocatedAggressiveRemoval({
1873
2096
  return best;
1874
2097
  }
1875
2098
 
2099
+ function shouldSkipLocatedAggressiveForCleanCanonical96({
2100
+ config,
2101
+ alphaGain,
2102
+ originalSpatialScore,
2103
+ originalGradientScore,
2104
+ currentSpatialScore,
2105
+ currentGradientScore
2106
+ }) {
2107
+ if (
2108
+ config?.logoSize !== 96 ||
2109
+ config.marginRight !== 64 ||
2110
+ config.marginBottom !== 64
2111
+ ) {
2112
+ return false;
2113
+ }
2114
+
2115
+ const resolvedAlphaGain = Number(alphaGain);
2116
+ const originalSpatial = Number(originalSpatialScore);
2117
+ const originalGradient = Number(originalGradientScore);
2118
+ const currentSpatial = Number(currentSpatialScore);
2119
+ const currentGradient = Number(currentGradientScore);
2120
+ if (
2121
+ !Number.isFinite(resolvedAlphaGain) ||
2122
+ !Number.isFinite(originalSpatial) ||
2123
+ !Number.isFinite(originalGradient) ||
2124
+ !Number.isFinite(currentSpatial) ||
2125
+ !Number.isFinite(currentGradient)
2126
+ ) {
2127
+ return false;
2128
+ }
2129
+
2130
+ const cleanStandardAlpha =
2131
+ resolvedAlphaGain <= 1 &&
2132
+ currentSpatial >= 0 &&
2133
+ currentSpatial <= 0.35 &&
2134
+ Math.max(0, currentGradient) <= 0.08;
2135
+ const cleanBalancedAlpha =
2136
+ resolvedAlphaGain <= 1.1 &&
2137
+ currentSpatial >= 0 &&
2138
+ currentSpatial <= 0.22 &&
2139
+ Math.max(0, currentGradient) <= 0.1;
2140
+
2141
+ return originalSpatial >= 0.55 &&
2142
+ originalGradient >= 0.2 &&
2143
+ (cleanStandardAlpha || cleanBalancedAlpha);
2144
+ }
2145
+
1876
2146
  function shouldRelocateSmallFixedLocalAnchor({
1877
2147
  source,
1878
2148
  config,
@@ -2784,9 +3054,23 @@ export function processWatermarkImageData(imageData, options = {}) {
2784
3054
  }
2785
3055
 
2786
3056
  const locatedAggressiveStartedAt = nowMs();
3057
+ const locatedAggressiveResidualVisibility = assessWatermarkResidualVisibility({
3058
+ imageData: finalImageData,
3059
+ position,
3060
+ alphaMap
3061
+ });
2787
3062
  if (
2788
3063
  options.locatedAggressiveRemoval !== false &&
2789
- smallFixedLocalRelocated?.residualVisibility?.visible !== false
3064
+ smallFixedLocalRelocated?.residualVisibility?.visible !== false &&
3065
+ locatedAggressiveResidualVisibility?.visible !== false &&
3066
+ !shouldSkipLocatedAggressiveForCleanCanonical96({
3067
+ config,
3068
+ alphaGain,
3069
+ originalSpatialScore,
3070
+ originalGradientScore,
3071
+ currentSpatialScore: finalProcessedSpatialScore,
3072
+ currentGradientScore: finalProcessedGradientScore
3073
+ })
2790
3074
  ) {
2791
3075
  const aggressiveRefined = refineLocatedAggressiveRemoval({
2792
3076
  originalImageData,
@@ -2835,10 +3119,42 @@ export function processWatermarkImageData(imageData, options = {}) {
2835
3119
  : `${source}+located-aggressive`;
2836
3120
  }
2837
3121
  }
3122
+
3123
+ const smoothPriorStartedAt = nowMs();
3124
+ const smoothPriorRefined = refineSmoothLocatedResidualWithEstimatedPrior({
3125
+ originalImageData,
3126
+ currentImageData: finalImageData,
3127
+ alphaMap,
3128
+ position,
3129
+ source,
3130
+ alphaGain,
3131
+ baselineSpatialScore: finalProcessedSpatialScore,
3132
+ baselineGradientScore: finalProcessedGradientScore
3133
+ });
3134
+ if (smoothPriorRefined) {
3135
+ recordAlphaAdjustmentStage({
3136
+ stage: 'smooth-located-estimated-prior',
3137
+ fromAlphaGain: alphaGain,
3138
+ toAlphaGain: alphaGain,
3139
+ beforeSpatialScore: finalProcessedSpatialScore,
3140
+ beforeGradientScore: finalProcessedGradientScore,
3141
+ afterSpatialScore: smoothPriorRefined.spatialScore,
3142
+ afterGradientScore: smoothPriorRefined.gradientScore,
3143
+ suppressionGain: originalSpatialScore - smoothPriorRefined.spatialScore,
3144
+ cost: smoothPriorRefined.cost,
3145
+ allowSameAlphaGain: true
3146
+ });
3147
+ finalImageData = smoothPriorRefined.imageData;
3148
+ finalProcessedSpatialScore = smoothPriorRefined.spatialScore;
3149
+ finalProcessedGradientScore = smoothPriorRefined.gradientScore;
3150
+ suppressionGain = originalSpatialScore - finalProcessedSpatialScore;
3151
+ source = `${source}+smooth-prior`;
3152
+ }
2838
3153
  if (debugTimingsEnabled) {
2839
3154
  debugTimings.previewEdgeCleanupMs = previewEdgeCleanupElapsedMs;
2840
3155
  debugTimings.smallPreviewRefinementMs = nowMs() - smallPreviewRefinementStartedAt;
2841
3156
  debugTimings.locatedAggressiveRemovalMs = nowMs() - locatedAggressiveStartedAt;
3157
+ debugTimings.smoothPriorCleanupMs = nowMs() - smoothPriorStartedAt;
2842
3158
  debugTimings.totalMs = nowMs() - totalStartedAt;
2843
3159
  }
2844
3160