@pilio/gemini-watermark-remover 1.0.23 → 1.0.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pilio/gemini-watermark-remover",
|
|
3
3
|
"description": "Automatically removes watermarks from Gemini AI generated images",
|
|
4
|
-
"version": "1.0.
|
|
4
|
+
"version": "1.0.25",
|
|
5
5
|
"author": "GargantuaX",
|
|
6
6
|
"sideEffects": false,
|
|
7
7
|
"files": [
|
|
@@ -96,7 +96,8 @@
|
|
|
96
96
|
"release:readiness": "node scripts/create-release-readiness-report.js",
|
|
97
97
|
"release:quality-gate": "pnpm compare:allenk-v2 -- --fail-on-incomplete && pnpm release:readiness -- --fail-on-not-ready",
|
|
98
98
|
"release:goal-audit": "node scripts/create-release-goal-audit-report.js",
|
|
99
|
-
"release:
|
|
99
|
+
"release:ci-check": "node scripts/check-github-ci.js --workflow ci.yml --commit HEAD --fail-closed",
|
|
100
|
+
"release:preflight": "pnpm test && pnpm build && pnpm package:extension && pnpm release:quality-gate && pnpm release:goal-audit -- --fail-on-incomplete && pnpm release:ci-check",
|
|
100
101
|
"visible-residual:loop": "node scripts/run-visible-residual-loop.js",
|
|
101
102
|
"visible-residual:verify": "node scripts/verify-visible-residual-loop.js",
|
|
102
103
|
"visible-residual:validate-human-review": "node scripts/validate-visible-residual-human-review.js",
|
|
@@ -112,6 +113,9 @@
|
|
|
112
113
|
"render:skipped-crops": "node scripts/render-skipped-candidate-crops.js",
|
|
113
114
|
"render:video-crops": "node scripts/render-video-crop-sheet.js",
|
|
114
115
|
"analyze:video-residual": "node scripts/analyze-video-residual.js",
|
|
116
|
+
"score:video-candidates": "node scripts/score-video-watermark-candidates.js",
|
|
117
|
+
"sweep:veo-text-cleanup": "node scripts/sweep-veo-text-cleanup.js",
|
|
118
|
+
"score:video-output-residual": "node scripts/score-video-output-residual.js",
|
|
115
119
|
"benchmark:video-crops": "node scripts/video-crop-benchmark.js",
|
|
116
120
|
"report:video-crops": "node scripts/report-video-crop-benchmark.js",
|
|
117
121
|
"diagnose:video-lowbody": "node scripts/diagnose-video-lowbody-regression.js",
|
|
@@ -144,7 +148,12 @@
|
|
|
144
148
|
"export:allenk-fdncnn-onnx-video": "node scripts/export-allenk-fdncnn-onnx-video.js",
|
|
145
149
|
"export:allenk-fdncnn-onnx-frame-video": "node scripts/export-allenk-fdncnn-onnx-frame-video.js",
|
|
146
150
|
"report:allenk-fdncnn-onnx-video-evidence": "node scripts/create-allenk-fdncnn-onnx-video-evidence.js",
|
|
151
|
+
"report:allenk-catalog-audit": "node scripts/create-allenk-catalog-audit.js",
|
|
152
|
+
"report:allenk-video-binary-analysis": "node scripts/create-allenk-video-binary-analysis.js",
|
|
147
153
|
"export:video-ui-preset": "node scripts/export-video-ui-preset.js",
|
|
154
|
+
"verify:video-ui-preset-output": "node scripts/verify-video-ui-preset-output.js",
|
|
155
|
+
"verify:video-ui-preset-batch": "node scripts/verify-video-ui-preset-batch.js",
|
|
156
|
+
"verify:video-regression-samples": "pnpm verify:video-ui-preset-batch -- --manifest tests/fixtures/video-regression-samples/gemini-video-regression-samples.json --output-dir .artifacts/video-regression-samples --no-screenshots --fail-on-residual",
|
|
148
157
|
"render:video-comparison-grid": "node scripts/render-video-comparison-grid.js",
|
|
149
158
|
"package:extension": "node scripts/package-extension-release.js",
|
|
150
159
|
"cli:smoke": "node bin/gwr.mjs --help",
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { createAlphaGradientMask } from './alphaGradientMask.js';
|
|
2
|
-
|
|
3
1
|
const ALLENK_FDNCNN_MODEL = Object.freeze({
|
|
4
2
|
name: 'FDnCNN Color FP16',
|
|
5
3
|
upstream: 'allenk/GeminiWatermarkTool',
|
|
@@ -20,6 +18,15 @@ function clamp(value, min, max) {
|
|
|
20
18
|
return Math.max(min, Math.min(max, value));
|
|
21
19
|
}
|
|
22
20
|
|
|
21
|
+
function reflect101(index, length) {
|
|
22
|
+
if (length <= 1) return 0;
|
|
23
|
+
let value = Math.round(index);
|
|
24
|
+
while (value < 0 || value >= length) {
|
|
25
|
+
value = value < 0 ? -value : (length * 2 - value - 2);
|
|
26
|
+
}
|
|
27
|
+
return value;
|
|
28
|
+
}
|
|
29
|
+
|
|
23
30
|
function createGaussianKernel(sigma, radius = Math.ceil(sigma * 3)) {
|
|
24
31
|
const safeSigma = Math.max(0.01, sigma);
|
|
25
32
|
const safeRadius = Math.max(1, Math.round(radius));
|
|
@@ -52,7 +59,7 @@ function gaussianBlurFloatMap(source, width, height, sigma, radius = Math.ceil(s
|
|
|
52
59
|
for (let x = 0; x < width; x++) {
|
|
53
60
|
let sum = 0;
|
|
54
61
|
for (let dx = -r; dx <= r; dx++) {
|
|
55
|
-
const xx =
|
|
62
|
+
const xx = reflect101(x + dx, width);
|
|
56
63
|
sum += source[y * width + xx] * kernel[dx + r];
|
|
57
64
|
}
|
|
58
65
|
temp[y * width + x] = sum;
|
|
@@ -63,7 +70,7 @@ function gaussianBlurFloatMap(source, width, height, sigma, radius = Math.ceil(s
|
|
|
63
70
|
for (let x = 0; x < width; x++) {
|
|
64
71
|
let sum = 0;
|
|
65
72
|
for (let dy = -r; dy <= r; dy++) {
|
|
66
|
-
const yy =
|
|
73
|
+
const yy = reflect101(y + dy, height);
|
|
67
74
|
sum += temp[yy * width + x] * kernel[dy + r];
|
|
68
75
|
}
|
|
69
76
|
output[y * width + x] = sum;
|
|
@@ -123,6 +130,139 @@ function resizeSquareAlphaMapArea(sourceAlpha, sourceSize, targetWidth, targetHe
|
|
|
123
130
|
return output;
|
|
124
131
|
}
|
|
125
132
|
|
|
133
|
+
function resizeSquareAlphaMapLinear(sourceAlpha, sourceSize, targetWidth, targetHeight = targetWidth) {
|
|
134
|
+
if (!sourceAlpha || sourceSize <= 0 || targetWidth <= 0 || targetHeight <= 0) {
|
|
135
|
+
return new Float32Array(0);
|
|
136
|
+
}
|
|
137
|
+
if (sourceSize === targetWidth && sourceSize === targetHeight) {
|
|
138
|
+
return new Float32Array(sourceAlpha);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const output = new Float32Array(targetWidth * targetHeight);
|
|
142
|
+
const scaleX = sourceSize / targetWidth;
|
|
143
|
+
const scaleY = sourceSize / targetHeight;
|
|
144
|
+
|
|
145
|
+
for (let y = 0; y < targetHeight; y++) {
|
|
146
|
+
const sourceY = (y + 0.5) * scaleY - 0.5;
|
|
147
|
+
const y0 = Math.floor(sourceY);
|
|
148
|
+
const y1 = y0 + 1;
|
|
149
|
+
const wy = sourceY - y0;
|
|
150
|
+
|
|
151
|
+
for (let x = 0; x < targetWidth; x++) {
|
|
152
|
+
const sourceX = (x + 0.5) * scaleX - 0.5;
|
|
153
|
+
const x0 = Math.floor(sourceX);
|
|
154
|
+
const x1 = x0 + 1;
|
|
155
|
+
const wx = sourceX - x0;
|
|
156
|
+
|
|
157
|
+
const topLeft = sourceAlpha[clamp(y0, 0, sourceSize - 1) * sourceSize + clamp(x0, 0, sourceSize - 1)] || 0;
|
|
158
|
+
const topRight = sourceAlpha[clamp(y0, 0, sourceSize - 1) * sourceSize + clamp(x1, 0, sourceSize - 1)] || 0;
|
|
159
|
+
const bottomLeft = sourceAlpha[clamp(y1, 0, sourceSize - 1) * sourceSize + clamp(x0, 0, sourceSize - 1)] || 0;
|
|
160
|
+
const bottomRight = sourceAlpha[clamp(y1, 0, sourceSize - 1) * sourceSize + clamp(x1, 0, sourceSize - 1)] || 0;
|
|
161
|
+
const top = topLeft * (1 - wx) + topRight * wx;
|
|
162
|
+
const bottom = bottomLeft * (1 - wx) + bottomRight * wx;
|
|
163
|
+
|
|
164
|
+
output[y * targetWidth + x] = top * (1 - wy) + bottom * wy;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return output;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function resizeSquareAlphaMapOpenCv(sourceAlpha, sourceSize, targetWidth, targetHeight = targetWidth) {
|
|
172
|
+
return targetWidth > sourceSize
|
|
173
|
+
? resizeSquareAlphaMapLinear(sourceAlpha, sourceSize, targetWidth, targetHeight)
|
|
174
|
+
: resizeSquareAlphaMapArea(sourceAlpha, sourceSize, targetWidth, targetHeight);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const ALLENK_ELLIPSE_5X5 = Object.freeze([
|
|
178
|
+
[0, 0, 1, 0, 0],
|
|
179
|
+
[1, 1, 1, 1, 1],
|
|
180
|
+
[1, 1, 1, 1, 1],
|
|
181
|
+
[1, 1, 1, 1, 1],
|
|
182
|
+
[0, 0, 1, 0, 0]
|
|
183
|
+
]);
|
|
184
|
+
|
|
185
|
+
function dilateAllenkEllipse5x5(values, width, height) {
|
|
186
|
+
const output = new Float32Array(values.length);
|
|
187
|
+
|
|
188
|
+
for (let y = 0; y < height; y++) {
|
|
189
|
+
for (let x = 0; x < width; x++) {
|
|
190
|
+
let maxValue = 0;
|
|
191
|
+
for (let ky = 0; ky < 5; ky++) {
|
|
192
|
+
for (let kx = 0; kx < 5; kx++) {
|
|
193
|
+
if (!ALLENK_ELLIPSE_5X5[ky][kx]) continue;
|
|
194
|
+
const sx = x + kx - 2;
|
|
195
|
+
const sy = y + ky - 2;
|
|
196
|
+
if (sx < 0 || sy < 0 || sx >= width || sy >= height) continue;
|
|
197
|
+
maxValue = Math.max(maxValue, values[sy * width + sx] || 0);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
output[y * width + x] = maxValue;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return output;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function createAllenkOpenCvGradientMask({ alphaMap, width, height, strength }) {
|
|
208
|
+
if (!alphaMap || width <= 0 || height <= 0 || alphaMap.length < width * height) {
|
|
209
|
+
return new Float32Array(0);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const gradient = new Float32Array(width * height);
|
|
213
|
+
let minGradient = Number.POSITIVE_INFINITY;
|
|
214
|
+
let maxGradient = Number.NEGATIVE_INFINITY;
|
|
215
|
+
|
|
216
|
+
for (let y = 0; y < height; y++) {
|
|
217
|
+
for (let x = 0; x < width; x++) {
|
|
218
|
+
const top = reflect101(y - 1, height);
|
|
219
|
+
const bottom = reflect101(y + 1, height);
|
|
220
|
+
const left = reflect101(x - 1, width);
|
|
221
|
+
const right = reflect101(x + 1, width);
|
|
222
|
+
const gx = (
|
|
223
|
+
-alphaMap[top * width + left] -
|
|
224
|
+
2 * alphaMap[y * width + left] -
|
|
225
|
+
alphaMap[bottom * width + left] +
|
|
226
|
+
alphaMap[top * width + right] +
|
|
227
|
+
2 * alphaMap[y * width + right] +
|
|
228
|
+
alphaMap[bottom * width + right]
|
|
229
|
+
);
|
|
230
|
+
const gy = (
|
|
231
|
+
-alphaMap[top * width + left] -
|
|
232
|
+
2 * alphaMap[top * width + x] -
|
|
233
|
+
alphaMap[top * width + right] +
|
|
234
|
+
alphaMap[bottom * width + left] +
|
|
235
|
+
2 * alphaMap[bottom * width + x] +
|
|
236
|
+
alphaMap[bottom * width + right]
|
|
237
|
+
);
|
|
238
|
+
const value = Math.sqrt(gx * gx + gy * gy);
|
|
239
|
+
gradient[y * width + x] = value;
|
|
240
|
+
minGradient = Math.min(minGradient, value);
|
|
241
|
+
maxGradient = Math.max(maxGradient, value);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (!Number.isFinite(minGradient) || maxGradient <= minGradient) {
|
|
246
|
+
return new Float32Array(width * height);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const normalized = new Float32Array(width * height);
|
|
250
|
+
const scale = 1 / (maxGradient - minGradient);
|
|
251
|
+
for (let i = 0; i < normalized.length; i++) {
|
|
252
|
+
normalized[i] = Math.sqrt(clamp((gradient[i] - minGradient) * scale, 0, 1));
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const expanded = dilateAllenkEllipse5x5(normalized, width, height);
|
|
256
|
+
const blurred = gaussianBlurFloatMap(expanded, width, height, 2);
|
|
257
|
+
const safeStrength = Number.isFinite(strength) ? Math.max(0, strength) : 1;
|
|
258
|
+
|
|
259
|
+
for (let i = 0; i < blurred.length; i++) {
|
|
260
|
+
blurred[i] = clamp(blurred[i] * safeStrength, 0, 1);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
return blurred;
|
|
264
|
+
}
|
|
265
|
+
|
|
126
266
|
function normalizeAllenkFdncnnOptions(options = {}) {
|
|
127
267
|
const sigma = Number.isFinite(options.sigma)
|
|
128
268
|
? clamp(options.sigma, 0, ALLENK_FDNCNN_MODEL.maxSigma)
|
|
@@ -145,17 +285,14 @@ function createAllenkGradientMask({
|
|
|
145
285
|
} = {}) {
|
|
146
286
|
const sourceSize = inferSquareSize(alphaMap);
|
|
147
287
|
const resizedAlphaMap = sourceSize > 0
|
|
148
|
-
?
|
|
288
|
+
? resizeSquareAlphaMapOpenCv(alphaMap, sourceSize, width, height)
|
|
149
289
|
: alphaMap;
|
|
150
290
|
|
|
151
|
-
return
|
|
291
|
+
return createAllenkOpenCvGradientMask({
|
|
152
292
|
alphaMap: resizedAlphaMap,
|
|
153
293
|
width,
|
|
154
294
|
height,
|
|
155
|
-
strength
|
|
156
|
-
gamma: 0.5,
|
|
157
|
-
dilateRadius: 2,
|
|
158
|
-
blurSigma: 2
|
|
295
|
+
strength
|
|
159
296
|
});
|
|
160
297
|
}
|
|
161
298
|
|
|
@@ -188,6 +325,58 @@ function calculateAllenkPaddedRoi({ imageWidth, imageHeight, region, padding = A
|
|
|
188
325
|
};
|
|
189
326
|
}
|
|
190
327
|
|
|
328
|
+
function calculateAllenkVirtualPaddedRoi({
|
|
329
|
+
imageWidth,
|
|
330
|
+
imageHeight,
|
|
331
|
+
region,
|
|
332
|
+
padding = ALLENK_FDNCNN_MODEL.defaultPadding,
|
|
333
|
+
targetWidth = null,
|
|
334
|
+
targetHeight = null
|
|
335
|
+
} = {}) {
|
|
336
|
+
if (!region || imageWidth <= 0 || imageHeight <= 0 || region.width <= 0 || region.height <= 0) {
|
|
337
|
+
return null;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
const safePadding = Math.max(0, Math.round(padding));
|
|
341
|
+
const x = Math.round(region.x - safePadding);
|
|
342
|
+
const y = Math.round(region.y - safePadding);
|
|
343
|
+
const defaultWidth = Math.round(region.width + safePadding * 2);
|
|
344
|
+
const defaultHeight = Math.round(region.height + safePadding * 2);
|
|
345
|
+
const width = Number.isInteger(targetWidth) && targetWidth > 0 ? targetWidth : defaultWidth;
|
|
346
|
+
const height = Number.isInteger(targetHeight) && targetHeight > 0 ? targetHeight : defaultHeight;
|
|
347
|
+
|
|
348
|
+
if (width < 4 || height < 4) return null;
|
|
349
|
+
|
|
350
|
+
const visibleX = clamp(x, 0, imageWidth);
|
|
351
|
+
const visibleY = clamp(y, 0, imageHeight);
|
|
352
|
+
const visibleRight = clamp(x + width, 0, imageWidth);
|
|
353
|
+
const visibleBottom = clamp(y + height, 0, imageHeight);
|
|
354
|
+
const visibleWidth = visibleRight - visibleX;
|
|
355
|
+
const visibleHeight = visibleBottom - visibleY;
|
|
356
|
+
if (visibleWidth <= 0 || visibleHeight <= 0) return null;
|
|
357
|
+
|
|
358
|
+
return {
|
|
359
|
+
x,
|
|
360
|
+
y,
|
|
361
|
+
width,
|
|
362
|
+
height,
|
|
363
|
+
inner: {
|
|
364
|
+
x: clamp(Math.round(region.x - x), 0, width),
|
|
365
|
+
y: clamp(Math.round(region.y - y), 0, height),
|
|
366
|
+
width: clamp(Math.round(region.width), 0, width),
|
|
367
|
+
height: clamp(Math.round(region.height), 0, height)
|
|
368
|
+
},
|
|
369
|
+
visible: {
|
|
370
|
+
x: visibleX,
|
|
371
|
+
y: visibleY,
|
|
372
|
+
width: visibleWidth,
|
|
373
|
+
height: visibleHeight,
|
|
374
|
+
offsetX: visibleX - x,
|
|
375
|
+
offsetY: visibleY - y
|
|
376
|
+
}
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
|
|
191
380
|
function calculateAllenkRuntimeRoi({
|
|
192
381
|
imageWidth,
|
|
193
382
|
imageHeight,
|
|
@@ -231,6 +420,59 @@ function calculateAllenkRuntimeRoi({
|
|
|
231
420
|
};
|
|
232
421
|
}
|
|
233
422
|
|
|
423
|
+
function extractAllenkVirtualImageData({
|
|
424
|
+
imageData,
|
|
425
|
+
roi,
|
|
426
|
+
imageX = 0,
|
|
427
|
+
imageY = 0,
|
|
428
|
+
canvasWidth = imageData?.width || 0,
|
|
429
|
+
canvasHeight = imageData?.height || 0
|
|
430
|
+
} = {}) {
|
|
431
|
+
if (
|
|
432
|
+
!imageData?.data ||
|
|
433
|
+
!roi ||
|
|
434
|
+
imageData.width <= 0 ||
|
|
435
|
+
imageData.height <= 0 ||
|
|
436
|
+
roi.width <= 0 ||
|
|
437
|
+
roi.height <= 0 ||
|
|
438
|
+
canvasWidth <= 0 ||
|
|
439
|
+
canvasHeight <= 0
|
|
440
|
+
) {
|
|
441
|
+
return { width: 0, height: 0, data: new Uint8ClampedArray(0) };
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
const width = Math.max(1, Math.round(roi.width));
|
|
445
|
+
const height = Math.max(1, Math.round(roi.height));
|
|
446
|
+
const output = new Uint8ClampedArray(width * height * 4);
|
|
447
|
+
const sourceStride = imageData.data.length >= imageData.width * imageData.height * 4 ? 4 : 3;
|
|
448
|
+
|
|
449
|
+
for (let y = 0; y < height; y++) {
|
|
450
|
+
const globalY = clamp(Math.round(roi.y + y), 0, canvasHeight - 1);
|
|
451
|
+
const localY = clamp(globalY - Math.round(imageY), 0, imageData.height - 1);
|
|
452
|
+
for (let x = 0; x < width; x++) {
|
|
453
|
+
const globalX = clamp(Math.round(roi.x + x), 0, canvasWidth - 1);
|
|
454
|
+
const localX = clamp(globalX - Math.round(imageX), 0, imageData.width - 1);
|
|
455
|
+
const src = (localY * imageData.width + localX) * sourceStride;
|
|
456
|
+
const dst = (y * width + x) * 4;
|
|
457
|
+
output[dst] = imageData.data[src] || 0;
|
|
458
|
+
output[dst + 1] = imageData.data[src + 1] || 0;
|
|
459
|
+
output[dst + 2] = imageData.data[src + 2] || 0;
|
|
460
|
+
output[dst + 3] = sourceStride >= 4 ? (imageData.data[src + 3] ?? 255) : 255;
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
const ImageDataCtor = typeof imageData.constructor === 'function' && imageData.constructor !== Object
|
|
465
|
+
? imageData.constructor
|
|
466
|
+
: typeof ImageData === 'function'
|
|
467
|
+
? ImageData
|
|
468
|
+
: null;
|
|
469
|
+
if (ImageDataCtor) {
|
|
470
|
+
return new ImageDataCtor(output, width, height);
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
return { width, height, data: output };
|
|
474
|
+
}
|
|
475
|
+
|
|
234
476
|
function embedAllenkRoiWeights({ roiWeights, roiWidth, roiHeight, paddedRoi, blurSigma = 1 } = {}) {
|
|
235
477
|
if (!roiWeights || !paddedRoi?.inner || roiWidth <= 0 || roiHeight <= 0) {
|
|
236
478
|
return new Float32Array(0);
|
|
@@ -363,8 +605,10 @@ export {
|
|
|
363
605
|
buildAllenkFdncnnInput,
|
|
364
606
|
calculateAllenkPaddedRoi,
|
|
365
607
|
calculateAllenkRuntimeRoi,
|
|
608
|
+
calculateAllenkVirtualPaddedRoi,
|
|
366
609
|
convertAllenkFdncnnOutputToRgba,
|
|
367
610
|
createAllenkGradientMask,
|
|
368
611
|
embedAllenkRoiWeights,
|
|
612
|
+
extractAllenkVirtualImageData,
|
|
369
613
|
normalizeAllenkFdncnnOptions
|
|
370
614
|
};
|
|
@@ -231,12 +231,13 @@ function createGraphProto({
|
|
|
231
231
|
segments,
|
|
232
232
|
inputName,
|
|
233
233
|
outputName,
|
|
234
|
-
|
|
234
|
+
roiWidth,
|
|
235
|
+
roiHeight
|
|
235
236
|
}) {
|
|
236
237
|
const initializers = createInitializers({ bin, segments });
|
|
237
238
|
const nodes = createNodes({ segments, inputName, outputName });
|
|
238
|
-
const inputShape = [1, segments[0].inputChannels,
|
|
239
|
-
const outputShape = [1, segments[segments.length - 1].outputChannels,
|
|
239
|
+
const inputShape = [1, segments[0].inputChannels, roiHeight, roiWidth];
|
|
240
|
+
const outputShape = [1, segments[segments.length - 1].outputChannels, roiHeight, roiWidth];
|
|
240
241
|
|
|
241
242
|
return {
|
|
242
243
|
nodeCount: nodes.length,
|
|
@@ -275,6 +276,8 @@ function exportAllenkFdncnnOnnx({
|
|
|
275
276
|
bin,
|
|
276
277
|
weightLayout,
|
|
277
278
|
roiSize = 72,
|
|
279
|
+
roiWidth = roiSize,
|
|
280
|
+
roiHeight = roiSize,
|
|
278
281
|
inputName = 'fdncnn_input',
|
|
279
282
|
outputName = 'fdncnn_output',
|
|
280
283
|
graphName = 'allenk_fdncnn_color',
|
|
@@ -285,8 +288,11 @@ function exportAllenkFdncnnOnnx({
|
|
|
285
288
|
if (!segments.length) {
|
|
286
289
|
throw new Error('allenk FDnCNN ONNX export requires weightLayout.segments');
|
|
287
290
|
}
|
|
288
|
-
if (!Number.isInteger(
|
|
289
|
-
throw new Error(`Invalid ONNX ROI
|
|
291
|
+
if (!Number.isInteger(roiWidth) || roiWidth <= 0) {
|
|
292
|
+
throw new Error(`Invalid ONNX ROI width: ${roiWidth}`);
|
|
293
|
+
}
|
|
294
|
+
if (!Number.isInteger(roiHeight) || roiHeight <= 0) {
|
|
295
|
+
throw new Error(`Invalid ONNX ROI height: ${roiHeight}`);
|
|
290
296
|
}
|
|
291
297
|
|
|
292
298
|
const graph = createGraphProto({
|
|
@@ -295,7 +301,8 @@ function exportAllenkFdncnnOnnx({
|
|
|
295
301
|
segments,
|
|
296
302
|
inputName,
|
|
297
303
|
outputName,
|
|
298
|
-
|
|
304
|
+
roiWidth,
|
|
305
|
+
roiHeight
|
|
299
306
|
});
|
|
300
307
|
const model = createModelProto({
|
|
301
308
|
graph: graph.bytes,
|
|
@@ -313,13 +320,15 @@ function exportAllenkFdncnnOnnx({
|
|
|
313
320
|
graphName,
|
|
314
321
|
inputName,
|
|
315
322
|
outputName,
|
|
316
|
-
inputShape: [1, segments[0].inputChannels,
|
|
317
|
-
outputShape: [1, segments[segments.length - 1].outputChannels,
|
|
323
|
+
inputShape: [1, segments[0].inputChannels, roiHeight, roiWidth],
|
|
324
|
+
outputShape: [1, segments[segments.length - 1].outputChannels, roiHeight, roiWidth],
|
|
318
325
|
nodeCount: graph.nodeCount,
|
|
319
326
|
convolutionNodeCount: segments.length,
|
|
320
327
|
reluNodeCount: segments.filter((segment, index) => index < segments.length - 1 && segment.activationType === 1).length,
|
|
321
328
|
initializerCount: graph.initializerCount,
|
|
322
|
-
roiSize
|
|
329
|
+
roiSize: roiWidth === roiHeight ? roiWidth : null,
|
|
330
|
+
roiWidth,
|
|
331
|
+
roiHeight
|
|
323
332
|
}
|
|
324
333
|
};
|
|
325
334
|
}
|
|
@@ -139,7 +139,7 @@ const PREVIEW_TEMPLATE_ALIGN_SHIFTS = [-1, -0.5, 0, 0.5, 1];
|
|
|
139
139
|
const PREVIEW_TEMPLATE_ALIGN_SCALES = [0.985, 1, 1.015];
|
|
140
140
|
const PREVIEW_ANCHOR_GAIN_SKIP_RESIDUAL_THRESHOLD = 0.22;
|
|
141
141
|
const PREVIEW_ANCHOR_GAIN_SKIP_GRADIENT_THRESHOLD = 0.24;
|
|
142
|
-
const CORE_ALPHA_PRIORITY_GAINS = Object.freeze([0.6, 1, 1.15, 1.3, 0.45, 0.7, 0.85, 0.55]);
|
|
142
|
+
const CORE_ALPHA_PRIORITY_GAINS = Object.freeze([0.6, 1, 1.1, 1.15, 1.3, 0.45, 0.7, 0.85, 0.55]);
|
|
143
143
|
const CURRENT_LARGE_MARGIN_ULTRA_WEAK_ALPHA_GAINS = Object.freeze([0.25, 0.3, 0.35, 0.4]);
|
|
144
144
|
const STANDARD_ANCHOR_WEAK_ALPHA_RESCUE_GAINS = Object.freeze([0.55, 0.7, 0.85]);
|
|
145
145
|
const STANDARD_ANCHOR_WEAK_RESCUE_MAX_SPATIAL = 0.35;
|
|
@@ -1044,6 +1044,24 @@ export function pickBetterCandidate(currentBest, candidate, minCostDelta = 0.005
|
|
|
1044
1044
|
if (shouldPreserveStrongCanonical96AgainstWeakCurrentLargeMargin(candidate, currentBest)) {
|
|
1045
1045
|
return candidate;
|
|
1046
1046
|
}
|
|
1047
|
+
if (shouldPreserveStrongCanonical96AgainstWeakNewMargin(currentBest, candidate)) {
|
|
1048
|
+
return currentBest;
|
|
1049
|
+
}
|
|
1050
|
+
if (shouldPreserveStrongCanonical96AgainstWeakNewMargin(candidate, currentBest)) {
|
|
1051
|
+
return candidate;
|
|
1052
|
+
}
|
|
1053
|
+
if (shouldPreserveStandardAlphaCanonical96AgainstDarkGain(currentBest, candidate)) {
|
|
1054
|
+
return currentBest;
|
|
1055
|
+
}
|
|
1056
|
+
if (shouldPreserveStandardAlphaCanonical96AgainstDarkGain(candidate, currentBest)) {
|
|
1057
|
+
return candidate;
|
|
1058
|
+
}
|
|
1059
|
+
if (shouldPreserveBalancedAlphaCanonical96(currentBest, candidate)) {
|
|
1060
|
+
return currentBest;
|
|
1061
|
+
}
|
|
1062
|
+
if (shouldPreserveBalancedAlphaCanonical96(candidate, currentBest)) {
|
|
1063
|
+
return candidate;
|
|
1064
|
+
}
|
|
1047
1065
|
if (shouldPreferCatalogOriginalSignal(candidate, currentBest)) {
|
|
1048
1066
|
return candidate;
|
|
1049
1067
|
}
|
|
@@ -1198,6 +1216,16 @@ function isDefault96GeometryCandidate(candidate) {
|
|
|
1198
1216
|
candidate.config.marginBottom === 64;
|
|
1199
1217
|
}
|
|
1200
1218
|
|
|
1219
|
+
function isNewMargin96Candidate(candidate) {
|
|
1220
|
+
return isStandardCandidateSource(candidate) &&
|
|
1221
|
+
candidate?.provenance?.localShift !== true &&
|
|
1222
|
+
candidate?.provenance?.sizeJitter !== true &&
|
|
1223
|
+
candidate?.provenance?.previewAnchor !== true &&
|
|
1224
|
+
candidate?.config?.logoSize === 96 &&
|
|
1225
|
+
candidate.config.marginRight === 192 &&
|
|
1226
|
+
candidate.config.marginBottom === 192;
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1201
1229
|
function shouldPreserveStrongCanonical96AgainstWeakCurrentLargeMargin(currentBest, candidate) {
|
|
1202
1230
|
if (!isCanonicalDefault96Candidate(currentBest)) return false;
|
|
1203
1231
|
if (!isCurrentLargeMarginCatalogCandidate(candidate)) return false;
|
|
@@ -1238,6 +1266,111 @@ function shouldPreserveStrongCanonical96AgainstWeakCurrentLargeMargin(currentBes
|
|
|
1238
1266
|
candidateHasWeakOriginalSignal;
|
|
1239
1267
|
}
|
|
1240
1268
|
|
|
1269
|
+
function shouldPreserveStrongCanonical96AgainstWeakNewMargin(currentBest, candidate) {
|
|
1270
|
+
if (!isCanonicalDefault96Candidate(currentBest)) return false;
|
|
1271
|
+
if (!isNewMargin96Candidate(candidate)) return false;
|
|
1272
|
+
|
|
1273
|
+
const currentSpatial = Number(currentBest.originalSpatialScore);
|
|
1274
|
+
const currentGradient = Number(currentBest.originalGradientScore);
|
|
1275
|
+
const candidateSpatial = Number(candidate.originalSpatialScore);
|
|
1276
|
+
const candidateGradient = Number(candidate.originalGradientScore);
|
|
1277
|
+
if (
|
|
1278
|
+
!Number.isFinite(currentSpatial) ||
|
|
1279
|
+
!Number.isFinite(currentGradient) ||
|
|
1280
|
+
!Number.isFinite(candidateSpatial) ||
|
|
1281
|
+
!Number.isFinite(candidateGradient)
|
|
1282
|
+
) {
|
|
1283
|
+
return false;
|
|
1284
|
+
}
|
|
1285
|
+
|
|
1286
|
+
const currentHasStrongCanonicalSignal =
|
|
1287
|
+
currentSpatial >= 0.55 &&
|
|
1288
|
+
currentGradient >= 0.2;
|
|
1289
|
+
const candidateHasStrongNewMarginSignal =
|
|
1290
|
+
candidateSpatial >= 0.55 &&
|
|
1291
|
+
candidateGradient >= 0.3;
|
|
1292
|
+
const candidateHasWeakOrMuchWeakerSignal =
|
|
1293
|
+
candidateSpatial < STANDARD_VALIDATION_MIN_ORIGINAL_SPATIAL_SCORE ||
|
|
1294
|
+
candidateGradient < STANDARD_VALIDATION_MIN_ORIGINAL_GRADIENT_SCORE ||
|
|
1295
|
+
(
|
|
1296
|
+
candidateSpatial <= currentSpatial - STRONG_ORIGINAL_SIGNAL_SPATIAL_ADVANTAGE &&
|
|
1297
|
+
candidateGradient <= currentGradient - STRONG_ORIGINAL_SIGNAL_GRADIENT_ADVANTAGE
|
|
1298
|
+
);
|
|
1299
|
+
|
|
1300
|
+
return currentHasStrongCanonicalSignal &&
|
|
1301
|
+
!candidateHasStrongNewMarginSignal &&
|
|
1302
|
+
candidateHasWeakOrMuchWeakerSignal;
|
|
1303
|
+
}
|
|
1304
|
+
|
|
1305
|
+
function isBalancedCanonical96AlphaGain(candidate) {
|
|
1306
|
+
const alphaGain = Number(candidate?.alphaGain);
|
|
1307
|
+
return Number.isFinite(alphaGain) && alphaGain >= 1 && alphaGain <= 1.1;
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1310
|
+
function shouldPreserveStandardAlphaCanonical96AgainstDarkGain(currentBest, candidate) {
|
|
1311
|
+
if (!sameCandidateAnchor(currentBest, candidate)) return false;
|
|
1312
|
+
if (!isCanonicalDefault96Candidate(currentBest)) return false;
|
|
1313
|
+
|
|
1314
|
+
const currentAlphaGain = Number(currentBest?.alphaGain);
|
|
1315
|
+
const candidateAlphaGain = Number(candidate?.alphaGain);
|
|
1316
|
+
if (
|
|
1317
|
+
!Number.isFinite(currentAlphaGain) ||
|
|
1318
|
+
!Number.isFinite(candidateAlphaGain) ||
|
|
1319
|
+
currentAlphaGain !== 1 ||
|
|
1320
|
+
candidateAlphaGain <= 1 ||
|
|
1321
|
+
candidate?.tooDark !== true
|
|
1322
|
+
) {
|
|
1323
|
+
return false;
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1326
|
+
const originalSpatial = Number(currentBest.originalSpatialScore);
|
|
1327
|
+
const originalGradient = Number(currentBest.originalGradientScore);
|
|
1328
|
+
const currentSpatial = Number(currentBest.processedSpatialScore);
|
|
1329
|
+
const currentGradient = Number(currentBest.processedGradientScore);
|
|
1330
|
+
if (
|
|
1331
|
+
!Number.isFinite(originalSpatial) ||
|
|
1332
|
+
!Number.isFinite(originalGradient) ||
|
|
1333
|
+
!Number.isFinite(currentSpatial) ||
|
|
1334
|
+
!Number.isFinite(currentGradient)
|
|
1335
|
+
) {
|
|
1336
|
+
return false;
|
|
1337
|
+
}
|
|
1338
|
+
|
|
1339
|
+
return originalSpatial >= 0.55 &&
|
|
1340
|
+
originalGradient >= 0.2 &&
|
|
1341
|
+
currentSpatial >= 0 &&
|
|
1342
|
+
currentSpatial <= 0.35 &&
|
|
1343
|
+
Math.max(0, currentGradient) <= 0.08;
|
|
1344
|
+
}
|
|
1345
|
+
|
|
1346
|
+
function shouldPreserveBalancedAlphaCanonical96(currentBest, candidate) {
|
|
1347
|
+
if (!sameCandidateAnchor(currentBest, candidate)) return false;
|
|
1348
|
+
if (!isCanonicalDefault96Candidate(currentBest)) return false;
|
|
1349
|
+
if (!isBalancedCanonical96AlphaGain(currentBest)) return false;
|
|
1350
|
+
|
|
1351
|
+
const candidateAlphaGain = Number(candidate?.alphaGain);
|
|
1352
|
+
if (!Number.isFinite(candidateAlphaGain) || candidateAlphaGain <= 1.1) return false;
|
|
1353
|
+
|
|
1354
|
+
const originalSpatial = Number(currentBest.originalSpatialScore);
|
|
1355
|
+
const originalGradient = Number(currentBest.originalGradientScore);
|
|
1356
|
+
const currentSpatial = Number(currentBest.processedSpatialScore);
|
|
1357
|
+
const currentGradient = Number(currentBest.processedGradientScore);
|
|
1358
|
+
if (
|
|
1359
|
+
!Number.isFinite(originalSpatial) ||
|
|
1360
|
+
!Number.isFinite(originalGradient) ||
|
|
1361
|
+
!Number.isFinite(currentSpatial) ||
|
|
1362
|
+
!Number.isFinite(currentGradient)
|
|
1363
|
+
) {
|
|
1364
|
+
return false;
|
|
1365
|
+
}
|
|
1366
|
+
|
|
1367
|
+
return originalSpatial >= 0.55 &&
|
|
1368
|
+
originalGradient >= 0.2 &&
|
|
1369
|
+
currentSpatial >= 0 &&
|
|
1370
|
+
currentSpatial <= 0.22 &&
|
|
1371
|
+
Math.max(0, currentGradient) <= 0.1;
|
|
1372
|
+
}
|
|
1373
|
+
|
|
1241
1374
|
function hasWeakDriftEvidence(candidate) {
|
|
1242
1375
|
const candidateSpatial = Number(candidate?.originalSpatialScore);
|
|
1243
1376
|
const candidateGradient = Number(candidate?.originalGradientScore);
|
|
@@ -22,8 +22,12 @@ import { createSelectionDebugSummary } from './selectionDebug.js';
|
|
|
22
22
|
import {
|
|
23
23
|
calculateWatermarkPosition,
|
|
24
24
|
detectWatermarkConfig,
|
|
25
|
-
resolveInitialStandardConfig
|
|
26
|
-
} from './watermarkConfig.js';
|
|
25
|
+
resolveInitialStandardConfig
|
|
26
|
+
} from './watermarkConfig.js';
|
|
27
|
+
import {
|
|
28
|
+
blurAlphaMap,
|
|
29
|
+
buildPreviewNeighborhoodPrior
|
|
30
|
+
} from './previewAlphaCalibration.js';
|
|
27
31
|
|
|
28
32
|
const RESIDUAL_RECALIBRATION_THRESHOLD = 0.5;
|
|
29
33
|
const MIN_SUPPRESSION_FOR_SKIP_RECALIBRATION = 0.18;
|
|
@@ -36,6 +40,7 @@ const SUBPIXEL_REFINE_SCALES = [0.99, 1, 1.01];
|
|
|
36
40
|
const ALPHA_PARAMETER_GROUPS = Object.freeze([
|
|
37
41
|
{ name: 'gemini-weak-alpha-202606', alphaGain: 0.6, standardPriority: true },
|
|
38
42
|
{ name: 'gemini-standard-alpha', alphaGain: 1, standardPriority: true },
|
|
43
|
+
{ name: 'gemini-balanced-strong-alpha-202606', alphaGain: 1.1 },
|
|
39
44
|
{ name: 'gemini-strong-alpha-202606', alphaGain: 1.15 },
|
|
40
45
|
{ name: 'gemini-strong-alpha-high-202606', alphaGain: 1.3 },
|
|
41
46
|
{ name: 'weak-alpha-extra-conservative', alphaGain: 0.45 },
|
|
@@ -136,6 +141,21 @@ const PREVIEW_BACKGROUND_CLEANUP_MIN_RESIDUAL = 0.3;
|
|
|
136
141
|
const PREVIEW_BACKGROUND_CLEANUP_MAX_BORDER_STD = 24;
|
|
137
142
|
const PREVIEW_BACKGROUND_CLEANUP_PAD = 8;
|
|
138
143
|
const PREVIEW_BACKGROUND_CLEANUP_PRIOR_RADIUS = 10;
|
|
144
|
+
const SMOOTH_PRIOR_LOCATED_MIN_SIZE = 80;
|
|
145
|
+
const SMOOTH_PRIOR_LOCATED_MAX_SIZE = 160;
|
|
146
|
+
const SMOOTH_PRIOR_LOCATED_MIN_BORDER_MEAN = 120;
|
|
147
|
+
const SMOOTH_PRIOR_LOCATED_MIN_SPATIAL = 0.25;
|
|
148
|
+
const SMOOTH_PRIOR_LOCATED_MAX_GRADIENT = 0.22;
|
|
149
|
+
const SMOOTH_PRIOR_LOCATED_MIN_SPATIAL_IMPROVEMENT = 0.16;
|
|
150
|
+
const SMOOTH_PRIOR_LOCATED_MAX_GRADIENT_DRIFT = 0.05;
|
|
151
|
+
const SMOOTH_PRIOR_LOCATED_MIN_ARTIFACT_IMPROVEMENT = 0.025;
|
|
152
|
+
const SMOOTH_PRIOR_LOCATED_MAX_ACCEPTED_GRADIENT = 0.18;
|
|
153
|
+
const SMOOTH_PRIOR_LOCATED_PRESETS = Object.freeze([
|
|
154
|
+
{ radius: 24, threshold: 0, blurRadius: 0, strength: 0.75, gamma: 0.45 },
|
|
155
|
+
{ radius: 36, threshold: 0, blurRadius: 0, strength: 0.75, gamma: 0.45 },
|
|
156
|
+
{ radius: 24, threshold: 0.01, blurRadius: 0, strength: 0.75, gamma: 0.45 },
|
|
157
|
+
{ radius: 36, threshold: 0.01, blurRadius: 0, strength: 0.75, gamma: 0.45 }
|
|
158
|
+
]);
|
|
139
159
|
const OVER_SUBTRACTION_SPATIAL_THRESHOLD = -0.25;
|
|
140
160
|
const OVER_SUBTRACTION_GRADIENT_THRESHOLD = 0.35;
|
|
141
161
|
const OVER_SUBTRACTION_MIN_ABS_SPATIAL_IMPROVEMENT = 0.08;
|
|
@@ -1370,7 +1390,7 @@ function expandPosition(position, imageData, pad) {
|
|
|
1370
1390
|
};
|
|
1371
1391
|
}
|
|
1372
1392
|
|
|
1373
|
-
function
|
|
1393
|
+
function measureOuterBorderLuminanceStats(imageData, position, margin = 10) {
|
|
1374
1394
|
let sum = 0;
|
|
1375
1395
|
let sq = 0;
|
|
1376
1396
|
let count = 0;
|
|
@@ -1399,16 +1419,219 @@ function measureOuterBorderLuminanceStd(imageData, position, margin = 10) {
|
|
|
1399
1419
|
}
|
|
1400
1420
|
}
|
|
1401
1421
|
|
|
1402
|
-
if (count <= 0)
|
|
1422
|
+
if (count <= 0) {
|
|
1423
|
+
return {
|
|
1424
|
+
mean: Number.POSITIVE_INFINITY,
|
|
1425
|
+
std: Number.POSITIVE_INFINITY,
|
|
1426
|
+
count
|
|
1427
|
+
};
|
|
1428
|
+
}
|
|
1403
1429
|
|
|
1404
1430
|
const mean = sum / count;
|
|
1405
|
-
return
|
|
1431
|
+
return {
|
|
1432
|
+
mean,
|
|
1433
|
+
std: Math.sqrt(Math.max(0, sq / count - mean * mean)),
|
|
1434
|
+
count
|
|
1435
|
+
};
|
|
1436
|
+
}
|
|
1437
|
+
|
|
1438
|
+
function measureOuterBorderLuminanceStd(imageData, position, margin = 10) {
|
|
1439
|
+
return measureOuterBorderLuminanceStats(imageData, position, margin).std;
|
|
1440
|
+
}
|
|
1441
|
+
|
|
1442
|
+
function clamp01(value) {
|
|
1443
|
+
if (!Number.isFinite(value)) return 0;
|
|
1444
|
+
if (value <= 0) return 0;
|
|
1445
|
+
if (value >= 1) return 1;
|
|
1446
|
+
return value;
|
|
1406
1447
|
}
|
|
1407
1448
|
|
|
1408
1449
|
function clampChannel(value) {
|
|
1409
1450
|
return Math.max(0, Math.min(255, Math.round(value)));
|
|
1410
1451
|
}
|
|
1411
1452
|
|
|
1453
|
+
function estimateAlphaMapFromBackgroundPrior({
|
|
1454
|
+
originalImageData,
|
|
1455
|
+
priorImageData,
|
|
1456
|
+
position,
|
|
1457
|
+
threshold,
|
|
1458
|
+
blurRadius
|
|
1459
|
+
}) {
|
|
1460
|
+
const alphaMap = new Float32Array(position.width * position.height);
|
|
1461
|
+
|
|
1462
|
+
for (let row = 0; row < position.height; row++) {
|
|
1463
|
+
for (let col = 0; col < position.width; col++) {
|
|
1464
|
+
const localIndex = row * position.width + col;
|
|
1465
|
+
const pixelIndex = ((position.y + row) * originalImageData.width + position.x + col) * 4;
|
|
1466
|
+
let estimatedAlpha = 0;
|
|
1467
|
+
|
|
1468
|
+
for (let channel = 0; channel < 3; channel++) {
|
|
1469
|
+
const denominator = 255 - priorImageData.data[pixelIndex + channel];
|
|
1470
|
+
if (denominator <= 2) continue;
|
|
1471
|
+
|
|
1472
|
+
estimatedAlpha = Math.max(
|
|
1473
|
+
estimatedAlpha,
|
|
1474
|
+
(originalImageData.data[pixelIndex + channel] - priorImageData.data[pixelIndex + channel]) / denominator
|
|
1475
|
+
);
|
|
1476
|
+
}
|
|
1477
|
+
|
|
1478
|
+
alphaMap[localIndex] = Math.min(0.9, clamp01((estimatedAlpha - threshold) * 1.2));
|
|
1479
|
+
}
|
|
1480
|
+
}
|
|
1481
|
+
|
|
1482
|
+
return blurRadius > 0
|
|
1483
|
+
? blurAlphaMap(alphaMap, position.width, blurRadius)
|
|
1484
|
+
: alphaMap;
|
|
1485
|
+
}
|
|
1486
|
+
|
|
1487
|
+
function applyEstimatedPriorBlend({
|
|
1488
|
+
sourceImageData,
|
|
1489
|
+
priorImageData,
|
|
1490
|
+
estimatedAlphaMap,
|
|
1491
|
+
position,
|
|
1492
|
+
strength,
|
|
1493
|
+
gamma
|
|
1494
|
+
}) {
|
|
1495
|
+
const candidate = cloneImageData(sourceImageData);
|
|
1496
|
+
|
|
1497
|
+
for (let row = 0; row < position.height; row++) {
|
|
1498
|
+
for (let col = 0; col < position.width; col++) {
|
|
1499
|
+
const localIndex = row * position.width + col;
|
|
1500
|
+
const alpha = estimatedAlphaMap[localIndex];
|
|
1501
|
+
if (alpha <= 0.005) continue;
|
|
1502
|
+
|
|
1503
|
+
const blend = clamp01(Math.pow(alpha, gamma) * strength);
|
|
1504
|
+
if (blend <= 0.005) continue;
|
|
1505
|
+
|
|
1506
|
+
const pixelIndex = ((position.y + row) * sourceImageData.width + position.x + col) * 4;
|
|
1507
|
+
for (let channel = 0; channel < 3; channel++) {
|
|
1508
|
+
candidate.data[pixelIndex + channel] = clampChannel(
|
|
1509
|
+
sourceImageData.data[pixelIndex + channel] * (1 - blend) +
|
|
1510
|
+
priorImageData.data[pixelIndex + channel] * blend
|
|
1511
|
+
);
|
|
1512
|
+
}
|
|
1513
|
+
}
|
|
1514
|
+
}
|
|
1515
|
+
|
|
1516
|
+
return candidate;
|
|
1517
|
+
}
|
|
1518
|
+
|
|
1519
|
+
function refineSmoothLocatedResidualWithEstimatedPrior({
|
|
1520
|
+
originalImageData,
|
|
1521
|
+
currentImageData,
|
|
1522
|
+
alphaMap,
|
|
1523
|
+
position,
|
|
1524
|
+
source,
|
|
1525
|
+
alphaGain,
|
|
1526
|
+
baselineSpatialScore,
|
|
1527
|
+
baselineGradientScore
|
|
1528
|
+
}) {
|
|
1529
|
+
if (
|
|
1530
|
+
typeof source !== 'string' ||
|
|
1531
|
+
!source.includes('located-aggressive') ||
|
|
1532
|
+
position?.width < SMOOTH_PRIOR_LOCATED_MIN_SIZE ||
|
|
1533
|
+
position?.width > SMOOTH_PRIOR_LOCATED_MAX_SIZE ||
|
|
1534
|
+
baselineSpatialScore < SMOOTH_PRIOR_LOCATED_MIN_SPATIAL ||
|
|
1535
|
+
baselineGradientScore > SMOOTH_PRIOR_LOCATED_MAX_GRADIENT
|
|
1536
|
+
) {
|
|
1537
|
+
return null;
|
|
1538
|
+
}
|
|
1539
|
+
|
|
1540
|
+
const borderStats = measureOuterBorderLuminanceStats(originalImageData, position, 16);
|
|
1541
|
+
if (borderStats.mean < SMOOTH_PRIOR_LOCATED_MIN_BORDER_MEAN) {
|
|
1542
|
+
return null;
|
|
1543
|
+
}
|
|
1544
|
+
|
|
1545
|
+
const baselineArtifacts = assessRemovalDiffArtifacts({
|
|
1546
|
+
originalImageData,
|
|
1547
|
+
candidateImageData: currentImageData,
|
|
1548
|
+
alphaMap,
|
|
1549
|
+
position,
|
|
1550
|
+
alphaGain
|
|
1551
|
+
});
|
|
1552
|
+
const baselineArtifactCost = Number(baselineArtifacts?.visualArtifactCost);
|
|
1553
|
+
if (!Number.isFinite(baselineArtifactCost)) return null;
|
|
1554
|
+
|
|
1555
|
+
let best = null;
|
|
1556
|
+
const priorByRadius = new Map();
|
|
1557
|
+
|
|
1558
|
+
for (const preset of SMOOTH_PRIOR_LOCATED_PRESETS) {
|
|
1559
|
+
let priorImageData = priorByRadius.get(preset.radius);
|
|
1560
|
+
if (!priorImageData) {
|
|
1561
|
+
priorImageData = buildPreviewNeighborhoodPrior({
|
|
1562
|
+
previewImageData: originalImageData,
|
|
1563
|
+
position,
|
|
1564
|
+
radius: preset.radius
|
|
1565
|
+
});
|
|
1566
|
+
priorByRadius.set(preset.radius, priorImageData);
|
|
1567
|
+
}
|
|
1568
|
+
|
|
1569
|
+
const estimatedAlphaMap = estimateAlphaMapFromBackgroundPrior({
|
|
1570
|
+
originalImageData,
|
|
1571
|
+
priorImageData,
|
|
1572
|
+
position,
|
|
1573
|
+
threshold: preset.threshold,
|
|
1574
|
+
blurRadius: preset.blurRadius
|
|
1575
|
+
});
|
|
1576
|
+
const candidateImageData = applyEstimatedPriorBlend({
|
|
1577
|
+
sourceImageData: currentImageData,
|
|
1578
|
+
priorImageData,
|
|
1579
|
+
estimatedAlphaMap,
|
|
1580
|
+
position,
|
|
1581
|
+
strength: preset.strength,
|
|
1582
|
+
gamma: preset.gamma
|
|
1583
|
+
});
|
|
1584
|
+
const spatialScore = computeRegionSpatialCorrelation({
|
|
1585
|
+
imageData: candidateImageData,
|
|
1586
|
+
alphaMap,
|
|
1587
|
+
region: { x: position.x, y: position.y, size: position.width }
|
|
1588
|
+
});
|
|
1589
|
+
const gradientScore = computeRegionGradientCorrelation({
|
|
1590
|
+
imageData: candidateImageData,
|
|
1591
|
+
alphaMap,
|
|
1592
|
+
region: { x: position.x, y: position.y, size: position.width }
|
|
1593
|
+
});
|
|
1594
|
+
const artifacts = assessRemovalDiffArtifacts({
|
|
1595
|
+
originalImageData,
|
|
1596
|
+
candidateImageData,
|
|
1597
|
+
alphaMap,
|
|
1598
|
+
position,
|
|
1599
|
+
alphaGain
|
|
1600
|
+
});
|
|
1601
|
+
const artifactCost = Number(artifacts?.visualArtifactCost);
|
|
1602
|
+
if (!Number.isFinite(artifactCost)) continue;
|
|
1603
|
+
|
|
1604
|
+
const spatialImprovement = Math.abs(baselineSpatialScore) - Math.abs(spatialScore);
|
|
1605
|
+
const gradientDrift = gradientScore - baselineGradientScore;
|
|
1606
|
+
const artifactImprovement = baselineArtifactCost - artifactCost;
|
|
1607
|
+
if (
|
|
1608
|
+
spatialImprovement < SMOOTH_PRIOR_LOCATED_MIN_SPATIAL_IMPROVEMENT ||
|
|
1609
|
+
gradientDrift > SMOOTH_PRIOR_LOCATED_MAX_GRADIENT_DRIFT ||
|
|
1610
|
+
artifactImprovement < SMOOTH_PRIOR_LOCATED_MIN_ARTIFACT_IMPROVEMENT ||
|
|
1611
|
+
gradientScore > SMOOTH_PRIOR_LOCATED_MAX_ACCEPTED_GRADIENT
|
|
1612
|
+
) {
|
|
1613
|
+
continue;
|
|
1614
|
+
}
|
|
1615
|
+
|
|
1616
|
+
const cost = Math.abs(spatialScore) * 0.75 +
|
|
1617
|
+
Math.max(0, gradientScore) * 0.9 +
|
|
1618
|
+
artifactCost * 0.5;
|
|
1619
|
+
if (!best || cost < best.cost) {
|
|
1620
|
+
best = {
|
|
1621
|
+
imageData: candidateImageData,
|
|
1622
|
+
spatialScore,
|
|
1623
|
+
gradientScore,
|
|
1624
|
+
cost,
|
|
1625
|
+
artifactCost,
|
|
1626
|
+
borderStats,
|
|
1627
|
+
preset
|
|
1628
|
+
};
|
|
1629
|
+
}
|
|
1630
|
+
}
|
|
1631
|
+
|
|
1632
|
+
return best;
|
|
1633
|
+
}
|
|
1634
|
+
|
|
1412
1635
|
function averageStripColor(imageData, {
|
|
1413
1636
|
xFrom,
|
|
1414
1637
|
xTo,
|
|
@@ -1873,6 +2096,53 @@ function refineLocatedAggressiveRemoval({
|
|
|
1873
2096
|
return best;
|
|
1874
2097
|
}
|
|
1875
2098
|
|
|
2099
|
+
function shouldSkipLocatedAggressiveForCleanCanonical96({
|
|
2100
|
+
config,
|
|
2101
|
+
alphaGain,
|
|
2102
|
+
originalSpatialScore,
|
|
2103
|
+
originalGradientScore,
|
|
2104
|
+
currentSpatialScore,
|
|
2105
|
+
currentGradientScore
|
|
2106
|
+
}) {
|
|
2107
|
+
if (
|
|
2108
|
+
config?.logoSize !== 96 ||
|
|
2109
|
+
config.marginRight !== 64 ||
|
|
2110
|
+
config.marginBottom !== 64
|
|
2111
|
+
) {
|
|
2112
|
+
return false;
|
|
2113
|
+
}
|
|
2114
|
+
|
|
2115
|
+
const resolvedAlphaGain = Number(alphaGain);
|
|
2116
|
+
const originalSpatial = Number(originalSpatialScore);
|
|
2117
|
+
const originalGradient = Number(originalGradientScore);
|
|
2118
|
+
const currentSpatial = Number(currentSpatialScore);
|
|
2119
|
+
const currentGradient = Number(currentGradientScore);
|
|
2120
|
+
if (
|
|
2121
|
+
!Number.isFinite(resolvedAlphaGain) ||
|
|
2122
|
+
!Number.isFinite(originalSpatial) ||
|
|
2123
|
+
!Number.isFinite(originalGradient) ||
|
|
2124
|
+
!Number.isFinite(currentSpatial) ||
|
|
2125
|
+
!Number.isFinite(currentGradient)
|
|
2126
|
+
) {
|
|
2127
|
+
return false;
|
|
2128
|
+
}
|
|
2129
|
+
|
|
2130
|
+
const cleanStandardAlpha =
|
|
2131
|
+
resolvedAlphaGain <= 1 &&
|
|
2132
|
+
currentSpatial >= 0 &&
|
|
2133
|
+
currentSpatial <= 0.35 &&
|
|
2134
|
+
Math.max(0, currentGradient) <= 0.08;
|
|
2135
|
+
const cleanBalancedAlpha =
|
|
2136
|
+
resolvedAlphaGain <= 1.1 &&
|
|
2137
|
+
currentSpatial >= 0 &&
|
|
2138
|
+
currentSpatial <= 0.22 &&
|
|
2139
|
+
Math.max(0, currentGradient) <= 0.1;
|
|
2140
|
+
|
|
2141
|
+
return originalSpatial >= 0.55 &&
|
|
2142
|
+
originalGradient >= 0.2 &&
|
|
2143
|
+
(cleanStandardAlpha || cleanBalancedAlpha);
|
|
2144
|
+
}
|
|
2145
|
+
|
|
1876
2146
|
function shouldRelocateSmallFixedLocalAnchor({
|
|
1877
2147
|
source,
|
|
1878
2148
|
config,
|
|
@@ -2784,9 +3054,23 @@ export function processWatermarkImageData(imageData, options = {}) {
|
|
|
2784
3054
|
}
|
|
2785
3055
|
|
|
2786
3056
|
const locatedAggressiveStartedAt = nowMs();
|
|
3057
|
+
const locatedAggressiveResidualVisibility = assessWatermarkResidualVisibility({
|
|
3058
|
+
imageData: finalImageData,
|
|
3059
|
+
position,
|
|
3060
|
+
alphaMap
|
|
3061
|
+
});
|
|
2787
3062
|
if (
|
|
2788
3063
|
options.locatedAggressiveRemoval !== false &&
|
|
2789
|
-
smallFixedLocalRelocated?.residualVisibility?.visible !== false
|
|
3064
|
+
smallFixedLocalRelocated?.residualVisibility?.visible !== false &&
|
|
3065
|
+
locatedAggressiveResidualVisibility?.visible !== false &&
|
|
3066
|
+
!shouldSkipLocatedAggressiveForCleanCanonical96({
|
|
3067
|
+
config,
|
|
3068
|
+
alphaGain,
|
|
3069
|
+
originalSpatialScore,
|
|
3070
|
+
originalGradientScore,
|
|
3071
|
+
currentSpatialScore: finalProcessedSpatialScore,
|
|
3072
|
+
currentGradientScore: finalProcessedGradientScore
|
|
3073
|
+
})
|
|
2790
3074
|
) {
|
|
2791
3075
|
const aggressiveRefined = refineLocatedAggressiveRemoval({
|
|
2792
3076
|
originalImageData,
|
|
@@ -2835,10 +3119,42 @@ export function processWatermarkImageData(imageData, options = {}) {
|
|
|
2835
3119
|
: `${source}+located-aggressive`;
|
|
2836
3120
|
}
|
|
2837
3121
|
}
|
|
3122
|
+
|
|
3123
|
+
const smoothPriorStartedAt = nowMs();
|
|
3124
|
+
const smoothPriorRefined = refineSmoothLocatedResidualWithEstimatedPrior({
|
|
3125
|
+
originalImageData,
|
|
3126
|
+
currentImageData: finalImageData,
|
|
3127
|
+
alphaMap,
|
|
3128
|
+
position,
|
|
3129
|
+
source,
|
|
3130
|
+
alphaGain,
|
|
3131
|
+
baselineSpatialScore: finalProcessedSpatialScore,
|
|
3132
|
+
baselineGradientScore: finalProcessedGradientScore
|
|
3133
|
+
});
|
|
3134
|
+
if (smoothPriorRefined) {
|
|
3135
|
+
recordAlphaAdjustmentStage({
|
|
3136
|
+
stage: 'smooth-located-estimated-prior',
|
|
3137
|
+
fromAlphaGain: alphaGain,
|
|
3138
|
+
toAlphaGain: alphaGain,
|
|
3139
|
+
beforeSpatialScore: finalProcessedSpatialScore,
|
|
3140
|
+
beforeGradientScore: finalProcessedGradientScore,
|
|
3141
|
+
afterSpatialScore: smoothPriorRefined.spatialScore,
|
|
3142
|
+
afterGradientScore: smoothPriorRefined.gradientScore,
|
|
3143
|
+
suppressionGain: originalSpatialScore - smoothPriorRefined.spatialScore,
|
|
3144
|
+
cost: smoothPriorRefined.cost,
|
|
3145
|
+
allowSameAlphaGain: true
|
|
3146
|
+
});
|
|
3147
|
+
finalImageData = smoothPriorRefined.imageData;
|
|
3148
|
+
finalProcessedSpatialScore = smoothPriorRefined.spatialScore;
|
|
3149
|
+
finalProcessedGradientScore = smoothPriorRefined.gradientScore;
|
|
3150
|
+
suppressionGain = originalSpatialScore - finalProcessedSpatialScore;
|
|
3151
|
+
source = `${source}+smooth-prior`;
|
|
3152
|
+
}
|
|
2838
3153
|
if (debugTimingsEnabled) {
|
|
2839
3154
|
debugTimings.previewEdgeCleanupMs = previewEdgeCleanupElapsedMs;
|
|
2840
3155
|
debugTimings.smallPreviewRefinementMs = nowMs() - smallPreviewRefinementStartedAt;
|
|
2841
3156
|
debugTimings.locatedAggressiveRemovalMs = nowMs() - locatedAggressiveStartedAt;
|
|
3157
|
+
debugTimings.smoothPriorCleanupMs = nowMs() - smoothPriorStartedAt;
|
|
2842
3158
|
debugTimings.totalMs = nowMs() - totalStartedAt;
|
|
2843
3159
|
}
|
|
2844
3160
|
|