@pilio/gemini-watermark-remover 1.0.19 → 1.0.21
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 +17 -10
- package/README_zh.md +8 -1
- package/package.json +74 -3
- package/src/cli/gwrCli.js +1 -1
- package/src/cli/gwrRemoveCommand.js +82 -14
- package/src/core/adaptiveDetector.js +22 -3
- package/src/core/allenkFdncnnDenoise.js +326 -0
- package/src/core/allenkFdncnnNcnnModel.js +244 -0
- package/src/core/allenkFdncnnOnnxExport.js +337 -0
- package/src/core/allenkFdncnnOnnxRuntime.js +138 -0
- package/src/core/allenkFdncnnReferenceRuntime.js +169 -0
- package/src/core/alphaGradientMask.js +165 -0
- package/src/core/blendModes.js +9 -4
- package/src/core/candidateSelector.js +422 -33
- package/src/core/embeddedAlphaMaps.js +2 -0
- package/src/core/geminiSizeCatalog.js +44 -1
- package/src/core/restorationMetrics.js +44 -0
- package/src/core/watermarkEngine.js +2 -1
- package/src/core/watermarkProcessor.js +1048 -82
- package/src/sdk/index.d.ts +29 -1
- package/src/sdk/node.d.ts +13 -0
- package/src/sdk/node.js +7 -0
- package/src/sdk/video.d.ts +84 -0
- package/src/sdk/video.js +263 -0
- package/src/shared/debugFileHandoff.js +109 -0
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import { removeWatermark } from './blendModes.js';
|
|
2
2
|
import {
|
|
3
|
-
computeRegionGradientCorrelation,
|
|
4
|
-
computeRegionSpatialCorrelation,
|
|
5
|
-
warpAlphaMap
|
|
6
|
-
} from './adaptiveDetector.js';
|
|
3
|
+
computeRegionGradientCorrelation,
|
|
4
|
+
computeRegionSpatialCorrelation,
|
|
5
|
+
warpAlphaMap
|
|
6
|
+
} from './adaptiveDetector.js';
|
|
7
|
+
import {
|
|
8
|
+
createAlphaGradientMask,
|
|
9
|
+
getAlphaGradientWeight
|
|
10
|
+
} from './alphaGradientMask.js';
|
|
7
11
|
import {
|
|
8
12
|
calculateNearBlackRatio,
|
|
9
13
|
scoreRegion,
|
|
@@ -11,7 +15,8 @@ import {
|
|
|
11
15
|
} from './candidateSelector.js';
|
|
12
16
|
import {
|
|
13
17
|
assessAlphaBandHalo,
|
|
14
|
-
assessRemovalDiffArtifacts
|
|
18
|
+
assessRemovalDiffArtifacts,
|
|
19
|
+
assessWatermarkResidualVisibility
|
|
15
20
|
} from './restorationMetrics.js';
|
|
16
21
|
import { createSelectionDebugSummary } from './selectionDebug.js';
|
|
17
22
|
import {
|
|
@@ -39,13 +44,57 @@ const ALPHA_PARAMETER_GROUPS = Object.freeze([
|
|
|
39
44
|
{ name: 'weak-alpha-conservative', alphaGain: 0.55 }
|
|
40
45
|
]);
|
|
41
46
|
const ALPHA_GAIN_CANDIDATES = ALPHA_PARAMETER_GROUPS.map((group) => group.alphaGain);
|
|
47
|
+
const LOCATED_AGGRESSIVE_ALPHA_GAINS = Object.freeze([0.85, 1, 1.15, 1.3, 1.45, 1.7, 2, 2.4]);
|
|
42
48
|
const ENABLE_VISUAL_POST_PROCESSING = false;
|
|
43
49
|
const CATALOG_DARK_ALPHA_GAIN_CANDIDATES = Object.freeze([0.9, 0.85, 0.8, 0.95, 0.7, 0.6]);
|
|
44
50
|
const STANDARD_ALPHA_PRIORITY_GAINS = ALPHA_PARAMETER_GROUPS
|
|
45
51
|
.filter((group) => group.standardPriority === true)
|
|
46
52
|
.map((group) => group.alphaGain);
|
|
47
53
|
const PREVIEW_EDGE_CLEANUP_MAX_SIZE = 40;
|
|
48
|
-
const
|
|
54
|
+
const KNOWN_48_EDGE_CLEANUP_MIN_SIZE = 40;
|
|
55
|
+
const KNOWN_48_EDGE_CLEANUP_MAX_SIZE = 56;
|
|
56
|
+
const KNOWN_48_EDGE_CLEANUP_MIN_GRADIENT = 0.22;
|
|
57
|
+
const KNOWN_48_EDGE_CLEANUP_MAX_ABS_SPATIAL = 0.55;
|
|
58
|
+
const KNOWN_48_EDGE_CLEANUP_MIN_GRADIENT_IMPROVEMENT = 0.015;
|
|
59
|
+
const KNOWN_48_EDGE_CLEANUP_MAX_SPATIAL_DRIFT = 0.06;
|
|
60
|
+
const V2_SMALL_EDGE_CLEANUP_SIZE = 36;
|
|
61
|
+
const V2_SMALL_EDGE_CLEANUP_SIZE_TOLERANCE = 2;
|
|
62
|
+
const V2_SMALL_EDGE_CLEANUP_MIN_GRADIENT = 0.22;
|
|
63
|
+
const V2_SMALL_EDGE_CLEANUP_MAX_ABS_SPATIAL = 0.08;
|
|
64
|
+
const V2_SMALL_EDGE_CLEANUP_MIN_GRADIENT_IMPROVEMENT = 0.025;
|
|
65
|
+
const V2_SMALL_EDGE_CLEANUP_MAX_SPATIAL_DRIFT = 0.035;
|
|
66
|
+
const KNOWN_48_FLAT_FILL_MIN_GRADIENT = 0.28;
|
|
67
|
+
const KNOWN_48_FLAT_FILL_MAX_BACKGROUND_STD = 6;
|
|
68
|
+
const KNOWN_48_FLAT_FILL_MIN_GRADIENT_IMPROVEMENT = 0.045;
|
|
69
|
+
const KNOWN_48_FLAT_FILL_SECOND_PASS_MIN_GRADIENT_IMPROVEMENT = 0.025;
|
|
70
|
+
const KNOWN_48_FLAT_FILL_MAX_APPLIED_PASSES = 2;
|
|
71
|
+
const KNOWN_48_FLAT_FILL_MAX_SPATIAL_DRIFT = 0.12;
|
|
72
|
+
const KNOWN_48_FLAT_FILL_MAX_ACCEPTED_ABS_SPATIAL = 0.38;
|
|
73
|
+
const KNOWN_48_FLAT_FILL_PAD = 10;
|
|
74
|
+
const KNOWN_48_FLAT_FILL_OUTSIDE_ALPHA_MAX = 0.012;
|
|
75
|
+
const KNOWN_48_FLAT_FILL_PRESETS = Object.freeze([
|
|
76
|
+
{ name: 'edge', minAlpha: 0.012, maxAlpha: 0.5, strength: 0.9 },
|
|
77
|
+
{ name: 'wide', minAlpha: 0.008, maxAlpha: 0.99, strength: 0.92 },
|
|
78
|
+
{ name: 'hard', minAlpha: 0.004, maxAlpha: 0.99, strength: 1 }
|
|
79
|
+
]);
|
|
80
|
+
const NEW_MARGIN_96_FLAT_FILL_MIN_GRADIENT = 0.12;
|
|
81
|
+
const NEW_MARGIN_96_FLAT_FILL_MAX_BACKGROUND_STD = 7;
|
|
82
|
+
const NEW_MARGIN_96_FLAT_FILL_MIN_GRADIENT_IMPROVEMENT = 0.025;
|
|
83
|
+
const NEW_MARGIN_96_FLAT_FILL_MAX_SPATIAL_DRIFT = 0.08;
|
|
84
|
+
const NEW_MARGIN_96_FLAT_FILL_MAX_ACCEPTED_ABS_SPATIAL = 0.22;
|
|
85
|
+
const NEW_MARGIN_96_FLAT_FILL_PRESETS = Object.freeze([
|
|
86
|
+
{ name: 'edge', minAlpha: 0.006, maxAlpha: 0.45, strength: 0.75 }
|
|
87
|
+
]);
|
|
88
|
+
const KNOWN_48_LUMA_EDGE_MIN_GRADIENT = 0.2;
|
|
89
|
+
const KNOWN_48_LUMA_EDGE_MIN_GRADIENT_IMPROVEMENT = 0.02;
|
|
90
|
+
const KNOWN_48_LUMA_EDGE_MAX_SPATIAL_DRIFT = 0.04;
|
|
91
|
+
const KNOWN_48_LUMA_EDGE_MAX_ACCEPTED_ABS_SPATIAL = 0.38;
|
|
92
|
+
const KNOWN_48_LUMA_EDGE_PRESETS = Object.freeze([
|
|
93
|
+
{ name: 'soft', minAlpha: 0.012, maxAlpha: 0.7, referenceAlphaMax: 0.025, radius: 2, strength: 0.28, colorSigma: 34, maxDelta: 22 },
|
|
94
|
+
{ name: 'mid', minAlpha: 0.012, maxAlpha: 0.7, referenceAlphaMax: 0.04, radius: 3, strength: 0.42, colorSigma: 34, maxDelta: 32 },
|
|
95
|
+
{ name: 'wide', minAlpha: 0.012, maxAlpha: 0.7, referenceAlphaMax: 0.055, radius: 4, strength: 0.48, colorSigma: 34, maxDelta: 40 }
|
|
96
|
+
]);
|
|
97
|
+
const PREVIEW_EDGE_CLEANUP_SPATIAL_THRESHOLD = 0.08;
|
|
49
98
|
const PREVIEW_EDGE_CLEANUP_GRADIENT_THRESHOLD = 0.1;
|
|
50
99
|
const PREVIEW_EDGE_CLEANUP_MIN_GRADIENT_IMPROVEMENT = 0.03;
|
|
51
100
|
const PREVIEW_EDGE_CLEANUP_MAX_SPATIAL_DRIFT = 0.04;
|
|
@@ -76,6 +125,12 @@ const PREVIEW_EDGE_CLEANUP_AGGRESSIVE_PRESETS = Object.freeze([
|
|
|
76
125
|
maxAcceptedSpatial: 0.18
|
|
77
126
|
}
|
|
78
127
|
]);
|
|
128
|
+
const LOCATED_AGGRESSIVE_EDGE_PRESETS = Object.freeze([
|
|
129
|
+
{ minAlpha: 0.004, maxAlpha: 0.99, radius: 2, strength: 0.85, outsideAlphaMax: 0.08 },
|
|
130
|
+
{ minAlpha: 0.004, maxAlpha: 0.99, radius: 3, strength: 1.15, outsideAlphaMax: 0.12 },
|
|
131
|
+
{ minAlpha: 0.004, maxAlpha: 0.99, radius: 5, strength: 1.45, outsideAlphaMax: 0.18 },
|
|
132
|
+
{ minAlpha: 0.02, maxAlpha: 0.99, radius: 6, strength: 1.8, outsideAlphaMax: 0.25 }
|
|
133
|
+
]);
|
|
79
134
|
const PREVIEW_BACKGROUND_CLEANUP_MAX_SIZE = 52;
|
|
80
135
|
const PREVIEW_BACKGROUND_CLEANUP_MIN_RESIDUAL = 0.3;
|
|
81
136
|
const PREVIEW_BACKGROUND_CLEANUP_MAX_BORDER_STD = 24;
|
|
@@ -91,6 +146,10 @@ const WEAK_ALPHA_FINE_TUNE_MIN_ORIGINAL_SPATIAL = 0.45;
|
|
|
91
146
|
const WEAK_ALPHA_FINE_TUNE_MIN_POSITIVE_RESIDUAL = 0.05;
|
|
92
147
|
const WEAK_ALPHA_FINE_TUNE_MIN_ABS_SPATIAL_IMPROVEMENT = 0.04;
|
|
93
148
|
const WEAK_ALPHA_FINE_TUNE_MAX_GRADIENT_INCREASE = 0.08;
|
|
149
|
+
const STRONG_POSITIVE_FINE_TUNE_MIN_ORIGINAL_SPATIAL = 0.75;
|
|
150
|
+
const STRONG_POSITIVE_FINE_TUNE_MIN_ORIGINAL_GRADIENT = 0.65;
|
|
151
|
+
const STRONG_POSITIVE_FINE_TUNE_MIN_CURRENT_SPATIAL = 0.3;
|
|
152
|
+
const STRONG_POSITIVE_FINE_TUNE_EXTRA_GAINS = Object.freeze([0.95, 1]);
|
|
94
153
|
const CATALOG_ALPHA_DARK_FINE_TUNE_MIN_ORIGINAL_SPATIAL = 0.6;
|
|
95
154
|
const CATALOG_ALPHA_DARK_FINE_TUNE_MIN_ORIGINAL_GRADIENT = 0.45;
|
|
96
155
|
const CATALOG_ALPHA_DARK_FINE_TUNE_MAX_NEGATIVE_RESIDUAL = -0.12;
|
|
@@ -143,13 +202,20 @@ function normalizeMetaPosition(position) {
|
|
|
143
202
|
function normalizeMetaConfig(config) {
|
|
144
203
|
if (!config) return null;
|
|
145
204
|
|
|
146
|
-
const { logoSize, marginRight, marginBottom } = config;
|
|
147
|
-
if (![logoSize, marginRight, marginBottom].every((value) => Number.isFinite(value))) {
|
|
148
|
-
return null;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
return {
|
|
152
|
-
|
|
205
|
+
const { logoSize, marginRight, marginBottom } = config;
|
|
206
|
+
if (![logoSize, marginRight, marginBottom].every((value) => Number.isFinite(value))) {
|
|
207
|
+
return null;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return {
|
|
211
|
+
logoSize,
|
|
212
|
+
marginRight,
|
|
213
|
+
marginBottom,
|
|
214
|
+
...(typeof config.alphaVariant === 'string' && config.alphaVariant.length > 0
|
|
215
|
+
? { alphaVariant: config.alphaVariant }
|
|
216
|
+
: {})
|
|
217
|
+
};
|
|
218
|
+
}
|
|
153
219
|
|
|
154
220
|
function createWatermarkMeta({
|
|
155
221
|
position = null,
|
|
@@ -157,10 +223,11 @@ function createWatermarkMeta({
|
|
|
157
223
|
adaptiveConfidence = null,
|
|
158
224
|
originalSpatialScore = null,
|
|
159
225
|
originalGradientScore = null,
|
|
160
|
-
processedSpatialScore = null,
|
|
161
|
-
processedGradientScore = null,
|
|
162
|
-
suppressionGain = null,
|
|
163
|
-
|
|
226
|
+
processedSpatialScore = null,
|
|
227
|
+
processedGradientScore = null,
|
|
228
|
+
suppressionGain = null,
|
|
229
|
+
residualVisibility = null,
|
|
230
|
+
templateWarp = null,
|
|
164
231
|
alphaGain = 1,
|
|
165
232
|
passCount = 0,
|
|
166
233
|
attemptedPassCount = 0,
|
|
@@ -185,11 +252,12 @@ function createWatermarkMeta({
|
|
|
185
252
|
detection: {
|
|
186
253
|
adaptiveConfidence,
|
|
187
254
|
originalSpatialScore,
|
|
188
|
-
originalGradientScore,
|
|
189
|
-
processedSpatialScore,
|
|
190
|
-
processedGradientScore,
|
|
191
|
-
suppressionGain
|
|
192
|
-
|
|
255
|
+
originalGradientScore,
|
|
256
|
+
processedSpatialScore,
|
|
257
|
+
processedGradientScore,
|
|
258
|
+
suppressionGain,
|
|
259
|
+
residualVisibility
|
|
260
|
+
},
|
|
193
261
|
templateWarp: templateWarp ?? null,
|
|
194
262
|
alphaGain,
|
|
195
263
|
passCount,
|
|
@@ -470,6 +538,7 @@ function fineTuneWeakPositiveResidualAlpha({
|
|
|
470
538
|
currentGradientScore,
|
|
471
539
|
currentAlphaGain,
|
|
472
540
|
originalSpatialScore,
|
|
541
|
+
originalGradientScore,
|
|
473
542
|
originalNearBlackRatio
|
|
474
543
|
}) {
|
|
475
544
|
if (
|
|
@@ -483,10 +552,26 @@ function fineTuneWeakPositiveResidualAlpha({
|
|
|
483
552
|
const maxAllowedNearBlackRatio = Math.min(1, originalNearBlackRatio + MAX_NEAR_BLACK_RATIO_INCREASE);
|
|
484
553
|
let best = null;
|
|
485
554
|
const fineStepCount = Math.round(OVER_SUBTRACTION_FINE_ALPHA_WINDOW / OVER_SUBTRACTION_FINE_ALPHA_STEP);
|
|
555
|
+
const alphaGainCandidates = new Set();
|
|
486
556
|
|
|
487
557
|
for (let step = 1; step <= fineStepCount; step++) {
|
|
488
558
|
const alphaGain = Number((currentAlphaGain + step * OVER_SUBTRACTION_FINE_ALPHA_STEP).toFixed(2));
|
|
489
559
|
if (alphaGain >= 1) continue;
|
|
560
|
+
alphaGainCandidates.add(alphaGain);
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
if (
|
|
564
|
+
currentSpatialScore >= STRONG_POSITIVE_FINE_TUNE_MIN_CURRENT_SPATIAL &&
|
|
565
|
+
originalSpatialScore >= STRONG_POSITIVE_FINE_TUNE_MIN_ORIGINAL_SPATIAL &&
|
|
566
|
+
originalGradientScore >= STRONG_POSITIVE_FINE_TUNE_MIN_ORIGINAL_GRADIENT
|
|
567
|
+
) {
|
|
568
|
+
for (const alphaGain of STRONG_POSITIVE_FINE_TUNE_EXTRA_GAINS) {
|
|
569
|
+
alphaGainCandidates.add(alphaGain);
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
for (const alphaGain of alphaGainCandidates) {
|
|
574
|
+
if (alphaGain <= currentAlphaGain || alphaGain > 1) continue;
|
|
490
575
|
|
|
491
576
|
const candidate = cloneImageData(originalImageData);
|
|
492
577
|
removeWatermark(candidate, alphaMap, position, { alphaGain });
|
|
@@ -619,13 +704,31 @@ function fineTuneDarkCatalogAlpha({
|
|
|
619
704
|
return best;
|
|
620
705
|
}
|
|
621
706
|
|
|
622
|
-
function
|
|
707
|
+
function shouldRefineResidualEdge({
|
|
623
708
|
source,
|
|
624
709
|
position,
|
|
625
710
|
baselineSpatialScore,
|
|
626
711
|
baselineGradientScore,
|
|
627
|
-
baselinePositiveHalo
|
|
712
|
+
baselinePositiveHalo,
|
|
713
|
+
mode = 'preview'
|
|
628
714
|
}) {
|
|
715
|
+
if (mode === 'known-48') {
|
|
716
|
+
return position?.width >= KNOWN_48_EDGE_CLEANUP_MIN_SIZE &&
|
|
717
|
+
position?.width <= KNOWN_48_EDGE_CLEANUP_MAX_SIZE &&
|
|
718
|
+
Math.abs(baselineSpatialScore) <= KNOWN_48_EDGE_CLEANUP_MAX_ABS_SPATIAL &&
|
|
719
|
+
(
|
|
720
|
+
baselineGradientScore >= KNOWN_48_EDGE_CLEANUP_MIN_GRADIENT ||
|
|
721
|
+
baselinePositiveHalo >= PREVIEW_EDGE_CLEANUP_STRONG_HALO_THRESHOLD
|
|
722
|
+
);
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
if (mode === 'v2-small') {
|
|
726
|
+
return position?.width >= V2_SMALL_EDGE_CLEANUP_SIZE - V2_SMALL_EDGE_CLEANUP_SIZE_TOLERANCE &&
|
|
727
|
+
position?.width <= V2_SMALL_EDGE_CLEANUP_SIZE + V2_SMALL_EDGE_CLEANUP_SIZE_TOLERANCE &&
|
|
728
|
+
Math.abs(baselineSpatialScore) <= V2_SMALL_EDGE_CLEANUP_MAX_ABS_SPATIAL &&
|
|
729
|
+
baselineGradientScore >= V2_SMALL_EDGE_CLEANUP_MIN_GRADIENT;
|
|
730
|
+
}
|
|
731
|
+
|
|
629
732
|
return typeof source === 'string' &&
|
|
630
733
|
source.includes('preview-anchor') &&
|
|
631
734
|
position?.width >= 24 &&
|
|
@@ -645,11 +748,65 @@ function shouldUsePreviewAnchorFastCleanup(selectedTrial, position) {
|
|
|
645
748
|
position?.width >= 24 &&
|
|
646
749
|
position?.width <= PREVIEW_EDGE_CLEANUP_MAX_SIZE;
|
|
647
750
|
}
|
|
648
|
-
|
|
751
|
+
|
|
752
|
+
function isKnown48AnchorConfig(config) {
|
|
753
|
+
if (!config || config.logoSize < KNOWN_48_EDGE_CLEANUP_MIN_SIZE || config.logoSize > KNOWN_48_EDGE_CLEANUP_MAX_SIZE) {
|
|
754
|
+
return false;
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
const marginRight = Number(config.marginRight);
|
|
758
|
+
const marginBottom = Number(config.marginBottom);
|
|
759
|
+
if (!Number.isFinite(marginRight) || !Number.isFinite(marginBottom)) return false;
|
|
760
|
+
|
|
761
|
+
const isCurrentLargeMargin = Math.abs(marginRight - 96) <= 2 && Math.abs(marginBottom - 96) <= 2;
|
|
762
|
+
const isCurrentStandardMargin = marginRight >= 28 && marginRight <= 36 && marginBottom >= 28 && marginBottom <= 36;
|
|
763
|
+
return isCurrentLargeMargin || isCurrentStandardMargin;
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
function shouldUseKnown48EdgeCleanup({ selectedTrial, position, source }) {
|
|
767
|
+
if (selectedTrial?.provenance?.previewAnchor === true) return false;
|
|
768
|
+
if (position?.width < KNOWN_48_EDGE_CLEANUP_MIN_SIZE || position?.width > KNOWN_48_EDGE_CLEANUP_MAX_SIZE) return false;
|
|
769
|
+
if (!isKnown48AnchorConfig(selectedTrial?.config)) return false;
|
|
770
|
+
|
|
771
|
+
const sourceText = String(source || '');
|
|
772
|
+
return sourceText === 'standard' ||
|
|
773
|
+
sourceText.startsWith('standard+gain') ||
|
|
774
|
+
sourceText.includes('catalog') ||
|
|
775
|
+
sourceText.includes('fixed-local');
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
function isV2SmallAnchorConfig(config) {
|
|
779
|
+
if (!config || config.logoSize !== V2_SMALL_EDGE_CLEANUP_SIZE || config.alphaVariant !== 'v2') {
|
|
780
|
+
return false;
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
const marginRight = Number(config.marginRight);
|
|
784
|
+
const marginBottom = Number(config.marginBottom);
|
|
785
|
+
return Number.isFinite(marginRight) &&
|
|
786
|
+
Number.isFinite(marginBottom) &&
|
|
787
|
+
marginRight >= 48 &&
|
|
788
|
+
marginBottom >= 48;
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
function shouldUseV2SmallEdgeCleanup({ selectedTrial, position, source }) {
|
|
792
|
+
if (selectedTrial?.provenance?.previewAnchor === true) return false;
|
|
793
|
+
if (
|
|
794
|
+
position?.width < V2_SMALL_EDGE_CLEANUP_SIZE - V2_SMALL_EDGE_CLEANUP_SIZE_TOLERANCE ||
|
|
795
|
+
position?.width > V2_SMALL_EDGE_CLEANUP_SIZE + V2_SMALL_EDGE_CLEANUP_SIZE_TOLERANCE
|
|
796
|
+
) {
|
|
797
|
+
return false;
|
|
798
|
+
}
|
|
799
|
+
if (!isV2SmallAnchorConfig(selectedTrial?.config)) return false;
|
|
800
|
+
if (selectedTrial?.provenance?.catalogFamily !== 'gemini-v2-small') return false;
|
|
801
|
+
|
|
802
|
+
const sourceText = String(source || '');
|
|
803
|
+
return sourceText.includes('catalog');
|
|
804
|
+
}
|
|
805
|
+
|
|
649
806
|
function blendPreviewResidualEdge({
|
|
650
|
-
sourceImageData,
|
|
651
|
-
alphaMap,
|
|
652
|
-
position,
|
|
807
|
+
sourceImageData,
|
|
808
|
+
alphaMap,
|
|
809
|
+
position,
|
|
653
810
|
minAlpha,
|
|
654
811
|
maxAlpha,
|
|
655
812
|
radius,
|
|
@@ -657,14 +814,20 @@ function blendPreviewResidualEdge({
|
|
|
657
814
|
outsideAlphaMax
|
|
658
815
|
}) {
|
|
659
816
|
const candidate = cloneImageData(sourceImageData);
|
|
660
|
-
const { width: imageWidth, height: imageHeight, data } = sourceImageData;
|
|
661
|
-
const regionSize = position.width;
|
|
662
|
-
const maxAlphaSafe = Math.max(maxAlpha, 1e-6);
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
817
|
+
const { width: imageWidth, height: imageHeight, data } = sourceImageData;
|
|
818
|
+
const regionSize = position.width;
|
|
819
|
+
const maxAlphaSafe = Math.max(maxAlpha, 1e-6);
|
|
820
|
+
const edgeMask = createAlphaGradientMask({
|
|
821
|
+
alphaMap,
|
|
822
|
+
width: regionSize,
|
|
823
|
+
height: regionSize
|
|
824
|
+
});
|
|
825
|
+
|
|
826
|
+
for (let row = 0; row < regionSize; row++) {
|
|
827
|
+
for (let col = 0; col < regionSize; col++) {
|
|
828
|
+
const localIndex = row * regionSize + col;
|
|
829
|
+
const alpha = Math.abs(alphaMap[localIndex]);
|
|
830
|
+
if (alpha < minAlpha || alpha > maxAlpha) continue;
|
|
668
831
|
|
|
669
832
|
let sumR = 0;
|
|
670
833
|
let sumG = 0;
|
|
@@ -684,10 +847,10 @@ function blendPreviewResidualEdge({
|
|
|
684
847
|
continue;
|
|
685
848
|
}
|
|
686
849
|
|
|
687
|
-
let neighborAlpha = 0;
|
|
688
|
-
if (localY >= 0 && localX >= 0 && localY < regionSize && localX < regionSize) {
|
|
689
|
-
neighborAlpha = alphaMap[localY * regionSize + localX];
|
|
690
|
-
}
|
|
850
|
+
let neighborAlpha = 0;
|
|
851
|
+
if (localY >= 0 && localX >= 0 && localY < regionSize && localX < regionSize) {
|
|
852
|
+
neighborAlpha = Math.abs(alphaMap[localY * regionSize + localX]);
|
|
853
|
+
}
|
|
691
854
|
if (neighborAlpha > outsideAlphaMax) continue;
|
|
692
855
|
|
|
693
856
|
const distance = Math.sqrt(dx * dx + dy * dy) || 1;
|
|
@@ -702,15 +865,472 @@ function blendPreviewResidualEdge({
|
|
|
702
865
|
|
|
703
866
|
if (sumWeight <= 0) continue;
|
|
704
867
|
|
|
705
|
-
const
|
|
706
|
-
const
|
|
868
|
+
const edgeWeight = getAlphaGradientWeight(edgeMask, localIndex);
|
|
869
|
+
const blend = Math.max(0, Math.min(1, strength * alpha / maxAlphaSafe * edgeWeight));
|
|
870
|
+
const pixelIndex = ((position.y + row) * imageWidth + (position.x + col)) * 4;
|
|
707
871
|
candidate.data[pixelIndex] = Math.round(data[pixelIndex] * (1 - blend) + (sumR / sumWeight) * blend);
|
|
708
872
|
candidate.data[pixelIndex + 1] = Math.round(data[pixelIndex + 1] * (1 - blend) + (sumG / sumWeight) * blend);
|
|
709
873
|
candidate.data[pixelIndex + 2] = Math.round(data[pixelIndex + 2] * (1 - blend) + (sumB / sumWeight) * blend);
|
|
710
874
|
}
|
|
711
875
|
}
|
|
712
|
-
|
|
713
|
-
return candidate;
|
|
876
|
+
|
|
877
|
+
return candidate;
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
function solveLinear3x3(matrix, vector) {
|
|
881
|
+
const augmented = matrix.map((row, index) => [...row, vector[index]]);
|
|
882
|
+
|
|
883
|
+
for (let column = 0; column < 3; column++) {
|
|
884
|
+
let pivot = column;
|
|
885
|
+
for (let row = column + 1; row < 3; row++) {
|
|
886
|
+
if (Math.abs(augmented[row][column]) > Math.abs(augmented[pivot][column])) {
|
|
887
|
+
pivot = row;
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
if (Math.abs(augmented[pivot][column]) < 1e-8) return null;
|
|
892
|
+
if (pivot !== column) {
|
|
893
|
+
[augmented[pivot], augmented[column]] = [augmented[column], augmented[pivot]];
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
const divisor = augmented[column][column];
|
|
897
|
+
for (let next = column; next < 4; next++) {
|
|
898
|
+
augmented[column][next] /= divisor;
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
for (let row = 0; row < 3; row++) {
|
|
902
|
+
if (row === column) continue;
|
|
903
|
+
const factor = augmented[row][column];
|
|
904
|
+
for (let next = column; next < 4; next++) {
|
|
905
|
+
augmented[row][next] -= factor * augmented[column][next];
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
return [augmented[0][3], augmented[1][3], augmented[2][3]];
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
function fitColorPlane(samples, channel) {
|
|
914
|
+
const matrix = [
|
|
915
|
+
[0, 0, 0],
|
|
916
|
+
[0, 0, 0],
|
|
917
|
+
[0, 0, 0]
|
|
918
|
+
];
|
|
919
|
+
const vector = [0, 0, 0];
|
|
920
|
+
|
|
921
|
+
for (const sample of samples) {
|
|
922
|
+
const terms = [1, sample.x, sample.y];
|
|
923
|
+
const value = sample[channel];
|
|
924
|
+
for (let row = 0; row < 3; row++) {
|
|
925
|
+
vector[row] += terms[row] * value;
|
|
926
|
+
for (let column = 0; column < 3; column++) {
|
|
927
|
+
matrix[row][column] += terms[row] * terms[column];
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
return solveLinear3x3(matrix, vector);
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
function calculateBackgroundSampleStats(samples) {
|
|
936
|
+
let sum = 0;
|
|
937
|
+
let squareSum = 0;
|
|
938
|
+
|
|
939
|
+
for (const sample of samples) {
|
|
940
|
+
const luminance = 0.2126 * sample.r + 0.7152 * sample.g + 0.0722 * sample.b;
|
|
941
|
+
sum += luminance;
|
|
942
|
+
squareSum += luminance * luminance;
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
const count = samples.length;
|
|
946
|
+
const mean = count > 0 ? sum / count : 0;
|
|
947
|
+
return {
|
|
948
|
+
count,
|
|
949
|
+
mean,
|
|
950
|
+
std: count > 0 ? Math.sqrt(Math.max(0, squareSum / count - mean * mean)) : Number.POSITIVE_INFINITY
|
|
951
|
+
};
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
function sampleFlatBackgroundPixels(imageData, alphaMap, position, {
|
|
955
|
+
pad = KNOWN_48_FLAT_FILL_PAD,
|
|
956
|
+
outsideAlphaMax = KNOWN_48_FLAT_FILL_OUTSIDE_ALPHA_MAX
|
|
957
|
+
} = {}) {
|
|
958
|
+
const samples = [];
|
|
959
|
+
const left = Math.max(0, position.x - pad);
|
|
960
|
+
const top = Math.max(0, position.y - pad);
|
|
961
|
+
const right = Math.min(imageData.width - 1, position.x + position.width + pad - 1);
|
|
962
|
+
const bottom = Math.min(imageData.height - 1, position.y + position.height + pad - 1);
|
|
963
|
+
|
|
964
|
+
for (let y = top; y <= bottom; y++) {
|
|
965
|
+
for (let x = left; x <= right; x++) {
|
|
966
|
+
const inside = x >= position.x &&
|
|
967
|
+
x < position.x + position.width &&
|
|
968
|
+
y >= position.y &&
|
|
969
|
+
y < position.y + position.height;
|
|
970
|
+
|
|
971
|
+
if (inside) {
|
|
972
|
+
const row = y - position.y;
|
|
973
|
+
const col = x - position.x;
|
|
974
|
+
if (alphaMap[row * position.width + col] > outsideAlphaMax) {
|
|
975
|
+
continue;
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
const pixelIndex = (y * imageData.width + x) * 4;
|
|
980
|
+
samples.push({
|
|
981
|
+
x: (x - position.x) / Math.max(1, position.width),
|
|
982
|
+
y: (y - position.y) / Math.max(1, position.height),
|
|
983
|
+
r: imageData.data[pixelIndex],
|
|
984
|
+
g: imageData.data[pixelIndex + 1],
|
|
985
|
+
b: imageData.data[pixelIndex + 2]
|
|
986
|
+
});
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
return samples;
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
function applyFlatBackgroundFill({
|
|
994
|
+
sourceImageData,
|
|
995
|
+
alphaMap,
|
|
996
|
+
position,
|
|
997
|
+
minAlpha,
|
|
998
|
+
maxAlpha,
|
|
999
|
+
strength,
|
|
1000
|
+
maxBackgroundStd = KNOWN_48_FLAT_FILL_MAX_BACKGROUND_STD,
|
|
1001
|
+
edgeWeightFloor = 0.35
|
|
1002
|
+
}) {
|
|
1003
|
+
const samples = sampleFlatBackgroundPixels(sourceImageData, alphaMap, position);
|
|
1004
|
+
const stats = calculateBackgroundSampleStats(samples);
|
|
1005
|
+
if (
|
|
1006
|
+
stats.count < 24 ||
|
|
1007
|
+
stats.std > maxBackgroundStd
|
|
1008
|
+
) {
|
|
1009
|
+
return null;
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
const redPlane = fitColorPlane(samples, 'r');
|
|
1013
|
+
const greenPlane = fitColorPlane(samples, 'g');
|
|
1014
|
+
const bluePlane = fitColorPlane(samples, 'b');
|
|
1015
|
+
if (!redPlane || !greenPlane || !bluePlane) return null;
|
|
1016
|
+
|
|
1017
|
+
const candidate = cloneImageData(sourceImageData);
|
|
1018
|
+
const edgeMask = createAlphaGradientMask({
|
|
1019
|
+
alphaMap,
|
|
1020
|
+
width: position.width,
|
|
1021
|
+
height: position.height
|
|
1022
|
+
});
|
|
1023
|
+
for (let row = 0; row < position.height; row++) {
|
|
1024
|
+
for (let col = 0; col < position.width; col++) {
|
|
1025
|
+
const localIndex = row * position.width + col;
|
|
1026
|
+
const alpha = alphaMap[localIndex];
|
|
1027
|
+
if (alpha < minAlpha || alpha > maxAlpha) continue;
|
|
1028
|
+
|
|
1029
|
+
const x = col / Math.max(1, position.width);
|
|
1030
|
+
const y = row / Math.max(1, position.height);
|
|
1031
|
+
const target = [
|
|
1032
|
+
redPlane[0] + redPlane[1] * x + redPlane[2] * y,
|
|
1033
|
+
greenPlane[0] + greenPlane[1] * x + greenPlane[2] * y,
|
|
1034
|
+
bluePlane[0] + bluePlane[1] * x + bluePlane[2] * y
|
|
1035
|
+
];
|
|
1036
|
+
const pixelIndex = ((position.y + row) * candidate.width + position.x + col) * 4;
|
|
1037
|
+
const edgeWeight = getAlphaGradientWeight(edgeMask, localIndex, edgeWeightFloor);
|
|
1038
|
+
const blend = Math.max(0, Math.min(1, strength * Math.min(1, alpha / 0.2) * edgeWeight));
|
|
1039
|
+
for (let channel = 0; channel < 3; channel++) {
|
|
1040
|
+
candidate.data[pixelIndex + channel] = clampChannel(
|
|
1041
|
+
candidate.data[pixelIndex + channel] * (1 - blend) + target[channel] * blend
|
|
1042
|
+
);
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
return { imageData: candidate, stats };
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
function refineKnown48FlatBackgroundResidual({
|
|
1051
|
+
sourceImageData,
|
|
1052
|
+
alphaMap,
|
|
1053
|
+
position,
|
|
1054
|
+
baselineSpatialScore,
|
|
1055
|
+
baselineGradientScore,
|
|
1056
|
+
minGradientImprovement = KNOWN_48_FLAT_FILL_MIN_GRADIENT_IMPROVEMENT
|
|
1057
|
+
}) {
|
|
1058
|
+
if (
|
|
1059
|
+
position?.width < KNOWN_48_EDGE_CLEANUP_MIN_SIZE ||
|
|
1060
|
+
position?.width > KNOWN_48_EDGE_CLEANUP_MAX_SIZE ||
|
|
1061
|
+
baselineGradientScore < KNOWN_48_FLAT_FILL_MIN_GRADIENT
|
|
1062
|
+
) {
|
|
1063
|
+
return null;
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
let best = null;
|
|
1067
|
+
for (const preset of KNOWN_48_FLAT_FILL_PRESETS) {
|
|
1068
|
+
const filled = applyFlatBackgroundFill({
|
|
1069
|
+
sourceImageData,
|
|
1070
|
+
alphaMap,
|
|
1071
|
+
position,
|
|
1072
|
+
...preset
|
|
1073
|
+
});
|
|
1074
|
+
if (!filled) continue;
|
|
1075
|
+
|
|
1076
|
+
const spatialScore = computeRegionSpatialCorrelation({
|
|
1077
|
+
imageData: filled.imageData,
|
|
1078
|
+
alphaMap,
|
|
1079
|
+
region: { x: position.x, y: position.y, size: position.width }
|
|
1080
|
+
});
|
|
1081
|
+
const gradientScore = computeRegionGradientCorrelation({
|
|
1082
|
+
imageData: filled.imageData,
|
|
1083
|
+
alphaMap,
|
|
1084
|
+
region: { x: position.x, y: position.y, size: position.width }
|
|
1085
|
+
});
|
|
1086
|
+
const gradientImprovement = baselineGradientScore - gradientScore;
|
|
1087
|
+
const keptSpatial = Math.abs(spatialScore) <= Math.abs(baselineSpatialScore) + KNOWN_48_FLAT_FILL_MAX_SPATIAL_DRIFT;
|
|
1088
|
+
const acceptedSpatial = Math.abs(spatialScore) <= KNOWN_48_FLAT_FILL_MAX_ACCEPTED_ABS_SPATIAL;
|
|
1089
|
+
if (
|
|
1090
|
+
gradientImprovement < minGradientImprovement ||
|
|
1091
|
+
!keptSpatial ||
|
|
1092
|
+
!acceptedSpatial
|
|
1093
|
+
) {
|
|
1094
|
+
continue;
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
const cost = Math.abs(spatialScore) * 0.45 + Math.max(0, gradientScore);
|
|
1098
|
+
if (!best || cost < best.cost) {
|
|
1099
|
+
best = {
|
|
1100
|
+
imageData: filled.imageData,
|
|
1101
|
+
spatialScore,
|
|
1102
|
+
gradientScore,
|
|
1103
|
+
stats: filled.stats,
|
|
1104
|
+
preset: preset.name,
|
|
1105
|
+
cost
|
|
1106
|
+
};
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
return best;
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
function isNewMargin96AlphaVariantConfig(config) {
|
|
1114
|
+
return config?.logoSize === 96 &&
|
|
1115
|
+
config.marginRight === 192 &&
|
|
1116
|
+
config.marginBottom === 192 &&
|
|
1117
|
+
config.alphaVariant === '20260520';
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
function refineNewMargin96FlatBackgroundResidual({
|
|
1121
|
+
sourceImageData,
|
|
1122
|
+
alphaMap,
|
|
1123
|
+
position,
|
|
1124
|
+
config,
|
|
1125
|
+
alphaGain,
|
|
1126
|
+
baselineSpatialScore,
|
|
1127
|
+
baselineGradientScore
|
|
1128
|
+
}) {
|
|
1129
|
+
if (
|
|
1130
|
+
!isNewMargin96AlphaVariantConfig(config) ||
|
|
1131
|
+
alphaGain !== 1 ||
|
|
1132
|
+
position?.width !== 96 ||
|
|
1133
|
+
baselineGradientScore < NEW_MARGIN_96_FLAT_FILL_MIN_GRADIENT
|
|
1134
|
+
) {
|
|
1135
|
+
return null;
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
let best = null;
|
|
1139
|
+
for (const preset of NEW_MARGIN_96_FLAT_FILL_PRESETS) {
|
|
1140
|
+
const filled = applyFlatBackgroundFill({
|
|
1141
|
+
sourceImageData,
|
|
1142
|
+
alphaMap,
|
|
1143
|
+
position,
|
|
1144
|
+
maxBackgroundStd: NEW_MARGIN_96_FLAT_FILL_MAX_BACKGROUND_STD,
|
|
1145
|
+
edgeWeightFloor: 0.85,
|
|
1146
|
+
...preset
|
|
1147
|
+
});
|
|
1148
|
+
if (!filled) continue;
|
|
1149
|
+
|
|
1150
|
+
const spatialScore = computeRegionSpatialCorrelation({
|
|
1151
|
+
imageData: filled.imageData,
|
|
1152
|
+
alphaMap,
|
|
1153
|
+
region: { x: position.x, y: position.y, size: position.width }
|
|
1154
|
+
});
|
|
1155
|
+
const gradientScore = computeRegionGradientCorrelation({
|
|
1156
|
+
imageData: filled.imageData,
|
|
1157
|
+
alphaMap,
|
|
1158
|
+
region: { x: position.x, y: position.y, size: position.width }
|
|
1159
|
+
});
|
|
1160
|
+
const gradientImprovement = baselineGradientScore - gradientScore;
|
|
1161
|
+
const keptSpatial = Math.abs(spatialScore) <= Math.abs(baselineSpatialScore) + NEW_MARGIN_96_FLAT_FILL_MAX_SPATIAL_DRIFT;
|
|
1162
|
+
const acceptedSpatial = Math.abs(spatialScore) <= NEW_MARGIN_96_FLAT_FILL_MAX_ACCEPTED_ABS_SPATIAL;
|
|
1163
|
+
if (
|
|
1164
|
+
gradientImprovement < NEW_MARGIN_96_FLAT_FILL_MIN_GRADIENT_IMPROVEMENT ||
|
|
1165
|
+
!keptSpatial ||
|
|
1166
|
+
!acceptedSpatial
|
|
1167
|
+
) {
|
|
1168
|
+
continue;
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
const cost = Math.abs(spatialScore) * 0.6 + Math.max(0, gradientScore);
|
|
1172
|
+
if (!best || cost < best.cost) {
|
|
1173
|
+
best = {
|
|
1174
|
+
imageData: filled.imageData,
|
|
1175
|
+
spatialScore,
|
|
1176
|
+
gradientScore,
|
|
1177
|
+
stats: filled.stats,
|
|
1178
|
+
preset: preset.name,
|
|
1179
|
+
cost
|
|
1180
|
+
};
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
return best;
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
function pixelLuminance(data, index) {
|
|
1188
|
+
return 0.2126 * data[index] + 0.7152 * data[index + 1] + 0.0722 * data[index + 2];
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
function applyLumaEdgeCorrection({
|
|
1192
|
+
sourceImageData,
|
|
1193
|
+
alphaMap,
|
|
1194
|
+
position,
|
|
1195
|
+
minAlpha,
|
|
1196
|
+
maxAlpha,
|
|
1197
|
+
referenceAlphaMax,
|
|
1198
|
+
radius,
|
|
1199
|
+
strength,
|
|
1200
|
+
colorSigma,
|
|
1201
|
+
maxDelta
|
|
1202
|
+
}) {
|
|
1203
|
+
const candidate = cloneImageData(sourceImageData);
|
|
1204
|
+
const { data, width: imageWidth, height: imageHeight } = sourceImageData;
|
|
1205
|
+
const size = position.width;
|
|
1206
|
+
const colorSigmaSafe = Math.max(1, colorSigma);
|
|
1207
|
+
const edgeMask = createAlphaGradientMask({
|
|
1208
|
+
alphaMap,
|
|
1209
|
+
width: size,
|
|
1210
|
+
height: size
|
|
1211
|
+
});
|
|
1212
|
+
|
|
1213
|
+
for (let row = 0; row < size; row++) {
|
|
1214
|
+
for (let col = 0; col < size; col++) {
|
|
1215
|
+
const localIndex = row * size + col;
|
|
1216
|
+
const alpha = alphaMap[localIndex];
|
|
1217
|
+
if (alpha < minAlpha || alpha > maxAlpha) continue;
|
|
1218
|
+
|
|
1219
|
+
const x = position.x + col;
|
|
1220
|
+
const y = position.y + row;
|
|
1221
|
+
const pixelIndex = (y * imageWidth + x) * 4;
|
|
1222
|
+
const currentLum = pixelLuminance(data, pixelIndex);
|
|
1223
|
+
let weightedLum = 0;
|
|
1224
|
+
let sumWeight = 0;
|
|
1225
|
+
|
|
1226
|
+
for (let dy = -radius; dy <= radius; dy++) {
|
|
1227
|
+
for (let dx = -radius; dx <= radius; dx++) {
|
|
1228
|
+
if (dx === 0 && dy === 0) continue;
|
|
1229
|
+
const distanceSquared = dx * dx + dy * dy;
|
|
1230
|
+
if (distanceSquared > radius * radius) continue;
|
|
1231
|
+
|
|
1232
|
+
const localX = col + dx;
|
|
1233
|
+
const localY = row + dy;
|
|
1234
|
+
const pixelX = x + dx;
|
|
1235
|
+
const pixelY = y + dy;
|
|
1236
|
+
if (pixelX < 0 || pixelY < 0 || pixelX >= imageWidth || pixelY >= imageHeight) continue;
|
|
1237
|
+
|
|
1238
|
+
let neighborAlpha = 0;
|
|
1239
|
+
if (localX >= 0 && localY >= 0 && localX < size && localY < size) {
|
|
1240
|
+
neighborAlpha = alphaMap[localY * size + localX];
|
|
1241
|
+
}
|
|
1242
|
+
if (neighborAlpha > referenceAlphaMax && neighborAlpha >= alpha) continue;
|
|
1243
|
+
|
|
1244
|
+
const neighborIndex = (pixelY * imageWidth + pixelX) * 4;
|
|
1245
|
+
const neighborLum = pixelLuminance(data, neighborIndex);
|
|
1246
|
+
const colorDistance =
|
|
1247
|
+
Math.abs(data[pixelIndex] - data[neighborIndex]) +
|
|
1248
|
+
Math.abs(data[pixelIndex + 1] - data[neighborIndex + 1]) +
|
|
1249
|
+
Math.abs(data[pixelIndex + 2] - data[neighborIndex + 2]);
|
|
1250
|
+
const colorWeight = Math.exp(
|
|
1251
|
+
-(colorDistance * colorDistance) / (2 * colorSigmaSafe * colorSigmaSafe * 9)
|
|
1252
|
+
);
|
|
1253
|
+
const alphaWeight = neighborAlpha <= referenceAlphaMax ? 1.25 : 0.65;
|
|
1254
|
+
const distanceWeight = 1 / Math.sqrt(distanceSquared);
|
|
1255
|
+
const weight = colorWeight * alphaWeight * distanceWeight;
|
|
1256
|
+
weightedLum += neighborLum * weight;
|
|
1257
|
+
sumWeight += weight;
|
|
1258
|
+
}
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1261
|
+
if (sumWeight <= 0) continue;
|
|
1262
|
+
|
|
1263
|
+
const targetLum = weightedLum / sumWeight;
|
|
1264
|
+
const delta = Math.max(-maxDelta, Math.min(maxDelta, targetLum - currentLum)) * strength;
|
|
1265
|
+
const edgeWeight = getAlphaGradientWeight(edgeMask, localIndex);
|
|
1266
|
+
const scaledStrength = Math.min(1, Math.max(0, alpha / Math.max(maxAlpha, 1e-6))) * edgeWeight;
|
|
1267
|
+
const finalDelta = delta * scaledStrength;
|
|
1268
|
+
candidate.data[pixelIndex] = clampChannel(data[pixelIndex] + finalDelta);
|
|
1269
|
+
candidate.data[pixelIndex + 1] = clampChannel(data[pixelIndex + 1] + finalDelta);
|
|
1270
|
+
candidate.data[pixelIndex + 2] = clampChannel(data[pixelIndex + 2] + finalDelta);
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1273
|
+
|
|
1274
|
+
return candidate;
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
function refineKnown48LumaEdgeResidual({
|
|
1278
|
+
sourceImageData,
|
|
1279
|
+
alphaMap,
|
|
1280
|
+
position,
|
|
1281
|
+
baselineSpatialScore,
|
|
1282
|
+
baselineGradientScore
|
|
1283
|
+
}) {
|
|
1284
|
+
if (
|
|
1285
|
+
position?.width < KNOWN_48_EDGE_CLEANUP_MIN_SIZE ||
|
|
1286
|
+
position?.width > KNOWN_48_EDGE_CLEANUP_MAX_SIZE ||
|
|
1287
|
+
baselineGradientScore < KNOWN_48_LUMA_EDGE_MIN_GRADIENT
|
|
1288
|
+
) {
|
|
1289
|
+
return null;
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
let best = null;
|
|
1293
|
+
for (const preset of KNOWN_48_LUMA_EDGE_PRESETS) {
|
|
1294
|
+
const candidate = applyLumaEdgeCorrection({
|
|
1295
|
+
sourceImageData,
|
|
1296
|
+
alphaMap,
|
|
1297
|
+
position,
|
|
1298
|
+
...preset
|
|
1299
|
+
});
|
|
1300
|
+
const spatialScore = computeRegionSpatialCorrelation({
|
|
1301
|
+
imageData: candidate,
|
|
1302
|
+
alphaMap,
|
|
1303
|
+
region: { x: position.x, y: position.y, size: position.width }
|
|
1304
|
+
});
|
|
1305
|
+
const gradientScore = computeRegionGradientCorrelation({
|
|
1306
|
+
imageData: candidate,
|
|
1307
|
+
alphaMap,
|
|
1308
|
+
region: { x: position.x, y: position.y, size: position.width }
|
|
1309
|
+
});
|
|
1310
|
+
const gradientImprovement = baselineGradientScore - gradientScore;
|
|
1311
|
+
const keptSpatial = Math.abs(spatialScore) <= Math.abs(baselineSpatialScore) + KNOWN_48_LUMA_EDGE_MAX_SPATIAL_DRIFT;
|
|
1312
|
+
const acceptedSpatial = Math.abs(spatialScore) <= KNOWN_48_LUMA_EDGE_MAX_ACCEPTED_ABS_SPATIAL;
|
|
1313
|
+
if (
|
|
1314
|
+
gradientImprovement < KNOWN_48_LUMA_EDGE_MIN_GRADIENT_IMPROVEMENT ||
|
|
1315
|
+
!keptSpatial ||
|
|
1316
|
+
!acceptedSpatial
|
|
1317
|
+
) {
|
|
1318
|
+
continue;
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
const cost = Math.abs(spatialScore) * 0.45 + Math.max(0, gradientScore);
|
|
1322
|
+
if (!best || cost < best.cost) {
|
|
1323
|
+
best = {
|
|
1324
|
+
imageData: candidate,
|
|
1325
|
+
spatialScore,
|
|
1326
|
+
gradientScore,
|
|
1327
|
+
preset: preset.name,
|
|
1328
|
+
cost
|
|
1329
|
+
};
|
|
1330
|
+
}
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
return best;
|
|
714
1334
|
}
|
|
715
1335
|
|
|
716
1336
|
function expandPosition(position, imageData, pad) {
|
|
@@ -1006,10 +1626,11 @@ function refinePreviewResidualEdge({
|
|
|
1006
1626
|
position,
|
|
1007
1627
|
source,
|
|
1008
1628
|
baselineSpatialScore,
|
|
1009
|
-
baselineGradientScore,
|
|
1629
|
+
baselineGradientScore,
|
|
1010
1630
|
minGradientImprovement = PREVIEW_EDGE_CLEANUP_MIN_GRADIENT_IMPROVEMENT,
|
|
1011
1631
|
maxSpatialDrift = PREVIEW_EDGE_CLEANUP_MAX_SPATIAL_DRIFT,
|
|
1012
|
-
allowAggressivePresets = false
|
|
1632
|
+
allowAggressivePresets = false,
|
|
1633
|
+
mode = 'preview'
|
|
1013
1634
|
}) {
|
|
1014
1635
|
const baselineHalo = assessAlphaBandHalo({
|
|
1015
1636
|
imageData: sourceImageData,
|
|
@@ -1017,12 +1638,13 @@ function refinePreviewResidualEdge({
|
|
|
1017
1638
|
alphaMap
|
|
1018
1639
|
});
|
|
1019
1640
|
const baselinePositiveHalo = baselineHalo.positiveDeltaLum;
|
|
1020
|
-
if (!
|
|
1641
|
+
if (!shouldRefineResidualEdge({
|
|
1021
1642
|
source,
|
|
1022
1643
|
position,
|
|
1023
1644
|
baselineSpatialScore,
|
|
1024
1645
|
baselineGradientScore,
|
|
1025
|
-
baselinePositiveHalo
|
|
1646
|
+
baselinePositiveHalo,
|
|
1647
|
+
mode
|
|
1026
1648
|
})) {
|
|
1027
1649
|
return null;
|
|
1028
1650
|
}
|
|
@@ -1093,10 +1715,141 @@ function refinePreviewResidualEdge({
|
|
|
1093
1715
|
};
|
|
1094
1716
|
}
|
|
1095
1717
|
}
|
|
1096
|
-
|
|
1097
|
-
return best;
|
|
1098
|
-
}
|
|
1099
|
-
|
|
1718
|
+
|
|
1719
|
+
return best;
|
|
1720
|
+
}
|
|
1721
|
+
|
|
1722
|
+
function scoreLocatedAggressiveCandidate({
|
|
1723
|
+
imageData,
|
|
1724
|
+
alphaMap,
|
|
1725
|
+
position
|
|
1726
|
+
}) {
|
|
1727
|
+
const spatialScore = computeRegionSpatialCorrelation({
|
|
1728
|
+
imageData,
|
|
1729
|
+
alphaMap,
|
|
1730
|
+
region: { x: position.x, y: position.y, size: position.width }
|
|
1731
|
+
});
|
|
1732
|
+
const gradientScore = computeRegionGradientCorrelation({
|
|
1733
|
+
imageData,
|
|
1734
|
+
alphaMap,
|
|
1735
|
+
region: { x: position.x, y: position.y, size: position.width }
|
|
1736
|
+
});
|
|
1737
|
+
const nearBlackRatio = calculateNearBlackRatio(imageData, position);
|
|
1738
|
+
return {
|
|
1739
|
+
spatialScore,
|
|
1740
|
+
gradientScore,
|
|
1741
|
+
nearBlackRatio,
|
|
1742
|
+
cost: Math.abs(spatialScore) * 0.8 + Math.max(0, gradientScore) * 0.75
|
|
1743
|
+
};
|
|
1744
|
+
}
|
|
1745
|
+
|
|
1746
|
+
function pickLocatedAggressiveCandidate(currentBest, candidate) {
|
|
1747
|
+
if (!candidate) return currentBest;
|
|
1748
|
+
if (!currentBest || candidate.cost < currentBest.cost) return candidate;
|
|
1749
|
+
return currentBest;
|
|
1750
|
+
}
|
|
1751
|
+
|
|
1752
|
+
function buildLocatedAggressiveCandidate({
|
|
1753
|
+
sourceImageData,
|
|
1754
|
+
alphaMap,
|
|
1755
|
+
position,
|
|
1756
|
+
alphaGain,
|
|
1757
|
+
repeatCount
|
|
1758
|
+
}) {
|
|
1759
|
+
const candidate = cloneImageData(sourceImageData);
|
|
1760
|
+
for (let passIndex = 0; passIndex < repeatCount; passIndex++) {
|
|
1761
|
+
removeWatermark(candidate, alphaMap, position, { alphaGain });
|
|
1762
|
+
}
|
|
1763
|
+
return {
|
|
1764
|
+
imageData: candidate,
|
|
1765
|
+
alphaGain,
|
|
1766
|
+
repeatCount,
|
|
1767
|
+
...scoreLocatedAggressiveCandidate({
|
|
1768
|
+
imageData: candidate,
|
|
1769
|
+
alphaMap,
|
|
1770
|
+
position
|
|
1771
|
+
})
|
|
1772
|
+
};
|
|
1773
|
+
}
|
|
1774
|
+
|
|
1775
|
+
function refineLocatedAggressiveRemoval({
|
|
1776
|
+
originalImageData,
|
|
1777
|
+
currentImageData,
|
|
1778
|
+
alphaMap,
|
|
1779
|
+
position,
|
|
1780
|
+
currentSpatialScore,
|
|
1781
|
+
currentGradientScore,
|
|
1782
|
+
currentAlphaGain
|
|
1783
|
+
}) {
|
|
1784
|
+
if (!position || !alphaMap || position.width !== position.height) return null;
|
|
1785
|
+
const current = {
|
|
1786
|
+
imageData: currentImageData,
|
|
1787
|
+
alphaGain: currentAlphaGain,
|
|
1788
|
+
repeatCount: 0,
|
|
1789
|
+
spatialScore: currentSpatialScore,
|
|
1790
|
+
gradientScore: currentGradientScore,
|
|
1791
|
+
nearBlackRatio: calculateNearBlackRatio(currentImageData, position),
|
|
1792
|
+
cost: Math.abs(currentSpatialScore) * 0.8 + Math.max(0, currentGradientScore) * 0.75
|
|
1793
|
+
};
|
|
1794
|
+
let best = current;
|
|
1795
|
+
const gains = new Set([
|
|
1796
|
+
currentAlphaGain,
|
|
1797
|
+
...LOCATED_AGGRESSIVE_ALPHA_GAINS
|
|
1798
|
+
].filter((value) => Number.isFinite(value) && value > 0));
|
|
1799
|
+
|
|
1800
|
+
for (const alphaGain of gains) {
|
|
1801
|
+
for (const repeatCount of [1, 2]) {
|
|
1802
|
+
best = pickLocatedAggressiveCandidate(
|
|
1803
|
+
best,
|
|
1804
|
+
buildLocatedAggressiveCandidate({
|
|
1805
|
+
sourceImageData: originalImageData,
|
|
1806
|
+
alphaMap,
|
|
1807
|
+
position,
|
|
1808
|
+
alphaGain,
|
|
1809
|
+
repeatCount
|
|
1810
|
+
})
|
|
1811
|
+
);
|
|
1812
|
+
}
|
|
1813
|
+
}
|
|
1814
|
+
|
|
1815
|
+
const edgeSources = [{
|
|
1816
|
+
imageData: best.imageData,
|
|
1817
|
+
alphaGain: best.alphaGain,
|
|
1818
|
+
repeatCount: best.repeatCount
|
|
1819
|
+
}];
|
|
1820
|
+
if (best.imageData !== currentImageData) {
|
|
1821
|
+
edgeSources.push({
|
|
1822
|
+
imageData: currentImageData,
|
|
1823
|
+
alphaGain: currentAlphaGain,
|
|
1824
|
+
repeatCount: 0
|
|
1825
|
+
});
|
|
1826
|
+
}
|
|
1827
|
+
for (const edgeSource of edgeSources) {
|
|
1828
|
+
for (const preset of LOCATED_AGGRESSIVE_EDGE_PRESETS) {
|
|
1829
|
+
const edgeCandidate = blendPreviewResidualEdge({
|
|
1830
|
+
sourceImageData: edgeSource.imageData,
|
|
1831
|
+
alphaMap,
|
|
1832
|
+
position,
|
|
1833
|
+
...preset
|
|
1834
|
+
});
|
|
1835
|
+
best = pickLocatedAggressiveCandidate(best, {
|
|
1836
|
+
imageData: edgeCandidate,
|
|
1837
|
+
alphaGain: edgeSource.alphaGain,
|
|
1838
|
+
repeatCount: edgeSource.repeatCount,
|
|
1839
|
+
edgeCleanup: true,
|
|
1840
|
+
...scoreLocatedAggressiveCandidate({
|
|
1841
|
+
imageData: edgeCandidate,
|
|
1842
|
+
alphaMap,
|
|
1843
|
+
position
|
|
1844
|
+
})
|
|
1845
|
+
});
|
|
1846
|
+
}
|
|
1847
|
+
}
|
|
1848
|
+
|
|
1849
|
+
if (best.imageData === currentImageData) return null;
|
|
1850
|
+
return best;
|
|
1851
|
+
}
|
|
1852
|
+
|
|
1100
1853
|
export function processWatermarkImageData(imageData, options = {}) {
|
|
1101
1854
|
const totalStartedAt = nowMs();
|
|
1102
1855
|
const debugTimingsEnabled = options.debugTimings === true;
|
|
@@ -1145,10 +1898,11 @@ export function processWatermarkImageData(imageData, options = {}) {
|
|
|
1145
1898
|
afterSpatialScore,
|
|
1146
1899
|
afterGradientScore,
|
|
1147
1900
|
suppressionGain: stageSuppressionGain = null,
|
|
1148
|
-
cost = null
|
|
1901
|
+
cost = null,
|
|
1902
|
+
allowSameAlphaGain = false
|
|
1149
1903
|
}) => {
|
|
1150
1904
|
if (!stage || !Number.isFinite(fromAlphaGain) || !Number.isFinite(toAlphaGain)) return;
|
|
1151
|
-
if (Math.abs(fromAlphaGain - toAlphaGain) < 0.0001) return;
|
|
1905
|
+
if (!allowSameAlphaGain && Math.abs(fromAlphaGain - toAlphaGain) < 0.0001) return;
|
|
1152
1906
|
|
|
1153
1907
|
alphaAdjustmentStages.push({
|
|
1154
1908
|
stage,
|
|
@@ -1162,12 +1916,12 @@ export function processWatermarkImageData(imageData, options = {}) {
|
|
|
1162
1916
|
cost: Number.isFinite(cost) ? cost : null
|
|
1163
1917
|
});
|
|
1164
1918
|
};
|
|
1165
|
-
|
|
1166
|
-
const initialSelectionStartedAt = nowMs();
|
|
1167
|
-
|
|
1168
|
-
originalImageData,
|
|
1169
|
-
config,
|
|
1170
|
-
position,
|
|
1919
|
+
|
|
1920
|
+
const initialSelectionStartedAt = nowMs();
|
|
1921
|
+
let initialSelection = selectInitialCandidate({
|
|
1922
|
+
originalImageData,
|
|
1923
|
+
config,
|
|
1924
|
+
position,
|
|
1171
1925
|
alpha48,
|
|
1172
1926
|
alpha96,
|
|
1173
1927
|
alpha96Variants: options.alpha96Variants ?? null,
|
|
@@ -1177,9 +1931,37 @@ export function processWatermarkImageData(imageData, options = {}) {
|
|
|
1177
1931
|
alphaGainCandidates,
|
|
1178
1932
|
alphaPriorityGains
|
|
1179
1933
|
});
|
|
1180
|
-
if (
|
|
1181
|
-
|
|
1182
|
-
|
|
1934
|
+
if (
|
|
1935
|
+
!initialSelection.selectedTrial &&
|
|
1936
|
+
options.aggressiveLocatedFallback !== false
|
|
1937
|
+
) {
|
|
1938
|
+
const aggressiveSelection = selectInitialCandidate({
|
|
1939
|
+
originalImageData,
|
|
1940
|
+
config,
|
|
1941
|
+
position,
|
|
1942
|
+
alpha48,
|
|
1943
|
+
alpha96,
|
|
1944
|
+
alpha96Variants: options.alpha96Variants ?? null,
|
|
1945
|
+
getAlphaMap: options.getAlphaMap,
|
|
1946
|
+
allowAdaptiveSearch,
|
|
1947
|
+
allowAutomaticSearch: true,
|
|
1948
|
+
allowAggressiveStrongLocated: true,
|
|
1949
|
+
alphaGainCandidates,
|
|
1950
|
+
alphaPriorityGains
|
|
1951
|
+
});
|
|
1952
|
+
if (aggressiveSelection.selectedTrial) {
|
|
1953
|
+
initialSelection = {
|
|
1954
|
+
...aggressiveSelection,
|
|
1955
|
+
source: aggressiveSelection.source.includes('aggressive-located')
|
|
1956
|
+
? aggressiveSelection.source
|
|
1957
|
+
: `${aggressiveSelection.source}+aggressive-located`,
|
|
1958
|
+
decisionTier: aggressiveSelection.decisionTier || 'direct-match'
|
|
1959
|
+
};
|
|
1960
|
+
}
|
|
1961
|
+
}
|
|
1962
|
+
if (debugTimingsEnabled) {
|
|
1963
|
+
debugTimings.initialSelectionMs = nowMs() - initialSelectionStartedAt;
|
|
1964
|
+
}
|
|
1183
1965
|
|
|
1184
1966
|
if (!initialSelection.selectedTrial) {
|
|
1185
1967
|
if (debugTimingsEnabled) {
|
|
@@ -1216,6 +1998,16 @@ export function processWatermarkImageData(imageData, options = {}) {
|
|
|
1216
1998
|
|
|
1217
1999
|
const selectedTrial = initialSelection.selectedTrial;
|
|
1218
2000
|
const usePreviewAnchorFastCleanup = shouldUsePreviewAnchorFastCleanup(selectedTrial, position);
|
|
2001
|
+
const useKnown48EdgeCleanup = shouldUseKnown48EdgeCleanup({
|
|
2002
|
+
selectedTrial,
|
|
2003
|
+
position,
|
|
2004
|
+
source
|
|
2005
|
+
});
|
|
2006
|
+
const useV2SmallEdgeCleanup = shouldUseV2SmallEdgeCleanup({
|
|
2007
|
+
selectedTrial,
|
|
2008
|
+
position,
|
|
2009
|
+
source
|
|
2010
|
+
});
|
|
1219
2011
|
|
|
1220
2012
|
let finalImageData = selectedTrial.imageData;
|
|
1221
2013
|
|
|
@@ -1418,6 +2210,7 @@ export function processWatermarkImageData(imageData, options = {}) {
|
|
|
1418
2210
|
currentGradientScore: finalProcessedGradientScore,
|
|
1419
2211
|
currentAlphaGain: alphaGain,
|
|
1420
2212
|
originalSpatialScore,
|
|
2213
|
+
originalGradientScore,
|
|
1421
2214
|
originalNearBlackRatio: calculateNearBlackRatio(originalImageData, position)
|
|
1422
2215
|
});
|
|
1423
2216
|
if (weakAlphaFineTune) {
|
|
@@ -1503,7 +2296,24 @@ export function processWatermarkImageData(imageData, options = {}) {
|
|
|
1503
2296
|
source,
|
|
1504
2297
|
baselineSpatialScore: finalProcessedSpatialScore,
|
|
1505
2298
|
baselineGradientScore: finalProcessedGradientScore,
|
|
1506
|
-
|
|
2299
|
+
minGradientImprovement: useKnown48EdgeCleanup
|
|
2300
|
+
? KNOWN_48_EDGE_CLEANUP_MIN_GRADIENT_IMPROVEMENT
|
|
2301
|
+
: (
|
|
2302
|
+
useV2SmallEdgeCleanup
|
|
2303
|
+
? V2_SMALL_EDGE_CLEANUP_MIN_GRADIENT_IMPROVEMENT
|
|
2304
|
+
: PREVIEW_EDGE_CLEANUP_MIN_GRADIENT_IMPROVEMENT
|
|
2305
|
+
),
|
|
2306
|
+
maxSpatialDrift: useKnown48EdgeCleanup
|
|
2307
|
+
? KNOWN_48_EDGE_CLEANUP_MAX_SPATIAL_DRIFT
|
|
2308
|
+
: (
|
|
2309
|
+
useV2SmallEdgeCleanup
|
|
2310
|
+
? V2_SMALL_EDGE_CLEANUP_MAX_SPATIAL_DRIFT
|
|
2311
|
+
: PREVIEW_EDGE_CLEANUP_MAX_SPATIAL_DRIFT
|
|
2312
|
+
),
|
|
2313
|
+
allowAggressivePresets: usePreviewAnchorFastCleanup,
|
|
2314
|
+
mode: useKnown48EdgeCleanup
|
|
2315
|
+
? 'known-48'
|
|
2316
|
+
: (useV2SmallEdgeCleanup ? 'v2-small' : 'preview')
|
|
1507
2317
|
});
|
|
1508
2318
|
previewEdgeCleanupElapsedMs += nowMs() - previewEdgeStartedAt;
|
|
1509
2319
|
|
|
@@ -1511,13 +2321,13 @@ export function processWatermarkImageData(imageData, options = {}) {
|
|
|
1511
2321
|
return false;
|
|
1512
2322
|
}
|
|
1513
2323
|
|
|
1514
|
-
finalImageData = previewEdgeRefined.imageData;
|
|
1515
|
-
finalProcessedSpatialScore = previewEdgeRefined.spatialScore;
|
|
1516
|
-
finalProcessedGradientScore = previewEdgeRefined.gradientScore;
|
|
1517
|
-
suppressionGain = originalSpatialScore - finalProcessedSpatialScore;
|
|
1518
|
-
source = `${source}
|
|
1519
|
-
return true;
|
|
1520
|
-
};
|
|
2324
|
+
finalImageData = previewEdgeRefined.imageData;
|
|
2325
|
+
finalProcessedSpatialScore = previewEdgeRefined.spatialScore;
|
|
2326
|
+
finalProcessedGradientScore = previewEdgeRefined.gradientScore;
|
|
2327
|
+
suppressionGain = originalSpatialScore - finalProcessedSpatialScore;
|
|
2328
|
+
source = `${source}+${useV2SmallEdgeCleanup ? 'v2-small-edge-cleanup' : 'edge-cleanup'}`;
|
|
2329
|
+
return true;
|
|
2330
|
+
};
|
|
1521
2331
|
|
|
1522
2332
|
const subpixelStartedAt = nowMs();
|
|
1523
2333
|
if (
|
|
@@ -1570,13 +2380,111 @@ export function processWatermarkImageData(imageData, options = {}) {
|
|
|
1570
2380
|
}
|
|
1571
2381
|
|
|
1572
2382
|
let previewEdgeCleanupPassCount = 0;
|
|
1573
|
-
|
|
2383
|
+
const shouldRunEdgeCleanup = ENABLE_VISUAL_POST_PROCESSING || useKnown48EdgeCleanup || useV2SmallEdgeCleanup;
|
|
2384
|
+
while (shouldRunEdgeCleanup && previewEdgeCleanupPassCount < PREVIEW_EDGE_CLEANUP_MAX_APPLIED_PASSES) {
|
|
1574
2385
|
if (!applyPreviewEdgeCleanup()) {
|
|
1575
2386
|
break;
|
|
1576
2387
|
}
|
|
1577
2388
|
previewEdgeCleanupPassCount++;
|
|
1578
2389
|
}
|
|
1579
2390
|
|
|
2391
|
+
if (useKnown48EdgeCleanup) {
|
|
2392
|
+
let flatFillPassCount = 0;
|
|
2393
|
+
while (flatFillPassCount < KNOWN_48_FLAT_FILL_MAX_APPLIED_PASSES) {
|
|
2394
|
+
const flatBackgroundRefined = refineKnown48FlatBackgroundResidual({
|
|
2395
|
+
sourceImageData: finalImageData,
|
|
2396
|
+
alphaMap,
|
|
2397
|
+
position,
|
|
2398
|
+
baselineSpatialScore: finalProcessedSpatialScore,
|
|
2399
|
+
baselineGradientScore: finalProcessedGradientScore,
|
|
2400
|
+
minGradientImprovement: flatFillPassCount === 0
|
|
2401
|
+
? KNOWN_48_FLAT_FILL_MIN_GRADIENT_IMPROVEMENT
|
|
2402
|
+
: KNOWN_48_FLAT_FILL_SECOND_PASS_MIN_GRADIENT_IMPROVEMENT
|
|
2403
|
+
});
|
|
2404
|
+
|
|
2405
|
+
if (!flatBackgroundRefined) {
|
|
2406
|
+
break;
|
|
2407
|
+
}
|
|
2408
|
+
|
|
2409
|
+
recordAlphaAdjustmentStage({
|
|
2410
|
+
stage: 'known-48-flat-background-fill',
|
|
2411
|
+
fromAlphaGain: alphaGain,
|
|
2412
|
+
toAlphaGain: alphaGain,
|
|
2413
|
+
beforeSpatialScore: finalProcessedSpatialScore,
|
|
2414
|
+
beforeGradientScore: finalProcessedGradientScore,
|
|
2415
|
+
afterSpatialScore: flatBackgroundRefined.spatialScore,
|
|
2416
|
+
afterGradientScore: flatBackgroundRefined.gradientScore,
|
|
2417
|
+
suppressionGain: originalSpatialScore - flatBackgroundRefined.spatialScore,
|
|
2418
|
+
cost: flatBackgroundRefined.cost,
|
|
2419
|
+
allowSameAlphaGain: true
|
|
2420
|
+
});
|
|
2421
|
+
finalImageData = flatBackgroundRefined.imageData;
|
|
2422
|
+
finalProcessedSpatialScore = flatBackgroundRefined.spatialScore;
|
|
2423
|
+
finalProcessedGradientScore = flatBackgroundRefined.gradientScore;
|
|
2424
|
+
suppressionGain = originalSpatialScore - finalProcessedSpatialScore;
|
|
2425
|
+
source = `${source}+flat-fill`;
|
|
2426
|
+
flatFillPassCount++;
|
|
2427
|
+
}
|
|
2428
|
+
|
|
2429
|
+
const lumaEdgeRefined = refineKnown48LumaEdgeResidual({
|
|
2430
|
+
sourceImageData: finalImageData,
|
|
2431
|
+
alphaMap,
|
|
2432
|
+
position,
|
|
2433
|
+
baselineSpatialScore: finalProcessedSpatialScore,
|
|
2434
|
+
baselineGradientScore: finalProcessedGradientScore
|
|
2435
|
+
});
|
|
2436
|
+
|
|
2437
|
+
if (lumaEdgeRefined) {
|
|
2438
|
+
recordAlphaAdjustmentStage({
|
|
2439
|
+
stage: 'known-48-luma-edge-correction',
|
|
2440
|
+
fromAlphaGain: alphaGain,
|
|
2441
|
+
toAlphaGain: alphaGain,
|
|
2442
|
+
beforeSpatialScore: finalProcessedSpatialScore,
|
|
2443
|
+
beforeGradientScore: finalProcessedGradientScore,
|
|
2444
|
+
afterSpatialScore: lumaEdgeRefined.spatialScore,
|
|
2445
|
+
afterGradientScore: lumaEdgeRefined.gradientScore,
|
|
2446
|
+
suppressionGain: originalSpatialScore - lumaEdgeRefined.spatialScore,
|
|
2447
|
+
cost: lumaEdgeRefined.cost,
|
|
2448
|
+
allowSameAlphaGain: true
|
|
2449
|
+
});
|
|
2450
|
+
finalImageData = lumaEdgeRefined.imageData;
|
|
2451
|
+
finalProcessedSpatialScore = lumaEdgeRefined.spatialScore;
|
|
2452
|
+
finalProcessedGradientScore = lumaEdgeRefined.gradientScore;
|
|
2453
|
+
suppressionGain = originalSpatialScore - finalProcessedSpatialScore;
|
|
2454
|
+
source = `${source}+luma-edge`;
|
|
2455
|
+
}
|
|
2456
|
+
}
|
|
2457
|
+
|
|
2458
|
+
const newMargin96FlatFillRefined = refineNewMargin96FlatBackgroundResidual({
|
|
2459
|
+
sourceImageData: finalImageData,
|
|
2460
|
+
alphaMap,
|
|
2461
|
+
position,
|
|
2462
|
+
config,
|
|
2463
|
+
alphaGain,
|
|
2464
|
+
baselineSpatialScore: finalProcessedSpatialScore,
|
|
2465
|
+
baselineGradientScore: finalProcessedGradientScore
|
|
2466
|
+
});
|
|
2467
|
+
|
|
2468
|
+
if (newMargin96FlatFillRefined) {
|
|
2469
|
+
recordAlphaAdjustmentStage({
|
|
2470
|
+
stage: 'new-margin-96-flat-background-fill',
|
|
2471
|
+
fromAlphaGain: alphaGain,
|
|
2472
|
+
toAlphaGain: alphaGain,
|
|
2473
|
+
beforeSpatialScore: finalProcessedSpatialScore,
|
|
2474
|
+
beforeGradientScore: finalProcessedGradientScore,
|
|
2475
|
+
afterSpatialScore: newMargin96FlatFillRefined.spatialScore,
|
|
2476
|
+
afterGradientScore: newMargin96FlatFillRefined.gradientScore,
|
|
2477
|
+
suppressionGain: originalSpatialScore - newMargin96FlatFillRefined.spatialScore,
|
|
2478
|
+
cost: newMargin96FlatFillRefined.cost,
|
|
2479
|
+
allowSameAlphaGain: true
|
|
2480
|
+
});
|
|
2481
|
+
finalImageData = newMargin96FlatFillRefined.imageData;
|
|
2482
|
+
finalProcessedSpatialScore = newMargin96FlatFillRefined.spatialScore;
|
|
2483
|
+
finalProcessedGradientScore = newMargin96FlatFillRefined.gradientScore;
|
|
2484
|
+
suppressionGain = originalSpatialScore - finalProcessedSpatialScore;
|
|
2485
|
+
source = `${source}+flat-fill`;
|
|
2486
|
+
}
|
|
2487
|
+
|
|
1580
2488
|
const smallPreviewRefinementStartedAt = nowMs();
|
|
1581
2489
|
const smallPreviewRefined = ENABLE_VISUAL_POST_PROCESSING
|
|
1582
2490
|
? refineSmallPreviewAnchorCandidate({
|
|
@@ -1617,25 +2525,83 @@ export function processWatermarkImageData(imageData, options = {}) {
|
|
|
1617
2525
|
suppressionGain = originalSpatialScore - finalProcessedSpatialScore;
|
|
1618
2526
|
source = `${source}+small-preview-refine`;
|
|
1619
2527
|
}
|
|
2528
|
+
|
|
2529
|
+
const locatedAggressiveStartedAt = nowMs();
|
|
2530
|
+
if (options.locatedAggressiveRemoval !== false) {
|
|
2531
|
+
const aggressiveRefined = refineLocatedAggressiveRemoval({
|
|
2532
|
+
originalImageData,
|
|
2533
|
+
currentImageData: finalImageData,
|
|
2534
|
+
alphaMap,
|
|
2535
|
+
position,
|
|
2536
|
+
currentSpatialScore: finalProcessedSpatialScore,
|
|
2537
|
+
currentGradientScore: finalProcessedGradientScore,
|
|
2538
|
+
currentAlphaGain: alphaGain
|
|
2539
|
+
});
|
|
2540
|
+
if (aggressiveRefined) {
|
|
2541
|
+
recordAlphaAdjustmentStage({
|
|
2542
|
+
stage: 'located-aggressive-removal',
|
|
2543
|
+
fromAlphaGain: alphaGain,
|
|
2544
|
+
toAlphaGain: aggressiveRefined.alphaGain,
|
|
2545
|
+
beforeSpatialScore: finalProcessedSpatialScore,
|
|
2546
|
+
beforeGradientScore: finalProcessedGradientScore,
|
|
2547
|
+
afterSpatialScore: aggressiveRefined.spatialScore,
|
|
2548
|
+
afterGradientScore: aggressiveRefined.gradientScore,
|
|
2549
|
+
suppressionGain: originalSpatialScore - aggressiveRefined.spatialScore,
|
|
2550
|
+
cost: aggressiveRefined.cost,
|
|
2551
|
+
allowSameAlphaGain: true
|
|
2552
|
+
});
|
|
2553
|
+
passes.push({
|
|
2554
|
+
index: passes.length + 1,
|
|
2555
|
+
beforeSpatialScore: finalProcessedSpatialScore,
|
|
2556
|
+
beforeGradientScore: finalProcessedGradientScore,
|
|
2557
|
+
afterSpatialScore: aggressiveRefined.spatialScore,
|
|
2558
|
+
afterGradientScore: aggressiveRefined.gradientScore,
|
|
2559
|
+
improvement: Math.abs(finalProcessedSpatialScore) - Math.abs(aggressiveRefined.spatialScore),
|
|
2560
|
+
gradientDelta: aggressiveRefined.gradientScore - finalProcessedGradientScore,
|
|
2561
|
+
nearBlackRatio: aggressiveRefined.nearBlackRatio
|
|
2562
|
+
});
|
|
2563
|
+
finalImageData = aggressiveRefined.imageData;
|
|
2564
|
+
alphaGain = aggressiveRefined.alphaGain;
|
|
2565
|
+
finalProcessedSpatialScore = aggressiveRefined.spatialScore;
|
|
2566
|
+
finalProcessedGradientScore = aggressiveRefined.gradientScore;
|
|
2567
|
+
suppressionGain = originalSpatialScore - finalProcessedSpatialScore;
|
|
2568
|
+
passCount += Math.max(1, aggressiveRefined.repeatCount || 1);
|
|
2569
|
+
attemptedPassCount += Math.max(1, aggressiveRefined.repeatCount || 1);
|
|
2570
|
+
passStopReason = aggressiveRefined.edgeCleanup
|
|
2571
|
+
? 'located-aggressive-edge-cleanup'
|
|
2572
|
+
: 'located-aggressive-alpha';
|
|
2573
|
+
source = source.includes('+located-aggressive')
|
|
2574
|
+
? source
|
|
2575
|
+
: `${source}+located-aggressive`;
|
|
2576
|
+
}
|
|
2577
|
+
}
|
|
1620
2578
|
if (debugTimingsEnabled) {
|
|
1621
2579
|
debugTimings.previewEdgeCleanupMs = previewEdgeCleanupElapsedMs;
|
|
1622
2580
|
debugTimings.smallPreviewRefinementMs = nowMs() - smallPreviewRefinementStartedAt;
|
|
2581
|
+
debugTimings.locatedAggressiveRemovalMs = nowMs() - locatedAggressiveStartedAt;
|
|
1623
2582
|
debugTimings.totalMs = nowMs() - totalStartedAt;
|
|
1624
2583
|
}
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
imageData: finalImageData,
|
|
1628
|
-
|
|
2584
|
+
|
|
2585
|
+
const residualVisibility = assessWatermarkResidualVisibility({
|
|
2586
|
+
imageData: finalImageData,
|
|
2587
|
+
position,
|
|
2588
|
+
alphaMap
|
|
2589
|
+
});
|
|
2590
|
+
|
|
2591
|
+
return {
|
|
2592
|
+
imageData: finalImageData,
|
|
2593
|
+
meta: createWatermarkMeta({
|
|
1629
2594
|
position,
|
|
1630
2595
|
config,
|
|
1631
2596
|
adaptiveConfidence,
|
|
1632
2597
|
originalSpatialScore,
|
|
1633
|
-
originalGradientScore,
|
|
1634
|
-
processedSpatialScore: finalProcessedSpatialScore,
|
|
1635
|
-
processedGradientScore: finalProcessedGradientScore,
|
|
1636
|
-
suppressionGain,
|
|
1637
|
-
|
|
1638
|
-
|
|
2598
|
+
originalGradientScore,
|
|
2599
|
+
processedSpatialScore: finalProcessedSpatialScore,
|
|
2600
|
+
processedGradientScore: finalProcessedGradientScore,
|
|
2601
|
+
suppressionGain,
|
|
2602
|
+
residualVisibility,
|
|
2603
|
+
templateWarp,
|
|
2604
|
+
alphaGain,
|
|
1639
2605
|
passCount,
|
|
1640
2606
|
attemptedPassCount,
|
|
1641
2607
|
passStopReason,
|