Automatically removes watermarks from Gemini AI generated images
// ==UserScript==
// @name Gemini NanoBanana Watermark Remover
// @name:zh-CN Gemini NanoBanana 图片水印移除
// @namespace https://github.com/GargantuaX
// @version 0.1.8
// @description Automatically removes watermarks from Gemini AI generated images
// @description:zh-CN 自动移除 Gemini AI 生成图像中的水印
// @icon https://www.google.com/s2/favicons?domain=gemini.google.com
// @author journey-ad
// @license MIT
// @match https://gemini.google.com/*
// @match https://business.gemini.google/*
// @connect googleusercontent.com
// @grant GM_xmlhttpRequest
// @grant unsafeWindow
// @run-at document-end
// ==/UserScript==
(() => {
// src/core/alphaMap.js
function calculateAlphaMap(bgCaptureImageData) {
const { width, height, data } = bgCaptureImageData;
const alphaMap = new Float32Array(width * height);
for (let i = 0; i < alphaMap.length; i++) {
const idx = i * 4;
const r = data[idx];
const g = data[idx + 1];
const b = data[idx + 2];
const maxChannel = Math.max(r, g, b);
alphaMap[i] = maxChannel / 255;
}
return alphaMap;
}
// src/core/blendModes.js
var ALPHA_NOISE_FLOOR = 3 / 255;
var ALPHA_THRESHOLD = 2e-3;
var MAX_ALPHA = 0.99;
var LOGO_VALUE = 255;
function removeWatermark(imageData, alphaMap, position, options = {}) {
const { x, y, width, height } = position;
const alphaGain = Number.isFinite(options.alphaGain) && options.alphaGain > 0 ? options.alphaGain : 1;
for (let row = 0; row < height; row++) {
for (let col = 0; col < width; col++) {
const imgIdx = ((y + row) * imageData.width + (x + col)) * 4;
const alphaIdx = row * width + col;
const rawAlpha = alphaMap[alphaIdx];
const signalAlpha = Math.max(0, rawAlpha - ALPHA_NOISE_FLOOR) * alphaGain;
if (signalAlpha < ALPHA_THRESHOLD) {
continue;
}
const alpha = Math.min(rawAlpha * alphaGain, MAX_ALPHA);
const oneMinusAlpha = 1 - alpha;
for (let c = 0; c < 3; c++) {
const watermarked = imageData.data[imgIdx + c];
const original = (watermarked - alpha * LOGO_VALUE) / oneMinusAlpha;
imageData.data[imgIdx + c] = Math.max(0, Math.min(255, Math.round(original)));
}
}
}
}
// src/core/adaptiveDetector.js
var DEFAULT_THRESHOLD = 0.35;
var EPSILON = 1e-8;
var clamp = (v, min, max) => Math.max(min, Math.min(max, v));
function meanAndVariance(values) {
let sum = 0;
for (let i = 0; i < values.length; i++) sum += values[i];
const mean = sum / values.length;
let sq = 0;
for (let i = 0; i < values.length; i++) {
const d = values[i] - mean;
sq += d * d;
}
return { mean, variance: sq / values.length };
}
function normalizedCrossCorrelation(a, b) {
if (a.length !== b.length || a.length === 0) return 0;
const statsA = meanAndVariance(a);
const statsB = meanAndVariance(b);
const den = Math.sqrt(statsA.variance * statsB.variance) * a.length;
if (den < EPSILON) return 0;
let num = 0;
for (let i = 0; i < a.length; i++) {
num += (a[i] - statsA.mean) * (b[i] - statsB.mean);
}
return num / den;
}
function getRegion(data, width, x, y, size) {
const out = new Float32Array(size * size);
for (let row = 0; row < size; row++) {
const srcBase = (y + row) * width + x;
const dstBase = row * size;
for (let col = 0; col < size; col++) {
out[dstBase + col] = data[srcBase + col];
}
}
return out;
}
function toRegionGrayscale(imageData, region) {
const { width, height, data } = imageData;
const size = region.size ?? Math.min(region.width, region.height);
if (!size || size <= 0) return new Float32Array(0);
if (region.x < 0 || region.y < 0 || region.x + size > width || region.y + size > height) {
return new Float32Array(0);
}
const out = new Float32Array(size * size);
for (let row = 0; row < size; row++) {
for (let col = 0; col < size; col++) {
const idx = ((region.y + row) * width + (region.x + col)) * 4;
out[row * size + col] = (0.2126 * data[idx] + 0.7152 * data[idx + 1] + 0.0722 * data[idx + 2]) / 255;
}
}
return out;
}
function toGrayscale(imageData) {
const { width, height, data } = imageData;
const out = new Float32Array(width * height);
for (let i = 0; i < out.length; i++) {
const j = i * 4;
out[i] = (0.2126 * data[j] + 0.7152 * data[j + 1] + 0.0722 * data[j + 2]) / 255;
}
return out;
}
function sobelMagnitude(gray, width, height) {
const grad = new Float32Array(width * height);
for (let y = 1; y < height - 1; y++) {
for (let x = 1; x < width - 1; x++) {
const i = y * width + x;
const gx = -gray[i - width - 1] - 2 * gray[i - 1] - gray[i + width - 1] + gray[i - width + 1] + 2 * gray[i + 1] + gray[i + width + 1];
const gy = -gray[i - width - 1] - 2 * gray[i - width] - gray[i - width + 1] + gray[i + width - 1] + 2 * gray[i + width] + gray[i + width + 1];
grad[i] = Math.sqrt(gx * gx + gy * gy);
}
}
return grad;
}
function stdDevRegion(data, width, x, y, size) {
let sum = 0;
let sq = 0;
let n = 0;
for (let row = 0; row < size; row++) {
const base = (y + row) * width + x;
for (let col = 0; col < size; col++) {
const v = data[base + col];
sum += v;
sq += v * v;
n++;
}
}
if (n === 0) return 0;
const mean = sum / n;
const variance = Math.max(0, sq / n - mean * mean);
return Math.sqrt(variance);
}
function buildTemplateGradient(alphaMap, size) {
return sobelMagnitude(alphaMap, size, size);
}
function scoreCandidate({ gray, grad, width, height }, alphaMap, templateGrad, candidate) {
const { x, y, size } = candidate;
if (x < 0 || y < 0 || x + size > width || y + size > height) {
return null;
}
const grayRegion = getRegion(gray, width, x, y, size);
const gradRegion = getRegion(grad, width, x, y, size);
const spatial = normalizedCrossCorrelation(grayRegion, alphaMap);
const gradient = normalizedCrossCorrelation(gradRegion, templateGrad);
let varianceScore = 0;
if (y > 8) {
const refY = Math.max(0, y - size);
const refH = Math.min(size, y - refY);
if (refH > 8) {
const wmStd = stdDevRegion(gray, width, x, y, size);
const refStd = stdDevRegion(gray, width, x, refY, refH);
if (refStd > EPSILON) {
varianceScore = clamp(1 - wmStd / refStd, 0, 1);
}
}
}
const confidence = Math.max(0, spatial) * 0.5 + Math.max(0, gradient) * 0.3 + varianceScore * 0.2;
return {
confidence: clamp(confidence, 0, 1),
spatialScore: spatial,
gradientScore: gradient,
varianceScore
};
}
function createScaleList(minSize, maxSize) {
const set = /* @__PURE__ */ new Set();
for (let s = minSize; s <= maxSize; s += 8) set.add(s);
if (48 >= minSize && 48 <= maxSize) set.add(48);
if (96 >= minSize && 96 <= maxSize) set.add(96);
return [...set].sort((a, b) => a - b);
}
function getTemplate(cache, alpha96, size) {
if (cache.has(size)) return cache.get(size);
const alpha = size === 96 ? alpha96 : interpolateAlphaMap(alpha96, 96, size);
const grad = buildTemplateGradient(alpha, size);
const tpl = { alpha, grad };
cache.set(size, tpl);
return tpl;
}
function warpAlphaMap(alphaMap, size, { dx = 0, dy = 0, scale = 1 } = {}) {
if (size <= 0) return new Float32Array(0);
if (!Number.isFinite(dx) || !Number.isFinite(dy) || !Number.isFinite(scale) || scale <= 0) {
return new Float32Array(0);
}
if (dx === 0 && dy === 0 && scale === 1) return new Float32Array(alphaMap);
const sample = (x, y) => {
const x0 = Math.floor(x);
const y0 = Math.floor(y);
const fx = x - x0;
const fy = y - y0;
const ix0 = clamp(x0, 0, size - 1);
const iy0 = clamp(y0, 0, size - 1);
const ix1 = clamp(x0 + 1, 0, size - 1);
const iy1 = clamp(y0 + 1, 0, size - 1);
const p00 = alphaMap[iy0 * size + ix0];
const p10 = alphaMap[iy0 * size + ix1];
const p01 = alphaMap[iy1 * size + ix0];
const p11 = alphaMap[iy1 * size + ix1];
const top = p00 + (p10 - p00) * fx;
const bottom = p01 + (p11 - p01) * fx;
return top + (bottom - top) * fy;
};
const out = new Float32Array(size * size);
const c = (size - 1) / 2;
for (let y = 0; y < size; y++) {
for (let x = 0; x < size; x++) {
const sx = (x - c) / scale + c + dx;
const sy = (y - c) / scale + c + dy;
out[y * size + x] = sample(sx, sy);
}
}
return out;
}
function interpolateAlphaMap(sourceAlpha, sourceSize, targetSize) {
if (targetSize <= 0) return new Float32Array(0);
if (sourceSize === targetSize) return new Float32Array(sourceAlpha);
const out = new Float32Array(targetSize * targetSize);
const scale = (sourceSize - 1) / Math.max(1, targetSize - 1);
for (let y = 0; y < targetSize; y++) {
const sy = y * scale;
const y0 = Math.floor(sy);
const y1 = Math.min(sourceSize - 1, y0 + 1);
const fy = sy - y0;
for (let x = 0; x < targetSize; x++) {
const sx = x * scale;
const x0 = Math.floor(sx);
const x1 = Math.min(sourceSize - 1, x0 + 1);
const fx = sx - x0;
const p00 = sourceAlpha[y0 * sourceSize + x0];
const p10 = sourceAlpha[y0 * sourceSize + x1];
const p01 = sourceAlpha[y1 * sourceSize + x0];
const p11 = sourceAlpha[y1 * sourceSize + x1];
const top = p00 + (p10 - p00) * fx;
const bottom = p01 + (p11 - p01) * fx;
out[y * targetSize + x] = top + (bottom - top) * fy;
}
}
return out;
}
function computeRegionSpatialCorrelation({ imageData, alphaMap, region }) {
const patch = toRegionGrayscale(imageData, region);
if (patch.length === 0 || patch.length !== alphaMap.length) return 0;
return normalizedCrossCorrelation(patch, alphaMap);
}
function computeRegionGradientCorrelation({ imageData, alphaMap, region }) {
const patch = toRegionGrayscale(imageData, region);
if (patch.length === 0 || patch.length !== alphaMap.length) return 0;
const size = region.size ?? Math.min(region.width, region.height);
if (!size || size <= 2) return 0;
const patchGrad = sobelMagnitude(patch, size, size);
const alphaGrad = sobelMagnitude(alphaMap, size, size);
return normalizedCrossCorrelation(patchGrad, alphaGrad);
}
function shouldAttemptAdaptiveFallback({
processedImageData,
alphaMap,
position,
residualThreshold = 0.22,
originalImageData = null,
originalSpatialMismatchThreshold = 0
}) {
const residualScore = computeRegionSpatialCorrelation({
imageData: processedImageData,
alphaMap,
region: {
x: position.x,
y: position.y,
size: position.width ?? position.size
}
});
if (residualScore >= residualThreshold) {
return true;
}
if (originalImageData) {
const originalScore = computeRegionSpatialCorrelation({
imageData: originalImageData,
alphaMap,
region: {
x: position.x,
y: position.y,
size: position.width ?? position.size
}
});
if (originalScore <= originalSpatialMismatchThreshold) {
return true;
}
}
return false;
}
function detectAdaptiveWatermarkRegion({
imageData,
alpha96,
defaultConfig,
threshold = DEFAULT_THRESHOLD
}) {
const { width, height } = imageData;
const gray = toGrayscale(imageData);
const grad = sobelMagnitude(gray, width, height);
const context = { gray, grad, width, height };
const templateCache = /* @__PURE__ */ new Map();
const baseSize = defaultConfig.logoSize;
const defaultCandidate = {
size: baseSize,
x: width - defaultConfig.marginRight - baseSize,
y: height - defaultConfig.marginBottom - baseSize
};
const defaultTemplate = getTemplate(templateCache, alpha96, baseSize);
const defaultScore = scoreCandidate(context, defaultTemplate.alpha, defaultTemplate.grad, defaultCandidate);
if (defaultScore && defaultScore.confidence >= threshold + 0.08) {
return {
found: true,
confidence: defaultScore.confidence,
spatialScore: defaultScore.spatialScore,
gradientScore: defaultScore.gradientScore,
varianceScore: defaultScore.varianceScore,
region: defaultCandidate
};
}
const minSize = clamp(Math.round(baseSize * 0.65), 24, 144);
const maxSize = clamp(
Math.min(Math.round(baseSize * 2.8), Math.floor(Math.min(width, height) * 0.4)),
minSize,
192
);
const scaleList = createScaleList(minSize, maxSize);
const marginRange = Math.max(32, Math.round(baseSize * 0.75));
const minMarginRight = clamp(defaultConfig.marginRight - marginRange, 8, width - minSize - 1);
const maxMarginRight = clamp(defaultConfig.marginRight + marginRange, minMarginRight, width - minSize - 1);
const minMarginBottom = clamp(defaultConfig.marginBottom - marginRange, 8, height - minSize - 1);
const maxMarginBottom = clamp(defaultConfig.marginBottom + marginRange, minMarginBottom, height - minSize - 1);
const topK = [];
const pushTopK = (candidate) => {
topK.push(candidate);
topK.sort((a, b) => b.adjustedScore - a.adjustedScore);
if (topK.length > 5) topK.length = 5;
};
for (const size of scaleList) {
const tpl = getTemplate(templateCache, alpha96, size);
for (let mr = minMarginRight; mr <= maxMarginRight; mr += 8) {
const x = width - mr - size;
if (x < 0) continue;
for (let mb = minMarginBottom; mb <= maxMarginBottom; mb += 8) {
const y = height - mb - size;
if (y < 0) continue;
const score = scoreCandidate(context, tpl.alpha, tpl.grad, { x, y, size });
if (!score) continue;
const adjustedScore = score.confidence * Math.min(1, Math.sqrt(size / 96));
if (adjustedScore < 0.08) continue;
pushTopK({
size,
x,
y,
adjustedScore
});
}
}
}
let best = defaultScore ? {
...defaultCandidate,
...defaultScore
} : {
...defaultCandidate,
confidence: 0,
spatialScore: 0,
gradientScore: 0,
varianceScore: 0
};
for (const coarse of topK) {
const scaleLo = clamp(coarse.size - 10, minSize, maxSize);
const scaleHi = clamp(coarse.size + 10, minSize, maxSize);
for (let size = scaleLo; size <= scaleHi; size += 2) {
const tpl = getTemplate(templateCache, alpha96, size);
for (let x = coarse.x - 8; x <= coarse.x + 8; x += 2) {
if (x < 0 || x + size > width) continue;
for (let y = coarse.y - 8; y <= coarse.y + 8; y += 2) {
if (y < 0 || y + size > height) continue;
const score = scoreCandidate(context, tpl.alpha, tpl.grad, { x, y, size });
if (!score) continue;
if (score.confidence > best.confidence) {
best = {
x,
y,
size,
...score
};
}
}
}
}
}
return {
found: best.confidence >= threshold,
confidence: best.confidence,
spatialScore: best.spatialScore,
gradientScore: best.gradientScore,
varianceScore: best.varianceScore,
region: {
x: best.x,
y: best.y,
size: best.size
}
};
}
// src/core/watermarkConfig.js
function detectWatermarkConfig(imageWidth, imageHeight) {
if (imageWidth > 1024 && imageHeight > 1024) {
return {
logoSize: 96,
marginRight: 64,
marginBottom: 64
};
}
return {
logoSize: 48,
marginRight: 32,
marginBottom: 32
};
}
function calculateWatermarkPosition(imageWidth, imageHeight, config) {
const { logoSize, marginRight, marginBottom } = config;
return {
x: imageWidth - marginRight - logoSize,
y: imageHeight - marginBottom - logoSize,
width: logoSize,
height: logoSize
};
}
function getStandardConfig(size) {
return size === 96 ? { logoSize: 96, marginRight: 64, marginBottom: 64 } : { logoSize: 48, marginRight: 32, marginBottom: 32 };
}
function getAlphaMapForConfig(config, alpha48, alpha96) {
return config.logoSize === 96 ? alpha96 : alpha48;
}
function isRegionInsideImage(imageData, region) {
return region.x >= 0 && region.y >= 0 && region.x + region.width <= imageData.width && region.y + region.height <= imageData.height;
}
function resolveInitialStandardConfig({
imageData,
defaultConfig,
alpha48,
alpha96,
minSwitchScore = 0.25,
minScoreDelta = 0.08
}) {
if (!imageData || !defaultConfig || !alpha48 || !alpha96) return defaultConfig;
const fallbackConfig = getStandardConfig(48);
const primaryConfig = defaultConfig.logoSize === 96 ? getStandardConfig(96) : fallbackConfig;
const alternateConfig = defaultConfig.logoSize === 96 ? fallbackConfig : getStandardConfig(96);
const primaryRegion = calculateWatermarkPosition(imageData.width, imageData.height, primaryConfig);
const alternateRegion = calculateWatermarkPosition(imageData.width, imageData.height, alternateConfig);
if (!isRegionInsideImage(imageData, primaryRegion)) return defaultConfig;
const primaryScore = computeRegionSpatialCorrelation({
imageData,
alphaMap: getAlphaMapForConfig(primaryConfig, alpha48, alpha96),
region: {
x: primaryRegion.x,
y: primaryRegion.y,
size: primaryRegion.width
}
});
if (!isRegionInsideImage(imageData, alternateRegion)) return primaryConfig;
const alternateScore = computeRegionSpatialCorrelation({
imageData,
alphaMap: getAlphaMapForConfig(alternateConfig, alpha48, alpha96),
region: {
x: alternateRegion.x,
y: alternateRegion.y,
size: alternateRegion.width
}
});
const shouldSwitch = alternateScore >= minSwitchScore && alternateScore > primaryScore + minScoreDelta;
return shouldSwitch ? alternateConfig : primaryConfig;
}
// src/core/watermarkPresence.js
var MIN_STANDARD_SPATIAL_SCORE = 0.3;
var MIN_STANDARD_GRADIENT_SCORE = 0.12;
var MIN_ADAPTIVE_CONFIDENCE = 0.5;
var MIN_ADAPTIVE_SPATIAL_SCORE = 0.45;
var MIN_ADAPTIVE_GRADIENT_SCORE = 0.12;
var MIN_ADAPTIVE_SIZE = 40;
var MAX_ADAPTIVE_SIZE = 192;
function toFiniteNumber(value) {
return typeof value === "number" && Number.isFinite(value) ? value : null;
}
function hasReliableStandardWatermarkSignal({ spatialScore, gradientScore }) {
const spatial = toFiniteNumber(spatialScore);
const gradient = toFiniteNumber(gradientScore);
return spatial !== null && gradient !== null && spatial >= MIN_STANDARD_SPATIAL_SCORE && gradient >= MIN_STANDARD_GRADIENT_SCORE;
}
function hasReliableAdaptiveWatermarkSignal(adaptiveResult) {
if (!adaptiveResult || adaptiveResult.found !== true) return false;
const confidence = toFiniteNumber(adaptiveResult.confidence);
const spatial = toFiniteNumber(adaptiveResult.spatialScore);
const gradient = toFiniteNumber(adaptiveResult.gradientScore);
const size = toFiniteNumber(adaptiveResult?.region?.size);
return confidence !== null && spatial !== null && gradient !== null && size !== null && confidence >= MIN_ADAPTIVE_CONFIDENCE && spatial >= MIN_ADAPTIVE_SPATIAL_SCORE && gradient >= MIN_ADAPTIVE_GRADIENT_SCORE && size >= MIN_ADAPTIVE_SIZE && size <= MAX_ADAPTIVE_SIZE;
}
// src/assets/bg_48.png
var bg_48_default = "data:image/png;base64,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";
// src/assets/bg_96.png
var bg_96_default = "data:image/png;base64,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";
// src/core/watermarkEngine.js
var RESIDUAL_RECALIBRATION_THRESHOLD = 0.5;
var MIN_SUPPRESSION_FOR_SKIP_RECALIBRATION = 0.18;
var MIN_RECALIBRATION_SCORE_DELTA = 0.18;
var NEAR_BLACK_THRESHOLD = 5;
var MAX_NEAR_BLACK_RATIO_INCREASE = 0.05;
var OUTLINE_REFINEMENT_THRESHOLD = 0.42;
var OUTLINE_REFINEMENT_MIN_GAIN = 1.2;
var TEMPLATE_ALIGN_SHIFTS = [-0.5, -0.25, 0, 0.25, 0.5];
var TEMPLATE_ALIGN_SCALES = [0.99, 1, 1.01];
var SUBPIXEL_REFINE_SHIFTS = [-0.25, 0, 0.25];
var SUBPIXEL_REFINE_SCALES = [0.99, 1, 1.01];
var ALPHA_GAIN_CANDIDATES = [1.05, 1.12, 1.2, 1.28, 1.36, 1.45, 1.52, 1.6, 1.7, 1.85, 2, 2.2, 2.4, 2.6];
function createRuntimeCanvas(width, height) {
if (typeof OffscreenCanvas !== "undefined") {
return new OffscreenCanvas(width, height);
}
if (typeof document !== "undefined") {
const canvas = document.createElement("canvas");
canvas.width = width;
canvas.height = height;
return canvas;
}
throw new Error("Canvas runtime not available");
}
function getCanvasContext2D(canvas) {
const ctx = canvas.getContext("2d", { willReadFrequently: true });
if (!ctx) {
throw new Error("Failed to get 2D canvas context");
}
return ctx;
}
async function loadBackgroundCapture(source) {
if (typeof Image !== "undefined") {
const image = new Image();
image.src = source;
await image.decode();
return image;
}
if (typeof createImageBitmap !== "undefined" && typeof fetch !== "undefined") {
const response = await fetch(source);
if (!response.ok) {
throw new Error(`Failed to load background capture: ${response.status}`);
}
const blob = await response.blob();
return await createImageBitmap(blob);
}
throw new Error("No image loader available in current runtime");
}
function cloneImageData(imageData) {
return new ImageData(
new Uint8ClampedArray(imageData.data),
imageData.width,
imageData.height
);
}
function normalizeMetaPosition(position) {
if (!position) return null;
const { x, y, width, height } = position;
if (![x, y, width, height].every((value) => Number.isFinite(value))) {
return null;
}
return { x, y, width, height };
}
function normalizeMetaConfig(config) {
if (!config) return null;
const { logoSize, marginRight, marginBottom } = config;
if (![logoSize, marginRight, marginBottom].every((value) => Number.isFinite(value))) {
return null;
}
return { logoSize, marginRight, marginBottom };
}
function createWatermarkMeta({
position = null,
config = null,
adaptiveConfidence = null,
originalSpatialScore = null,
originalGradientScore = null,
processedSpatialScore = null,
processedGradientScore = null,
suppressionGain = null,
templateWarp = null,
alphaGain = 1,
source = "standard",
applied = true,
skipReason = null,
subpixelShift = null
} = {}) {
const normalizedPosition = normalizeMetaPosition(position);
return {
applied,
skipReason: applied ? null : skipReason,
size: normalizedPosition ? normalizedPosition.width : null,
position: normalizedPosition,
config: normalizeMetaConfig(config),
detection: {
adaptiveConfidence,
originalSpatialScore,
originalGradientScore,
processedSpatialScore,
processedGradientScore,
suppressionGain
},
templateWarp: templateWarp ?? null,
alphaGain,
source,
subpixelShift: subpixelShift ?? null
};
}
function shouldRecalibrateAlphaStrength({ originalScore, processedScore, suppressionGain }) {
return originalScore >= 0.6 && processedScore >= RESIDUAL_RECALIBRATION_THRESHOLD && suppressionGain <= MIN_SUPPRESSION_FOR_SKIP_RECALIBRATION;
}
function calculateNearBlackRatio(imageData, position) {
let nearBlack = 0;
let total = 0;
for (let row = 0; row < position.height; row++) {
for (let col = 0; col < position.width; col++) {
const idx = ((position.y + row) * imageData.width + (position.x + col)) * 4;
const r = imageData.data[idx];
const g = imageData.data[idx + 1];
const b = imageData.data[idx + 2];
if (r <= NEAR_BLACK_THRESHOLD && g <= NEAR_BLACK_THRESHOLD && b <= NEAR_BLACK_THRESHOLD) {
nearBlack++;
}
total++;
}
}
return total > 0 ? nearBlack / total : 0;
}
function findBestTemplateWarp({
originalImageData,
alphaMap,
position,
baselineSpatialScore,
baselineGradientScore
}) {
const size = position.width;
if (!size || size <= 8) return null;
let best = {
spatialScore: baselineSpatialScore,
gradientScore: baselineGradientScore,
shift: { dx: 0, dy: 0, scale: 1 },
alphaMap
};
for (const scale of TEMPLATE_ALIGN_SCALES) {
for (const dy of TEMPLATE_ALIGN_SHIFTS) {
for (const dx of TEMPLATE_ALIGN_SHIFTS) {
if (dx === 0 && dy === 0 && scale === 1) continue;
const warped = warpAlphaMap(alphaMap, size, { dx, dy, scale });
const spatialScore = computeRegionSpatialCorrelation({
imageData: originalImageData,
alphaMap: warped,
region: { x: position.x, y: position.y, size }
});
const gradientScore = computeRegionGradientCorrelation({
imageData: originalImageData,
alphaMap: warped,
region: { x: position.x, y: position.y, size }
});
const confidence = Math.max(0, spatialScore) * 0.7 + Math.max(0, gradientScore) * 0.3;
const bestConfidence = Math.max(0, best.spatialScore) * 0.7 + Math.max(0, best.gradientScore) * 0.3;
if (confidence > bestConfidence + 0.01) {
best = {
spatialScore,
gradientScore,
shift: { dx, dy, scale },
alphaMap: warped
};
}
}
}
}
const improvedSpatial = best.spatialScore >= baselineSpatialScore + 0.01;
const improvedGradient = best.gradientScore >= baselineGradientScore + 0.01;
return improvedSpatial || improvedGradient ? best : null;
}
function refineSubpixelOutline({
originalImageData,
alphaMap,
position,
alphaGain,
originalNearBlackRatio,
baselineSpatialScore,
baselineGradientScore,
baselineShift
}) {
const size = position.width;
if (!size || size <= 8) return null;
if (alphaGain < OUTLINE_REFINEMENT_MIN_GAIN) return null;
const maxAllowedNearBlackRatio = Math.min(1, originalNearBlackRatio + MAX_NEAR_BLACK_RATIO_INCREASE);
const gainCandidates = [alphaGain];
const lower = Math.max(1, Number((alphaGain - 0.01).toFixed(2)));
const upper = Number((alphaGain + 0.01).toFixed(2));
if (lower !== alphaGain) gainCandidates.push(lower);
if (upper !== alphaGain) gainCandidates.push(upper);
const baseDx = baselineShift?.dx ?? 0;
const baseDy = baselineShift?.dy ?? 0;
const baseScale = baselineShift?.scale ?? 1;
let best = null;
for (const scaleDelta of SUBPIXEL_REFINE_SCALES) {
const scale = Number((baseScale * scaleDelta).toFixed(4));
for (const dyDelta of SUBPIXEL_REFINE_SHIFTS) {
const dy = baseDy + dyDelta;
for (const dxDelta of SUBPIXEL_REFINE_SHIFTS) {
const dx = baseDx + dxDelta;
const warped = warpAlphaMap(alphaMap, size, { dx, dy, scale });
for (const gain of gainCandidates) {
const candidate = cloneImageData(originalImageData);
removeWatermark(candidate, warped, position, { alphaGain: gain });
const nearBlackRatio = calculateNearBlackRatio(candidate, position);
if (nearBlackRatio > maxAllowedNearBlackRatio) continue;
const spatialScore = computeRegionSpatialCorrelation({
imageData: candidate,
alphaMap: warped,
region: { x: position.x, y: position.y, size }
});
const gradientScore = computeRegionGradientCorrelation({
imageData: candidate,
alphaMap: warped,
region: { x: position.x, y: position.y, size }
});
const cost = Math.abs(spatialScore) * 0.6 + Math.max(0, gradientScore);
if (!best || cost < best.cost) {
best = {
imageData: candidate,
alphaMap: warped,
alphaGain: gain,
shift: { dx, dy, scale },
spatialScore,
gradientScore,
nearBlackRatio,
cost
};
}
}
}
}
}
if (!best) return null;
const improvedGradient = best.gradientScore <= baselineGradientScore - 0.04;
const keptSpatial = Math.abs(best.spatialScore) <= Math.abs(baselineSpatialScore) + 0.08;
if (!improvedGradient || !keptSpatial) return null;
return best;
}
function recalibrateAlphaStrength({
originalImageData,
alphaMap,
position,
originalSpatialScore,
processedSpatialScore,
originalNearBlackRatio
}) {
let bestScore = processedSpatialScore;
let bestGain = 1;
let bestImageData = null;
const maxAllowedNearBlackRatio = Math.min(1, originalNearBlackRatio + MAX_NEAR_BLACK_RATIO_INCREASE);
for (const alphaGain of ALPHA_GAIN_CANDIDATES) {
const candidate = cloneImageData(originalImageData);
removeWatermark(candidate, alphaMap, position, { alphaGain });
const candidateNearBlackRatio = calculateNearBlackRatio(candidate, position);
if (candidateNearBlackRatio > maxAllowedNearBlackRatio) {
continue;
}
const score = computeRegionSpatialCorrelation({
imageData: candidate,
alphaMap,
region: {
x: position.x,
y: position.y,
size: position.width
}
});
if (score < bestScore) {
bestScore = score;
bestGain = alphaGain;
bestImageData = candidate;
}
}
const refinedCandidates = [];
for (let delta = -0.05; delta <= 0.05; delta += 0.01) {
refinedCandidates.push(Number((bestGain + delta).toFixed(2)));
}
for (const alphaGain of refinedCandidates) {
if (alphaGain <= 1 || alphaGain >= 3) continue;
const candidate = cloneImageData(originalImageData);
removeWatermark(candidate, alphaMap, position, { alphaGain });
const candidateNearBlackRatio = calculateNearBlackRatio(candidate, position);
if (candidateNearBlackRatio > maxAllowedNearBlackRatio) {
continue;
}
const score = computeRegionSpatialCorrelation({
imageData: candidate,
alphaMap,
region: {
x: position.x,
y: position.y,
size: position.width
}
});
if (score < bestScore) {
bestScore = score;
bestGain = alphaGain;
bestImageData = candidate;
}
}
const scoreDelta = processedSpatialScore - bestScore;
if (!bestImageData || scoreDelta < MIN_RECALIBRATION_SCORE_DELTA) {
return null;
}
return {
imageData: bestImageData,
alphaGain: bestGain,
processedSpatialScore: bestScore,
suppressionGain: originalSpatialScore - bestScore
};
}
var WatermarkEngine = class _WatermarkEngine {
constructor(bgCaptures) {
this.bgCaptures = bgCaptures;
this.alphaMaps = {};
}
static async create() {
const [bg48, bg96] = await Promise.all([
loadBackgroundCapture(bg_48_default),
loadBackgroundCapture(bg_96_default)
]);
return new _WatermarkEngine({ bg48, bg96 });
}
/**
* Get alpha map from background captured image based on watermark size
* @param {number} size - Watermark size (48 or 96)
* @returns {Promise<Float32Array>} Alpha map
*/
async getAlphaMap(size) {
if (size !== 48 && size !== 96) {
if (this.alphaMaps[size]) return this.alphaMaps[size];
const alpha96 = await this.getAlphaMap(96);
const interpolated = interpolateAlphaMap(alpha96, 96, size);
this.alphaMaps[size] = interpolated;
return interpolated;
}
if (this.alphaMaps[size]) {
return this.alphaMaps[size];
}
const bgImage = size === 48 ? this.bgCaptures.bg48 : this.bgCaptures.bg96;
const canvas = createRuntimeCanvas(size, size);
const ctx = getCanvasContext2D(canvas);
ctx.drawImage(bgImage, 0, 0);
const imageData = ctx.getImageData(0, 0, size, size);
const alphaMap = calculateAlphaMap(imageData);
this.alphaMaps[size] = alphaMap;
return alphaMap;
}
/**
* Remove watermark from image based on watermark size
* @param {HTMLImageElement|HTMLCanvasElement} image - Input image
* @returns {Promise<HTMLCanvasElement>} Processed canvas
*/
async removeWatermarkFromImage(image, options = {}) {
const adaptiveMode = options.adaptiveMode || "auto";
const allowAdaptiveSearch = adaptiveMode !== "never" && adaptiveMode !== "off";
const canvas = createRuntimeCanvas(image.width, image.height);
const ctx = getCanvasContext2D(canvas);
ctx.drawImage(image, 0, 0);
const originalImageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const defaultConfig = detectWatermarkConfig(canvas.width, canvas.height);
const alpha48 = await this.getAlphaMap(48);
const alpha96 = await this.getAlphaMap(96);
const resolvedConfig = resolveInitialStandardConfig({
imageData: originalImageData,
defaultConfig,
alpha48,
alpha96
});
let config = resolvedConfig;
let position = calculateWatermarkPosition(canvas.width, canvas.height, config);
let alphaMap = config.logoSize === 96 ? alpha96 : alpha48;
let source = "standard";
let adaptiveConfidence = null;
let alphaGain = 1;
let subpixelShift = null;
const standardSpatialScore = computeRegionSpatialCorrelation({
imageData: originalImageData,
alphaMap,
region: {
x: position.x,
y: position.y,
size: position.width
}
});
const standardGradientScore = computeRegionGradientCorrelation({
imageData: originalImageData,
alphaMap,
region: {
x: position.x,
y: position.y,
size: position.width
}
});
if (!hasReliableStandardWatermarkSignal({
spatialScore: standardSpatialScore,
gradientScore: standardGradientScore
})) {
const adaptive = allowAdaptiveSearch ? detectAdaptiveWatermarkRegion({
imageData: originalImageData,
alpha96,
defaultConfig: config
}) : null;
adaptiveConfidence = adaptive?.confidence ?? null;
if (!hasReliableAdaptiveWatermarkSignal(adaptive)) {
canvas.__watermarkMeta = createWatermarkMeta({
adaptiveConfidence,
originalSpatialScore: standardSpatialScore,
originalGradientScore: standardGradientScore,
processedSpatialScore: standardSpatialScore,
processedGradientScore: standardGradientScore,
suppressionGain: 0,
alphaGain: 1,
source: "skipped",
applied: false,
skipReason: "no-watermark-detected"
});
return canvas;
}
const size = adaptive.region.size;
position = {
x: adaptive.region.x,
y: adaptive.region.y,
width: size,
height: size
};
alphaMap = await this.getAlphaMap(size);
config = {
logoSize: size,
marginRight: canvas.width - position.x - size,
marginBottom: canvas.height - position.y - size
};
source = "adaptive";
}
const fixedImageData = cloneImageData(originalImageData);
removeWatermark(fixedImageData, alphaMap, position);
let finalImageData = fixedImageData;
const shouldFallback = adaptiveMode === "always" ? true : shouldAttemptAdaptiveFallback({
processedImageData: fixedImageData,
alphaMap,
position,
originalImageData,
originalSpatialMismatchThreshold: 0
});
if (shouldFallback && allowAdaptiveSearch) {
const adaptive = detectAdaptiveWatermarkRegion({
imageData: originalImageData,
alpha96,
defaultConfig: config
});
if (hasReliableAdaptiveWatermarkSignal(adaptive)) {
adaptiveConfidence = adaptive.confidence;
const size = adaptive.region.size;
const adaptivePosition = {
x: adaptive.region.x,
y: adaptive.region.y,
width: size,
height: size
};
const positionDelta = Math.abs(adaptivePosition.x - position.x) + Math.abs(adaptivePosition.y - position.y) + Math.abs(adaptivePosition.width - position.width);
if (positionDelta >= 4) {
position = adaptivePosition;
alphaMap = await this.getAlphaMap(size);
config = {
logoSize: size,
marginRight: canvas.width - adaptivePosition.x - size,
marginBottom: canvas.height - adaptivePosition.y - size
};
source = "adaptive";
const adaptiveImageData = cloneImageData(originalImageData);
removeWatermark(adaptiveImageData, alphaMap, position);
finalImageData = adaptiveImageData;
}
}
}
let originalSpatialScore = computeRegionSpatialCorrelation({
imageData: originalImageData,
alphaMap,
region: {
x: position.x,
y: position.y,
size: position.width
}
});
let originalGradientScore = computeRegionGradientCorrelation({
imageData: originalImageData,
alphaMap,
region: {
x: position.x,
y: position.y,
size: position.width
}
});
const templateWarp = findBestTemplateWarp({
originalImageData,
alphaMap,
position,
baselineSpatialScore: originalSpatialScore,
baselineGradientScore: originalGradientScore
});
if (templateWarp) {
alphaMap = templateWarp.alphaMap;
originalSpatialScore = templateWarp.spatialScore;
originalGradientScore = templateWarp.gradientScore;
}
const processedSpatialScore = computeRegionSpatialCorrelation({
imageData: finalImageData,
alphaMap,
region: {
x: position.x,
y: position.y,
size: position.width
}
});
const processedGradientScore = computeRegionGradientCorrelation({
imageData: finalImageData,
alphaMap,
region: {
x: position.x,
y: position.y,
size: position.width
}
});
let finalProcessedSpatialScore = processedSpatialScore;
let finalProcessedGradientScore = processedGradientScore;
let suppressionGain = originalSpatialScore - finalProcessedSpatialScore;
if (shouldRecalibrateAlphaStrength({
originalScore: originalSpatialScore,
processedScore: finalProcessedSpatialScore,
suppressionGain
})) {
const originalNearBlackRatio = calculateNearBlackRatio(originalImageData, position);
const recalibrated = recalibrateAlphaStrength({
originalImageData,
alphaMap,
position,
originalSpatialScore,
processedSpatialScore: finalProcessedSpatialScore,
originalNearBlackRatio
});
if (recalibrated) {
finalImageData = recalibrated.imageData;
alphaGain = recalibrated.alphaGain;
finalProcessedSpatialScore = recalibrated.processedSpatialScore;
suppressionGain = recalibrated.suppressionGain;
source = source === "adaptive" ? "adaptive+gain" : "standard+gain";
}
}
if (finalProcessedSpatialScore <= 0.3 && finalProcessedGradientScore >= OUTLINE_REFINEMENT_THRESHOLD) {
const originalNearBlackRatio = calculateNearBlackRatio(originalImageData, position);
const baselineShift = templateWarp?.shift ?? { dx: 0, dy: 0, scale: 1 };
const refined = refineSubpixelOutline({
originalImageData,
alphaMap,
position,
alphaGain,
originalNearBlackRatio,
baselineSpatialScore: finalProcessedSpatialScore,
baselineGradientScore: finalProcessedGradientScore,
baselineShift
});
if (refined) {
finalImageData = refined.imageData;
alphaMap = refined.alphaMap;
alphaGain = refined.alphaGain;
finalProcessedSpatialScore = refined.spatialScore;
finalProcessedGradientScore = refined.gradientScore;
suppressionGain = originalSpatialScore - finalProcessedSpatialScore;
source = `${source}+subpixel`;
subpixelShift = refined.shift;
}
}
ctx.putImageData(finalImageData, 0, 0);
canvas.__watermarkMeta = createWatermarkMeta({
position,
config,
adaptiveConfidence,
originalSpatialScore,
originalGradientScore,
processedSpatialScore: finalProcessedSpatialScore,
processedGradientScore: finalProcessedGradientScore,
suppressionGain,
templateWarp: templateWarp?.shift ?? null,
alphaGain,
source,
applied: true,
subpixelShift
});
return canvas;
}
/**
* Get watermark information (for display)
* @param {number} imageWidth - Image width
* @param {number} imageHeight - Image height
* @returns {Object} Watermark information {size, position, config}
*/
getWatermarkInfo(imageWidth, imageHeight) {
const config = detectWatermarkConfig(imageWidth, imageHeight);
const position = calculateWatermarkPosition(imageWidth, imageHeight, config);
return {
size: config.logoSize,
position,
config
};
}
};
// src/core/canvasBlob.js
async function canvasToBlob(canvas, type = "image/png") {
if (typeof canvas?.convertToBlob === "function") {
return await canvas.convertToBlob({ type });
}
if (typeof canvas?.toBlob === "function") {
return await new Promise((resolve, reject) => {
canvas.toBlob((blob) => {
if (blob) {
resolve(blob);
} else {
reject(new Error("Failed to encode image blob"));
}
}, type);
});
}
throw new Error("Canvas blob export API is unavailable");
}
// src/userscript/urlUtils.js
function isGoogleusercontentHost(hostname) {
return hostname === "googleusercontent.com" || hostname.endsWith(".googleusercontent.com");
}
function hasGeminiAssetPath(pathname) {
return /^\/rd-[^/]+\//.test(pathname);
}
function isGeminiGeneratedAssetUrl(url) {
if (typeof url !== "string" || url.length === 0) return false;
try {
const parsed = new URL(url);
return isGoogleusercontentHost(parsed.hostname) && hasGeminiAssetPath(parsed.pathname);
} catch {
return false;
}
}
function normalizeGoogleusercontentImageUrl(url) {
if (!isGeminiGeneratedAssetUrl(url)) return url;
try {
const parsed = new URL(url);
const path = parsed.pathname;
const tailTransform = path.match(/=([^/?#=]+)$/);
if (tailTransform && /^(?:s|w|h)\d+/i.test(tailTransform[1])) {
const keepDownloadFlag = tailTransform[1].endsWith("-d") ? "-d" : "";
parsed.pathname = `${path.slice(0, tailTransform.index)}=s0${keepDownloadFlag}`;
return parsed.toString();
}
const sizeTransformAtTail = /=s\d+([^/]*)$/;
if (sizeTransformAtTail.test(path)) {
parsed.pathname = path.replace(sizeTransformAtTail, "=s0$1");
return parsed.toString();
}
parsed.pathname = `${path}=s0`;
return parsed.toString();
} catch {
return url;
}
}
// src/userscript/trustedTypes.js
var USERSCRIPT_TRUSTED_TYPES_POLICY = "gemini-watermark-remover";
function toWorkerScriptUrl(url, env = globalThis) {
const trustedTypesApi = env?.trustedTypes;
if (!trustedTypesApi || typeof trustedTypesApi.createPolicy !== "function") {
return url;
}
try {
const existingPolicy = typeof trustedTypesApi.getPolicy === "function" ? trustedTypesApi.getPolicy(USERSCRIPT_TRUSTED_TYPES_POLICY) : null;
const policy = existingPolicy || trustedTypesApi.createPolicy(
USERSCRIPT_TRUSTED_TYPES_POLICY,
{ createScriptURL: (value) => value }
);
if (!policy || typeof policy.createScriptURL !== "function") return null;
return policy.createScriptURL(url);
} catch {
return null;
}
}
// src/userscript/runtimeFlags.js
var INLINE_WORKER_DEFAULT_ENABLED = true ? false : false;
function shouldUseInlineWorker(workerCode, env = globalThis) {
const forceEnable = env?.__GWR_FORCE_INLINE_WORKER__ === true;
if (!INLINE_WORKER_DEFAULT_ENABLED && !forceEnable) return false;
if (typeof workerCode !== "string" || workerCode.length === 0) return false;
return typeof env?.Worker !== "undefined" && typeof env?.Blob !== "undefined";
}
// src/userscript/retryPolicy.js
var MAX_PROCESS_RETRIES = 3;
var BASE_RETRY_DELAY_MS = 1e3;
var MAX_RETRY_DELAY_MS = 3e4;
var RETRY_JITTER_MS = 300;
var toSafeInteger = (value, fallback = 0) => {
const parsed = Number.parseInt(value, 10);
return Number.isFinite(parsed) ? parsed : fallback;
};
function readRetryState(dataset = {}) {
const failureCount = Math.max(0, toSafeInteger(dataset.watermarkFailureCount, 0));
const retryExhausted = dataset.watermarkRetryExhausted === "true" || failureCount >= MAX_PROCESS_RETRIES;
const nextRetryAt = retryExhausted ? 0 : Math.max(0, toSafeInteger(dataset.watermarkNextRetryAt, 0));
return {
failureCount,
nextRetryAt,
retryExhausted
};
}
function shouldProcessNow(state, now = Date.now()) {
if (!state || state.retryExhausted) return false;
return now >= state.nextRetryAt;
}
function resetRetryState(dataset = {}) {
dataset.watermarkFailureCount = "0";
dataset.watermarkNextRetryAt = "0";
dataset.watermarkRetryExhausted = "false";
}
function computeRetryDelayMs(failureCount, { random = Math.random } = {}) {
const safeFailures = Math.max(1, toSafeInteger(failureCount, 1));
const exponential = Math.min(MAX_RETRY_DELAY_MS, BASE_RETRY_DELAY_MS * 2 ** (safeFailures - 1));
const jitter = Math.floor(Math.max(0, random()) * RETRY_JITTER_MS);
return exponential + jitter;
}
function registerProcessFailure(dataset = {}, { now = Date.now(), random = Math.random } = {}) {
const current = readRetryState(dataset);
const failureCount = current.failureCount + 1;
dataset.watermarkFailureCount = String(failureCount);
if (failureCount >= MAX_PROCESS_RETRIES) {
dataset.watermarkRetryExhausted = "true";
dataset.watermarkNextRetryAt = "0";
return {
failureCount,
exhausted: true,
delayMs: 0,
nextRetryAt: 0
};
}
const delayMs = computeRetryDelayMs(failureCount, { random });
const nextRetryAt = now + delayMs;
dataset.watermarkRetryExhausted = "false";
dataset.watermarkNextRetryAt = String(nextRetryAt);
return {
failureCount,
exhausted: false,
delayMs,
nextRetryAt
};
}
// src/userscript/index.js
var USERSCRIPT_WORKER_CODE = true ? '(()=>{function at(n){let{width:t,height:e,data:a}=n,o=new Float32Array(t*e);for(let r=0;r<o.length;r++){let i=r*4,c=a[i],l=a[i+1],f=a[i+2],d=Math.max(c,l,f);o[r]=d/255}return o}var It=.011764705882352941,Mt=.002,Dt=.99,bt=255;function P(n,t,e,a={}){let{x:o,y:r,width:i,height:c}=e,l=Number.isFinite(a.alphaGain)&&a.alphaGain>0?a.alphaGain:1;for(let f=0;f<c;f++)for(let d=0;d<i;d++){let g=((r+f)*n.width+(o+d))*4,u=f*i+d,s=t[u];if(Math.max(0,s-It)*l<Mt)continue;let p=Math.min(s*l,Dt),m=1-p;for(let S=0;S<3;S++){let b=(n.data[g+S]-p*bt)/m;n.data[g+S]=Math.max(0,Math.min(255,Math.round(b)))}}}var F=(n,t,e)=>Math.max(t,Math.min(e,n));function ot(n){let t=0;for(let o=0;o<n.length;o++)t+=n[o];let e=t/n.length,a=0;for(let o=0;o<n.length;o++){let r=n[o]-e;a+=r*r}return{mean:e,variance:a/n.length}}function X(n,t){if(n.length!==t.length||n.length===0)return 0;let e=ot(n),a=ot(t),o=Math.sqrt(e.variance*a.variance)*n.length;if(o<1e-8)return 0;let r=0;for(let i=0;i<n.length;i++)r+=(n[i]-e.mean)*(t[i]-a.mean);return r/o}function rt(n,t,e,a,o){let r=new Float32Array(o*o);for(let i=0;i<o;i++){let c=(a+i)*t+e,l=i*o;for(let f=0;f<o;f++)r[l+f]=n[c+f]}return r}function ct(n,t){let{width:e,height:a,data:o}=n,r=t.size??Math.min(t.width,t.height);if(!r||r<=0)return new Float32Array(0);if(t.x<0||t.y<0||t.x+r>e||t.y+r>a)return new Float32Array(0);let i=new Float32Array(r*r);for(let c=0;c<r;c++)for(let l=0;l<r;l++){let f=((t.y+c)*e+(t.x+l))*4;i[c*r+l]=(.2126*o[f]+.7152*o[f+1]+.0722*o[f+2])/255}return i}function Rt(n){let{width:t,height:e,data:a}=n,o=new Float32Array(t*e);for(let r=0;r<o.length;r++){let i=r*4;o[r]=(.2126*a[i]+.7152*a[i+1]+.0722*a[i+2])/255}return o}function H(n,t,e){let a=new Float32Array(t*e);for(let o=1;o<e-1;o++)for(let r=1;r<t-1;r++){let i=o*t+r,c=-n[i-t-1]-2*n[i-1]-n[i+t-1]+n[i-t+1]+2*n[i+1]+n[i+t+1],l=-n[i-t-1]-2*n[i-t]-n[i-t+1]+n[i+t-1]+2*n[i+t]+n[i+t+1];a[i]=Math.sqrt(c*c+l*l)}return a}function it(n,t,e,a,o){let r=0,i=0,c=0;for(let d=0;d<o;d++){let g=(a+d)*t+e;for(let u=0;u<o;u++){let s=n[g+u];r+=s,i+=s*s,c++}}if(c===0)return 0;let l=r/c,f=Math.max(0,i/c-l*l);return Math.sqrt(f)}function Et(n,t){return H(n,t,t)}function z({gray:n,grad:t,width:e,height:a},o,r,i){let{x:c,y:l,size:f}=i;if(c<0||l<0||c+f>e||l+f>a)return null;let d=rt(n,e,c,l,f),g=rt(t,e,c,l,f),u=X(d,o),s=X(g,r),h=0;if(l>8){let m=Math.max(0,l-f),S=Math.min(f,l-m);if(S>8){let N=it(n,e,c,l,f),b=it(n,e,c,m,S);b>1e-8&&(h=F(1-N/b,0,1))}}let p=Math.max(0,u)*.5+Math.max(0,s)*.3+h*.2;return{confidence:F(p,0,1),spatialScore:u,gradientScore:s,varianceScore:h}}function _t(n,t){let e=new Set;for(let a=n;a<=t;a+=8)e.add(a);return 48>=n&&48<=t&&e.add(48),96>=n&&96<=t&&e.add(96),[...e].sort((a,o)=>a-o)}function Q(n,t,e){if(n.has(e))return n.get(e);let a=e===96?t:Z(t,96,e),o=Et(a,e),r={alpha:a,grad:o};return n.set(e,r),r}function Y(n,t,{dx:e=0,dy:a=0,scale:o=1}={}){if(t<=0)return new Float32Array(0);if(!Number.isFinite(e)||!Number.isFinite(a)||!Number.isFinite(o)||o<=0)return new Float32Array(0);if(e===0&&a===0&&o===1)return new Float32Array(n);let r=(l,f)=>{let d=Math.floor(l),g=Math.floor(f),u=l-d,s=f-g,h=F(d,0,t-1),p=F(g,0,t-1),m=F(d+1,0,t-1),S=F(g+1,0,t-1),N=n[p*t+h],b=n[p*t+m],G=n[S*t+h],B=n[S*t+m],M=N+(b-N)*u,C=G+(B-G)*u;return M+(C-M)*s},i=new Float32Array(t*t),c=(t-1)/2;for(let l=0;l<t;l++)for(let f=0;f<t;f++){let d=(f-c)/o+c+e,g=(l-c)/o+c+a;i[l*t+f]=r(d,g)}return i}function Z(n,t,e){if(e<=0)return new Float32Array(0);if(t===e)return new Float32Array(n);let a=new Float32Array(e*e),o=(t-1)/Math.max(1,e-1);for(let r=0;r<e;r++){let i=r*o,c=Math.floor(i),l=Math.min(t-1,c+1),f=i-c;for(let d=0;d<e;d++){let g=d*o,u=Math.floor(g),s=Math.min(t-1,u+1),h=g-u,p=n[c*t+u],m=n[c*t+s],S=n[l*t+u],N=n[l*t+s],b=p+(m-p)*h,G=S+(N-S)*h;a[r*e+d]=b+(G-b)*f}}return a}function k({imageData:n,alphaMap:t,region:e}){let a=ct(n,e);return a.length===0||a.length!==t.length?0:X(a,t)}function L({imageData:n,alphaMap:t,region:e}){let a=ct(n,e);if(a.length===0||a.length!==t.length)return 0;let o=e.size??Math.min(e.width,e.height);if(!o||o<=2)return 0;let r=H(a,o,o),i=H(t,o,o);return X(r,i)}function st({processedImageData:n,alphaMap:t,position:e,residualThreshold:a=.22,originalImageData:o=null,originalSpatialMismatchThreshold:r=0}){return!!(k({imageData:n,alphaMap:t,region:{x:e.x,y:e.y,size:e.width??e.size}})>=a||o&&k({imageData:o,alphaMap:t,region:{x:e.x,y:e.y,size:e.width??e.size}})<=r)}function q({imageData:n,alpha96:t,defaultConfig:e,threshold:a=.35}){let{width:o,height:r}=n,i=Rt(n),c=H(i,o,r),l={gray:i,grad:c,width:o,height:r},f=new Map,d=e.logoSize,g={size:d,x:o-e.marginRight-d,y:r-e.marginBottom-d},u=Q(f,t,d),s=z(l,u.alpha,u.grad,g);if(s&&s.confidence>=a+.08)return{found:!0,confidence:s.confidence,spatialScore:s.spatialScore,gradientScore:s.gradientScore,varianceScore:s.varianceScore,region:g};let h=F(Math.round(d*.65),24,144),p=F(Math.min(Math.round(d*2.8),Math.floor(Math.min(o,r)*.4)),h,192),m=_t(h,p),S=Math.max(32,Math.round(d*.75)),N=F(e.marginRight-S,8,o-h-1),b=F(e.marginRight+S,N,o-h-1),G=F(e.marginBottom-S,8,r-h-1),B=F(e.marginBottom+S,G,r-h-1),M=[],C=x=>{M.push(x),M.sort((E,v)=>v.adjustedScore-E.adjustedScore),M.length>5&&(M.length=5)};for(let x of m){let E=Q(f,t,x);for(let v=N;v<=b;v+=8){let _=o-v-x;if(!(_<0))for(let I=G;I<=B;I+=8){let D=r-I-x;if(D<0)continue;let R=z(l,E.alpha,E.grad,{x:_,y:D,size:x});if(!R)continue;let A=R.confidence*Math.min(1,Math.sqrt(x/96));A<.08||C({size:x,x:_,y:D,adjustedScore:A})}}}let y=s?{...g,...s}:{...g,confidence:0,spatialScore:0,gradientScore:0,varianceScore:0};for(let x of M){let E=F(x.size-10,h,p),v=F(x.size+10,h,p);for(let _=E;_<=v;_+=2){let I=Q(f,t,_);for(let D=x.x-8;D<=x.x+8;D+=2)if(!(D<0||D+_>o))for(let R=x.y-8;R<=x.y+8;R+=2){if(R<0||R+_>r)continue;let A=z(l,I.alpha,I.grad,{x:D,y:R,size:_});A&&A.confidence>y.confidence&&(y={x:D,y:R,size:_,...A})}}}return{found:y.confidence>=a,confidence:y.confidence,spatialScore:y.spatialScore,gradientScore:y.gradientScore,varianceScore:y.varianceScore,region:{x:y.x,y:y.y,size:y.size}}}function j(n,t){return n>1024&&t>1024?{logoSize:96,marginRight:64,marginBottom:64}:{logoSize:48,marginRight:32,marginBottom:32}}function O(n,t,e){let{logoSize:a,marginRight:o,marginBottom:r}=e;return{x:n-o-a,y:t-r-a,width:a,height:a}}function K(n){return n===96?{logoSize:96,marginRight:64,marginBottom:64}:{logoSize:48,marginRight:32,marginBottom:32}}function lt(n,t,e){return n.logoSize===96?e:t}function ft(n,t){return t.x>=0&&t.y>=0&&t.x+t.width<=n.width&&t.y+t.height<=n.height}function dt({imageData:n,defaultConfig:t,alpha48:e,alpha96:a,minSwitchScore:o=.25,minScoreDelta:r=.08}){if(!n||!t||!e||!a)return t;let i=K(48),c=t.logoSize===96?K(96):i,l=t.logoSize===96?i:K(96),f=O(n.width,n.height,c),d=O(n.width,n.height,l);if(!ft(n,f))return t;let g=k({imageData:n,alphaMap:lt(c,e,a),region:{x:f.x,y:f.y,size:f.width}});if(!ft(n,d))return c;let u=k({imageData:n,alphaMap:lt(l,e,a),region:{x:d.x,y:d.y,size:d.width}});return u>=o&&u>g+r?l:c}function U(n){return typeof n=="number"&&Number.isFinite(n)?n:null}function ut({spatialScore:n,gradientScore:t}){let e=U(n),a=U(t);return e!==null&&a!==null&&e>=.3&&a>=.12}function $(n){if(!n||n.found!==!0)return!1;let t=U(n.confidence),e=U(n.spatialScore),a=U(n.gradientScore),o=U(n?.region?.size);return t!==null&&e!==null&&a!==null&&o!==null&&t>=.5&&e>=.45&&a>=.12&&o>=40&&o<=192}var ht="data:image/png;base64,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";var gt="data:image/png;base64,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";var Gt=.5,Tt=.18,Ft=.18,tt=5,wt=.05,kt=.42,Bt=1.2,pt=[-.5,-.25,0,.25,.5],Ct=[.99,1,1.01],mt=[-.25,0,.25],Pt=[.99,1,1.01],Lt=[1.05,1.12,1.2,1.28,1.36,1.45,1.52,1.6,1.7,1.85,2,2.2,2.4,2.6];function St(n,t){if(typeof OffscreenCanvas<"u")return new OffscreenCanvas(n,t);if(typeof document<"u"){let e=document.createElement("canvas");return e.width=n,e.height=t,e}throw new Error("Canvas runtime not available")}function xt(n){let t=n.getContext("2d",{willReadFrequently:!0});if(!t)throw new Error("Failed to get 2D canvas context");return t}async function At(n){if(typeof Image<"u"){let t=new Image;return t.src=n,await t.decode(),t}if(typeof createImageBitmap<"u"&&typeof fetch<"u"){let t=await fetch(n);if(!t.ok)throw new Error(`Failed to load background capture: ${t.status}`);let e=await t.blob();return await createImageBitmap(e)}throw new Error("No image loader available in current runtime")}function W(n){return new ImageData(new Uint8ClampedArray(n.data),n.width,n.height)}function Ot(n){if(!n)return null;let{x:t,y:e,width:a,height:o}=n;return[t,e,a,o].every(r=>Number.isFinite(r))?{x:t,y:e,width:a,height:o}:null}function Ut(n){if(!n)return null;let{logoSize:t,marginRight:e,marginBottom:a}=n;return[t,e,a].every(o=>Number.isFinite(o))?{logoSize:t,marginRight:e,marginBottom:a}:null}function yt({position:n=null,config:t=null,adaptiveConfidence:e=null,originalSpatialScore:a=null,originalGradientScore:o=null,processedSpatialScore:r=null,processedGradientScore:i=null,suppressionGain:c=null,templateWarp:l=null,alphaGain:f=1,source:d="standard",applied:g=!0,skipReason:u=null,subpixelShift:s=null}={}){let h=Ot(n);return{applied:g,skipReason:g?null:u,size:h?h.width:null,position:h,config:Ut(t),detection:{adaptiveConfidence:e,originalSpatialScore:a,originalGradientScore:o,processedSpatialScore:r,processedGradientScore:i,suppressionGain:c},templateWarp:l??null,alphaGain:f,source:d,subpixelShift:s??null}}function Wt({originalScore:n,processedScore:t,suppressionGain:e}){return n>=.6&&t>=Gt&&e<=Tt}function V(n,t){let e=0,a=0;for(let o=0;o<t.height;o++)for(let r=0;r<t.width;r++){let i=((t.y+o)*n.width+(t.x+r))*4,c=n.data[i],l=n.data[i+1],f=n.data[i+2];c<=tt&&l<=tt&&f<=tt&&e++,a++}return a>0?e/a:0}function Vt({originalImageData:n,alphaMap:t,position:e,baselineSpatialScore:a,baselineGradientScore:o}){let r=e.width;if(!r||r<=8)return null;let i={spatialScore:a,gradientScore:o,shift:{dx:0,dy:0,scale:1},alphaMap:t};for(let f of Ct)for(let d of pt)for(let g of pt){if(g===0&&d===0&&f===1)continue;let u=Y(t,r,{dx:g,dy:d,scale:f}),s=k({imageData:n,alphaMap:u,region:{x:e.x,y:e.y,size:r}}),h=L({imageData:n,alphaMap:u,region:{x:e.x,y:e.y,size:r}}),p=Math.max(0,s)*.7+Math.max(0,h)*.3,m=Math.max(0,i.spatialScore)*.7+Math.max(0,i.gradientScore)*.3;p>m+.01&&(i={spatialScore:s,gradientScore:h,shift:{dx:g,dy:d,scale:f},alphaMap:u})}let c=i.spatialScore>=a+.01,l=i.gradientScore>=o+.01;return c||l?i:null}function Xt({originalImageData:n,alphaMap:t,position:e,alphaGain:a,originalNearBlackRatio:o,baselineSpatialScore:r,baselineGradientScore:i,baselineShift:c}){let l=e.width;if(!l||l<=8||a<Bt)return null;let f=Math.min(1,o+wt),d=[a],g=Math.max(1,Number((a-.01).toFixed(2))),u=Number((a+.01).toFixed(2));g!==a&&d.push(g),u!==a&&d.push(u);let s=c?.dx??0,h=c?.dy??0,p=c?.scale??1,m=null;for(let b of Pt){let G=Number((p*b).toFixed(4));for(let B of mt){let M=h+B;for(let C of mt){let y=s+C,x=Y(t,l,{dx:y,dy:M,scale:G});for(let E of d){let v=W(n);P(v,x,e,{alphaGain:E});let _=V(v,e);if(_>f)continue;let I=k({imageData:v,alphaMap:x,region:{x:e.x,y:e.y,size:l}}),D=L({imageData:v,alphaMap:x,region:{x:e.x,y:e.y,size:l}}),R=Math.abs(I)*.6+Math.max(0,D);(!m||R<m.cost)&&(m={imageData:v,alphaMap:x,alphaGain:E,shift:{dx:y,dy:M,scale:G},spatialScore:I,gradientScore:D,nearBlackRatio:_,cost:R})}}}}if(!m)return null;let S=m.gradientScore<=i-.04,N=Math.abs(m.spatialScore)<=Math.abs(r)+.08;return!S||!N?null:m}function Ht({originalImageData:n,alphaMap:t,position:e,originalSpatialScore:a,processedSpatialScore:o,originalNearBlackRatio:r}){let i=o,c=1,l=null,f=Math.min(1,r+wt);for(let u of Lt){let s=W(n);if(P(s,t,e,{alphaGain:u}),V(s,e)>f)continue;let p=k({imageData:s,alphaMap:t,region:{x:e.x,y:e.y,size:e.width}});p<i&&(i=p,c=u,l=s)}let d=[];for(let u=-.05;u<=.05;u+=.01)d.push(Number((c+u).toFixed(2)));for(let u of d){if(u<=1||u>=3)continue;let s=W(n);if(P(s,t,e,{alphaGain:u}),V(s,e)>f)continue;let p=k({imageData:s,alphaMap:t,region:{x:e.x,y:e.y,size:e.width}});p<i&&(i=p,c=u,l=s)}let g=o-i;return!l||g<Ft?null:{imageData:l,alphaGain:c,processedSpatialScore:i,suppressionGain:a-i}}var J=class n{constructor(t){this.bgCaptures=t,this.alphaMaps={}}static async create(){let[t,e]=await Promise.all([At(ht),At(gt)]);return new n({bg48:t,bg96:e})}async getAlphaMap(t){if(t!==48&&t!==96){if(this.alphaMaps[t])return this.alphaMaps[t];let c=await this.getAlphaMap(96),l=Z(c,96,t);return this.alphaMaps[t]=l,l}if(this.alphaMaps[t])return this.alphaMaps[t];let e=t===48?this.bgCaptures.bg48:this.bgCaptures.bg96,a=St(t,t),o=xt(a);o.drawImage(e,0,0);let r=o.getImageData(0,0,t,t),i=at(r);return this.alphaMaps[t]=i,i}async removeWatermarkFromImage(t,e={}){let a=e.adaptiveMode||"auto",o=a!=="never"&&a!=="off",r=St(t.width,t.height),i=xt(r);i.drawImage(t,0,0);let c=i.getImageData(0,0,r.width,r.height),l=j(r.width,r.height),f=await this.getAlphaMap(48),d=await this.getAlphaMap(96),u=dt({imageData:c,defaultConfig:l,alpha48:f,alpha96:d}),s=O(r.width,r.height,u),h=u.logoSize===96?d:f,p="standard",m=null,S=1,N=null,b=k({imageData:c,alphaMap:h,region:{x:s.x,y:s.y,size:s.width}}),G=L({imageData:c,alphaMap:h,region:{x:s.x,y:s.y,size:s.width}});if(!ut({spatialScore:b,gradientScore:G})){let A=o?q({imageData:c,alpha96:d,defaultConfig:u}):null;if(m=A?.confidence??null,!$(A))return r.__watermarkMeta=yt({adaptiveConfidence:m,originalSpatialScore:b,originalGradientScore:G,processedSpatialScore:b,processedGradientScore:G,suppressionGain:0,alphaGain:1,source:"skipped",applied:!1,skipReason:"no-watermark-detected"}),r;let w=A.region.size;s={x:A.region.x,y:A.region.y,width:w,height:w},h=await this.getAlphaMap(w),u={logoSize:w,marginRight:r.width-s.x-w,marginBottom:r.height-s.y-w},p="adaptive"}let B=W(c);P(B,h,s);let M=B;if((a==="always"?!0:st({processedImageData:B,alphaMap:h,position:s,originalImageData:c,originalSpatialMismatchThreshold:0}))&&o){let A=q({imageData:c,alpha96:d,defaultConfig:u});if($(A)){m=A.confidence;let w=A.region.size,T={x:A.region.x,y:A.region.y,width:w,height:w};if(Math.abs(T.x-s.x)+Math.abs(T.y-s.y)+Math.abs(T.width-s.width)>=4){s=T,h=await this.getAlphaMap(w),u={logoSize:w,marginRight:r.width-T.x-w,marginBottom:r.height-T.y-w},p="adaptive";let et=W(c);P(et,h,s),M=et}}}let y=k({imageData:c,alphaMap:h,region:{x:s.x,y:s.y,size:s.width}}),x=L({imageData:c,alphaMap:h,region:{x:s.x,y:s.y,size:s.width}}),E=Vt({originalImageData:c,alphaMap:h,position:s,baselineSpatialScore:y,baselineGradientScore:x});E&&(h=E.alphaMap,y=E.spatialScore,x=E.gradientScore);let v=k({imageData:M,alphaMap:h,region:{x:s.x,y:s.y,size:s.width}}),_=L({imageData:M,alphaMap:h,region:{x:s.x,y:s.y,size:s.width}}),I=v,D=_,R=y-I;if(Wt({originalScore:y,processedScore:I,suppressionGain:R})){let A=V(c,s),w=Ht({originalImageData:c,alphaMap:h,position:s,originalSpatialScore:y,processedSpatialScore:I,originalNearBlackRatio:A});w&&(M=w.imageData,S=w.alphaGain,I=w.processedSpatialScore,R=w.suppressionGain,p=p==="adaptive"?"adaptive+gain":"standard+gain")}if(I<=.3&&D>=kt){let A=V(c,s),w=E?.shift??{dx:0,dy:0,scale:1},T=Xt({originalImageData:c,alphaMap:h,position:s,alphaGain:S,originalNearBlackRatio:A,baselineSpatialScore:I,baselineGradientScore:D,baselineShift:w});T&&(M=T.imageData,h=T.alphaMap,S=T.alphaGain,I=T.spatialScore,D=T.gradientScore,R=y-I,p=`${p}+subpixel`,N=T.shift)}return i.putImageData(M,0,0),r.__watermarkMeta=yt({position:s,config:u,adaptiveConfidence:m,originalSpatialScore:y,originalGradientScore:x,processedSpatialScore:I,processedGradientScore:D,suppressionGain:R,templateWarp:E?.shift??null,alphaGain:S,source:p,applied:!0,subpixelShift:N}),r}getWatermarkInfo(t,e){let a=j(t,e),o=O(t,e,a);return{size:a.logoSize,position:o,config:a}}};var nt=null;function jt(){return nt||(nt=J.create()),nt}function Jt(n){return n?{message:n.message||String(n),stack:n.stack||null}:{message:"Unknown error"}}async function zt(n){if(typeof n.convertToBlob=="function")return await n.convertToBlob({type:"image/png"});if(typeof n.toBlob=="function")return await new Promise((t,e)=>{n.toBlob(a=>{a?t(a):e(new Error("Failed to encode PNG blob"))},"image/png")});throw new Error("Canvas blob export API is unavailable")}self.addEventListener("message",async n=>{let t=n.data;if(!t||t.type!=="process-image")return;let{id:e,inputBuffer:a,mimeType:o,options:r}=t;try{let i=await jt(),c=new Blob([a],{type:o||"image/png"}),l=await createImageBitmap(c),f=await i.removeWatermarkFromImage(l,r||{});typeof l.close=="function"&&l.close();let g=await(await zt(f)).arrayBuffer();self.postMessage({id:e,ok:!0,result:{processedBuffer:g,mimeType:"image/png",meta:f.__watermarkMeta||null}},[g])}catch(i){self.postMessage({id:e,ok:!1,error:Jt(i)})}});})();\n' : "";
var enginePromise = null;
var workerClient = null;
var processingQueue = /* @__PURE__ */ new Set();
var retryTimers = /* @__PURE__ */ new WeakMap();
var debounce = (func, wait) => {
let timeout;
return (...args) => {
clearTimeout(timeout);
timeout = setTimeout(() => func(...args), wait);
};
};
var loadImage = (src) => new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => resolve(img);
img.onerror = reject;
img.src = src;
});
var canUseInlineWorker = () => shouldUseInlineWorker(USERSCRIPT_WORKER_CODE);
var toError = (errorLike, fallback = "Inline worker error") => {
if (errorLike instanceof Error) return errorLike;
if (typeof errorLike === "string" && errorLike.length > 0) return new Error(errorLike);
if (errorLike && typeof errorLike.message === "string" && errorLike.message.length > 0) {
return new Error(errorLike.message);
}
return new Error(fallback);
};
var InlineWorkerClient = class {
constructor(workerCode) {
const blob = new Blob([workerCode], { type: "text/javascript" });
this.workerUrl = URL.createObjectURL(blob);
const workerScriptUrl = toWorkerScriptUrl(this.workerUrl);
if (!workerScriptUrl) {
URL.revokeObjectURL(this.workerUrl);
this.workerUrl = null;
throw new Error("Trusted Types policy unavailable for inline worker");
}
try {
this.worker = new Worker(workerScriptUrl);
} catch (error) {
URL.revokeObjectURL(this.workerUrl);
this.workerUrl = null;
throw error;
}
this.pending = /* @__PURE__ */ new Map();
this.requestId = 0;
this.handleMessage = this.handleMessage.bind(this);
this.handleError = this.handleError.bind(this);
this.worker.addEventListener("message", this.handleMessage);
this.worker.addEventListener("error", this.handleError);
}
dispose() {
this.worker.removeEventListener("message", this.handleMessage);
this.worker.removeEventListener("error", this.handleError);
this.worker.terminate();
if (this.workerUrl) {
URL.revokeObjectURL(this.workerUrl);
this.workerUrl = null;
}
const error = new Error("Inline worker disposed");
for (const pending of this.pending.values()) {
clearTimeout(pending.timeoutId);
pending.reject(error);
}
this.pending.clear();
}
handleMessage(event) {
const payload = event?.data;
if (!payload || typeof payload.id === "undefined") return;
const pending = this.pending.get(payload.id);
if (!pending) return;
this.pending.delete(payload.id);
clearTimeout(pending.timeoutId);
if (payload.ok) {
pending.resolve(payload.result);
return;
}
pending.reject(new Error(payload.error?.message || "Inline worker request failed"));
}
handleError(event) {
const error = new Error(event?.message || "Inline worker crashed");
for (const pending of this.pending.values()) {
clearTimeout(pending.timeoutId);
pending.reject(error);
}
this.pending.clear();
}
request(type, payload, transferList = [], timeoutMs = 12e4) {
const id = ++this.requestId;
return new Promise((resolve, reject) => {
const timeoutId = setTimeout(() => {
this.pending.delete(id);
reject(new Error(`Inline worker request timed out: ${type}`));
}, timeoutMs);
this.pending.set(id, { resolve, reject, timeoutId });
try {
this.worker.postMessage({ id, type, ...payload }, transferList);
} catch (error) {
clearTimeout(timeoutId);
this.pending.delete(id);
reject(toError(error));
}
});
}
async processBlob(blob, options = {}) {
const inputBuffer = await blob.arrayBuffer();
const result = await this.request(
"process-image",
{ inputBuffer, mimeType: blob.type || "image/png", options },
[inputBuffer]
);
return new Blob([result.processedBuffer], { type: result.mimeType || "image/png" });
}
};
var isValidGeminiImage = (img) => img.closest("generated-image,.generated-image-container") !== null;
var findGeminiImages = () => [...document.querySelectorAll('img[src*="googleusercontent.com"]')].filter(isValidGeminiImage);
var fetchBlob = (url) => new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: "GET",
url,
responseType: "blob",
onload: (response) => resolve(response.response),
onerror: reject
});
});
async function getEngine() {
if (!enginePromise) {
enginePromise = WatermarkEngine.create().catch((error) => {
enginePromise = null;
throw error;
});
}
return enginePromise;
}
function disableInlineWorker(reason) {
if (!workerClient) return;
console.warn("[Gemini Watermark Remover] Disable worker path:", reason);
workerClient.dispose();
workerClient = null;
}
async function processBlobWithBestPath(blob, options = {}) {
if (workerClient) {
try {
return await workerClient.processBlob(blob, options);
} catch (error) {
console.warn("[Gemini Watermark Remover] Worker path failed, fallback to main thread:", error);
disableInlineWorker(error);
}
}
const engine = await getEngine();
const blobUrl = URL.createObjectURL(blob);
try {
const img = await loadImage(blobUrl);
const canvas = await engine.removeWatermarkFromImage(img, options);
return await canvasToBlob(canvas);
} finally {
URL.revokeObjectURL(blobUrl);
}
}
function clearRetryTimer(imgElement) {
const timerId = retryTimers.get(imgElement);
if (timerId) {
clearTimeout(timerId);
retryTimers.delete(imgElement);
}
}
function scheduleRetry(imgElement, delayMs) {
clearRetryTimer(imgElement);
const timerId = setTimeout(() => {
retryTimers.delete(imgElement);
if (!document.contains(imgElement)) return;
processImage(imgElement);
}, delayMs);
retryTimers.set(imgElement, timerId);
}
async function processImage(imgElement) {
if (imgElement?.dataset?.watermarkProcessed === "true") return;
const retryState = readRetryState(imgElement?.dataset);
if (!shouldProcessNow(retryState)) return;
if (processingQueue.has(imgElement)) return;
processingQueue.add(imgElement);
imgElement.dataset.watermarkProcessed = "processing";
const originalSrc = imgElement.src;
try {
imgElement.src = "";
const normalSizeBlob = await fetchBlob(normalizeGoogleusercontentImageUrl(originalSrc));
const processedBlob = await processBlobWithBestPath(normalSizeBlob, { adaptiveMode: "always" });
const previousObjectUrl = imgElement.dataset.watermarkObjectUrl;
if (previousObjectUrl) {
URL.revokeObjectURL(previousObjectUrl);
}
const objectUrl = URL.createObjectURL(processedBlob);
imgElement.dataset.watermarkObjectUrl = objectUrl;
imgElement.src = objectUrl;
clearRetryTimer(imgElement);
resetRetryState(imgElement.dataset);
imgElement.dataset.watermarkProcessed = "true";
console.log("[Gemini Watermark Remover] Processed image");
} catch (error) {
const retry = registerProcessFailure(imgElement.dataset);
imgElement.src = originalSrc;
if (retry.exhausted) {
clearRetryTimer(imgElement);
imgElement.dataset.watermarkProcessed = "failed";
console.warn(
`[Gemini Watermark Remover] Failed ${retry.failureCount} times, stop retrying to avoid resource leaks:`,
error
);
} else {
imgElement.dataset.watermarkProcessed = "retrying";
scheduleRetry(imgElement, retry.delayMs);
console.warn(
`[Gemini Watermark Remover] Failed to process image, retry ${retry.failureCount}/${MAX_PROCESS_RETRIES} in ${retry.delayMs}ms:`,
error
);
}
} finally {
processingQueue.delete(imgElement);
}
}
var processAllImages = () => {
const images = findGeminiImages();
if (images.length === 0) return;
console.log(`[Gemini Watermark Remover] Found ${images.length} images to process`);
images.forEach(processImage);
};
var setupMutationObserver = () => {
new MutationObserver(debounce(processAllImages, 100)).observe(document.body, { childList: true, subtree: true });
console.log("[Gemini Watermark Remover] MutationObserver active");
};
async function processImageBlob(blob) {
return processBlobWithBestPath(blob, { adaptiveMode: "always" });
}
var { fetch: origFetch } = unsafeWindow;
unsafeWindow.fetch = async (...args) => {
const input = args[0];
const url = typeof input === "string" ? input : input?.url;
if (isGeminiGeneratedAssetUrl(url)) {
console.log("[Gemini Watermark Remover] Intercepting:", url);
const normalizedUrl = normalizeGoogleusercontentImageUrl(url);
if (typeof input === "string") {
args[0] = normalizedUrl;
} else if (typeof Request !== "undefined" && input instanceof Request) {
args[0] = new Request(normalizedUrl, input);
} else {
args[0] = normalizedUrl;
}
const response = await origFetch(...args);
if (!response.ok) return response;
try {
const processedBlob = await processImageBlob(await response.blob());
return new Response(processedBlob, {
status: response.status,
statusText: response.statusText,
headers: response.headers
});
} catch (error) {
console.warn("[Gemini Watermark Remover] Processing failed:", error);
return response;
}
}
return origFetch(...args);
};
(async function init() {
try {
console.log("[Gemini Watermark Remover] Initializing...");
if (canUseInlineWorker()) {
try {
workerClient = new InlineWorkerClient(USERSCRIPT_WORKER_CODE);
console.log("[Gemini Watermark Remover] Worker acceleration enabled");
} catch (workerError) {
workerClient = null;
console.warn("[Gemini Watermark Remover] Worker initialization failed, using main thread:", workerError);
}
}
if (!workerClient) {
getEngine().catch((error) => {
console.warn("[Gemini Watermark Remover] Engine warmup failed:", error);
});
}
processAllImages();
setupMutationObserver();
window.addEventListener("beforeunload", () => {
disableInlineWorker("beforeunload");
});
console.log("[Gemini Watermark Remover] Ready");
} catch (error) {
console.error("[Gemini Watermark Remover] Initialization failed:", error);
}
})();
})();