@pilio/gemini-watermark-remover 1.0.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +371 -0
  3. package/README_zh.md +371 -0
  4. package/bin/gwr.mjs +12 -0
  5. package/package.json +76 -0
  6. package/skills/gemini-watermark-remover/SKILL.md +28 -0
  7. package/skills/gemini-watermark-remover/agents/openai.yaml +3 -0
  8. package/skills/gemini-watermark-remover/references/inputs-and-outputs.md +9 -0
  9. package/skills/gemini-watermark-remover/references/limitations.md +5 -0
  10. package/skills/gemini-watermark-remover/references/usage.md +19 -0
  11. package/skills/gemini-watermark-remover/scripts/run.mjs +153 -0
  12. package/src/cli/gwrCli.js +17 -0
  13. package/src/cli/gwrRemoveCommand.js +317 -0
  14. package/src/core/adaptiveDetector.js +488 -0
  15. package/src/core/alphaMap.js +30 -0
  16. package/src/core/blendModes.js +70 -0
  17. package/src/core/candidateSelector.js +1446 -0
  18. package/src/core/canvasBlob.js +26 -0
  19. package/src/core/embeddedAlphaMaps.js +49 -0
  20. package/src/core/geminiSizeCatalog.js +239 -0
  21. package/src/core/multiPassRemoval.js +93 -0
  22. package/src/core/previewAlphaCalibration.js +822 -0
  23. package/src/core/restorationMetrics.js +251 -0
  24. package/src/core/selectionDebug.js +47 -0
  25. package/src/core/watermarkConfig.js +146 -0
  26. package/src/core/watermarkDecisionPolicy.js +163 -0
  27. package/src/core/watermarkDisplay.js +60 -0
  28. package/src/core/watermarkEngine.js +150 -0
  29. package/src/core/watermarkPresence.js +12 -0
  30. package/src/core/watermarkProcessor.js +875 -0
  31. package/src/core/workerClient.js +114 -0
  32. package/src/sdk/browser.d.ts +13 -0
  33. package/src/sdk/browser.js +29 -0
  34. package/src/sdk/image-data.d.ts +14 -0
  35. package/src/sdk/image-data.js +55 -0
  36. package/src/sdk/index.d.ts +144 -0
  37. package/src/sdk/index.js +8 -0
  38. package/src/sdk/node.d.ts +42 -0
  39. package/src/sdk/node.js +77 -0
@@ -0,0 +1,114 @@
1
+ export function canUseWatermarkWorker(env = globalThis) {
2
+ return typeof env.Worker !== 'undefined' && typeof env.Blob !== 'undefined';
3
+ }
4
+
5
+ function normalizeError(errorLike) {
6
+ if (!errorLike) return 'Unknown worker error';
7
+ if (typeof errorLike === 'string') return errorLike;
8
+ if (typeof errorLike.message === 'string' && errorLike.message.length > 0) {
9
+ return errorLike.message;
10
+ }
11
+ return 'Unknown worker error';
12
+ }
13
+
14
+ function toError(errorLike) {
15
+ if (errorLike instanceof Error) return errorLike;
16
+ return new Error(normalizeError(errorLike));
17
+ }
18
+
19
+ export class WatermarkWorkerClient {
20
+ constructor({
21
+ workerUrl = './workers/watermark-worker.js',
22
+ WorkerClass = globalThis.Worker
23
+ } = {}) {
24
+ if (typeof WorkerClass === 'undefined') {
25
+ throw new Error('Worker is not supported in this runtime');
26
+ }
27
+
28
+ this.worker = new WorkerClass(workerUrl, { type: 'module' });
29
+ this.pending = new Map();
30
+ this.requestId = 0;
31
+
32
+ this.onMessage = this.onMessage.bind(this);
33
+ this.onError = this.onError.bind(this);
34
+ this.worker.addEventListener('message', this.onMessage);
35
+ this.worker.addEventListener('error', this.onError);
36
+ }
37
+
38
+ dispose() {
39
+ this.worker.removeEventListener('message', this.onMessage);
40
+ this.worker.removeEventListener('error', this.onError);
41
+ this.worker.terminate();
42
+ this.rejectAllPending(new Error('Worker disposed'));
43
+ }
44
+
45
+ onMessage(event) {
46
+ const payload = event?.data;
47
+ if (!payload || typeof payload.id === 'undefined') return;
48
+ const pending = this.pending.get(payload.id);
49
+ if (!pending) return;
50
+
51
+ this.pending.delete(payload.id);
52
+ clearTimeout(pending.timeoutId);
53
+ if (payload.ok) {
54
+ pending.resolve(payload.result);
55
+ return;
56
+ }
57
+
58
+ pending.reject(new Error(normalizeError(payload.error)));
59
+ }
60
+
61
+ onError(event) {
62
+ const reason = event?.message || 'Worker execution failed';
63
+ this.rejectAllPending(new Error(reason));
64
+ }
65
+
66
+ rejectAllPending(error) {
67
+ for (const pending of this.pending.values()) {
68
+ clearTimeout(pending.timeoutId);
69
+ pending.reject(error);
70
+ }
71
+ this.pending.clear();
72
+ }
73
+
74
+ request(type, payload, transferList = [], timeoutMs = 120000) {
75
+ const id = ++this.requestId;
76
+ return new Promise((resolve, reject) => {
77
+ const timeoutId = setTimeout(() => {
78
+ this.pending.delete(id);
79
+ reject(new Error(`Worker request timed out: ${type}`));
80
+ }, timeoutMs);
81
+
82
+ this.pending.set(id, { resolve, reject, timeoutId });
83
+ try {
84
+ this.worker.postMessage({ id, type, ...payload }, transferList);
85
+ } catch (error) {
86
+ clearTimeout(timeoutId);
87
+ this.pending.delete(id);
88
+ reject(toError(error));
89
+ }
90
+ });
91
+ }
92
+
93
+ async ping(timeoutMs = 3000) {
94
+ await this.request('ping', {}, [], timeoutMs);
95
+ }
96
+
97
+ async processBlob(blob, options = {}) {
98
+ const inputBuffer = await blob.arrayBuffer();
99
+ const result = await this.request(
100
+ 'process-image',
101
+ {
102
+ inputBuffer,
103
+ mimeType: blob.type || 'image/png',
104
+ options
105
+ },
106
+ [inputBuffer]
107
+ );
108
+
109
+ return {
110
+ blob: new Blob([result.processedBuffer], { type: result.mimeType || 'image/png' }),
111
+ meta: result.meta || null
112
+ };
113
+ }
114
+ }
@@ -0,0 +1,13 @@
1
+ export {
2
+ WatermarkEngine,
3
+ WatermarkMeta,
4
+ WatermarkPosition,
5
+ WatermarkConfig,
6
+ RemoveOptions,
7
+ ImageRemovalResult,
8
+ createWatermarkEngine,
9
+ removeWatermarkFromImage,
10
+ detectWatermarkConfig,
11
+ calculateWatermarkPosition,
12
+ removeRepeatedWatermarkLayers
13
+ } from './index.js';
@@ -0,0 +1,29 @@
1
+ import {
2
+ WatermarkEngine,
3
+ calculateWatermarkPosition,
4
+ detectWatermarkConfig,
5
+ removeRepeatedWatermarkLayers
6
+ } from '../core/watermarkEngine.js';
7
+
8
+ export async function createWatermarkEngine() {
9
+ return WatermarkEngine.create();
10
+ }
11
+
12
+ export async function removeWatermarkFromImage(image, options = {}) {
13
+ const engine = options.engine instanceof WatermarkEngine
14
+ ? options.engine
15
+ : await createWatermarkEngine();
16
+ const canvas = await engine.removeWatermarkFromImage(image, options);
17
+
18
+ return {
19
+ canvas,
20
+ meta: canvas.__watermarkMeta || null
21
+ };
22
+ }
23
+
24
+ export {
25
+ WatermarkEngine,
26
+ calculateWatermarkPosition,
27
+ detectWatermarkConfig,
28
+ removeRepeatedWatermarkLayers
29
+ };
@@ -0,0 +1,14 @@
1
+ export {
2
+ WatermarkEngine,
3
+ WatermarkMeta,
4
+ WatermarkPosition,
5
+ WatermarkConfig,
6
+ RemoveOptions,
7
+ ImageDataRemovalResult,
8
+ createWatermarkEngine,
9
+ removeWatermarkFromImageData,
10
+ removeWatermarkFromImageDataSync,
11
+ detectWatermarkConfig,
12
+ calculateWatermarkPosition,
13
+ removeRepeatedWatermarkLayers
14
+ } from './index.js';
@@ -0,0 +1,55 @@
1
+ import { interpolateAlphaMap } from '../core/adaptiveDetector.js';
2
+ import { getEmbeddedAlphaMap } from '../core/embeddedAlphaMaps.js';
3
+ import {
4
+ WatermarkEngine,
5
+ calculateWatermarkPosition,
6
+ detectWatermarkConfig,
7
+ removeRepeatedWatermarkLayers
8
+ } from '../core/watermarkEngine.js';
9
+ import { processWatermarkImageData } from '../core/watermarkProcessor.js';
10
+
11
+ export async function createWatermarkEngine() {
12
+ return WatermarkEngine.create();
13
+ }
14
+
15
+ function buildEmbeddedGetAlphaMap(alpha48, alpha96) {
16
+ return (size) => {
17
+ if (size === 48) return alpha48;
18
+ if (size === 96) return alpha96;
19
+ return interpolateAlphaMap(alpha96, 96, size);
20
+ };
21
+ }
22
+
23
+ export function removeWatermarkFromImageDataSync(imageData, options = {}) {
24
+ const alpha48 = options.alpha48 || getEmbeddedAlphaMap(48);
25
+ const alpha96 = options.alpha96 || getEmbeddedAlphaMap(96);
26
+
27
+ return processWatermarkImageData(imageData, {
28
+ ...options,
29
+ alpha48,
30
+ alpha96,
31
+ getAlphaMap: options.getAlphaMap || buildEmbeddedGetAlphaMap(alpha48, alpha96)
32
+ });
33
+ }
34
+
35
+ export async function removeWatermarkFromImageData(imageData, options = {}) {
36
+ const engine = options.engine instanceof WatermarkEngine
37
+ ? options.engine
38
+ : await createWatermarkEngine();
39
+ const alpha48 = await engine.getAlphaMap(48);
40
+ const alpha96 = await engine.getAlphaMap(96);
41
+
42
+ return processWatermarkImageData(imageData, {
43
+ ...options,
44
+ alpha48,
45
+ alpha96,
46
+ getAlphaMap: options.getAlphaMap || buildEmbeddedGetAlphaMap(alpha48, alpha96)
47
+ });
48
+ }
49
+
50
+ export {
51
+ WatermarkEngine,
52
+ calculateWatermarkPosition,
53
+ detectWatermarkConfig,
54
+ removeRepeatedWatermarkLayers
55
+ };
@@ -0,0 +1,144 @@
1
+ export interface WatermarkPosition {
2
+ x: number;
3
+ y: number;
4
+ width: number;
5
+ height: number;
6
+ }
7
+
8
+ export interface ImageDataLike {
9
+ width: number;
10
+ height: number;
11
+ data: Uint8ClampedArray;
12
+ }
13
+
14
+ export interface BrowserImageLike {
15
+ width: number;
16
+ height: number;
17
+ }
18
+
19
+ export interface BrowserCanvasLike extends BrowserImageLike {
20
+ getContext(contextId: string, options?: unknown): unknown;
21
+ }
22
+
23
+ type GlobalHtmlImageElementLike = typeof globalThis extends {
24
+ HTMLImageElement: { prototype: infer TPrototype }
25
+ }
26
+ ? TPrototype
27
+ : BrowserImageLike;
28
+
29
+ type GlobalHtmlCanvasElementLike = typeof globalThis extends {
30
+ HTMLCanvasElement: { prototype: infer TPrototype }
31
+ }
32
+ ? TPrototype
33
+ : BrowserCanvasLike;
34
+
35
+ type GlobalOffscreenCanvasLike = typeof globalThis extends {
36
+ OffscreenCanvas: { prototype: infer TPrototype }
37
+ }
38
+ ? TPrototype
39
+ : BrowserCanvasLike;
40
+
41
+ export type BrowserImageInput = GlobalHtmlImageElementLike | GlobalHtmlCanvasElementLike;
42
+ export type BrowserCanvasOutput = GlobalOffscreenCanvasLike | GlobalHtmlCanvasElementLike;
43
+
44
+ export interface WatermarkConfig {
45
+ logoSize: number;
46
+ marginRight: number;
47
+ marginBottom: number;
48
+ }
49
+
50
+ export interface WatermarkDetectionMeta {
51
+ adaptiveConfidence: number | null;
52
+ originalSpatialScore: number | null;
53
+ originalGradientScore: number | null;
54
+ processedSpatialScore: number | null;
55
+ processedGradientScore: number | null;
56
+ suppressionGain: number | null;
57
+ }
58
+
59
+ export interface WatermarkSelectionDebug {
60
+ candidateSource: string | null;
61
+ initialConfig: WatermarkConfig | null;
62
+ initialPosition: WatermarkPosition | null;
63
+ finalConfig: WatermarkConfig | null;
64
+ finalPosition: WatermarkPosition | null;
65
+ texturePenalty: number | null;
66
+ tooDark: boolean;
67
+ tooFlat: boolean;
68
+ hardReject: boolean;
69
+ usedCatalogVariant: boolean;
70
+ usedSizeJitter: boolean;
71
+ usedLocalShift: boolean;
72
+ usedAdaptive: boolean;
73
+ usedPreviewAnchor: boolean;
74
+ }
75
+
76
+ export interface WatermarkMeta {
77
+ applied: boolean;
78
+ skipReason: string | null;
79
+ size: number | null;
80
+ position: WatermarkPosition | null;
81
+ config: WatermarkConfig | null;
82
+ detection: WatermarkDetectionMeta;
83
+ source: string;
84
+ decisionTier: string | null;
85
+ alphaGain: number;
86
+ passCount: number;
87
+ attemptedPassCount: number;
88
+ passStopReason: string | null;
89
+ selectionDebug?: WatermarkSelectionDebug | null;
90
+ }
91
+
92
+ export interface RemoveOptions {
93
+ adaptiveMode?: 'auto' | 'always' | 'never' | 'off';
94
+ maxPasses?: number;
95
+ engine?: WatermarkEngine;
96
+ alpha48?: Float32Array;
97
+ alpha96?: Float32Array;
98
+ getAlphaMap?: (size: number) => Float32Array;
99
+ }
100
+
101
+ export interface ImageDataRemovalResult {
102
+ imageData: ImageDataLike;
103
+ meta: WatermarkMeta;
104
+ }
105
+
106
+ export interface ImageRemovalResult {
107
+ canvas: BrowserCanvasOutput;
108
+ meta: WatermarkMeta | null;
109
+ }
110
+
111
+ export class WatermarkEngine {
112
+ static create(): Promise<WatermarkEngine>;
113
+ getAlphaMap(size: number): Promise<Float32Array>;
114
+ removeWatermarkFromImage(
115
+ image: BrowserImageInput,
116
+ options?: Omit<RemoveOptions, 'engine'>
117
+ ): Promise<BrowserCanvasOutput>;
118
+ getWatermarkInfo(imageWidth: number, imageHeight: number): {
119
+ size: number;
120
+ position: WatermarkPosition;
121
+ config: WatermarkConfig;
122
+ };
123
+ }
124
+
125
+ export function createWatermarkEngine(): Promise<WatermarkEngine>;
126
+ export function removeWatermarkFromImage(
127
+ image: BrowserImageInput,
128
+ options?: RemoveOptions
129
+ ): Promise<ImageRemovalResult>;
130
+ export function removeWatermarkFromImageData(
131
+ imageData: ImageDataLike,
132
+ options?: RemoveOptions
133
+ ): Promise<ImageDataRemovalResult>;
134
+ export function removeWatermarkFromImageDataSync(
135
+ imageData: ImageDataLike,
136
+ options?: Omit<RemoveOptions, 'engine'>
137
+ ): ImageDataRemovalResult;
138
+ export function detectWatermarkConfig(imageWidth: number, imageHeight: number): WatermarkConfig;
139
+ export function calculateWatermarkPosition(
140
+ imageWidth: number,
141
+ imageHeight: number,
142
+ config: WatermarkConfig
143
+ ): WatermarkPosition;
144
+ export function removeRepeatedWatermarkLayers(...args: unknown[]): unknown;
@@ -0,0 +1,8 @@
1
+ export { createWatermarkEngine, removeWatermarkFromImage } from './browser.js';
2
+ export { removeWatermarkFromImageData, removeWatermarkFromImageDataSync } from './image-data.js';
3
+ export {
4
+ WatermarkEngine,
5
+ calculateWatermarkPosition,
6
+ detectWatermarkConfig,
7
+ removeRepeatedWatermarkLayers
8
+ } from './browser.js';
@@ -0,0 +1,42 @@
1
+ import type { ImageDataRemovalResult, RemoveOptions, WatermarkMeta } from './index.js';
2
+
3
+ export interface NodeCodecContext {
4
+ mimeType: string;
5
+ filePath?: string;
6
+ meta?: WatermarkMeta;
7
+ }
8
+
9
+ export interface NodeBufferRemovalOptions extends Omit<RemoveOptions, 'engine'> {
10
+ mimeType?: string;
11
+ filePath?: string;
12
+ decodeImageData: (
13
+ input: Buffer | Uint8Array | ArrayBuffer,
14
+ context: NodeCodecContext
15
+ ) => Promise<ImageDataRemovalResult['imageData']> | ImageDataRemovalResult['imageData'];
16
+ encodeImageData: (
17
+ imageData: ImageDataRemovalResult['imageData'],
18
+ context: NodeCodecContext
19
+ ) => Promise<Buffer | Uint8Array | ArrayBuffer> | Buffer | Uint8Array | ArrayBuffer;
20
+ }
21
+
22
+ export interface NodeFileRemovalOptions extends NodeBufferRemovalOptions {
23
+ outputPath?: string | null;
24
+ }
25
+
26
+ export interface NodeBufferRemovalResult extends ImageDataRemovalResult {
27
+ buffer: Buffer;
28
+ }
29
+
30
+ export interface NodeFileRemovalResult extends NodeBufferRemovalResult {
31
+ outputPath: string | null;
32
+ }
33
+
34
+ export function inferMimeTypeFromPath(filePath: string): string;
35
+ export function removeWatermarkFromBuffer(
36
+ inputBuffer: Buffer | Uint8Array | ArrayBuffer,
37
+ options: NodeBufferRemovalOptions
38
+ ): Promise<NodeBufferRemovalResult>;
39
+ export function removeWatermarkFromFile(
40
+ inputPath: string,
41
+ options: NodeFileRemovalOptions
42
+ ): Promise<NodeFileRemovalResult>;
@@ -0,0 +1,77 @@
1
+ import path from 'node:path';
2
+ import { readFile, writeFile } from 'node:fs/promises';
3
+
4
+ import { removeWatermarkFromImageDataSync } from './image-data.js';
5
+
6
+ function normalizeBufferLike(value) {
7
+ if (Buffer.isBuffer(value)) return value;
8
+ if (value instanceof Uint8Array) return Buffer.from(value);
9
+ if (value instanceof ArrayBuffer) return Buffer.from(value);
10
+ throw new TypeError('Expected Buffer, Uint8Array, or ArrayBuffer');
11
+ }
12
+
13
+ function assertFunction(value, name) {
14
+ if (typeof value !== 'function') {
15
+ throw new TypeError(`${name} must be a function`);
16
+ }
17
+ }
18
+
19
+ export function inferMimeTypeFromPath(filePath) {
20
+ const ext = path.extname(filePath).toLowerCase();
21
+ if (ext === '.jpg' || ext === '.jpeg') return 'image/jpeg';
22
+ if (ext === '.webp') return 'image/webp';
23
+ if (ext === '.png') return 'image/png';
24
+ return 'application/octet-stream';
25
+ }
26
+
27
+ export async function removeWatermarkFromBuffer(inputBuffer, options = {}) {
28
+ const {
29
+ decodeImageData,
30
+ encodeImageData,
31
+ mimeType = 'application/octet-stream',
32
+ filePath,
33
+ ...removeOptions
34
+ } = options;
35
+
36
+ assertFunction(decodeImageData, 'decodeImageData');
37
+ assertFunction(encodeImageData, 'encodeImageData');
38
+
39
+ const normalizedInput = normalizeBufferLike(inputBuffer);
40
+ const imageData = await decodeImageData(normalizedInput, { mimeType, filePath });
41
+ const result = removeWatermarkFromImageDataSync(imageData, removeOptions);
42
+ const encoded = await encodeImageData(result.imageData, {
43
+ mimeType,
44
+ filePath,
45
+ meta: result.meta
46
+ });
47
+
48
+ return {
49
+ buffer: normalizeBufferLike(encoded),
50
+ imageData: result.imageData,
51
+ meta: result.meta
52
+ };
53
+ }
54
+
55
+ export async function removeWatermarkFromFile(inputPath, options = {}) {
56
+ const {
57
+ outputPath = null,
58
+ mimeType = inferMimeTypeFromPath(inputPath),
59
+ ...restOptions
60
+ } = options;
61
+
62
+ const inputBuffer = await readFile(inputPath);
63
+ const result = await removeWatermarkFromBuffer(inputBuffer, {
64
+ ...restOptions,
65
+ mimeType,
66
+ filePath: inputPath
67
+ });
68
+
69
+ if (outputPath) {
70
+ await writeFile(outputPath, result.buffer);
71
+ }
72
+
73
+ return {
74
+ ...result,
75
+ outputPath
76
+ };
77
+ }