@pilio/gemini-watermark-remover 1.0.19 → 1.0.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -10
- package/README_zh.md +8 -1
- package/package.json +74 -3
- package/src/cli/gwrCli.js +1 -1
- package/src/cli/gwrRemoveCommand.js +82 -14
- package/src/core/adaptiveDetector.js +22 -3
- package/src/core/allenkFdncnnDenoise.js +326 -0
- package/src/core/allenkFdncnnNcnnModel.js +244 -0
- package/src/core/allenkFdncnnOnnxExport.js +337 -0
- package/src/core/allenkFdncnnOnnxRuntime.js +138 -0
- package/src/core/allenkFdncnnReferenceRuntime.js +169 -0
- package/src/core/alphaGradientMask.js +165 -0
- package/src/core/blendModes.js +9 -4
- package/src/core/candidateSelector.js +422 -33
- package/src/core/embeddedAlphaMaps.js +2 -0
- package/src/core/geminiSizeCatalog.js +44 -1
- package/src/core/restorationMetrics.js +44 -0
- package/src/core/watermarkEngine.js +2 -1
- package/src/core/watermarkProcessor.js +1048 -82
- package/src/sdk/index.d.ts +29 -1
- package/src/sdk/node.d.ts +13 -0
- package/src/sdk/node.js +7 -0
- package/src/sdk/video.d.ts +84 -0
- package/src/sdk/video.js +263 -0
- package/src/shared/debugFileHandoff.js +109 -0
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
const EPSILON = 1e-8;
|
|
2
|
+
|
|
3
|
+
function clamp(value, min, max) {
|
|
4
|
+
return Math.max(min, Math.min(max, value));
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
function gaussianKernel1D(sigma) {
|
|
8
|
+
if (!Number.isFinite(sigma) || sigma <= 0) return null;
|
|
9
|
+
|
|
10
|
+
const radius = Math.max(1, Math.ceil(sigma * 3));
|
|
11
|
+
const kernel = new Float32Array(radius * 2 + 1);
|
|
12
|
+
let sum = 0;
|
|
13
|
+
|
|
14
|
+
for (let i = -radius; i <= radius; i++) {
|
|
15
|
+
const value = Math.exp(-(i * i) / (2 * sigma * sigma));
|
|
16
|
+
kernel[i + radius] = value;
|
|
17
|
+
sum += value;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (sum <= EPSILON) return null;
|
|
21
|
+
|
|
22
|
+
for (let i = 0; i < kernel.length; i++) {
|
|
23
|
+
kernel[i] /= sum;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return { kernel, radius };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function blurHorizontal(values, width, height, kernelInfo) {
|
|
30
|
+
const out = new Float32Array(values.length);
|
|
31
|
+
const { kernel, radius } = kernelInfo;
|
|
32
|
+
|
|
33
|
+
for (let y = 0; y < height; y++) {
|
|
34
|
+
const rowBase = y * width;
|
|
35
|
+
for (let x = 0; x < width; x++) {
|
|
36
|
+
let sum = 0;
|
|
37
|
+
for (let k = -radius; k <= radius; k++) {
|
|
38
|
+
const sx = clamp(x + k, 0, width - 1);
|
|
39
|
+
sum += values[rowBase + sx] * kernel[k + radius];
|
|
40
|
+
}
|
|
41
|
+
out[rowBase + x] = sum;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return out;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function blurVertical(values, width, height, kernelInfo) {
|
|
49
|
+
const out = new Float32Array(values.length);
|
|
50
|
+
const { kernel, radius } = kernelInfo;
|
|
51
|
+
|
|
52
|
+
for (let y = 0; y < height; y++) {
|
|
53
|
+
for (let x = 0; x < width; x++) {
|
|
54
|
+
let sum = 0;
|
|
55
|
+
for (let k = -radius; k <= radius; k++) {
|
|
56
|
+
const sy = clamp(y + k, 0, height - 1);
|
|
57
|
+
sum += values[sy * width + x] * kernel[k + radius];
|
|
58
|
+
}
|
|
59
|
+
out[y * width + x] = sum;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return out;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function gaussianBlur(values, width, height, sigma) {
|
|
67
|
+
const kernelInfo = gaussianKernel1D(sigma);
|
|
68
|
+
if (!kernelInfo) return new Float32Array(values);
|
|
69
|
+
|
|
70
|
+
return blurVertical(
|
|
71
|
+
blurHorizontal(values, width, height, kernelInfo),
|
|
72
|
+
width,
|
|
73
|
+
height,
|
|
74
|
+
kernelInfo
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function dilate(values, width, height, radius) {
|
|
79
|
+
if (!Number.isFinite(radius) || radius <= 0) return new Float32Array(values);
|
|
80
|
+
|
|
81
|
+
const roundedRadius = Math.max(1, Math.round(radius));
|
|
82
|
+
const radiusSquared = roundedRadius * roundedRadius;
|
|
83
|
+
const out = new Float32Array(values.length);
|
|
84
|
+
|
|
85
|
+
for (let y = 0; y < height; y++) {
|
|
86
|
+
for (let x = 0; x < width; x++) {
|
|
87
|
+
let maxValue = 0;
|
|
88
|
+
for (let dy = -roundedRadius; dy <= roundedRadius; dy++) {
|
|
89
|
+
for (let dx = -roundedRadius; dx <= roundedRadius; dx++) {
|
|
90
|
+
if (dx * dx + dy * dy > radiusSquared) continue;
|
|
91
|
+
const sx = x + dx;
|
|
92
|
+
const sy = y + dy;
|
|
93
|
+
if (sx < 0 || sy < 0 || sx >= width || sy >= height) continue;
|
|
94
|
+
maxValue = Math.max(maxValue, values[sy * width + sx]);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
out[y * width + x] = maxValue;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return out;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function createAlphaGradientMask({
|
|
105
|
+
alphaMap,
|
|
106
|
+
width,
|
|
107
|
+
height = width,
|
|
108
|
+
strength = 1,
|
|
109
|
+
gamma = 0.5,
|
|
110
|
+
dilateRadius = 2,
|
|
111
|
+
blurSigma = 2
|
|
112
|
+
}) {
|
|
113
|
+
if (!alphaMap || width <= 0 || height <= 0 || alphaMap.length < width * height) {
|
|
114
|
+
return new Float32Array(0);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const gradient = new Float32Array(width * height);
|
|
118
|
+
let minGradient = Number.POSITIVE_INFINITY;
|
|
119
|
+
let maxGradient = 0;
|
|
120
|
+
|
|
121
|
+
for (let y = 1; y < height - 1; y++) {
|
|
122
|
+
for (let x = 1; x < width - 1; x++) {
|
|
123
|
+
const i = y * width + x;
|
|
124
|
+
const gx =
|
|
125
|
+
-alphaMap[i - width - 1] - 2 * alphaMap[i - 1] - alphaMap[i + width - 1] +
|
|
126
|
+
alphaMap[i - width + 1] + 2 * alphaMap[i + 1] + alphaMap[i + width + 1];
|
|
127
|
+
const gy =
|
|
128
|
+
-alphaMap[i - width - 1] - 2 * alphaMap[i - width] - alphaMap[i - width + 1] +
|
|
129
|
+
alphaMap[i + width - 1] + 2 * alphaMap[i + width] + alphaMap[i + width + 1];
|
|
130
|
+
const value = Math.sqrt(gx * gx + gy * gy);
|
|
131
|
+
gradient[i] = value;
|
|
132
|
+
minGradient = Math.min(minGradient, value);
|
|
133
|
+
maxGradient = Math.max(maxGradient, value);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (!Number.isFinite(minGradient) || maxGradient <= minGradient + EPSILON) {
|
|
138
|
+
return new Float32Array(width * height);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const normalized = new Float32Array(width * height);
|
|
142
|
+
const exponent = Number.isFinite(gamma) && gamma > 0 ? gamma : 1;
|
|
143
|
+
for (let i = 0; i < normalized.length; i++) {
|
|
144
|
+
const value = (gradient[i] - minGradient) / (maxGradient - minGradient);
|
|
145
|
+
normalized[i] = Math.pow(clamp(value, 0, 1), exponent);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const expanded = dilate(normalized, width, height, dilateRadius);
|
|
149
|
+
const blurred = gaussianBlur(expanded, width, height, blurSigma);
|
|
150
|
+
const safeStrength = Number.isFinite(strength) ? Math.max(0, strength) : 1;
|
|
151
|
+
|
|
152
|
+
for (let i = 0; i < blurred.length; i++) {
|
|
153
|
+
blurred[i] = clamp(blurred[i] * safeStrength, 0, 1);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return blurred;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export function getAlphaGradientWeight(mask, index, floor = 0.35) {
|
|
160
|
+
if (!mask || index < 0 || index >= mask.length) {
|
|
161
|
+
return clamp(floor, 0, 1);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return Math.max(clamp(floor, 0, 1), clamp(mask[index], 0, 1));
|
|
165
|
+
}
|
package/src/core/blendModes.js
CHANGED
|
@@ -37,11 +37,16 @@ export function removeWatermark(imageData, alphaMap, position, options = {}) {
|
|
|
37
37
|
// Calculate index in alpha map
|
|
38
38
|
const alphaIdx = row * width + col;
|
|
39
39
|
|
|
40
|
-
// Get alpha value
|
|
40
|
+
// Get alpha value. A negative alpha map marks a dark-polarity
|
|
41
|
+
// watermark: same opacity mask, black logo value.
|
|
41
42
|
const rawAlpha = alphaMap[alphaIdx];
|
|
43
|
+
const alphaMagnitude = Math.abs(rawAlpha);
|
|
44
|
+
const logoValue = Number.isFinite(options.logoValue)
|
|
45
|
+
? options.logoValue
|
|
46
|
+
: (rawAlpha < 0 ? 0 : LOGO_VALUE);
|
|
42
47
|
|
|
43
48
|
// Remove low-level alpha noise from compressed background capture.
|
|
44
|
-
const signalAlpha = Math.max(0,
|
|
49
|
+
const signalAlpha = Math.max(0, alphaMagnitude - ALPHA_NOISE_FLOOR) * alphaGain;
|
|
45
50
|
|
|
46
51
|
// Skip very small alpha values (noise)
|
|
47
52
|
if (signalAlpha < ALPHA_THRESHOLD) {
|
|
@@ -49,7 +54,7 @@ export function removeWatermark(imageData, alphaMap, position, options = {}) {
|
|
|
49
54
|
}
|
|
50
55
|
|
|
51
56
|
// Use original alpha for inverse solve; use denoised alpha as activation signal.
|
|
52
|
-
const alpha = Math.min(
|
|
57
|
+
const alpha = Math.min(alphaMagnitude * alphaGain, MAX_ALPHA);
|
|
53
58
|
const oneMinusAlpha = 1.0 - alpha;
|
|
54
59
|
|
|
55
60
|
// Apply reverse alpha blending to each RGB channel
|
|
@@ -57,7 +62,7 @@ export function removeWatermark(imageData, alphaMap, position, options = {}) {
|
|
|
57
62
|
const watermarked = imageData.data[imgIdx + c];
|
|
58
63
|
|
|
59
64
|
// Reverse alpha blending formula
|
|
60
|
-
const original = (watermarked - alpha *
|
|
65
|
+
const original = (watermarked - alpha * logoValue) / oneMinusAlpha;
|
|
61
66
|
|
|
62
67
|
// Clip to [0, 255] range
|
|
63
68
|
imageData.data[imgIdx + c] = Math.max(0, Math.min(255, Math.round(original)));
|