@useknockout/node 0.0.8 โ 0.0.9
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 +21 -21
- package/README.md +457 -388
- package/dist/index.cjs +5 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +10 -3
- package/dist/index.d.ts +10 -3
- package/dist/index.js +5 -2
- package/dist/index.js.map +1 -1
- package/package.json +57 -57
package/README.md
CHANGED
|
@@ -1,388 +1,457 @@
|
|
|
1
|
-
<div align="center">
|
|
2
|
-
|
|
3
|
-
# ๐ฅ @useknockout/node
|
|
4
|
-
|
|
5
|
-
**Official TypeScript / Node.js client for [useknockout](https://github.com/useknockout/api) โ state-of-the-art background removal API.**
|
|
6
|
-
|
|
7
|
-
[](./LICENSE)
|
|
8
|
-
[](https://www.npmjs.com/package/@useknockout/node)
|
|
9
|
-
[](https://www.npmjs.com/package/@useknockout/node)
|
|
10
|
-
[](https://github.com/useknockout/node)
|
|
11
|
-
[](https://www.typescriptlang.org/)
|
|
12
|
-
[](https://nodejs.org)
|
|
13
|
-
[](./package.json)
|
|
14
|
-
|
|
15
|
-
[**Install**](#install) ยท [**Quick Start**](#quick-start) ยท [**API**](#api) ยท [**Framework examples**](#framework-examples) ยท [**API repo**](https://github.com/useknockout/api)
|
|
16
|
-
|
|
17
|
-
<br/>
|
|
18
|
-
|
|
19
|
-
<img src="https://raw.githubusercontent.com/useknockout/api/main/docs/hero.png" alt="useknockout before/after โ background removal demo" width="800"/>
|
|
20
|
-
|
|
21
|
-
<br/>
|
|
22
|
-
|
|
23
|
-
*One method call. Transparent PNG out. ~200ms per image.*
|
|
24
|
-
|
|
25
|
-
</div>
|
|
26
|
-
|
|
27
|
-
---
|
|
28
|
-
|
|
29
|
-
- **Zero runtime dependencies** โ uses the native `fetch` built into Node 18+
|
|
30
|
-
- **First-class TypeScript** โ full types, no `any`s in the public API
|
|
31
|
-
- **Works anywhere `fetch` works** โ Node, Bun, Deno, Vercel/Cloudflare Workers, serverless
|
|
32
|
-
- **MIT licensed**
|
|
33
|
-
|
|
34
|
-
---
|
|
35
|
-
|
|
36
|
-
## Install
|
|
37
|
-
|
|
38
|
-
```bash
|
|
39
|
-
npm install @useknockout/node
|
|
40
|
-
# or
|
|
41
|
-
pnpm add @useknockout/node
|
|
42
|
-
# or
|
|
43
|
-
yarn add @useknockout/node
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
Requires **Node 18+** (for global `fetch` and `FormData`).
|
|
47
|
-
|
|
48
|
-
## Quick start
|
|
49
|
-
|
|
50
|
-
### Public beta token
|
|
51
|
-
|
|
52
|
-
During the public beta, anyone can use this shared bearer token:
|
|
53
|
-
|
|
54
|
-
```
|
|
55
|
-
kno_public_beta_4d7e9f1a3c5b2e8d6a9f7c1b3e5d8a2f
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
No signup โ copy, paste, call the API. Need your own key / production limits? DM [@useknockout](https://x.com/useknockout) on X.
|
|
59
|
-
|
|
60
|
-
### One-minute example
|
|
61
|
-
|
|
62
|
-
```ts
|
|
63
|
-
import { writeFile } from "node:fs/promises";
|
|
64
|
-
import { Knockout } from "@useknockout/node";
|
|
65
|
-
|
|
66
|
-
const client = new Knockout({ token: "kno_public_beta_4d7e9f1a3c5b2e8d6a9f7c1b3e5d8a2f" });
|
|
67
|
-
|
|
68
|
-
// Remove background โ returns transparent PNG buffer
|
|
69
|
-
const png = await client.remove({ file: "./input.jpg" });
|
|
70
|
-
await writeFile("out.png", png);
|
|
71
|
-
|
|
72
|
-
// Replace background with a hex color
|
|
73
|
-
const jpg = await client.replaceBackground({
|
|
74
|
-
file: "./input.jpg",
|
|
75
|
-
bgColor: "#FF5733",
|
|
76
|
-
format: "jpg",
|
|
77
|
-
});
|
|
78
|
-
await writeFile("out.jpg", jpg);
|
|
79
|
-
|
|
80
|
-
// Replace background with a remote image
|
|
81
|
-
const composed = await client.replaceBackground({
|
|
82
|
-
file: "./input.jpg",
|
|
83
|
-
bgUrl: "https://example.com/beach.jpg",
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
// Batch โ remove bg from up to 10 URLs in one call
|
|
87
|
-
const batch = await client.removeBatchUrl({
|
|
88
|
-
urls: ["https://example.com/a.jpg", "https://example.com/b.jpg"],
|
|
89
|
-
});
|
|
90
|
-
for (const r of batch.results) {
|
|
91
|
-
if (r.success) {
|
|
92
|
-
await writeFile(`out.png`, Buffer.from(r.data_base64!, "base64"));
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
```
|
|
96
|
-
|
|
97
|
-
### From a Buffer or remote URL
|
|
98
|
-
|
|
99
|
-
```ts
|
|
100
|
-
// From a Buffer (e.g. uploaded via multer)
|
|
101
|
-
const buf = await fs.readFile("./input.jpg");
|
|
102
|
-
const out = await client.remove({ file: buf, filename: "input.jpg" });
|
|
103
|
-
|
|
104
|
-
// From a remote URL
|
|
105
|
-
const png = await client.removeUrl({ url: "https://example.com/cat.jpg" });
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
### New in 0.0.5 โ presets
|
|
109
|
-
|
|
110
|
-
```ts
|
|
111
|
-
// Sticker โ thick outline, transparent bg (iMessage / WhatsApp style)
|
|
112
|
-
const sticker = await client.sticker({ file: "./photo.jpg", strokeWidth: 24 });
|
|
113
|
-
|
|
114
|
-
// Outline โ thin stroke around subject
|
|
115
|
-
const outlined = await client.outline({ file: "./photo.jpg", outlineColor: "#000000", outlineWidth: 4 });
|
|
116
|
-
|
|
117
|
-
// Smart crop โ auto-crop to subject bbox + padding
|
|
118
|
-
const cropped = await client.smartCrop({ file: "./photo.jpg", padding: 32 });
|
|
119
|
-
|
|
120
|
-
// Shadow โ drop shadow on a new background
|
|
121
|
-
const withShadow = await client.shadow({ file: "./photo.jpg", bgColor: "#F3F4F6" });
|
|
122
|
-
|
|
123
|
-
// Studio shot โ e-commerce preset (centered subject, white bg, shadow, square)
|
|
124
|
-
const studio = await client.studioShot({ file: "./photo.jpg", aspect: "1:1" });
|
|
125
|
-
|
|
126
|
-
// Compare โ before/after side-by-side image
|
|
127
|
-
const preview = await client.compare({ file: "./photo.jpg" });
|
|
128
|
-
|
|
129
|
-
// Mask โ just the black/white mask for your own pipeline
|
|
130
|
-
const mask = await client.mask({ file: "./photo.jpg" });
|
|
131
|
-
```
|
|
132
|
-
|
|
133
|
-
## API
|
|
134
|
-
|
|
135
|
-
### `new Knockout(options?)`
|
|
136
|
-
|
|
137
|
-
| Option | Type | Default | Description |
|
|
138
|
-
|---|---|---|---|
|
|
139
|
-
| `token` | `string` | โ | Bearer token. Required unless self-hosting without auth. |
|
|
140
|
-
| `baseUrl` | `string` | `https://useknockout--api.modal.run` | Override the API endpoint. |
|
|
141
|
-
| `timeoutMs` | `number` | `60_000` | Per-request timeout. |
|
|
142
|
-
| `fetch` | `typeof fetch` | `globalThis.fetch` | Custom fetch (for edge runtimes or polyfills). |
|
|
143
|
-
|
|
144
|
-
### `client.remove(input)`
|
|
145
|
-
|
|
146
|
-
Remove the background from a local file or in-memory buffer.
|
|
147
|
-
|
|
148
|
-
| Field | Type | Description |
|
|
149
|
-
|---|---|---|
|
|
150
|
-
| `file` | `string \| Buffer \| Blob \| ArrayBuffer \| Uint8Array` | File path or raw image bytes. |
|
|
151
|
-
| `filename` | `string?` | Optional override โ auto-inferred from path if omitted. |
|
|
152
|
-
| `format` | `"png" \| "webp"` | Output format. Default `"png"`. |
|
|
153
|
-
|
|
154
|
-
Returns: `Buffer` of the processed image (PNG or WebP with transparent alpha).
|
|
155
|
-
|
|
156
|
-
### `client.removeUrl(input)`
|
|
157
|
-
|
|
158
|
-
Remove the background from a remote image URL.
|
|
159
|
-
|
|
160
|
-
| Field | Type | Description |
|
|
161
|
-
|---|---|---|
|
|
162
|
-
| `url` | `string` | Remote image URL. |
|
|
163
|
-
| `format` | `"png" \| "webp"` | Output format. Default `"png"`. |
|
|
164
|
-
|
|
165
|
-
Returns: `Buffer` of the processed image.
|
|
166
|
-
|
|
167
|
-
### `client.replaceBackground(input)`
|
|
168
|
-
|
|
169
|
-
Remove the background and composite the subject onto a new background โ solid color or remote image.
|
|
170
|
-
|
|
171
|
-
| Field | Type | Description |
|
|
172
|
-
|---|---|---|
|
|
173
|
-
| `file` | `string \| Buffer \| Blob \| ArrayBuffer \| Uint8Array` | Foreground image (path or bytes). |
|
|
174
|
-
| `filename` | `string?` | Optional filename. |
|
|
175
|
-
| `bgColor` | `string?` | Hex color for the new background. Default `"#FFFFFF"`. Ignored if `bgUrl` is set. |
|
|
176
|
-
| `bgUrl` | `string?` | Remote URL of a background image. Takes precedence over `bgColor`. |
|
|
177
|
-
| `format` | `"png" \| "webp" \| "jpg"` | Output format. Default `"png"`. `"jpg"` for smallest file. |
|
|
178
|
-
|
|
179
|
-
Returns: `Buffer` of the composited image. Edges refined via closed-form foreground matting (no halos).
|
|
180
|
-
|
|
181
|
-
### `client.removeBatch(input)`
|
|
182
|
-
|
|
183
|
-
Remove backgrounds from up to 10 images in a single call.
|
|
184
|
-
|
|
185
|
-
| Field | Type | Description |
|
|
186
|
-
|---|---|---|
|
|
187
|
-
| `files` | `Array<string \| Buffer \| Blob \| ArrayBuffer \| Uint8Array>` | 1โ10 file paths or buffers. |
|
|
188
|
-
| `filenames` | `string[]?` | Optional filename aligned to each file. |
|
|
189
|
-
| `format` | `"png" \| "webp"` | Output format. Default `"png"`. |
|
|
190
|
-
|
|
191
|
-
Returns: `BatchResponse` โ `{ count, format, results: [{ filename, success, format, size_bytes, data_base64 | error }] }`. Decode `data_base64` with `Buffer.from(b64, "base64")`.
|
|
192
|
-
|
|
193
|
-
### `client.removeBatchUrl(input)`
|
|
194
|
-
|
|
195
|
-
Same as `removeBatch` but takes a JSON array of remote URLs.
|
|
196
|
-
|
|
197
|
-
| Field | Type | Description |
|
|
198
|
-
|---|---|---|
|
|
199
|
-
| `urls` | `string[]` | 1โ10 remote image URLs. |
|
|
200
|
-
| `format` | `"png" \| "webp"` | Output format. Default `"png"`. |
|
|
201
|
-
|
|
202
|
-
Returns: `BatchResponse` with `url` in place of `filename` in each result.
|
|
203
|
-
|
|
204
|
-
### `client.mask(input)`
|
|
205
|
-
|
|
206
|
-
Return just the black/white alpha mask as a grayscale PNG/WebP.
|
|
207
|
-
|
|
208
|
-
| Field | Type | Description |
|
|
209
|
-
|---|---|---|
|
|
210
|
-
| `file` | `FileInput` | Image to process. |
|
|
211
|
-
| `format` | `"png" \| "webp"` | Default `"png"`. |
|
|
212
|
-
|
|
213
|
-
Returns: `Buffer` of a grayscale image (0 = bg, 255 = subject).
|
|
214
|
-
|
|
215
|
-
### `client.smartCrop(input)`
|
|
216
|
-
|
|
217
|
-
Auto-crop to the subject's tight bounding box + padding.
|
|
218
|
-
|
|
219
|
-
| Field | Type | Description |
|
|
220
|
-
|---|---|---|
|
|
221
|
-
| `file` | `FileInput` | Image to process. |
|
|
222
|
-
| `padding` | `number` | Padding in pixels. Default `24`. |
|
|
223
|
-
| `transparent` | `boolean` | `true` โ transparent cutout. `false` โ cropped from original. Default `true`. |
|
|
224
|
-
| `format` | `"png" \| "webp" \| "jpg"` | Default `"png"` (or `"jpg"` when `transparent=false`). |
|
|
225
|
-
|
|
226
|
-
### `client.shadow(input)`
|
|
227
|
-
|
|
228
|
-
Composite subject onto a new background with a configurable drop shadow.
|
|
229
|
-
|
|
230
|
-
| Field | Type | Description |
|
|
231
|
-
|---|---|---|
|
|
232
|
-
| `file` | `FileInput` | Image to process. |
|
|
233
|
-
| `bgColor` | `string` | Hex color. Default `"#FFFFFF"`. |
|
|
234
|
-
| `bgUrl` | `string` | Remote URL. Takes precedence over `bgColor`. |
|
|
235
|
-
| `shadowColor` | `string` | Default `"#000000"`. |
|
|
236
|
-
| `shadowOffsetX` | `number` | Default `8`. |
|
|
237
|
-
| `shadowOffsetY` | `number` | Default `12`. |
|
|
238
|
-
| `shadowBlur` | `number` | Default `14`. |
|
|
239
|
-
| `shadowOpacity` | `number` | 0.0โ1.0. Default `0.45`. |
|
|
240
|
-
| `format` | `"png" \| "webp" \| "jpg"` | Default `"png"`. |
|
|
241
|
-
|
|
242
|
-
### `client.sticker(input)`
|
|
243
|
-
|
|
244
|
-
Subject with a thick outline on transparent background โ sticker style.
|
|
245
|
-
|
|
246
|
-
| Field | Type | Description |
|
|
247
|
-
|---|---|---|
|
|
248
|
-
| `file` | `FileInput` | Image to process. |
|
|
249
|
-
| `strokeColor` | `string` | Default `"#FFFFFF"`. |
|
|
250
|
-
| `strokeWidth` | `number` | Pixels. Default `20`, capped at `80`. |
|
|
251
|
-
| `format` | `"png" \| "webp"` | Default `"png"`. |
|
|
252
|
-
|
|
253
|
-
### `client.outline(input)`
|
|
254
|
-
|
|
255
|
-
Subject with a thin outline on transparent background.
|
|
256
|
-
|
|
257
|
-
| Field | Type | Description |
|
|
258
|
-
|---|---|---|
|
|
259
|
-
| `file` | `FileInput` | Image to process. |
|
|
260
|
-
| `outlineColor` | `string` | Default `"#000000"`. |
|
|
261
|
-
| `outlineWidth` | `number` | Pixels. Default `4`, capped at `60`. |
|
|
262
|
-
| `format` | `"png" \| "webp"` | Default `"png"`. |
|
|
263
|
-
|
|
264
|
-
### `client.studioShot(input)`
|
|
265
|
-
|
|
266
|
-
E-commerce preset: cutout + centered on canvas + optional drop shadow + standardized aspect ratio.
|
|
267
|
-
|
|
268
|
-
| Field | Type | Description |
|
|
269
|
-
|---|---|---|
|
|
270
|
-
| `file` | `FileInput` | Image to process. |
|
|
271
|
-
| `bgColor` | `string` | Canvas color. Default `"#FFFFFF"`. |
|
|
272
|
-
| `aspect` | `string` | `"W:H"` format, e.g. `"1:1"`, `"4:5"`, `"16:9"`. Default `"1:1"`. |
|
|
273
|
-
| `padding` | `number` | Padding in pixels. Default `48`. |
|
|
274
|
-
| `shadow` | `boolean` | Include drop shadow. Default `true`. |
|
|
275
|
-
| `format` | `"png" \| "webp" \| "jpg"` | Default `"jpg"`. |
|
|
276
|
-
|
|
277
|
-
### `client.compare(input)`
|
|
278
|
-
|
|
279
|
-
Before/after side-by-side preview โ original on the left, transparent cutout (on a checkerboard) on the right.
|
|
280
|
-
|
|
281
|
-
| Field | Type | Description |
|
|
282
|
-
|---|---|---|
|
|
283
|
-
| `file` | `FileInput` | Image to process. |
|
|
284
|
-
| `format` | `"png" \| "webp"` | Default `"png"`. |
|
|
285
|
-
|
|
286
|
-
### `client.
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
```
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
# ๐ฅ @useknockout/node
|
|
4
|
+
|
|
5
|
+
**Official TypeScript / Node.js client for [useknockout](https://github.com/useknockout/api) โ state-of-the-art background removal API.**
|
|
6
|
+
|
|
7
|
+
[](./LICENSE)
|
|
8
|
+
[](https://www.npmjs.com/package/@useknockout/node)
|
|
9
|
+
[](https://www.npmjs.com/package/@useknockout/node)
|
|
10
|
+
[](https://github.com/useknockout/node)
|
|
11
|
+
[](https://www.typescriptlang.org/)
|
|
12
|
+
[](https://nodejs.org)
|
|
13
|
+
[](./package.json)
|
|
14
|
+
|
|
15
|
+
[**Install**](#install) ยท [**Quick Start**](#quick-start) ยท [**API**](#api) ยท [**Framework examples**](#framework-examples) ยท [**API repo**](https://github.com/useknockout/api)
|
|
16
|
+
|
|
17
|
+
<br/>
|
|
18
|
+
|
|
19
|
+
<img src="https://raw.githubusercontent.com/useknockout/api/main/docs/hero.png" alt="useknockout before/after โ background removal demo" width="800"/>
|
|
20
|
+
|
|
21
|
+
<br/>
|
|
22
|
+
|
|
23
|
+
*One method call. Transparent PNG out. ~200ms per image.*
|
|
24
|
+
|
|
25
|
+
</div>
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
- **Zero runtime dependencies** โ uses the native `fetch` built into Node 18+
|
|
30
|
+
- **First-class TypeScript** โ full types, no `any`s in the public API
|
|
31
|
+
- **Works anywhere `fetch` works** โ Node, Bun, Deno, Vercel/Cloudflare Workers, serverless
|
|
32
|
+
- **MIT licensed**
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## Install
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
npm install @useknockout/node
|
|
40
|
+
# or
|
|
41
|
+
pnpm add @useknockout/node
|
|
42
|
+
# or
|
|
43
|
+
yarn add @useknockout/node
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Requires **Node 18+** (for global `fetch` and `FormData`).
|
|
47
|
+
|
|
48
|
+
## Quick start
|
|
49
|
+
|
|
50
|
+
### Public beta token
|
|
51
|
+
|
|
52
|
+
During the public beta, anyone can use this shared bearer token:
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
kno_public_beta_4d7e9f1a3c5b2e8d6a9f7c1b3e5d8a2f
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
No signup โ copy, paste, call the API. Need your own key / production limits? DM [@useknockout](https://x.com/useknockout) on X.
|
|
59
|
+
|
|
60
|
+
### One-minute example
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
import { writeFile } from "node:fs/promises";
|
|
64
|
+
import { Knockout } from "@useknockout/node";
|
|
65
|
+
|
|
66
|
+
const client = new Knockout({ token: "kno_public_beta_4d7e9f1a3c5b2e8d6a9f7c1b3e5d8a2f" });
|
|
67
|
+
|
|
68
|
+
// Remove background โ returns transparent PNG buffer
|
|
69
|
+
const png = await client.remove({ file: "./input.jpg" });
|
|
70
|
+
await writeFile("out.png", png);
|
|
71
|
+
|
|
72
|
+
// Replace background with a hex color
|
|
73
|
+
const jpg = await client.replaceBackground({
|
|
74
|
+
file: "./input.jpg",
|
|
75
|
+
bgColor: "#FF5733",
|
|
76
|
+
format: "jpg",
|
|
77
|
+
});
|
|
78
|
+
await writeFile("out.jpg", jpg);
|
|
79
|
+
|
|
80
|
+
// Replace background with a remote image
|
|
81
|
+
const composed = await client.replaceBackground({
|
|
82
|
+
file: "./input.jpg",
|
|
83
|
+
bgUrl: "https://example.com/beach.jpg",
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
// Batch โ remove bg from up to 10 URLs in one call
|
|
87
|
+
const batch = await client.removeBatchUrl({
|
|
88
|
+
urls: ["https://example.com/a.jpg", "https://example.com/b.jpg"],
|
|
89
|
+
});
|
|
90
|
+
for (const r of batch.results) {
|
|
91
|
+
if (r.success) {
|
|
92
|
+
await writeFile(`out.png`, Buffer.from(r.data_base64!, "base64"));
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### From a Buffer or remote URL
|
|
98
|
+
|
|
99
|
+
```ts
|
|
100
|
+
// From a Buffer (e.g. uploaded via multer)
|
|
101
|
+
const buf = await fs.readFile("./input.jpg");
|
|
102
|
+
const out = await client.remove({ file: buf, filename: "input.jpg" });
|
|
103
|
+
|
|
104
|
+
// From a remote URL
|
|
105
|
+
const png = await client.removeUrl({ url: "https://example.com/cat.jpg" });
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### New in 0.0.5 โ presets
|
|
109
|
+
|
|
110
|
+
```ts
|
|
111
|
+
// Sticker โ thick outline, transparent bg (iMessage / WhatsApp style)
|
|
112
|
+
const sticker = await client.sticker({ file: "./photo.jpg", strokeWidth: 24 });
|
|
113
|
+
|
|
114
|
+
// Outline โ thin stroke around subject
|
|
115
|
+
const outlined = await client.outline({ file: "./photo.jpg", outlineColor: "#000000", outlineWidth: 4 });
|
|
116
|
+
|
|
117
|
+
// Smart crop โ auto-crop to subject bbox + padding
|
|
118
|
+
const cropped = await client.smartCrop({ file: "./photo.jpg", padding: 32 });
|
|
119
|
+
|
|
120
|
+
// Shadow โ drop shadow on a new background
|
|
121
|
+
const withShadow = await client.shadow({ file: "./photo.jpg", bgColor: "#F3F4F6" });
|
|
122
|
+
|
|
123
|
+
// Studio shot โ e-commerce preset (centered subject, white bg, shadow, square)
|
|
124
|
+
const studio = await client.studioShot({ file: "./photo.jpg", aspect: "1:1" });
|
|
125
|
+
|
|
126
|
+
// Compare โ before/after side-by-side image
|
|
127
|
+
const preview = await client.compare({ file: "./photo.jpg" });
|
|
128
|
+
|
|
129
|
+
// Mask โ just the black/white mask for your own pipeline
|
|
130
|
+
const mask = await client.mask({ file: "./photo.jpg" });
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## API
|
|
134
|
+
|
|
135
|
+
### `new Knockout(options?)`
|
|
136
|
+
|
|
137
|
+
| Option | Type | Default | Description |
|
|
138
|
+
|---|---|---|---|
|
|
139
|
+
| `token` | `string` | โ | Bearer token. Required unless self-hosting without auth. |
|
|
140
|
+
| `baseUrl` | `string` | `https://useknockout--api.modal.run` | Override the API endpoint. |
|
|
141
|
+
| `timeoutMs` | `number` | `60_000` | Per-request timeout. |
|
|
142
|
+
| `fetch` | `typeof fetch` | `globalThis.fetch` | Custom fetch (for edge runtimes or polyfills). |
|
|
143
|
+
|
|
144
|
+
### `client.remove(input)`
|
|
145
|
+
|
|
146
|
+
Remove the background from a local file or in-memory buffer.
|
|
147
|
+
|
|
148
|
+
| Field | Type | Description |
|
|
149
|
+
|---|---|---|
|
|
150
|
+
| `file` | `string \| Buffer \| Blob \| ArrayBuffer \| Uint8Array` | File path or raw image bytes. |
|
|
151
|
+
| `filename` | `string?` | Optional override โ auto-inferred from path if omitted. |
|
|
152
|
+
| `format` | `"png" \| "webp"` | Output format. Default `"png"`. |
|
|
153
|
+
|
|
154
|
+
Returns: `Buffer` of the processed image (PNG or WebP with transparent alpha).
|
|
155
|
+
|
|
156
|
+
### `client.removeUrl(input)`
|
|
157
|
+
|
|
158
|
+
Remove the background from a remote image URL.
|
|
159
|
+
|
|
160
|
+
| Field | Type | Description |
|
|
161
|
+
|---|---|---|
|
|
162
|
+
| `url` | `string` | Remote image URL. |
|
|
163
|
+
| `format` | `"png" \| "webp"` | Output format. Default `"png"`. |
|
|
164
|
+
|
|
165
|
+
Returns: `Buffer` of the processed image.
|
|
166
|
+
|
|
167
|
+
### `client.replaceBackground(input)`
|
|
168
|
+
|
|
169
|
+
Remove the background and composite the subject onto a new background โ solid color or remote image.
|
|
170
|
+
|
|
171
|
+
| Field | Type | Description |
|
|
172
|
+
|---|---|---|
|
|
173
|
+
| `file` | `string \| Buffer \| Blob \| ArrayBuffer \| Uint8Array` | Foreground image (path or bytes). |
|
|
174
|
+
| `filename` | `string?` | Optional filename. |
|
|
175
|
+
| `bgColor` | `string?` | Hex color for the new background. Default `"#FFFFFF"`. Ignored if `bgUrl` is set. |
|
|
176
|
+
| `bgUrl` | `string?` | Remote URL of a background image. Takes precedence over `bgColor`. |
|
|
177
|
+
| `format` | `"png" \| "webp" \| "jpg"` | Output format. Default `"png"`. `"jpg"` for smallest file. |
|
|
178
|
+
|
|
179
|
+
Returns: `Buffer` of the composited image. Edges refined via closed-form foreground matting (no halos).
|
|
180
|
+
|
|
181
|
+
### `client.removeBatch(input)`
|
|
182
|
+
|
|
183
|
+
Remove backgrounds from up to 10 images in a single call.
|
|
184
|
+
|
|
185
|
+
| Field | Type | Description |
|
|
186
|
+
|---|---|---|
|
|
187
|
+
| `files` | `Array<string \| Buffer \| Blob \| ArrayBuffer \| Uint8Array>` | 1โ10 file paths or buffers. |
|
|
188
|
+
| `filenames` | `string[]?` | Optional filename aligned to each file. |
|
|
189
|
+
| `format` | `"png" \| "webp"` | Output format. Default `"png"`. |
|
|
190
|
+
|
|
191
|
+
Returns: `BatchResponse` โ `{ count, format, results: [{ filename, success, format, size_bytes, data_base64 | error }] }`. Decode `data_base64` with `Buffer.from(b64, "base64")`.
|
|
192
|
+
|
|
193
|
+
### `client.removeBatchUrl(input)`
|
|
194
|
+
|
|
195
|
+
Same as `removeBatch` but takes a JSON array of remote URLs.
|
|
196
|
+
|
|
197
|
+
| Field | Type | Description |
|
|
198
|
+
|---|---|---|
|
|
199
|
+
| `urls` | `string[]` | 1โ10 remote image URLs. |
|
|
200
|
+
| `format` | `"png" \| "webp"` | Output format. Default `"png"`. |
|
|
201
|
+
|
|
202
|
+
Returns: `BatchResponse` with `url` in place of `filename` in each result.
|
|
203
|
+
|
|
204
|
+
### `client.mask(input)`
|
|
205
|
+
|
|
206
|
+
Return just the black/white alpha mask as a grayscale PNG/WebP.
|
|
207
|
+
|
|
208
|
+
| Field | Type | Description |
|
|
209
|
+
|---|---|---|
|
|
210
|
+
| `file` | `FileInput` | Image to process. |
|
|
211
|
+
| `format` | `"png" \| "webp"` | Default `"png"`. |
|
|
212
|
+
|
|
213
|
+
Returns: `Buffer` of a grayscale image (0 = bg, 255 = subject).
|
|
214
|
+
|
|
215
|
+
### `client.smartCrop(input)`
|
|
216
|
+
|
|
217
|
+
Auto-crop to the subject's tight bounding box + padding.
|
|
218
|
+
|
|
219
|
+
| Field | Type | Description |
|
|
220
|
+
|---|---|---|
|
|
221
|
+
| `file` | `FileInput` | Image to process. |
|
|
222
|
+
| `padding` | `number` | Padding in pixels. Default `24`. |
|
|
223
|
+
| `transparent` | `boolean` | `true` โ transparent cutout. `false` โ cropped from original. Default `true`. |
|
|
224
|
+
| `format` | `"png" \| "webp" \| "jpg"` | Default `"png"` (or `"jpg"` when `transparent=false`). |
|
|
225
|
+
|
|
226
|
+
### `client.shadow(input)`
|
|
227
|
+
|
|
228
|
+
Composite subject onto a new background with a configurable drop shadow.
|
|
229
|
+
|
|
230
|
+
| Field | Type | Description |
|
|
231
|
+
|---|---|---|
|
|
232
|
+
| `file` | `FileInput` | Image to process. |
|
|
233
|
+
| `bgColor` | `string` | Hex color. Default `"#FFFFFF"`. |
|
|
234
|
+
| `bgUrl` | `string` | Remote URL. Takes precedence over `bgColor`. |
|
|
235
|
+
| `shadowColor` | `string` | Default `"#000000"`. |
|
|
236
|
+
| `shadowOffsetX` | `number` | Default `8`. |
|
|
237
|
+
| `shadowOffsetY` | `number` | Default `12`. |
|
|
238
|
+
| `shadowBlur` | `number` | Default `14`. |
|
|
239
|
+
| `shadowOpacity` | `number` | 0.0โ1.0. Default `0.45`. |
|
|
240
|
+
| `format` | `"png" \| "webp" \| "jpg"` | Default `"png"`. |
|
|
241
|
+
|
|
242
|
+
### `client.sticker(input)`
|
|
243
|
+
|
|
244
|
+
Subject with a thick outline on transparent background โ sticker style.
|
|
245
|
+
|
|
246
|
+
| Field | Type | Description |
|
|
247
|
+
|---|---|---|
|
|
248
|
+
| `file` | `FileInput` | Image to process. |
|
|
249
|
+
| `strokeColor` | `string` | Default `"#FFFFFF"`. |
|
|
250
|
+
| `strokeWidth` | `number` | Pixels. Default `20`, capped at `80`. |
|
|
251
|
+
| `format` | `"png" \| "webp"` | Default `"png"`. |
|
|
252
|
+
|
|
253
|
+
### `client.outline(input)`
|
|
254
|
+
|
|
255
|
+
Subject with a thin outline on transparent background.
|
|
256
|
+
|
|
257
|
+
| Field | Type | Description |
|
|
258
|
+
|---|---|---|
|
|
259
|
+
| `file` | `FileInput` | Image to process. |
|
|
260
|
+
| `outlineColor` | `string` | Default `"#000000"`. |
|
|
261
|
+
| `outlineWidth` | `number` | Pixels. Default `4`, capped at `60`. |
|
|
262
|
+
| `format` | `"png" \| "webp"` | Default `"png"`. |
|
|
263
|
+
|
|
264
|
+
### `client.studioShot(input)`
|
|
265
|
+
|
|
266
|
+
E-commerce preset: cutout + centered on canvas + optional drop shadow + standardized aspect ratio.
|
|
267
|
+
|
|
268
|
+
| Field | Type | Description |
|
|
269
|
+
|---|---|---|
|
|
270
|
+
| `file` | `FileInput` | Image to process. |
|
|
271
|
+
| `bgColor` | `string` | Canvas color. Default `"#FFFFFF"`. |
|
|
272
|
+
| `aspect` | `string` | `"W:H"` format, e.g. `"1:1"`, `"4:5"`, `"16:9"`. Default `"1:1"`. |
|
|
273
|
+
| `padding` | `number` | Padding in pixels. Default `48`. |
|
|
274
|
+
| `shadow` | `boolean` | Include drop shadow. Default `true`. |
|
|
275
|
+
| `format` | `"png" \| "webp" \| "jpg"` | Default `"jpg"`. |
|
|
276
|
+
|
|
277
|
+
### `client.compare(input)`
|
|
278
|
+
|
|
279
|
+
Before/after side-by-side preview โ original on the left, transparent cutout (on a checkerboard) on the right.
|
|
280
|
+
|
|
281
|
+
| Field | Type | Description |
|
|
282
|
+
|---|---|---|
|
|
283
|
+
| `file` | `FileInput` | Image to process. |
|
|
284
|
+
| `format` | `"png" \| "webp"` | Default `"png"`. |
|
|
285
|
+
|
|
286
|
+
### `client.headshot(input)` โ v0.4.0
|
|
287
|
+
|
|
288
|
+
Studio-quality professional headshot โ background removed, neutral studio backdrop, optional soft drop shadow, smart crop to bust framing.
|
|
289
|
+
|
|
290
|
+
| Field | Type | Description |
|
|
291
|
+
|---|---|---|
|
|
292
|
+
| `file` | `FileInput` | Source portrait. |
|
|
293
|
+
| `bgColor` | `string` | Studio backdrop hex. Default `"#f5f5f5"`. |
|
|
294
|
+
| `addShadow` | `boolean` | Soft drop shadow. Default `true`. |
|
|
295
|
+
| `crop` | `"bust" \| "head" \| "full"` | Default `"bust"`. |
|
|
296
|
+
| `format` | `"png" \| "webp" \| "jpg"` | Default `"png"`. |
|
|
297
|
+
|
|
298
|
+
### `client.preview(input)` โ v0.4.0
|
|
299
|
+
|
|
300
|
+
Cheap, fast low-res preview โ 512px max, ~1.5s. Use for thumbnail UI before user pays for full-res.
|
|
301
|
+
|
|
302
|
+
| Field | Type | Description |
|
|
303
|
+
|---|---|---|
|
|
304
|
+
| `file` | `FileInput` | Source image. |
|
|
305
|
+
| `maxSize` | `number` | Max edge length. Default `512`. |
|
|
306
|
+
| `watermark` | `boolean` | Add `useknockout` watermark. Default `false`. |
|
|
307
|
+
|
|
308
|
+
### `client.estimate(input)` โ v0.4.0
|
|
309
|
+
|
|
310
|
+
Returns expected processing time + output size **without running the model**. Show users "this'll take ~3s, ~1.2 MB" before they hit submit.
|
|
311
|
+
|
|
312
|
+
```ts
|
|
313
|
+
const est = await client.estimate({ width: 2048, height: 1536, endpoint: "remove" });
|
|
314
|
+
// { estimated_seconds: 2.4, estimated_output_kb: 1180, warm: true }
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
### `client.stats()` โ v0.4.0
|
|
318
|
+
|
|
319
|
+
Public stats โ total images processed, last-24h count, last-7d trend. Powered by Modal Dict cross-container counter. No auth required.
|
|
320
|
+
|
|
321
|
+
```ts
|
|
322
|
+
const stats = await client.stats();
|
|
323
|
+
// { total: 12340, last_24h: 312, last_7d: [...] }
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
### `client.upscale(input)` โ v0.6.0
|
|
327
|
+
|
|
328
|
+
**Swin2SR / Real-ESRGAN x2/x4 super-resolution.** Defaults to **Swin2SR** (SwinV2 Transformer) โ sharper detail and natural texture on real photos. Pass `model: "realesrgan"` for the legacy backend (better on anime / illustrations).
|
|
329
|
+
|
|
330
|
+
```ts
|
|
331
|
+
const big = await client.upscale({ file: "./small.jpg", scale: 4 });
|
|
332
|
+
const anime = await client.upscale({ file: "./art.png", scale: 4, model: "realesrgan" });
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
| Field | Type | Description |
|
|
336
|
+
|---|---|---|
|
|
337
|
+
| `file` | `FileInput` | Source image. |
|
|
338
|
+
| `scale` | `2 \| 4` | Default `4`. |
|
|
339
|
+
| `model` | `"swin2sr" \| "realesrgan"` | Default `"swin2sr"`. |
|
|
340
|
+
| `format` | `"png" \| "webp" \| "jpg"` | Default `"png"`. |
|
|
341
|
+
|
|
342
|
+
### `client.faceRestore(input)` โ v0.5.0
|
|
343
|
+
|
|
344
|
+
**GFPGAN v1.4 face restoration.** Detects faces, restores blurred/compressed/damaged ones while preserving identity. Background also upscaled. Multi-face safe.
|
|
345
|
+
|
|
346
|
+
```ts
|
|
347
|
+
const restored = await client.faceRestore({ file: "./blurry-portrait.jpg" });
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
| Field | Type | Description |
|
|
351
|
+
|---|---|---|
|
|
352
|
+
| `file` | `FileInput` | Image with one or more faces. |
|
|
353
|
+
| `format` | `"png" \| "webp" \| "jpg"` | Default `"png"`. |
|
|
354
|
+
|
|
355
|
+
### `client.health()`
|
|
356
|
+
|
|
357
|
+
Returns: `Promise<{ status: string; model: string }>`. No auth required.
|
|
358
|
+
|
|
359
|
+
### `KnockoutError`
|
|
360
|
+
|
|
361
|
+
Thrown on any non-2xx response. Fields:
|
|
362
|
+
|
|
363
|
+
- `status` โ HTTP status code
|
|
364
|
+
- `code` โ `"auth" | "rate_limit" | "bad_request" | "payload_too_large" | "server" | "unknown"`
|
|
365
|
+
- `body` โ raw response body string
|
|
366
|
+
|
|
367
|
+
```ts
|
|
368
|
+
import { KnockoutError } from "@useknockout/node";
|
|
369
|
+
|
|
370
|
+
try {
|
|
371
|
+
await client.remove({ file: "./huge.jpg" });
|
|
372
|
+
} catch (err) {
|
|
373
|
+
if (err instanceof KnockoutError && err.code === "payload_too_large") {
|
|
374
|
+
// retry with a resized image
|
|
375
|
+
}
|
|
376
|
+
throw err;
|
|
377
|
+
}
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
## Framework examples
|
|
381
|
+
|
|
382
|
+
### Next.js App Router
|
|
383
|
+
|
|
384
|
+
```ts
|
|
385
|
+
// app/api/remove/route.ts
|
|
386
|
+
import { Knockout } from "@useknockout/node";
|
|
387
|
+
|
|
388
|
+
const client = new Knockout({ token: process.env.KNOCKOUT_TOKEN! });
|
|
389
|
+
|
|
390
|
+
export async function POST(req: Request) {
|
|
391
|
+
const form = await req.formData();
|
|
392
|
+
const file = form.get("file") as File;
|
|
393
|
+
const buf = Buffer.from(await file.arrayBuffer());
|
|
394
|
+
|
|
395
|
+
const png = await client.remove({ file: buf, filename: file.name });
|
|
396
|
+
|
|
397
|
+
return new Response(new Uint8Array(png), {
|
|
398
|
+
headers: { "Content-Type": "image/png" },
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
### Express
|
|
404
|
+
|
|
405
|
+
```ts
|
|
406
|
+
import express from "express";
|
|
407
|
+
import multer from "multer";
|
|
408
|
+
import { Knockout } from "@useknockout/node";
|
|
409
|
+
|
|
410
|
+
const app = express();
|
|
411
|
+
const upload = multer();
|
|
412
|
+
const client = new Knockout({ token: process.env.KNOCKOUT_TOKEN! });
|
|
413
|
+
|
|
414
|
+
app.post("/remove", upload.single("file"), async (req, res) => {
|
|
415
|
+
const png = await client.remove({
|
|
416
|
+
file: req.file!.buffer,
|
|
417
|
+
filename: req.file!.originalname,
|
|
418
|
+
});
|
|
419
|
+
res.type("image/png").send(png);
|
|
420
|
+
});
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
### Cloudflare Workers / Vercel Edge
|
|
424
|
+
|
|
425
|
+
```ts
|
|
426
|
+
import { Knockout } from "@useknockout/node";
|
|
427
|
+
|
|
428
|
+
const client = new Knockout({ token: env.KNOCKOUT_TOKEN });
|
|
429
|
+
|
|
430
|
+
export default {
|
|
431
|
+
async fetch(req: Request) {
|
|
432
|
+
const { searchParams } = new URL(req.url);
|
|
433
|
+
const imageUrl = searchParams.get("url")!;
|
|
434
|
+
const png = await client.removeUrl({ url: imageUrl });
|
|
435
|
+
return new Response(new Uint8Array(png), {
|
|
436
|
+
headers: { "Content-Type": "image/png" },
|
|
437
|
+
});
|
|
438
|
+
},
|
|
439
|
+
};
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
## Self-hosting
|
|
443
|
+
|
|
444
|
+
Point the SDK at your own Modal deployment:
|
|
445
|
+
|
|
446
|
+
```ts
|
|
447
|
+
const client = new Knockout({
|
|
448
|
+
token: "your-self-hosted-token",
|
|
449
|
+
baseUrl: "https://YOUR_WORKSPACE--api.modal.run",
|
|
450
|
+
});
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
See [useknockout/api](https://github.com/useknockout/api) for the Modal deployment.
|
|
454
|
+
|
|
455
|
+
## License
|
|
456
|
+
|
|
457
|
+
MIT โ see [LICENSE](./LICENSE).
|