@pilio/gemini-watermark-remover 1.0.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +22 -0
- package/README.md +371 -0
- package/README_zh.md +371 -0
- package/bin/gwr.mjs +12 -0
- package/package.json +76 -0
- package/skills/gemini-watermark-remover/SKILL.md +28 -0
- package/skills/gemini-watermark-remover/agents/openai.yaml +3 -0
- package/skills/gemini-watermark-remover/references/inputs-and-outputs.md +9 -0
- package/skills/gemini-watermark-remover/references/limitations.md +5 -0
- package/skills/gemini-watermark-remover/references/usage.md +19 -0
- package/skills/gemini-watermark-remover/scripts/run.mjs +153 -0
- package/src/cli/gwrCli.js +17 -0
- package/src/cli/gwrRemoveCommand.js +317 -0
- package/src/core/adaptiveDetector.js +488 -0
- package/src/core/alphaMap.js +30 -0
- package/src/core/blendModes.js +70 -0
- package/src/core/candidateSelector.js +1446 -0
- package/src/core/canvasBlob.js +26 -0
- package/src/core/embeddedAlphaMaps.js +49 -0
- package/src/core/geminiSizeCatalog.js +239 -0
- package/src/core/multiPassRemoval.js +93 -0
- package/src/core/previewAlphaCalibration.js +822 -0
- package/src/core/restorationMetrics.js +251 -0
- package/src/core/selectionDebug.js +47 -0
- package/src/core/watermarkConfig.js +146 -0
- package/src/core/watermarkDecisionPolicy.js +163 -0
- package/src/core/watermarkDisplay.js +60 -0
- package/src/core/watermarkEngine.js +150 -0
- package/src/core/watermarkPresence.js +12 -0
- package/src/core/watermarkProcessor.js +875 -0
- package/src/core/workerClient.js +114 -0
- package/src/sdk/browser.d.ts +13 -0
- package/src/sdk/browser.js +29 -0
- package/src/sdk/image-data.d.ts +14 -0
- package/src/sdk/image-data.js +55 -0
- package/src/sdk/index.d.ts +144 -0
- package/src/sdk/index.js +8 -0
- package/src/sdk/node.d.ts +42 -0
- package/src/sdk/node.js +77 -0
|
@@ -0,0 +1,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;
|
package/src/sdk/index.js
ADDED
|
@@ -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>;
|
package/src/sdk/node.js
ADDED
|
@@ -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
|
+
}
|