@pilio/gemini-watermark-remover 1.0.10
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/LICENSE +22 -0
- package/README.md +371 -0
- package/README_zh.md +371 -0
- package/bin/gwr.mjs +12 -0
- package/package.json +76 -0
- package/skills/gemini-watermark-remover/SKILL.md +28 -0
- package/skills/gemini-watermark-remover/agents/openai.yaml +3 -0
- package/skills/gemini-watermark-remover/references/inputs-and-outputs.md +9 -0
- package/skills/gemini-watermark-remover/references/limitations.md +5 -0
- package/skills/gemini-watermark-remover/references/usage.md +19 -0
- package/skills/gemini-watermark-remover/scripts/run.mjs +153 -0
- package/src/cli/gwrCli.js +17 -0
- package/src/cli/gwrRemoveCommand.js +317 -0
- package/src/core/adaptiveDetector.js +488 -0
- package/src/core/alphaMap.js +30 -0
- package/src/core/blendModes.js +70 -0
- package/src/core/candidateSelector.js +1446 -0
- package/src/core/canvasBlob.js +26 -0
- package/src/core/embeddedAlphaMaps.js +49 -0
- package/src/core/geminiSizeCatalog.js +239 -0
- package/src/core/multiPassRemoval.js +93 -0
- package/src/core/previewAlphaCalibration.js +822 -0
- package/src/core/restorationMetrics.js +251 -0
- package/src/core/selectionDebug.js +47 -0
- package/src/core/watermarkConfig.js +146 -0
- package/src/core/watermarkDecisionPolicy.js +163 -0
- package/src/core/watermarkDisplay.js +60 -0
- package/src/core/watermarkEngine.js +150 -0
- package/src/core/watermarkPresence.js +12 -0
- package/src/core/watermarkProcessor.js +875 -0
- package/src/core/workerClient.js +114 -0
- package/src/sdk/browser.d.ts +13 -0
- package/src/sdk/browser.js +29 -0
- package/src/sdk/image-data.d.ts +14 -0
- package/src/sdk/image-data.js +55 -0
- package/src/sdk/index.d.ts +144 -0
- package/src/sdk/index.js +8 -0
- package/src/sdk/node.d.ts +42 -0
- package/src/sdk/node.js +77 -0
|
@@ -0,0 +1,1446 @@
|
|
|
1
|
+
import { removeWatermark } from './blendModes.js';
|
|
2
|
+
import {
|
|
3
|
+
computeRegionGradientCorrelation,
|
|
4
|
+
computeRegionSpatialCorrelation,
|
|
5
|
+
detectAdaptiveWatermarkRegion,
|
|
6
|
+
interpolateAlphaMap,
|
|
7
|
+
shouldAttemptAdaptiveFallback,
|
|
8
|
+
warpAlphaMap
|
|
9
|
+
} from './adaptiveDetector.js';
|
|
10
|
+
import {
|
|
11
|
+
assessReferenceTextureAlignment,
|
|
12
|
+
assessReferenceTextureAlignmentFromStats,
|
|
13
|
+
calculateNearBlackRatio,
|
|
14
|
+
cloneImageData,
|
|
15
|
+
getRegionTextureStats,
|
|
16
|
+
scoreRegion
|
|
17
|
+
} from './restorationMetrics.js';
|
|
18
|
+
import {
|
|
19
|
+
hasReliableAdaptiveWatermarkSignal,
|
|
20
|
+
hasReliableStandardWatermarkSignal
|
|
21
|
+
} from './watermarkPresence.js';
|
|
22
|
+
import {
|
|
23
|
+
matchOfficialGeminiImageSize,
|
|
24
|
+
resolveGeminiWatermarkSearchConfigs
|
|
25
|
+
} from './geminiSizeCatalog.js';
|
|
26
|
+
|
|
27
|
+
const MAX_NEAR_BLACK_RATIO_INCREASE = 0.05;
|
|
28
|
+
const VALIDATION_MIN_IMPROVEMENT = 0.08;
|
|
29
|
+
const VALIDATION_TARGET_RESIDUAL = 0.22;
|
|
30
|
+
const VALIDATION_MAX_GRADIENT_INCREASE = 0.04;
|
|
31
|
+
const VALIDATION_MIN_CONFIDENCE_FOR_ADAPTIVE_TRIAL = 0.25;
|
|
32
|
+
const STANDARD_FAST_PATH_RESIDUAL_THRESHOLD = 0.22;
|
|
33
|
+
const STANDARD_FAST_PATH_GRADIENT_THRESHOLD = 0.08;
|
|
34
|
+
const STANDARD_NEARBY_SEARCH_RESIDUAL_THRESHOLD = 0.18;
|
|
35
|
+
const STANDARD_NEARBY_SEARCH_GRADIENT_THRESHOLD = 0.05;
|
|
36
|
+
const STANDARD_LOCAL_SHIFT_STRONG_BASE_GRADIENT_SCORE = 0.35;
|
|
37
|
+
const STANDARD_LOCAL_SHIFT_STRONG_BASE_SPATIAL_SCORE = 0.8;
|
|
38
|
+
const STANDARD_LOCAL_SHIFT_WEAK_CANDIDATE_GRADIENT_SCORE = 0.12;
|
|
39
|
+
const STANDARD_LOCAL_SHIFT_WEAK_CANDIDATE_SPATIAL_SCORE = 0.65;
|
|
40
|
+
const STANDARD_LOCAL_SHIFT_MIN_VALIDATION_ADVANTAGE = 0.3;
|
|
41
|
+
const STANDARD_LOCAL_SHIFT_SKIP_PROCESSED_GRADIENT_THRESHOLD = 0.02;
|
|
42
|
+
const STANDARD_LOCAL_SHIFT_PRESERVE_CLEAN_BASE_GRADIENT_THRESHOLD = 0.02;
|
|
43
|
+
const STANDARD_LOCAL_SHIFT_MAX_CANDIDATE_GRADIENT_FOR_CLEAN_BASE = 0.03;
|
|
44
|
+
const TEMPLATE_ALIGN_SHIFTS = [-0.5, -0.25, 0, 0.25, 0.5];
|
|
45
|
+
const TEMPLATE_ALIGN_SCALES = [0.99, 1, 1.01];
|
|
46
|
+
const STANDARD_NEARBY_SHIFTS = [-12, -8, -4, 0, 4, 8, 12];
|
|
47
|
+
const STANDARD_FINE_LOCAL_SHIFTS = [-2, -1, 0, 1, 2];
|
|
48
|
+
const STANDARD_SIZE_JITTERS = [-12, -10, -8, -6, -4, -2, 2, 4, 6, 8, 10, 12];
|
|
49
|
+
const PREVIEW_ANCHOR_MIN_SIZE = 24;
|
|
50
|
+
const PREVIEW_ANCHOR_MAX_SIZE_RATIO = 1.05;
|
|
51
|
+
const PREVIEW_ANCHOR_MIN_SIZE_RATIO = 0.55;
|
|
52
|
+
const PREVIEW_ANCHOR_MARGIN_WINDOW = 16;
|
|
53
|
+
const PREVIEW_ANCHOR_MARGIN_EXTENSION = 8;
|
|
54
|
+
const PREVIEW_ANCHOR_SIZE_STEP = 2;
|
|
55
|
+
const PREVIEW_ANCHOR_MARGIN_STEP = 2;
|
|
56
|
+
const PREVIEW_ANCHOR_TOP_K = 8;
|
|
57
|
+
const PREVIEW_ANCHOR_MIN_SCORE = 0.2;
|
|
58
|
+
const PREVIEW_ANCHOR_LOCAL_DELTAS = [-1, 0, 1];
|
|
59
|
+
const PREVIEW_TEMPLATE_ALIGN_SHIFTS = [-1, -0.5, 0, 0.5, 1];
|
|
60
|
+
const PREVIEW_TEMPLATE_ALIGN_SCALES = [0.985, 1, 1.015];
|
|
61
|
+
const PREVIEW_ANCHOR_GAIN_SKIP_RESIDUAL_THRESHOLD = 0.22;
|
|
62
|
+
const PREVIEW_ANCHOR_GAIN_SKIP_GRADIENT_THRESHOLD = 0.24;
|
|
63
|
+
|
|
64
|
+
export { assessReferenceTextureAlignment, calculateNearBlackRatio, scoreRegion } from './restorationMetrics.js';
|
|
65
|
+
|
|
66
|
+
const ORIGIN_REGION = Object.freeze({ x: 0, y: 0 });
|
|
67
|
+
|
|
68
|
+
function mergeCandidateProvenance(...provenanceParts) {
|
|
69
|
+
const merged = {};
|
|
70
|
+
for (const provenance of provenanceParts) {
|
|
71
|
+
if (!provenance || typeof provenance !== 'object') continue;
|
|
72
|
+
Object.assign(merged, provenance);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return Object.keys(merged).length > 0 ? merged : null;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function buildStandardCandidateSeeds({
|
|
79
|
+
originalImageData,
|
|
80
|
+
config,
|
|
81
|
+
position,
|
|
82
|
+
alpha48,
|
|
83
|
+
alpha96,
|
|
84
|
+
getAlphaMap,
|
|
85
|
+
resolveAlphaMap = null,
|
|
86
|
+
includeCatalogVariants = true
|
|
87
|
+
}) {
|
|
88
|
+
const configs = includeCatalogVariants
|
|
89
|
+
? resolveGeminiWatermarkSearchConfigs(
|
|
90
|
+
originalImageData.width,
|
|
91
|
+
originalImageData.height,
|
|
92
|
+
config
|
|
93
|
+
)
|
|
94
|
+
: [config];
|
|
95
|
+
const seeds = [];
|
|
96
|
+
|
|
97
|
+
for (const candidateConfig of configs) {
|
|
98
|
+
const candidatePosition = candidateConfig === config
|
|
99
|
+
? position
|
|
100
|
+
: {
|
|
101
|
+
x: originalImageData.width - candidateConfig.marginRight - candidateConfig.logoSize,
|
|
102
|
+
y: originalImageData.height - candidateConfig.marginBottom - candidateConfig.logoSize,
|
|
103
|
+
width: candidateConfig.logoSize,
|
|
104
|
+
height: candidateConfig.logoSize
|
|
105
|
+
};
|
|
106
|
+
if (
|
|
107
|
+
candidatePosition.x < 0 ||
|
|
108
|
+
candidatePosition.y < 0 ||
|
|
109
|
+
candidatePosition.x + candidatePosition.width > originalImageData.width ||
|
|
110
|
+
candidatePosition.y + candidatePosition.height > originalImageData.height
|
|
111
|
+
) {
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const alphaMap = typeof resolveAlphaMap === 'function'
|
|
116
|
+
? resolveAlphaMap(candidateConfig.logoSize)
|
|
117
|
+
: resolveAlphaMapForSize(candidateConfig.logoSize, {
|
|
118
|
+
alpha48,
|
|
119
|
+
alpha96,
|
|
120
|
+
getAlphaMap
|
|
121
|
+
});
|
|
122
|
+
if (!alphaMap) continue;
|
|
123
|
+
|
|
124
|
+
seeds.push({
|
|
125
|
+
config: candidateConfig,
|
|
126
|
+
position: candidatePosition,
|
|
127
|
+
alphaMap,
|
|
128
|
+
source: candidateConfig === config ? 'standard' : 'standard+catalog',
|
|
129
|
+
provenance: candidateConfig === config ? null : { catalogVariant: true }
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return seeds;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function inferDecisionTier(candidate, { directMatch = false } = {}) {
|
|
137
|
+
if (!candidate) return 'insufficient';
|
|
138
|
+
if (directMatch) return 'direct-match';
|
|
139
|
+
if (candidate.source?.includes('validated')) return 'validated-match';
|
|
140
|
+
if (candidate.accepted) return 'validated-match';
|
|
141
|
+
return 'safe-removal';
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function shouldEscalateSearch(candidate) {
|
|
145
|
+
if (!candidate) return true;
|
|
146
|
+
|
|
147
|
+
return Math.abs(candidate.processedSpatialScore) > STANDARD_FAST_PATH_RESIDUAL_THRESHOLD ||
|
|
148
|
+
Math.max(0, candidate.processedGradientScore) > STANDARD_FAST_PATH_GRADIENT_THRESHOLD;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function shouldSearchNearbyStandardCandidate(candidate, originalImageData) {
|
|
152
|
+
if (!candidate) return true;
|
|
153
|
+
|
|
154
|
+
return Number(candidate.position?.width) >= 72 &&
|
|
155
|
+
Number(originalImageData?.height) > Number(originalImageData?.width) * 1.25 &&
|
|
156
|
+
(
|
|
157
|
+
Math.abs(candidate.processedSpatialScore) > STANDARD_NEARBY_SEARCH_RESIDUAL_THRESHOLD ||
|
|
158
|
+
Math.max(0, candidate.processedGradientScore) > STANDARD_NEARBY_SEARCH_GRADIENT_THRESHOLD
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function resolveAlphaMapForSize(size, { alpha48, alpha96, getAlphaMap } = {}) {
|
|
163
|
+
if (size === 48) return alpha48;
|
|
164
|
+
if (size === 96) return alpha96;
|
|
165
|
+
|
|
166
|
+
const provided = typeof getAlphaMap === 'function' ? getAlphaMap(size) : null;
|
|
167
|
+
if (provided) return provided;
|
|
168
|
+
|
|
169
|
+
return alpha96 ? interpolateAlphaMap(alpha96, 96, size) : null;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function createAlphaMapResolver({ alpha48, alpha96, getAlphaMap }) {
|
|
173
|
+
const cache = new Map();
|
|
174
|
+
|
|
175
|
+
return (size) => {
|
|
176
|
+
if (cache.has(size)) {
|
|
177
|
+
return cache.get(size);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const resolved = resolveAlphaMapForSize(size, {
|
|
181
|
+
alpha48,
|
|
182
|
+
alpha96,
|
|
183
|
+
getAlphaMap
|
|
184
|
+
});
|
|
185
|
+
cache.set(size, resolved);
|
|
186
|
+
return resolved;
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function isPreviewAnchorGainSearchRequired(candidate) {
|
|
191
|
+
if (!candidate) return true;
|
|
192
|
+
|
|
193
|
+
return Math.abs(candidate.processedSpatialScore) > PREVIEW_ANCHOR_GAIN_SKIP_RESIDUAL_THRESHOLD ||
|
|
194
|
+
Math.max(0, candidate.processedGradientScore) > PREVIEW_ANCHOR_GAIN_SKIP_GRADIENT_THRESHOLD;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export function evaluateRestorationCandidate({
|
|
198
|
+
originalImageData,
|
|
199
|
+
alphaMap,
|
|
200
|
+
position,
|
|
201
|
+
source,
|
|
202
|
+
config,
|
|
203
|
+
baselineNearBlackRatio,
|
|
204
|
+
adaptiveConfidence = null,
|
|
205
|
+
alphaGain = 1,
|
|
206
|
+
provenance = null,
|
|
207
|
+
includeImageData = true
|
|
208
|
+
}) {
|
|
209
|
+
if (!alphaMap || !position) return null;
|
|
210
|
+
|
|
211
|
+
const originalScores = scoreRegion(originalImageData, alphaMap, position);
|
|
212
|
+
const regionImageData = createCandidateRegionImageData({
|
|
213
|
+
originalImageData,
|
|
214
|
+
alphaMap,
|
|
215
|
+
position,
|
|
216
|
+
alphaGain
|
|
217
|
+
});
|
|
218
|
+
const regionPosition = {
|
|
219
|
+
x: ORIGIN_REGION.x,
|
|
220
|
+
y: ORIGIN_REGION.y,
|
|
221
|
+
width: position.width,
|
|
222
|
+
height: position.height
|
|
223
|
+
};
|
|
224
|
+
const processedScores = scoreRegion(regionImageData, alphaMap, regionPosition);
|
|
225
|
+
const nearBlackRatio = calculateNearBlackRatio(regionImageData, regionPosition);
|
|
226
|
+
const nearBlackIncrease = nearBlackRatio - baselineNearBlackRatio;
|
|
227
|
+
// Signed suppression keeps legitimate "slight overshoot" restores eligible.
|
|
228
|
+
const improvement = originalScores.spatialScore - processedScores.spatialScore;
|
|
229
|
+
const gradientIncrease = processedScores.gradientScore - originalScores.gradientScore;
|
|
230
|
+
const textureAssessment = assessReferenceTextureAlignmentFromStats({
|
|
231
|
+
originalImageData,
|
|
232
|
+
referenceImageData: originalImageData,
|
|
233
|
+
candidateTextureStats: getRegionTextureStats(regionImageData, regionPosition),
|
|
234
|
+
position
|
|
235
|
+
});
|
|
236
|
+
const texturePenalty = textureAssessment.texturePenalty;
|
|
237
|
+
const accepted =
|
|
238
|
+
textureAssessment.hardReject !== true &&
|
|
239
|
+
nearBlackIncrease <= MAX_NEAR_BLACK_RATIO_INCREASE &&
|
|
240
|
+
improvement >= VALIDATION_MIN_IMPROVEMENT &&
|
|
241
|
+
(
|
|
242
|
+
Math.abs(processedScores.spatialScore) <= VALIDATION_TARGET_RESIDUAL ||
|
|
243
|
+
gradientIncrease <= VALIDATION_MAX_GRADIENT_INCREASE
|
|
244
|
+
);
|
|
245
|
+
|
|
246
|
+
return {
|
|
247
|
+
accepted,
|
|
248
|
+
source,
|
|
249
|
+
config,
|
|
250
|
+
position,
|
|
251
|
+
alphaMap,
|
|
252
|
+
adaptiveConfidence,
|
|
253
|
+
alphaGain,
|
|
254
|
+
provenance: mergeCandidateProvenance(provenance),
|
|
255
|
+
imageData: includeImageData
|
|
256
|
+
? materializeCandidateImageData(originalImageData, alphaMap, position, alphaGain)
|
|
257
|
+
: null,
|
|
258
|
+
originalSpatialScore: originalScores.spatialScore,
|
|
259
|
+
originalGradientScore: originalScores.gradientScore,
|
|
260
|
+
processedSpatialScore: processedScores.spatialScore,
|
|
261
|
+
processedGradientScore: processedScores.gradientScore,
|
|
262
|
+
improvement,
|
|
263
|
+
nearBlackRatio,
|
|
264
|
+
nearBlackIncrease,
|
|
265
|
+
gradientIncrease,
|
|
266
|
+
tooDark: textureAssessment.tooDark,
|
|
267
|
+
tooFlat: textureAssessment.tooFlat,
|
|
268
|
+
hardReject: textureAssessment.hardReject,
|
|
269
|
+
texturePenalty,
|
|
270
|
+
validationCost:
|
|
271
|
+
Math.abs(processedScores.spatialScore) +
|
|
272
|
+
Math.max(0, processedScores.gradientScore) * 0.6 +
|
|
273
|
+
Math.max(0, nearBlackIncrease) * 3 +
|
|
274
|
+
texturePenalty
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function pickBestValidatedCandidate(candidates) {
|
|
279
|
+
const accepted = candidates.filter((candidate) => candidate?.accepted);
|
|
280
|
+
if (accepted.length === 0) return null;
|
|
281
|
+
|
|
282
|
+
accepted.sort((a, b) => {
|
|
283
|
+
if (a.validationCost !== b.validationCost) {
|
|
284
|
+
return a.validationCost - b.validationCost;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
return b.improvement - a.improvement;
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
return accepted[0];
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function createCandidateRegionImageData({
|
|
294
|
+
originalImageData,
|
|
295
|
+
alphaMap,
|
|
296
|
+
position,
|
|
297
|
+
alphaGain
|
|
298
|
+
}) {
|
|
299
|
+
const regionImageData = {
|
|
300
|
+
width: position.width,
|
|
301
|
+
height: position.height,
|
|
302
|
+
data: new Uint8ClampedArray(position.width * position.height * 4)
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
for (let row = 0; row < position.height; row++) {
|
|
306
|
+
const srcStart = ((position.y + row) * originalImageData.width + position.x) * 4;
|
|
307
|
+
const srcEnd = srcStart + position.width * 4;
|
|
308
|
+
const destStart = row * position.width * 4;
|
|
309
|
+
regionImageData.data.set(originalImageData.data.subarray(srcStart, srcEnd), destStart);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
removeWatermark(regionImageData, alphaMap, {
|
|
313
|
+
x: 0,
|
|
314
|
+
y: 0,
|
|
315
|
+
width: position.width,
|
|
316
|
+
height: position.height
|
|
317
|
+
}, { alphaGain });
|
|
318
|
+
|
|
319
|
+
return regionImageData;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function materializeCandidateImageData(originalImageData, alphaMap, position, alphaGain) {
|
|
323
|
+
const candidateImageData = cloneImageData(originalImageData);
|
|
324
|
+
removeWatermark(candidateImageData, alphaMap, position, { alphaGain });
|
|
325
|
+
return candidateImageData;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
function ensureCandidateImageData(candidate, originalImageData) {
|
|
329
|
+
if (!candidate) return candidate;
|
|
330
|
+
if (candidate.imageData) return candidate;
|
|
331
|
+
|
|
332
|
+
return {
|
|
333
|
+
...candidate,
|
|
334
|
+
imageData: materializeCandidateImageData(
|
|
335
|
+
originalImageData,
|
|
336
|
+
candidate.alphaMap,
|
|
337
|
+
candidate.position,
|
|
338
|
+
candidate.alphaGain ?? 1
|
|
339
|
+
)
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
export function pickBetterCandidate(currentBest, candidate, minCostDelta = 0.005) {
|
|
344
|
+
if (!candidate?.accepted) return currentBest;
|
|
345
|
+
if (!currentBest) return candidate;
|
|
346
|
+
if (shouldPreserveStrongStandardAnchor(currentBest, candidate)) {
|
|
347
|
+
return currentBest;
|
|
348
|
+
}
|
|
349
|
+
if (shouldPreferPreviewAnchorCandidate(currentBest, candidate)) {
|
|
350
|
+
return candidate;
|
|
351
|
+
}
|
|
352
|
+
if (shouldPreferPreviewAnchorCandidate(candidate, currentBest)) {
|
|
353
|
+
return currentBest;
|
|
354
|
+
}
|
|
355
|
+
if (candidate.validationCost < currentBest.validationCost - minCostDelta) {
|
|
356
|
+
return candidate;
|
|
357
|
+
}
|
|
358
|
+
if (Math.abs(candidate.validationCost - currentBest.validationCost) <= minCostDelta &&
|
|
359
|
+
candidate.improvement > currentBest.improvement + 0.01) {
|
|
360
|
+
return candidate;
|
|
361
|
+
}
|
|
362
|
+
return currentBest;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
function isStandardCandidateSource(candidate) {
|
|
366
|
+
return typeof candidate?.source === 'string' && candidate.source.startsWith('standard');
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
function isDriftedStandardCandidate(candidate) {
|
|
370
|
+
return isStandardCandidateSource(candidate) &&
|
|
371
|
+
(
|
|
372
|
+
candidate?.provenance?.localShift === true ||
|
|
373
|
+
candidate?.provenance?.sizeJitter === true ||
|
|
374
|
+
String(candidate?.source || '').includes('+warp')
|
|
375
|
+
);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
function isCanonicalStandardCandidate(candidate) {
|
|
379
|
+
return isStandardCandidateSource(candidate) &&
|
|
380
|
+
candidate?.provenance?.localShift !== true &&
|
|
381
|
+
candidate?.provenance?.sizeJitter !== true;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
function hasStrongCanonicalAnchorSignal(candidate) {
|
|
385
|
+
const baseSpatial = Number(candidate?.originalSpatialScore);
|
|
386
|
+
const baseGradient = Number(candidate?.originalGradientScore);
|
|
387
|
+
if (!Number.isFinite(baseSpatial) || !Number.isFinite(baseGradient)) {
|
|
388
|
+
return false;
|
|
389
|
+
}
|
|
390
|
+
return baseGradient >= STANDARD_LOCAL_SHIFT_STRONG_BASE_GRADIENT_SCORE ||
|
|
391
|
+
baseSpatial >= STANDARD_LOCAL_SHIFT_STRONG_BASE_SPATIAL_SCORE;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
function hasWeakDriftEvidence(candidate) {
|
|
395
|
+
const candidateSpatial = Number(candidate?.originalSpatialScore);
|
|
396
|
+
const candidateGradient = Number(candidate?.originalGradientScore);
|
|
397
|
+
if (!Number.isFinite(candidateSpatial) || !Number.isFinite(candidateGradient)) {
|
|
398
|
+
return false;
|
|
399
|
+
}
|
|
400
|
+
return candidateGradient < STANDARD_LOCAL_SHIFT_WEAK_CANDIDATE_GRADIENT_SCORE ||
|
|
401
|
+
candidateSpatial < STANDARD_LOCAL_SHIFT_WEAK_CANDIDATE_SPATIAL_SCORE;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
function leavesWorseResidualGradientThanCanonical(canonicalCandidate, driftCandidate) {
|
|
405
|
+
const canonicalProcessedGradientRaw = Number(canonicalCandidate?.processedGradientScore);
|
|
406
|
+
const driftProcessedGradientRaw = Number(driftCandidate?.processedGradientScore);
|
|
407
|
+
if (
|
|
408
|
+
!Number.isFinite(canonicalProcessedGradientRaw) ||
|
|
409
|
+
!Number.isFinite(driftProcessedGradientRaw)
|
|
410
|
+
) {
|
|
411
|
+
return false;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
return Math.max(0, canonicalProcessedGradientRaw) <= STANDARD_LOCAL_SHIFT_PRESERVE_CLEAN_BASE_GRADIENT_THRESHOLD &&
|
|
415
|
+
Math.max(0, driftProcessedGradientRaw) >= STANDARD_LOCAL_SHIFT_MAX_CANDIDATE_GRADIENT_FOR_CLEAN_BASE;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
function shouldPreserveCanonicalAnchor(canonicalCandidate, driftCandidate) {
|
|
419
|
+
if (!isCanonicalStandardCandidate(canonicalCandidate)) return false;
|
|
420
|
+
if (!isDriftedStandardCandidate(driftCandidate)) return false;
|
|
421
|
+
|
|
422
|
+
const validationAdvantage = Number(canonicalCandidate.validationCost) - Number(driftCandidate.validationCost);
|
|
423
|
+
if (
|
|
424
|
+
!Number.isFinite(validationAdvantage)
|
|
425
|
+
) {
|
|
426
|
+
return false;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
return (
|
|
430
|
+
hasStrongCanonicalAnchorSignal(canonicalCandidate) &&
|
|
431
|
+
hasWeakDriftEvidence(driftCandidate) &&
|
|
432
|
+
validationAdvantage < STANDARD_LOCAL_SHIFT_MIN_VALIDATION_ADVANTAGE
|
|
433
|
+
) || leavesWorseResidualGradientThanCanonical(canonicalCandidate, driftCandidate);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
function shouldPreserveStrongStandardAnchor(currentBest, candidate) {
|
|
437
|
+
if (currentBest?.provenance?.localShift === true) return false;
|
|
438
|
+
if (!isStandardCandidateSource(candidate)) return false;
|
|
439
|
+
return shouldPreserveCanonicalAnchor(currentBest, candidate);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
function shouldRevertLocalShiftToStandardTrial(selectedCandidate, standardTrial) {
|
|
443
|
+
if (selectedCandidate?.provenance?.localShift !== true) return false;
|
|
444
|
+
if (!isStandardCandidateSource(selectedCandidate) || !isStandardCandidateSource(standardTrial)) return false;
|
|
445
|
+
if (!standardTrial?.accepted) return false;
|
|
446
|
+
return shouldPreserveCanonicalAnchor(standardTrial, selectedCandidate);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
function shouldSkipStandardLocalSearch(seedCandidate) {
|
|
450
|
+
if (!seedCandidate) return false;
|
|
451
|
+
|
|
452
|
+
return Math.max(0, Number(seedCandidate.processedGradientScore)) <=
|
|
453
|
+
STANDARD_LOCAL_SHIFT_SKIP_PROCESSED_GRADIENT_THRESHOLD;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
function isPreviewAnchorSearchEligible(originalImageData, config) {
|
|
457
|
+
if (!config || config.logoSize !== 48) return false;
|
|
458
|
+
|
|
459
|
+
const width = Number(originalImageData?.width);
|
|
460
|
+
const height = Number(originalImageData?.height);
|
|
461
|
+
if (!Number.isFinite(width) || !Number.isFinite(height)) return false;
|
|
462
|
+
if (width < 384 || width > 1536) return false;
|
|
463
|
+
if (height < 384 || height > 1536) return false;
|
|
464
|
+
if (Math.max(width, height) < 512) return false;
|
|
465
|
+
|
|
466
|
+
return matchOfficialGeminiImageSize(width, height) === null;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
function shouldPreferPreviewAnchorCandidate(currentBest, candidate) {
|
|
470
|
+
if (candidate?.provenance?.previewAnchor !== true) return false;
|
|
471
|
+
if (!currentBest || currentBest?.provenance?.previewAnchor === true) return false;
|
|
472
|
+
|
|
473
|
+
const currentSpatial = Number(currentBest.originalSpatialScore);
|
|
474
|
+
const currentGradient = Number(currentBest.originalGradientScore);
|
|
475
|
+
const candidateSpatial = Number(candidate.originalSpatialScore);
|
|
476
|
+
const candidateGradient = Number(candidate.originalGradientScore);
|
|
477
|
+
|
|
478
|
+
if (
|
|
479
|
+
!Number.isFinite(currentSpatial) ||
|
|
480
|
+
!Number.isFinite(currentGradient) ||
|
|
481
|
+
!Number.isFinite(candidateSpatial) ||
|
|
482
|
+
!Number.isFinite(candidateGradient)
|
|
483
|
+
) {
|
|
484
|
+
return false;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
const currentReliable = hasReliableStandardWatermarkSignal({
|
|
488
|
+
spatialScore: currentSpatial,
|
|
489
|
+
gradientScore: currentGradient
|
|
490
|
+
});
|
|
491
|
+
const candidateReliable = hasReliableStandardWatermarkSignal({
|
|
492
|
+
spatialScore: candidateSpatial,
|
|
493
|
+
gradientScore: candidateGradient
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
if (candidateReliable && !currentReliable) {
|
|
497
|
+
return true;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
return candidateGradient >= currentGradient + 0.2 &&
|
|
501
|
+
candidateSpatial >= currentSpatial + 0.05;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
function findBestTemplateWarp({
|
|
505
|
+
originalImageData,
|
|
506
|
+
alphaMap,
|
|
507
|
+
position,
|
|
508
|
+
baselineSpatialScore,
|
|
509
|
+
baselineGradientScore,
|
|
510
|
+
shiftCandidates = TEMPLATE_ALIGN_SHIFTS,
|
|
511
|
+
scaleCandidates = TEMPLATE_ALIGN_SCALES
|
|
512
|
+
}) {
|
|
513
|
+
const size = position.width;
|
|
514
|
+
if (!size || size <= 8) return null;
|
|
515
|
+
|
|
516
|
+
let best = {
|
|
517
|
+
spatialScore: baselineSpatialScore,
|
|
518
|
+
gradientScore: baselineGradientScore,
|
|
519
|
+
shift: { dx: 0, dy: 0, scale: 1 },
|
|
520
|
+
alphaMap
|
|
521
|
+
};
|
|
522
|
+
|
|
523
|
+
for (const scale of scaleCandidates) {
|
|
524
|
+
for (const dy of shiftCandidates) {
|
|
525
|
+
for (const dx of shiftCandidates) {
|
|
526
|
+
if (dx === 0 && dy === 0 && scale === 1) continue;
|
|
527
|
+
const warped = warpAlphaMap(alphaMap, size, { dx, dy, scale });
|
|
528
|
+
const spatialScore = computeRegionSpatialCorrelation({
|
|
529
|
+
imageData: originalImageData,
|
|
530
|
+
alphaMap: warped,
|
|
531
|
+
region: { x: position.x, y: position.y, size }
|
|
532
|
+
});
|
|
533
|
+
const gradientScore = computeRegionGradientCorrelation({
|
|
534
|
+
imageData: originalImageData,
|
|
535
|
+
alphaMap: warped,
|
|
536
|
+
region: { x: position.x, y: position.y, size }
|
|
537
|
+
});
|
|
538
|
+
|
|
539
|
+
const confidence =
|
|
540
|
+
Math.max(0, spatialScore) * 0.7 +
|
|
541
|
+
Math.max(0, gradientScore) * 0.3;
|
|
542
|
+
const bestConfidence =
|
|
543
|
+
Math.max(0, best.spatialScore) * 0.7 +
|
|
544
|
+
Math.max(0, best.gradientScore) * 0.3;
|
|
545
|
+
|
|
546
|
+
if (confidence > bestConfidence + 0.01) {
|
|
547
|
+
best = {
|
|
548
|
+
spatialScore,
|
|
549
|
+
gradientScore,
|
|
550
|
+
shift: { dx, dy, scale },
|
|
551
|
+
alphaMap: warped
|
|
552
|
+
};
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
const improvedSpatial = best.spatialScore >= baselineSpatialScore + 0.01;
|
|
559
|
+
const improvedGradient = best.gradientScore >= baselineGradientScore + 0.01;
|
|
560
|
+
return improvedSpatial || improvedGradient ? best : null;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
function searchNearbyStandardCandidate({
|
|
564
|
+
originalImageData,
|
|
565
|
+
candidateSeeds,
|
|
566
|
+
adaptiveConfidence = null
|
|
567
|
+
}) {
|
|
568
|
+
if (!Array.isArray(candidateSeeds) || candidateSeeds.length === 0) return null;
|
|
569
|
+
|
|
570
|
+
let bestCandidate = null;
|
|
571
|
+
for (const seed of candidateSeeds) {
|
|
572
|
+
if (shouldSkipStandardLocalSearch(seed)) continue;
|
|
573
|
+
for (const dy of STANDARD_NEARBY_SHIFTS) {
|
|
574
|
+
for (const dx of STANDARD_NEARBY_SHIFTS) {
|
|
575
|
+
if (dx === 0 && dy === 0) continue;
|
|
576
|
+
|
|
577
|
+
const candidatePosition = {
|
|
578
|
+
x: seed.position.x + dx,
|
|
579
|
+
y: seed.position.y + dy,
|
|
580
|
+
width: seed.position.width,
|
|
581
|
+
height: seed.position.height
|
|
582
|
+
};
|
|
583
|
+
if (candidatePosition.x < 0 || candidatePosition.y < 0) continue;
|
|
584
|
+
if (candidatePosition.x + candidatePosition.width > originalImageData.width) continue;
|
|
585
|
+
if (candidatePosition.y + candidatePosition.height > originalImageData.height) continue;
|
|
586
|
+
|
|
587
|
+
const candidate = evaluateRestorationCandidate({
|
|
588
|
+
originalImageData,
|
|
589
|
+
alphaMap: seed.alphaMap,
|
|
590
|
+
position: candidatePosition,
|
|
591
|
+
source: `${seed.source}+local`,
|
|
592
|
+
config: seed.config,
|
|
593
|
+
baselineNearBlackRatio: calculateNearBlackRatio(originalImageData, candidatePosition),
|
|
594
|
+
adaptiveConfidence,
|
|
595
|
+
provenance: mergeCandidateProvenance(seed.provenance, { localShift: true }),
|
|
596
|
+
includeImageData: false
|
|
597
|
+
});
|
|
598
|
+
|
|
599
|
+
if (!candidate?.accepted) continue;
|
|
600
|
+
bestCandidate = pickBetterCandidate(bestCandidate, candidate, 0.002);
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
return bestCandidate;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
function searchStandardSizeJitterCandidate({
|
|
609
|
+
originalImageData,
|
|
610
|
+
candidateSeeds,
|
|
611
|
+
alpha48,
|
|
612
|
+
alpha96,
|
|
613
|
+
getAlphaMap,
|
|
614
|
+
resolveAlphaMap = null,
|
|
615
|
+
adaptiveConfidence = null
|
|
616
|
+
}) {
|
|
617
|
+
if (!Array.isArray(candidateSeeds) || candidateSeeds.length === 0) return null;
|
|
618
|
+
|
|
619
|
+
let bestCandidate = null;
|
|
620
|
+
for (const seed of candidateSeeds) {
|
|
621
|
+
for (const delta of STANDARD_SIZE_JITTERS) {
|
|
622
|
+
const size = seed.position.width + delta;
|
|
623
|
+
if (size <= 24) continue;
|
|
624
|
+
if (size === seed.position.width) continue;
|
|
625
|
+
|
|
626
|
+
const candidatePosition = {
|
|
627
|
+
x: originalImageData.width - seed.config.marginRight - size,
|
|
628
|
+
y: originalImageData.height - seed.config.marginBottom - size,
|
|
629
|
+
width: size,
|
|
630
|
+
height: size
|
|
631
|
+
};
|
|
632
|
+
if (candidatePosition.x < 0 || candidatePosition.y < 0) continue;
|
|
633
|
+
if (candidatePosition.x + candidatePosition.width > originalImageData.width) continue;
|
|
634
|
+
if (candidatePosition.y + candidatePosition.height > originalImageData.height) continue;
|
|
635
|
+
|
|
636
|
+
const candidateAlphaMap = typeof resolveAlphaMap === 'function'
|
|
637
|
+
? resolveAlphaMap(size)
|
|
638
|
+
: resolveAlphaMapForSize(size, {
|
|
639
|
+
alpha48,
|
|
640
|
+
alpha96,
|
|
641
|
+
getAlphaMap
|
|
642
|
+
});
|
|
643
|
+
if (!candidateAlphaMap) continue;
|
|
644
|
+
|
|
645
|
+
const candidate = evaluateRestorationCandidate({
|
|
646
|
+
originalImageData,
|
|
647
|
+
alphaMap: candidateAlphaMap,
|
|
648
|
+
position: candidatePosition,
|
|
649
|
+
source: `${seed.source}+size`,
|
|
650
|
+
config: {
|
|
651
|
+
logoSize: size,
|
|
652
|
+
marginRight: seed.config.marginRight,
|
|
653
|
+
marginBottom: seed.config.marginBottom
|
|
654
|
+
},
|
|
655
|
+
baselineNearBlackRatio: calculateNearBlackRatio(originalImageData, candidatePosition),
|
|
656
|
+
adaptiveConfidence,
|
|
657
|
+
provenance: mergeCandidateProvenance(seed.provenance, { sizeJitter: true }),
|
|
658
|
+
includeImageData: false
|
|
659
|
+
});
|
|
660
|
+
|
|
661
|
+
if (!candidate?.accepted) continue;
|
|
662
|
+
bestCandidate = pickBetterCandidate(bestCandidate, candidate, 0.002);
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
return bestCandidate;
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
function searchFineStandardLocalCandidate({
|
|
670
|
+
originalImageData,
|
|
671
|
+
seedCandidate,
|
|
672
|
+
adaptiveConfidence = null,
|
|
673
|
+
shiftCandidates = STANDARD_FINE_LOCAL_SHIFTS
|
|
674
|
+
}) {
|
|
675
|
+
if (!seedCandidate?.alphaMap || !seedCandidate?.position) return null;
|
|
676
|
+
if (shouldSkipStandardLocalSearch(seedCandidate)) return null;
|
|
677
|
+
|
|
678
|
+
let bestCandidate = null;
|
|
679
|
+
for (const dy of shiftCandidates) {
|
|
680
|
+
for (const dx of shiftCandidates) {
|
|
681
|
+
if (dx === 0 && dy === 0) continue;
|
|
682
|
+
|
|
683
|
+
const candidatePosition = {
|
|
684
|
+
x: seedCandidate.position.x + dx,
|
|
685
|
+
y: seedCandidate.position.y + dy,
|
|
686
|
+
width: seedCandidate.position.width,
|
|
687
|
+
height: seedCandidate.position.height
|
|
688
|
+
};
|
|
689
|
+
if (candidatePosition.x < 0 || candidatePosition.y < 0) continue;
|
|
690
|
+
if (candidatePosition.x + candidatePosition.width > originalImageData.width) continue;
|
|
691
|
+
if (candidatePosition.y + candidatePosition.height > originalImageData.height) continue;
|
|
692
|
+
|
|
693
|
+
const candidate = evaluateRestorationCandidate({
|
|
694
|
+
originalImageData,
|
|
695
|
+
alphaMap: seedCandidate.alphaMap,
|
|
696
|
+
position: candidatePosition,
|
|
697
|
+
source: `${seedCandidate.source}+local`,
|
|
698
|
+
config: seedCandidate.config,
|
|
699
|
+
baselineNearBlackRatio: calculateNearBlackRatio(originalImageData, candidatePosition),
|
|
700
|
+
adaptiveConfidence,
|
|
701
|
+
provenance: mergeCandidateProvenance(seedCandidate.provenance, { localShift: true }),
|
|
702
|
+
includeImageData: false
|
|
703
|
+
});
|
|
704
|
+
|
|
705
|
+
if (!candidate?.accepted) continue;
|
|
706
|
+
bestCandidate = pickBetterCandidate(bestCandidate, candidate, 0.002);
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
return bestCandidate;
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
function searchCandidateAlphaGain({
|
|
714
|
+
originalImageData,
|
|
715
|
+
seedCandidate,
|
|
716
|
+
adaptiveConfidence = null,
|
|
717
|
+
alphaGainCandidates = []
|
|
718
|
+
}) {
|
|
719
|
+
if (!seedCandidate?.alphaMap || !seedCandidate?.position) return null;
|
|
720
|
+
|
|
721
|
+
let bestCandidate = null;
|
|
722
|
+
for (const candidateGain of alphaGainCandidates) {
|
|
723
|
+
if (!Number.isFinite(candidateGain) || candidateGain <= 1) continue;
|
|
724
|
+
|
|
725
|
+
const candidate = evaluateRestorationCandidate({
|
|
726
|
+
originalImageData,
|
|
727
|
+
alphaMap: seedCandidate.alphaMap,
|
|
728
|
+
position: seedCandidate.position,
|
|
729
|
+
source: `${seedCandidate.source}+gain`,
|
|
730
|
+
config: seedCandidate.config,
|
|
731
|
+
baselineNearBlackRatio: calculateNearBlackRatio(originalImageData, seedCandidate.position),
|
|
732
|
+
adaptiveConfidence,
|
|
733
|
+
alphaGain: candidateGain,
|
|
734
|
+
provenance: seedCandidate.provenance,
|
|
735
|
+
includeImageData: false
|
|
736
|
+
});
|
|
737
|
+
|
|
738
|
+
if (!candidate?.accepted) continue;
|
|
739
|
+
bestCandidate = pickBetterCandidate(bestCandidate, candidate, 0.002);
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
return bestCandidate;
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
function insertTopPreviewCandidate(topCandidates, candidate) {
|
|
746
|
+
topCandidates.push(candidate);
|
|
747
|
+
topCandidates.sort((a, b) => b.coarseScore - a.coarseScore);
|
|
748
|
+
if (topCandidates.length > PREVIEW_ANCHOR_TOP_K) {
|
|
749
|
+
topCandidates.length = PREVIEW_ANCHOR_TOP_K;
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
function searchBottomRightPreviewCandidate({
|
|
754
|
+
originalImageData,
|
|
755
|
+
config,
|
|
756
|
+
alpha48,
|
|
757
|
+
alpha96,
|
|
758
|
+
getAlphaMap,
|
|
759
|
+
resolveAlphaMap = null,
|
|
760
|
+
adaptiveConfidence = null
|
|
761
|
+
}) {
|
|
762
|
+
if (!isPreviewAnchorSearchEligible(originalImageData, config)) return null;
|
|
763
|
+
|
|
764
|
+
const minSize = Math.max(
|
|
765
|
+
PREVIEW_ANCHOR_MIN_SIZE,
|
|
766
|
+
Math.round(config.logoSize * PREVIEW_ANCHOR_MIN_SIZE_RATIO)
|
|
767
|
+
);
|
|
768
|
+
const maxSize = Math.max(
|
|
769
|
+
minSize,
|
|
770
|
+
Math.round(config.logoSize * PREVIEW_ANCHOR_MAX_SIZE_RATIO)
|
|
771
|
+
);
|
|
772
|
+
const minMarginRight = Math.max(8, config.marginRight - PREVIEW_ANCHOR_MARGIN_WINDOW);
|
|
773
|
+
const maxMarginRight = config.marginRight + PREVIEW_ANCHOR_MARGIN_EXTENSION;
|
|
774
|
+
const minMarginBottom = Math.max(8, config.marginBottom - PREVIEW_ANCHOR_MARGIN_WINDOW);
|
|
775
|
+
const maxMarginBottom = config.marginBottom + PREVIEW_ANCHOR_MARGIN_EXTENSION;
|
|
776
|
+
const topCandidates = [];
|
|
777
|
+
|
|
778
|
+
for (let size = minSize; size <= maxSize; size += PREVIEW_ANCHOR_SIZE_STEP) {
|
|
779
|
+
const alphaMap = typeof resolveAlphaMap === 'function'
|
|
780
|
+
? resolveAlphaMap(size)
|
|
781
|
+
: resolveAlphaMapForSize(size, {
|
|
782
|
+
alpha48,
|
|
783
|
+
alpha96,
|
|
784
|
+
getAlphaMap
|
|
785
|
+
});
|
|
786
|
+
if (!alphaMap) continue;
|
|
787
|
+
|
|
788
|
+
for (let marginRight = minMarginRight; marginRight <= maxMarginRight; marginRight += PREVIEW_ANCHOR_MARGIN_STEP) {
|
|
789
|
+
const x = originalImageData.width - marginRight - size;
|
|
790
|
+
if (x < 0 || x + size > originalImageData.width) continue;
|
|
791
|
+
|
|
792
|
+
for (let marginBottom = minMarginBottom; marginBottom <= maxMarginBottom; marginBottom += PREVIEW_ANCHOR_MARGIN_STEP) {
|
|
793
|
+
const y = originalImageData.height - marginBottom - size;
|
|
794
|
+
if (y < 0 || y + size > originalImageData.height) continue;
|
|
795
|
+
|
|
796
|
+
const coarseSpatialScore = computeRegionSpatialCorrelation({
|
|
797
|
+
imageData: originalImageData,
|
|
798
|
+
alphaMap,
|
|
799
|
+
region: { x, y, size }
|
|
800
|
+
});
|
|
801
|
+
const coarseGradientScore = computeRegionGradientCorrelation({
|
|
802
|
+
imageData: originalImageData,
|
|
803
|
+
alphaMap,
|
|
804
|
+
region: { x, y, size }
|
|
805
|
+
});
|
|
806
|
+
const coarseScore =
|
|
807
|
+
Math.max(0, coarseGradientScore) * 0.6 +
|
|
808
|
+
Math.max(0, coarseSpatialScore) * 0.4;
|
|
809
|
+
|
|
810
|
+
if (coarseScore < PREVIEW_ANCHOR_MIN_SCORE) continue;
|
|
811
|
+
|
|
812
|
+
insertTopPreviewCandidate(topCandidates, {
|
|
813
|
+
coarseScore,
|
|
814
|
+
alphaMap,
|
|
815
|
+
position: { x, y, width: size, height: size },
|
|
816
|
+
config: {
|
|
817
|
+
logoSize: size,
|
|
818
|
+
marginRight,
|
|
819
|
+
marginBottom
|
|
820
|
+
}
|
|
821
|
+
});
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
let bestCandidate = null;
|
|
827
|
+
for (const coarseCandidate of topCandidates) {
|
|
828
|
+
for (const sizeDelta of PREVIEW_ANCHOR_LOCAL_DELTAS) {
|
|
829
|
+
const size = coarseCandidate.position.width + sizeDelta;
|
|
830
|
+
if (size < PREVIEW_ANCHOR_MIN_SIZE) continue;
|
|
831
|
+
|
|
832
|
+
const alphaMap = typeof resolveAlphaMap === 'function'
|
|
833
|
+
? resolveAlphaMap(size)
|
|
834
|
+
: resolveAlphaMapForSize(size, {
|
|
835
|
+
alpha48,
|
|
836
|
+
alpha96,
|
|
837
|
+
getAlphaMap
|
|
838
|
+
});
|
|
839
|
+
if (!alphaMap) continue;
|
|
840
|
+
|
|
841
|
+
for (const dx of PREVIEW_ANCHOR_LOCAL_DELTAS) {
|
|
842
|
+
for (const dy of PREVIEW_ANCHOR_LOCAL_DELTAS) {
|
|
843
|
+
const position = {
|
|
844
|
+
x: coarseCandidate.position.x + dx,
|
|
845
|
+
y: coarseCandidate.position.y + dy,
|
|
846
|
+
width: size,
|
|
847
|
+
height: size
|
|
848
|
+
};
|
|
849
|
+
if (position.x < 0 || position.y < 0) continue;
|
|
850
|
+
if (position.x + position.width > originalImageData.width) continue;
|
|
851
|
+
if (position.y + position.height > originalImageData.height) continue;
|
|
852
|
+
|
|
853
|
+
const config = {
|
|
854
|
+
logoSize: size,
|
|
855
|
+
marginRight: originalImageData.width - position.x - size,
|
|
856
|
+
marginBottom: originalImageData.height - position.y - size
|
|
857
|
+
};
|
|
858
|
+
const candidate = evaluateRestorationCandidate({
|
|
859
|
+
originalImageData,
|
|
860
|
+
alphaMap,
|
|
861
|
+
position,
|
|
862
|
+
source: 'standard+preview-anchor',
|
|
863
|
+
config,
|
|
864
|
+
baselineNearBlackRatio: calculateNearBlackRatio(originalImageData, position),
|
|
865
|
+
adaptiveConfidence,
|
|
866
|
+
provenance: {
|
|
867
|
+
previewAnchor: true,
|
|
868
|
+
previewAnchorLocalRefine: sizeDelta !== 0 || dx !== 0 || dy !== 0
|
|
869
|
+
},
|
|
870
|
+
includeImageData: false
|
|
871
|
+
});
|
|
872
|
+
|
|
873
|
+
if (!candidate?.accepted) continue;
|
|
874
|
+
bestCandidate = pickBetterCandidate(bestCandidate, candidate, 0.002);
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
return bestCandidate;
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
function evaluateStandardTrialsForSeeds({
|
|
884
|
+
originalImageData,
|
|
885
|
+
candidateSeeds
|
|
886
|
+
}) {
|
|
887
|
+
const standardTrials = candidateSeeds
|
|
888
|
+
.map((seed) => evaluateRestorationCandidate({
|
|
889
|
+
originalImageData,
|
|
890
|
+
alphaMap: seed.alphaMap,
|
|
891
|
+
position: seed.position,
|
|
892
|
+
source: seed.source,
|
|
893
|
+
config: seed.config,
|
|
894
|
+
baselineNearBlackRatio: calculateNearBlackRatio(originalImageData, seed.position),
|
|
895
|
+
provenance: seed.provenance,
|
|
896
|
+
includeImageData: false
|
|
897
|
+
}))
|
|
898
|
+
.filter(Boolean);
|
|
899
|
+
const standardTrial = standardTrials.find((candidate) => candidate.source === 'standard') ?? standardTrials[0] ?? null;
|
|
900
|
+
const standardSpatialScore = standardTrial?.originalSpatialScore ?? null;
|
|
901
|
+
const standardGradientScore = standardTrial?.originalGradientScore ?? null;
|
|
902
|
+
const hasReliableStandardMatch = hasReliableStandardWatermarkSignal({
|
|
903
|
+
spatialScore: standardSpatialScore,
|
|
904
|
+
gradientScore: standardGradientScore
|
|
905
|
+
});
|
|
906
|
+
|
|
907
|
+
return {
|
|
908
|
+
standardTrials,
|
|
909
|
+
standardTrial,
|
|
910
|
+
standardSpatialScore,
|
|
911
|
+
standardGradientScore,
|
|
912
|
+
hasReliableStandardMatch
|
|
913
|
+
};
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
function resolveStandardAnchorSelection({
|
|
917
|
+
originalImageData,
|
|
918
|
+
config,
|
|
919
|
+
position,
|
|
920
|
+
alpha48,
|
|
921
|
+
alpha96,
|
|
922
|
+
getAlphaMap,
|
|
923
|
+
resolveAlphaMap
|
|
924
|
+
}) {
|
|
925
|
+
let standardCandidateSeeds = buildStandardCandidateSeeds({
|
|
926
|
+
originalImageData,
|
|
927
|
+
config,
|
|
928
|
+
position,
|
|
929
|
+
alpha48,
|
|
930
|
+
alpha96,
|
|
931
|
+
getAlphaMap,
|
|
932
|
+
resolveAlphaMap,
|
|
933
|
+
includeCatalogVariants: false
|
|
934
|
+
});
|
|
935
|
+
let standardSelection = evaluateStandardTrialsForSeeds({
|
|
936
|
+
originalImageData,
|
|
937
|
+
candidateSeeds: standardCandidateSeeds
|
|
938
|
+
});
|
|
939
|
+
|
|
940
|
+
const shouldExpandStandardCatalog =
|
|
941
|
+
!standardSelection.hasReliableStandardMatch &&
|
|
942
|
+
(!standardSelection.standardTrial || shouldEscalateSearch(standardSelection.standardTrial));
|
|
943
|
+
|
|
944
|
+
if (shouldExpandStandardCatalog) {
|
|
945
|
+
standardCandidateSeeds = buildStandardCandidateSeeds({
|
|
946
|
+
originalImageData,
|
|
947
|
+
config,
|
|
948
|
+
position,
|
|
949
|
+
alpha48,
|
|
950
|
+
alpha96,
|
|
951
|
+
getAlphaMap,
|
|
952
|
+
resolveAlphaMap,
|
|
953
|
+
includeCatalogVariants: true
|
|
954
|
+
});
|
|
955
|
+
standardSelection = evaluateStandardTrialsForSeeds({
|
|
956
|
+
originalImageData,
|
|
957
|
+
candidateSeeds: standardCandidateSeeds
|
|
958
|
+
});
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
return {
|
|
962
|
+
standardCandidateSeeds,
|
|
963
|
+
...standardSelection
|
|
964
|
+
};
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
function resolveCandidatePromotion(candidate, {
|
|
968
|
+
reliableMatch = false
|
|
969
|
+
} = {}) {
|
|
970
|
+
if (!candidate?.accepted) {
|
|
971
|
+
return null;
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
if (reliableMatch) {
|
|
975
|
+
return {
|
|
976
|
+
candidate,
|
|
977
|
+
decisionTier: 'direct-match'
|
|
978
|
+
};
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
return {
|
|
982
|
+
candidate: {
|
|
983
|
+
...candidate,
|
|
984
|
+
source: `${candidate.source}+validated`
|
|
985
|
+
},
|
|
986
|
+
decisionTier: 'validated-match'
|
|
987
|
+
};
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
function promoteBaseCandidate(baseCandidate, baseDecisionTier, candidate, {
|
|
991
|
+
reliableMatch = false,
|
|
992
|
+
minCostDelta = 0.002
|
|
993
|
+
} = {}) {
|
|
994
|
+
const promotion = resolveCandidatePromotion(candidate, {
|
|
995
|
+
reliableMatch
|
|
996
|
+
});
|
|
997
|
+
if (!promotion) {
|
|
998
|
+
return {
|
|
999
|
+
baseCandidate,
|
|
1000
|
+
baseDecisionTier
|
|
1001
|
+
};
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
if (
|
|
1005
|
+
shouldPreserveCanonicalAnchor(baseCandidate, promotion.candidate)
|
|
1006
|
+
) {
|
|
1007
|
+
return {
|
|
1008
|
+
baseCandidate,
|
|
1009
|
+
baseDecisionTier
|
|
1010
|
+
};
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
const previousCandidate = baseCandidate;
|
|
1014
|
+
const nextCandidate = pickBetterCandidate(baseCandidate, promotion.candidate, minCostDelta);
|
|
1015
|
+
return {
|
|
1016
|
+
baseCandidate: nextCandidate,
|
|
1017
|
+
baseDecisionTier: nextCandidate !== previousCandidate
|
|
1018
|
+
? promotion.decisionTier
|
|
1019
|
+
: baseDecisionTier
|
|
1020
|
+
};
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
function evaluateAdaptiveTrial({
|
|
1024
|
+
originalImageData,
|
|
1025
|
+
config,
|
|
1026
|
+
alpha96,
|
|
1027
|
+
resolveAlphaMap,
|
|
1028
|
+
allowAdaptiveSearch
|
|
1029
|
+
}) {
|
|
1030
|
+
if (!allowAdaptiveSearch || !alpha96) {
|
|
1031
|
+
return {
|
|
1032
|
+
adaptive: null,
|
|
1033
|
+
adaptiveConfidence: null,
|
|
1034
|
+
adaptiveTrial: null
|
|
1035
|
+
};
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
const adaptive = detectAdaptiveWatermarkRegion({
|
|
1039
|
+
imageData: originalImageData,
|
|
1040
|
+
alpha96,
|
|
1041
|
+
defaultConfig: config
|
|
1042
|
+
});
|
|
1043
|
+
const adaptiveConfidence = adaptive?.confidence ?? null;
|
|
1044
|
+
|
|
1045
|
+
if (!adaptive?.region || !(
|
|
1046
|
+
hasReliableAdaptiveWatermarkSignal(adaptive) ||
|
|
1047
|
+
adaptive.confidence >= VALIDATION_MIN_CONFIDENCE_FOR_ADAPTIVE_TRIAL
|
|
1048
|
+
)) {
|
|
1049
|
+
return {
|
|
1050
|
+
adaptive,
|
|
1051
|
+
adaptiveConfidence,
|
|
1052
|
+
adaptiveTrial: null
|
|
1053
|
+
};
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
const size = adaptive.region.size;
|
|
1057
|
+
const adaptivePosition = {
|
|
1058
|
+
x: adaptive.region.x,
|
|
1059
|
+
y: adaptive.region.y,
|
|
1060
|
+
width: size,
|
|
1061
|
+
height: size
|
|
1062
|
+
};
|
|
1063
|
+
const adaptiveAlphaMap = resolveAlphaMap(size);
|
|
1064
|
+
if (!adaptiveAlphaMap) {
|
|
1065
|
+
throw new Error(`Missing alpha map for adaptive size ${size}`);
|
|
1066
|
+
}
|
|
1067
|
+
const adaptiveConfig = {
|
|
1068
|
+
logoSize: size,
|
|
1069
|
+
marginRight: originalImageData.width - adaptivePosition.x - size,
|
|
1070
|
+
marginBottom: originalImageData.height - adaptivePosition.y - size
|
|
1071
|
+
};
|
|
1072
|
+
|
|
1073
|
+
return {
|
|
1074
|
+
adaptive,
|
|
1075
|
+
adaptiveConfidence,
|
|
1076
|
+
adaptiveTrial: evaluateRestorationCandidate({
|
|
1077
|
+
originalImageData,
|
|
1078
|
+
alphaMap: adaptiveAlphaMap,
|
|
1079
|
+
position: adaptivePosition,
|
|
1080
|
+
source: 'adaptive',
|
|
1081
|
+
config: adaptiveConfig,
|
|
1082
|
+
baselineNearBlackRatio: calculateNearBlackRatio(originalImageData, adaptivePosition),
|
|
1083
|
+
adaptiveConfidence: adaptive.confidence,
|
|
1084
|
+
provenance: { adaptive: true },
|
|
1085
|
+
includeImageData: false
|
|
1086
|
+
})
|
|
1087
|
+
};
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
function refineSelectedAnchorCandidate({
|
|
1091
|
+
originalImageData,
|
|
1092
|
+
baseCandidate,
|
|
1093
|
+
baseDecisionTier,
|
|
1094
|
+
adaptiveConfidence,
|
|
1095
|
+
alphaGainCandidates
|
|
1096
|
+
}) {
|
|
1097
|
+
let selectedTrial = ensureCandidateImageData(baseCandidate, originalImageData);
|
|
1098
|
+
let alphaMap = baseCandidate.alphaMap;
|
|
1099
|
+
let position = baseCandidate.position;
|
|
1100
|
+
let config = baseCandidate.config;
|
|
1101
|
+
let source = baseCandidate.source;
|
|
1102
|
+
let decisionTier = baseDecisionTier || inferDecisionTier(baseCandidate);
|
|
1103
|
+
let templateWarp = null;
|
|
1104
|
+
let selectedAlphaGain = baseCandidate.alphaGain ?? 1;
|
|
1105
|
+
|
|
1106
|
+
const warpCandidate = findBestTemplateWarp({
|
|
1107
|
+
originalImageData,
|
|
1108
|
+
alphaMap,
|
|
1109
|
+
position,
|
|
1110
|
+
baselineSpatialScore: selectedTrial.originalSpatialScore,
|
|
1111
|
+
baselineGradientScore: selectedTrial.originalGradientScore,
|
|
1112
|
+
shiftCandidates: selectedTrial.provenance?.previewAnchor === true
|
|
1113
|
+
? PREVIEW_TEMPLATE_ALIGN_SHIFTS
|
|
1114
|
+
: TEMPLATE_ALIGN_SHIFTS,
|
|
1115
|
+
scaleCandidates: selectedTrial.provenance?.previewAnchor === true
|
|
1116
|
+
? PREVIEW_TEMPLATE_ALIGN_SCALES
|
|
1117
|
+
: TEMPLATE_ALIGN_SCALES
|
|
1118
|
+
});
|
|
1119
|
+
if (warpCandidate) {
|
|
1120
|
+
const warpedTrial = evaluateRestorationCandidate({
|
|
1121
|
+
originalImageData,
|
|
1122
|
+
alphaMap: warpCandidate.alphaMap,
|
|
1123
|
+
position,
|
|
1124
|
+
source: `${source}+warp`,
|
|
1125
|
+
config,
|
|
1126
|
+
baselineNearBlackRatio: calculateNearBlackRatio(originalImageData, position),
|
|
1127
|
+
adaptiveConfidence,
|
|
1128
|
+
provenance: selectedTrial.provenance,
|
|
1129
|
+
includeImageData: false
|
|
1130
|
+
});
|
|
1131
|
+
const betterWarpTrial = pickBetterCandidate(selectedTrial, warpedTrial);
|
|
1132
|
+
if (betterWarpTrial !== selectedTrial) {
|
|
1133
|
+
alphaMap = warpedTrial.alphaMap;
|
|
1134
|
+
source = betterWarpTrial.source;
|
|
1135
|
+
selectedTrial = ensureCandidateImageData(betterWarpTrial, originalImageData);
|
|
1136
|
+
templateWarp = warpCandidate.shift;
|
|
1137
|
+
decisionTier = inferDecisionTier(betterWarpTrial, {
|
|
1138
|
+
directMatch: decisionTier === 'direct-match'
|
|
1139
|
+
});
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
const shouldRunGainSearch = selectedTrial.provenance?.previewAnchor === true
|
|
1144
|
+
? isPreviewAnchorGainSearchRequired(selectedTrial)
|
|
1145
|
+
: shouldEscalateSearch(selectedTrial);
|
|
1146
|
+
let bestGainTrial = selectedTrial;
|
|
1147
|
+
if (shouldRunGainSearch) {
|
|
1148
|
+
for (const candidateGain of alphaGainCandidates) {
|
|
1149
|
+
const gainTrial = evaluateRestorationCandidate({
|
|
1150
|
+
originalImageData,
|
|
1151
|
+
alphaMap,
|
|
1152
|
+
position,
|
|
1153
|
+
source: `${source}+gain`,
|
|
1154
|
+
config,
|
|
1155
|
+
baselineNearBlackRatio: calculateNearBlackRatio(originalImageData, position),
|
|
1156
|
+
adaptiveConfidence,
|
|
1157
|
+
alphaGain: candidateGain,
|
|
1158
|
+
provenance: selectedTrial.provenance,
|
|
1159
|
+
includeImageData: false
|
|
1160
|
+
});
|
|
1161
|
+
bestGainTrial = pickBetterCandidate(bestGainTrial, gainTrial);
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1164
|
+
if (bestGainTrial !== selectedTrial) {
|
|
1165
|
+
selectedTrial = ensureCandidateImageData(bestGainTrial, originalImageData);
|
|
1166
|
+
source = bestGainTrial.source;
|
|
1167
|
+
selectedAlphaGain = bestGainTrial.alphaGain;
|
|
1168
|
+
decisionTier = inferDecisionTier(bestGainTrial, {
|
|
1169
|
+
directMatch: decisionTier === 'direct-match'
|
|
1170
|
+
});
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
return {
|
|
1174
|
+
selectedTrial: ensureCandidateImageData(selectedTrial, originalImageData),
|
|
1175
|
+
source,
|
|
1176
|
+
alphaMap,
|
|
1177
|
+
position,
|
|
1178
|
+
config,
|
|
1179
|
+
templateWarp,
|
|
1180
|
+
alphaGain: selectedAlphaGain,
|
|
1181
|
+
decisionTier
|
|
1182
|
+
};
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1185
|
+
export function selectInitialCandidate({
|
|
1186
|
+
originalImageData,
|
|
1187
|
+
config,
|
|
1188
|
+
position,
|
|
1189
|
+
alpha48,
|
|
1190
|
+
alpha96,
|
|
1191
|
+
getAlphaMap,
|
|
1192
|
+
allowAdaptiveSearch,
|
|
1193
|
+
alphaGainCandidates
|
|
1194
|
+
}) {
|
|
1195
|
+
const resolveAlphaMap = createAlphaMapResolver({ alpha48, alpha96, getAlphaMap });
|
|
1196
|
+
const fallbackAlphaMap = config.logoSize === 96 ? alpha96 : alpha48;
|
|
1197
|
+
const {
|
|
1198
|
+
standardCandidateSeeds,
|
|
1199
|
+
standardTrials,
|
|
1200
|
+
standardTrial,
|
|
1201
|
+
standardSpatialScore,
|
|
1202
|
+
standardGradientScore,
|
|
1203
|
+
hasReliableStandardMatch
|
|
1204
|
+
} = resolveStandardAnchorSelection({
|
|
1205
|
+
originalImageData,
|
|
1206
|
+
config,
|
|
1207
|
+
position,
|
|
1208
|
+
alpha48,
|
|
1209
|
+
alpha96,
|
|
1210
|
+
getAlphaMap,
|
|
1211
|
+
resolveAlphaMap
|
|
1212
|
+
});
|
|
1213
|
+
let baseCandidate = null;
|
|
1214
|
+
let baseDecisionTier = 'insufficient';
|
|
1215
|
+
if (hasReliableStandardMatch && standardTrial?.accepted) {
|
|
1216
|
+
baseCandidate = standardTrial;
|
|
1217
|
+
baseDecisionTier = 'direct-match';
|
|
1218
|
+
} else if (standardTrial?.accepted) {
|
|
1219
|
+
baseCandidate = {
|
|
1220
|
+
...standardTrial,
|
|
1221
|
+
source: `${standardTrial.source}+validated`
|
|
1222
|
+
};
|
|
1223
|
+
baseDecisionTier = 'validated-match';
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
if (
|
|
1227
|
+
!baseCandidate &&
|
|
1228
|
+
standardTrial &&
|
|
1229
|
+
hasReliableStandardMatch
|
|
1230
|
+
) {
|
|
1231
|
+
const adaptiveConfidence = null;
|
|
1232
|
+
const gainedStandardCandidate = searchCandidateAlphaGain({
|
|
1233
|
+
originalImageData,
|
|
1234
|
+
seedCandidate: {
|
|
1235
|
+
...standardTrial,
|
|
1236
|
+
source: 'standard+validated'
|
|
1237
|
+
},
|
|
1238
|
+
adaptiveConfidence,
|
|
1239
|
+
alphaGainCandidates
|
|
1240
|
+
});
|
|
1241
|
+
if (gainedStandardCandidate) {
|
|
1242
|
+
baseCandidate = gainedStandardCandidate;
|
|
1243
|
+
baseDecisionTier = 'validated-match';
|
|
1244
|
+
}
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
let adaptive = null;
|
|
1248
|
+
let adaptiveConfidence = null;
|
|
1249
|
+
let adaptiveTrial = null;
|
|
1250
|
+
for (const candidate of standardTrials) {
|
|
1251
|
+
if (!candidate || candidate === standardTrial) continue;
|
|
1252
|
+
({
|
|
1253
|
+
baseCandidate,
|
|
1254
|
+
baseDecisionTier
|
|
1255
|
+
} = promoteBaseCandidate(baseCandidate, baseDecisionTier, candidate, {
|
|
1256
|
+
reliableMatch: hasReliableStandardWatermarkSignal({
|
|
1257
|
+
spatialScore: candidate.originalSpatialScore,
|
|
1258
|
+
gradientScore: candidate.originalGradientScore
|
|
1259
|
+
})
|
|
1260
|
+
}));
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
const previewAnchorCandidate = searchBottomRightPreviewCandidate({
|
|
1264
|
+
originalImageData,
|
|
1265
|
+
config,
|
|
1266
|
+
alpha48,
|
|
1267
|
+
alpha96,
|
|
1268
|
+
getAlphaMap,
|
|
1269
|
+
resolveAlphaMap,
|
|
1270
|
+
adaptiveConfidence
|
|
1271
|
+
});
|
|
1272
|
+
if (previewAnchorCandidate) {
|
|
1273
|
+
({
|
|
1274
|
+
baseCandidate,
|
|
1275
|
+
baseDecisionTier
|
|
1276
|
+
} = promoteBaseCandidate(baseCandidate, baseDecisionTier, previewAnchorCandidate));
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
if (
|
|
1280
|
+
baseDecisionTier !== 'direct-match' &&
|
|
1281
|
+
!baseCandidate?.provenance?.previewAnchor &&
|
|
1282
|
+
shouldEscalateSearch(baseCandidate)
|
|
1283
|
+
) {
|
|
1284
|
+
const sizeJitterCandidate = searchStandardSizeJitterCandidate({
|
|
1285
|
+
originalImageData,
|
|
1286
|
+
candidateSeeds: standardCandidateSeeds,
|
|
1287
|
+
alpha48,
|
|
1288
|
+
alpha96,
|
|
1289
|
+
getAlphaMap,
|
|
1290
|
+
resolveAlphaMap
|
|
1291
|
+
});
|
|
1292
|
+
if (sizeJitterCandidate) {
|
|
1293
|
+
({
|
|
1294
|
+
baseCandidate,
|
|
1295
|
+
baseDecisionTier
|
|
1296
|
+
} = promoteBaseCandidate(baseCandidate, baseDecisionTier, sizeJitterCandidate));
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1300
|
+
if (
|
|
1301
|
+
baseDecisionTier !== 'direct-match' &&
|
|
1302
|
+
baseCandidate?.provenance?.sizeJitter === true &&
|
|
1303
|
+
!baseCandidate?.provenance?.previewAnchor &&
|
|
1304
|
+
isStandardCandidateSource(baseCandidate) &&
|
|
1305
|
+
shouldEscalateSearch(baseCandidate)
|
|
1306
|
+
) {
|
|
1307
|
+
const fineLocalCandidate = searchFineStandardLocalCandidate({
|
|
1308
|
+
originalImageData,
|
|
1309
|
+
seedCandidate: baseCandidate,
|
|
1310
|
+
adaptiveConfidence
|
|
1311
|
+
});
|
|
1312
|
+
if (fineLocalCandidate) {
|
|
1313
|
+
({
|
|
1314
|
+
baseCandidate,
|
|
1315
|
+
baseDecisionTier
|
|
1316
|
+
} = promoteBaseCandidate(baseCandidate, baseDecisionTier, fineLocalCandidate));
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1320
|
+
const shouldEvaluateAdaptive = () => {
|
|
1321
|
+
if (!allowAdaptiveSearch || !alpha96) return false;
|
|
1322
|
+
if (!baseCandidate) return true;
|
|
1323
|
+
if (!shouldEscalateSearch(baseCandidate)) return false;
|
|
1324
|
+
|
|
1325
|
+
baseCandidate = ensureCandidateImageData(baseCandidate, originalImageData);
|
|
1326
|
+
|
|
1327
|
+
return shouldAttemptAdaptiveFallback({
|
|
1328
|
+
processedImageData: baseCandidate.imageData,
|
|
1329
|
+
alphaMap: baseCandidate.alphaMap,
|
|
1330
|
+
position: baseCandidate.position,
|
|
1331
|
+
originalImageData,
|
|
1332
|
+
originalSpatialMismatchThreshold: 0
|
|
1333
|
+
});
|
|
1334
|
+
};
|
|
1335
|
+
|
|
1336
|
+
if (shouldEvaluateAdaptive()) {
|
|
1337
|
+
({
|
|
1338
|
+
adaptive,
|
|
1339
|
+
adaptiveConfidence,
|
|
1340
|
+
adaptiveTrial
|
|
1341
|
+
} = evaluateAdaptiveTrial({
|
|
1342
|
+
originalImageData,
|
|
1343
|
+
config,
|
|
1344
|
+
alpha96,
|
|
1345
|
+
resolveAlphaMap,
|
|
1346
|
+
allowAdaptiveSearch
|
|
1347
|
+
}));
|
|
1348
|
+
}
|
|
1349
|
+
|
|
1350
|
+
if (adaptiveTrial) {
|
|
1351
|
+
({
|
|
1352
|
+
baseCandidate,
|
|
1353
|
+
baseDecisionTier
|
|
1354
|
+
} = promoteBaseCandidate(baseCandidate, baseDecisionTier, adaptiveTrial, {
|
|
1355
|
+
reliableMatch: hasReliableAdaptiveWatermarkSignal(adaptive)
|
|
1356
|
+
}));
|
|
1357
|
+
}
|
|
1358
|
+
|
|
1359
|
+
if (
|
|
1360
|
+
!baseCandidate?.provenance?.previewAnchor &&
|
|
1361
|
+
!hasReliableAdaptiveWatermarkSignal(adaptive) &&
|
|
1362
|
+
shouldSearchNearbyStandardCandidate(baseCandidate, originalImageData)
|
|
1363
|
+
) {
|
|
1364
|
+
const nearbyStandardCandidate = searchNearbyStandardCandidate({
|
|
1365
|
+
originalImageData,
|
|
1366
|
+
candidateSeeds: standardCandidateSeeds,
|
|
1367
|
+
adaptiveConfidence
|
|
1368
|
+
});
|
|
1369
|
+
if (nearbyStandardCandidate) {
|
|
1370
|
+
({
|
|
1371
|
+
baseCandidate,
|
|
1372
|
+
baseDecisionTier
|
|
1373
|
+
} = promoteBaseCandidate(baseCandidate, baseDecisionTier, nearbyStandardCandidate));
|
|
1374
|
+
}
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1377
|
+
if (!baseCandidate) {
|
|
1378
|
+
if (hasReliableStandardMatch && standardTrial) {
|
|
1379
|
+
baseCandidate = standardTrial;
|
|
1380
|
+
baseDecisionTier = 'direct-match';
|
|
1381
|
+
} else if (hasReliableAdaptiveWatermarkSignal(adaptive) && adaptiveTrial) {
|
|
1382
|
+
baseCandidate = adaptiveTrial;
|
|
1383
|
+
baseDecisionTier = 'direct-match';
|
|
1384
|
+
}
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1387
|
+
if (!baseCandidate) {
|
|
1388
|
+
const validatedCandidate = pickBestValidatedCandidate([standardTrial, adaptiveTrial]);
|
|
1389
|
+
if (!validatedCandidate) {
|
|
1390
|
+
return {
|
|
1391
|
+
selectedTrial: null,
|
|
1392
|
+
source: 'skipped',
|
|
1393
|
+
alphaMap: fallbackAlphaMap,
|
|
1394
|
+
position,
|
|
1395
|
+
config,
|
|
1396
|
+
adaptiveConfidence,
|
|
1397
|
+
standardSpatialScore,
|
|
1398
|
+
standardGradientScore,
|
|
1399
|
+
templateWarp: null,
|
|
1400
|
+
alphaGain: 1,
|
|
1401
|
+
decisionTier: 'insufficient'
|
|
1402
|
+
};
|
|
1403
|
+
}
|
|
1404
|
+
baseCandidate = {
|
|
1405
|
+
...validatedCandidate,
|
|
1406
|
+
source: `${validatedCandidate.source}+validated`
|
|
1407
|
+
};
|
|
1408
|
+
baseDecisionTier = 'validated-match';
|
|
1409
|
+
}
|
|
1410
|
+
|
|
1411
|
+
if (shouldRevertLocalShiftToStandardTrial(baseCandidate, standardTrial)) {
|
|
1412
|
+
baseCandidate = standardTrial;
|
|
1413
|
+
baseDecisionTier = hasReliableStandardMatch ? 'direct-match' : 'validated-match';
|
|
1414
|
+
}
|
|
1415
|
+
|
|
1416
|
+
const {
|
|
1417
|
+
selectedTrial,
|
|
1418
|
+
source,
|
|
1419
|
+
alphaMap,
|
|
1420
|
+
position: refinedPosition,
|
|
1421
|
+
config: refinedConfig,
|
|
1422
|
+
templateWarp,
|
|
1423
|
+
alphaGain,
|
|
1424
|
+
decisionTier
|
|
1425
|
+
} = refineSelectedAnchorCandidate({
|
|
1426
|
+
originalImageData,
|
|
1427
|
+
baseCandidate,
|
|
1428
|
+
baseDecisionTier,
|
|
1429
|
+
adaptiveConfidence,
|
|
1430
|
+
alphaGainCandidates
|
|
1431
|
+
});
|
|
1432
|
+
|
|
1433
|
+
return {
|
|
1434
|
+
selectedTrial: ensureCandidateImageData(selectedTrial, originalImageData),
|
|
1435
|
+
source,
|
|
1436
|
+
alphaMap,
|
|
1437
|
+
position: refinedPosition,
|
|
1438
|
+
config: refinedConfig,
|
|
1439
|
+
adaptiveConfidence,
|
|
1440
|
+
standardSpatialScore,
|
|
1441
|
+
standardGradientScore,
|
|
1442
|
+
templateWarp,
|
|
1443
|
+
alphaGain,
|
|
1444
|
+
decisionTier
|
|
1445
|
+
};
|
|
1446
|
+
}
|