background-remove 0.0.0
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 -0
- package/README.md +317 -0
- package/bin/cli.js +561 -0
- package/package.json +52 -0
- package/src/index.js +778 -0
package/bin/cli.js
ADDED
|
@@ -0,0 +1,561 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { Command } = require('commander');
|
|
4
|
+
const chalk = require('chalk');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const { removeBackground, listMethods, parseColor } = require('../src/index.js');
|
|
8
|
+
|
|
9
|
+
const program = new Command();
|
|
10
|
+
|
|
11
|
+
// ============================================================================
|
|
12
|
+
// UTILITY FUNCTIONS
|
|
13
|
+
// ============================================================================
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Validates and provides helpful error messages for common mistakes
|
|
17
|
+
*/
|
|
18
|
+
function validateInput(inputPath, options) {
|
|
19
|
+
const errors = [];
|
|
20
|
+
const suggestions = [];
|
|
21
|
+
|
|
22
|
+
// Check input file exists
|
|
23
|
+
if (!fs.existsSync(inputPath)) {
|
|
24
|
+
errors.push(`Input file not found: ${inputPath}`);
|
|
25
|
+
|
|
26
|
+
// Suggest alternatives
|
|
27
|
+
const dir = path.dirname(inputPath);
|
|
28
|
+
if (fs.existsSync(dir)) {
|
|
29
|
+
const files = fs.readdirSync(dir).filter(f =>
|
|
30
|
+
/\.(jpg|jpeg|png|webp|gif|tiff|avif)$/i.test(f)
|
|
31
|
+
).slice(0, 5);
|
|
32
|
+
if (files.length > 0) {
|
|
33
|
+
suggestions.push(`Found these image files in directory: ${files.join(', ')}`);
|
|
34
|
+
}
|
|
35
|
+
} else {
|
|
36
|
+
suggestions.push(`Directory does not exist: ${dir}`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Validate method
|
|
41
|
+
const validMethods = ['auto', 'color', 'inferred', 'chroma', 'flood', 'edges'];
|
|
42
|
+
if (!validMethods.includes(options.method)) {
|
|
43
|
+
errors.push(`Invalid method "${options.method}"`);
|
|
44
|
+
suggestions.push(`Valid methods are: ${validMethods.join(', ')}`);
|
|
45
|
+
suggestions.push(`Run 'background-remove methods' to see all methods with descriptions`);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Validate color format if provided
|
|
49
|
+
if (options.color && options.method === 'color') {
|
|
50
|
+
try {
|
|
51
|
+
parseColor(options.color);
|
|
52
|
+
} catch (e) {
|
|
53
|
+
errors.push(`Invalid color format: "${options.color}"`);
|
|
54
|
+
suggestions.push('Use hex format: #FFFFFF or #FFF');
|
|
55
|
+
suggestions.push('Use RGB format: rgb(255,255,255)');
|
|
56
|
+
suggestions.push('Use named colors: white, black, red, green, blue, yellow, cyan, magenta, gray');
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Validate tolerance range
|
|
61
|
+
if (options.tolerance !== undefined) {
|
|
62
|
+
if (options.tolerance < 0 || options.tolerance > 255) {
|
|
63
|
+
errors.push(`Tolerance must be between 0-255, got: ${options.tolerance}`);
|
|
64
|
+
suggestions.push('Low values (0-30): Remove only very similar colors');
|
|
65
|
+
suggestions.push('Medium values (30-60): Good for solid backgrounds');
|
|
66
|
+
suggestions.push('High values (60-255): Remove more varied colors');
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Validate quality range
|
|
71
|
+
if (options.quality !== undefined) {
|
|
72
|
+
if (options.quality < 1 || options.quality > 100) {
|
|
73
|
+
errors.push(`Quality must be between 1-100, got: ${options.quality}`);
|
|
74
|
+
suggestions.push('Use 90-100 for high quality output');
|
|
75
|
+
suggestions.push('Use 70-85 for balanced quality/size');
|
|
76
|
+
suggestions.push('Use 1-70 for smaller file sizes');
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Validate format
|
|
81
|
+
if (options.format) {
|
|
82
|
+
const validFormats = ['png', 'webp', 'jpeg', 'jpg'];
|
|
83
|
+
if (!validFormats.includes(options.format.toLowerCase())) {
|
|
84
|
+
errors.push(`Invalid format: "${options.format}"`);
|
|
85
|
+
suggestions.push(`Valid formats: ${validFormats.join(', ')}`);
|
|
86
|
+
suggestions.push('Note: Only PNG and WebP support transparency. JPEG will use white background.');
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Validate feather range
|
|
91
|
+
if (options.feather !== undefined) {
|
|
92
|
+
if (options.feather < 0 || options.feather > 20) {
|
|
93
|
+
errors.push(`Feather must be between 0-20, got: ${options.feather}`);
|
|
94
|
+
suggestions.push('Use 0 for no feathering');
|
|
95
|
+
suggestions.push('Use 2-5 for subtle edge softening');
|
|
96
|
+
suggestions.push('Use 10-20 for strong blur/soft focus effect');
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Check if using JPEG output with transparency-dependent options
|
|
101
|
+
if (options.format === 'jpeg' || options.format === 'jpg') {
|
|
102
|
+
if (options.invert) {
|
|
103
|
+
suggestions.push('Warning: JPEG output does not support transparency. Inverted selection will have white background.');
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return { errors, suggestions };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Print validation errors with suggestions
|
|
112
|
+
*/
|
|
113
|
+
function printValidationErrors(validation) {
|
|
114
|
+
console.error(chalk.red('\nā Validation errors found:\n'));
|
|
115
|
+
validation.errors.forEach(err => {
|
|
116
|
+
console.error(chalk.red(` ⢠${err}`));
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
if (validation.suggestions.length > 0) {
|
|
120
|
+
console.error(chalk.yellow('\nš” Suggestions:\n'));
|
|
121
|
+
validation.suggestions.forEach(sugg => {
|
|
122
|
+
console.error(chalk.yellow(` ⢠${sugg}`));
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
console.error(chalk.gray('\nRun with --help for usage examples\n'));
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// ============================================================================
|
|
130
|
+
// MAIN CLI SETUP
|
|
131
|
+
// ============================================================================
|
|
132
|
+
|
|
133
|
+
program
|
|
134
|
+
.name('background-remove')
|
|
135
|
+
.description(
|
|
136
|
+
'A CLI tool for removing backgrounds from images and creating transparent PNGs.\n' +
|
|
137
|
+
'Supports multiple algorithms: color-based, automatic detection, chroma key (green screen),\n' +
|
|
138
|
+
'flood fill, and edge detection. Use "methods" command to see all removal techniques.'
|
|
139
|
+
)
|
|
140
|
+
.version('1.0.0', '-v, --version', 'Display version number')
|
|
141
|
+
.helpOption('-h, --help', 'Display help for command')
|
|
142
|
+
.configureHelp({
|
|
143
|
+
sortSubcommands: true,
|
|
144
|
+
subcommandTerm: (cmd) => cmd.name()
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
// ============================================================================
|
|
148
|
+
// REMOVE COMMAND
|
|
149
|
+
// ============================================================================
|
|
150
|
+
|
|
151
|
+
program
|
|
152
|
+
.command('remove')
|
|
153
|
+
.alias('rm')
|
|
154
|
+
.description(
|
|
155
|
+
'Remove background from an image and save as PNG/WebP with transparency.\n' +
|
|
156
|
+
'If no output path is provided, creates "input-nobg.png" in the same directory.'
|
|
157
|
+
)
|
|
158
|
+
.argument('<input>',
|
|
159
|
+
'Path to input image file.\n' +
|
|
160
|
+
'Supported formats: JPG, PNG, WebP, GIF, TIFF, AVIF\n' +
|
|
161
|
+
'Example: photo.jpg or ./images/photo.png'
|
|
162
|
+
)
|
|
163
|
+
.argument('[output]',
|
|
164
|
+
'(Optional) Path for output image.\n' +
|
|
165
|
+
'Defaults to "<input-name>-nobg.<format>" in same directory.\n' +
|
|
166
|
+
'Example: output.png or ./cleaned/photo.png'
|
|
167
|
+
)
|
|
168
|
+
.option('-m, --method <method>',
|
|
169
|
+
'Background removal algorithm to use:\n' +
|
|
170
|
+
' ⢠auto - Automatically detect best method (default)\n' +
|
|
171
|
+
' ⢠color - Remove specific color (use with --color)\n' +
|
|
172
|
+
' ⢠inferred - Auto-detect background from image corners\n' +
|
|
173
|
+
' ⢠chroma - Chroma key / green screen removal\n' +
|
|
174
|
+
' ⢠flood - Flood fill from seed points\n' +
|
|
175
|
+
' ⢠edges - Edge detection based removal',
|
|
176
|
+
'auto'
|
|
177
|
+
)
|
|
178
|
+
.option('-c, --color <color>',
|
|
179
|
+
'Color to remove (only used with --method color).\n' +
|
|
180
|
+
'Formats: #FFFFFF (hex), rgb(255,255,255), or named colors.\n' +
|
|
181
|
+
'Named colors: white, black, red, green, blue, yellow, cyan, magenta, gray\n' +
|
|
182
|
+
'Default: #FFFFFF (white)',
|
|
183
|
+
'#FFFFFF'
|
|
184
|
+
)
|
|
185
|
+
.option('-t, --tolerance <number>',
|
|
186
|
+
'Color matching tolerance 0-255. Higher = more aggressive removal.\n' +
|
|
187
|
+
' ⢠10-20 - Very strict, exact color matches only\n' +
|
|
188
|
+
' ⢠30-40 - Good for solid color backgrounds (default: 32)\n' +
|
|
189
|
+
' ⢠60-80 - Handles gradients and variations\n' +
|
|
190
|
+
' ⢠100+ - Very aggressive, may remove subject parts',
|
|
191
|
+
parseFloat,
|
|
192
|
+
32
|
|
193
|
+
)
|
|
194
|
+
.option('--chroma-color <color>',
|
|
195
|
+
'Chroma key color for green/blue screen removal.\n' +
|
|
196
|
+
' ⢠green - Standard green screen (default)\n' +
|
|
197
|
+
' ⢠blue - Blue screen\n' +
|
|
198
|
+
' ⢠red - Red screen\n' +
|
|
199
|
+
' ⢠#RRGGBB - Any custom color',
|
|
200
|
+
'green'
|
|
201
|
+
)
|
|
202
|
+
.option('--flood-seed <positions...>',
|
|
203
|
+
'Seed positions for flood fill method (x,y pairs).\n' +
|
|
204
|
+
'Format: "x,y" (e.g., "0,0" or "100,50")\n' +
|
|
205
|
+
'Multiple: --flood-seed 0,0 100,50 200,100\n' +
|
|
206
|
+
'Default: 0,0 (top-left corner)',
|
|
207
|
+
['0,0']
|
|
208
|
+
)
|
|
209
|
+
.option('-r, --radius <number>',
|
|
210
|
+
'Corner sampling radius for inferred method.\n' +
|
|
211
|
+
'Larger radius = more aggressive background detection from edges.\n' +
|
|
212
|
+
'Range: 1-100, default: 10',
|
|
213
|
+
parseInt,
|
|
214
|
+
10
|
|
215
|
+
)
|
|
216
|
+
.option('-d, --distance <number>',
|
|
217
|
+
'Edge detection threshold for edges method.\n' +
|
|
218
|
+
'Lower values = more edges detected, more aggressive removal.\n' +
|
|
219
|
+
'Range: 1-50, default: 10',
|
|
220
|
+
parseInt,
|
|
221
|
+
10
|
|
222
|
+
)
|
|
223
|
+
.option('-f, --feather <number>',
|
|
224
|
+
'Edge feathering amount 0-20. Creates soft/transparent edges.\n' +
|
|
225
|
+
' ⢠0 - Hard edges (default)\n' +
|
|
226
|
+
' ⢠2-5 - Subtle softening\n' +
|
|
227
|
+
' ⢠10+ - Strong blur effect',
|
|
228
|
+
parseFloat,
|
|
229
|
+
0
|
|
230
|
+
)
|
|
231
|
+
.option('-s, --smooth',
|
|
232
|
+
'Apply edge smoothing to reduce jagged edges.\n' +
|
|
233
|
+
'Recommended for text or logos with hard edges.',
|
|
234
|
+
false
|
|
235
|
+
)
|
|
236
|
+
.option('-a, --antialias',
|
|
237
|
+
'Apply antialiasing to edges for smoother appearance.\n' +
|
|
238
|
+
'Similar to --smooth but uses different algorithm.',
|
|
239
|
+
false
|
|
240
|
+
)
|
|
241
|
+
.option('-i, --invert',
|
|
242
|
+
'Invert the selection - keep background, remove foreground.\n' +
|
|
243
|
+
'Useful for extracting just the background area.',
|
|
244
|
+
false
|
|
245
|
+
)
|
|
246
|
+
.option('--format <format>',
|
|
247
|
+
'Output image format.\n' +
|
|
248
|
+
' ⢠png - PNG with transparency (default)\n' +
|
|
249
|
+
' ⢠webp - WebP with transparency\n' +
|
|
250
|
+
' ⢠jpeg - JPEG (no transparency, white background)',
|
|
251
|
+
'png'
|
|
252
|
+
)
|
|
253
|
+
.option('-q, --quality <number>',
|
|
254
|
+
'Output quality for WebP/JPEG 1-100.\n' +
|
|
255
|
+
'Higher = better quality, larger file. No effect on PNG.\n' +
|
|
256
|
+
'Default: 90',
|
|
257
|
+
parseInt,
|
|
258
|
+
90
|
|
259
|
+
)
|
|
260
|
+
.option('-v, --verbose',
|
|
261
|
+
'Display detailed processing information including\n' +
|
|
262
|
+
'detected colors, timing, and method selection.',
|
|
263
|
+
false
|
|
264
|
+
)
|
|
265
|
+
.addHelpText('after', `
|
|
266
|
+
Examples:
|
|
267
|
+
# Auto-remove background (works for most images)
|
|
268
|
+
$ background-remove remove photo.jpg
|
|
269
|
+
|
|
270
|
+
# Remove white background with color method
|
|
271
|
+
$ background-remove remove logo.png --method color --color white
|
|
272
|
+
|
|
273
|
+
# Remove specific hex color with tolerance
|
|
274
|
+
$ background-remove remove image.png --method color --color "#FF5733" --tolerance 40
|
|
275
|
+
|
|
276
|
+
# Green screen removal with smoothing
|
|
277
|
+
$ background-remove remove video.jpg --method chroma --chroma-color green --smooth
|
|
278
|
+
|
|
279
|
+
# Auto-detect with high tolerance for gradients
|
|
280
|
+
$ background-remove remove photo.jpg --method inferred --tolerance 60
|
|
281
|
+
|
|
282
|
+
# Flood fill from multiple corners
|
|
283
|
+
$ background-remove remove diagram.png --method flood --flood-seed 0,0 800,600
|
|
284
|
+
|
|
285
|
+
# Remove with feathered edges and WebP output
|
|
286
|
+
$ background-remove remove portrait.jpg output.webp --method inferred --feather 5 --format webp
|
|
287
|
+
|
|
288
|
+
# Edge detection with custom threshold
|
|
289
|
+
$ background-remove remove product.jpg --method edges --distance 15
|
|
290
|
+
`)
|
|
291
|
+
.action(async (input, output, options) => {
|
|
292
|
+
try {
|
|
293
|
+
const inputPath = path.resolve(input);
|
|
294
|
+
|
|
295
|
+
// Generate default output path if not provided
|
|
296
|
+
if (!output) {
|
|
297
|
+
const parsed = path.parse(inputPath);
|
|
298
|
+
output = path.join(parsed.dir, `${parsed.name}-nobg.${options.format || 'png'}`);
|
|
299
|
+
}
|
|
300
|
+
const outputPath = path.resolve(output);
|
|
301
|
+
|
|
302
|
+
// Validate inputs with AI-friendly error messages
|
|
303
|
+
const validation = validateInput(inputPath, options);
|
|
304
|
+
if (validation.errors.length > 0) {
|
|
305
|
+
printValidationErrors(validation);
|
|
306
|
+
process.exit(1);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// Warn about JPEG transparency
|
|
310
|
+
if ((options.format === 'jpeg' || options.format === 'jpg') && !options.invert) {
|
|
311
|
+
console.log(chalk.yellow('ā Warning: JPEG format does not support transparency.'));
|
|
312
|
+
console.log(chalk.yellow(' Transparent areas will be filled with white.\n'));
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
const startTime = Date.now();
|
|
316
|
+
|
|
317
|
+
if (options.verbose) {
|
|
318
|
+
console.log(chalk.blue('ā'.repeat(60)));
|
|
319
|
+
console.log(chalk.blue('Processing Configuration:'));
|
|
320
|
+
console.log(chalk.blue('ā'.repeat(60)));
|
|
321
|
+
console.log(` Input file: ${inputPath}`);
|
|
322
|
+
console.log(` Output file: ${outputPath}`);
|
|
323
|
+
console.log(` Method: ${options.method}`);
|
|
324
|
+
console.log(` Format: ${options.format}`);
|
|
325
|
+
if (options.method === 'color') {
|
|
326
|
+
console.log(` Target color: ${options.color}`);
|
|
327
|
+
}
|
|
328
|
+
console.log(` Tolerance: ${options.tolerance}`);
|
|
329
|
+
if (options.feather > 0) console.log(` Feather: ${options.feather}`);
|
|
330
|
+
if (options.smooth) console.log(` Smoothing: enabled`);
|
|
331
|
+
if (options.antialias) console.log(` Antialias: enabled`);
|
|
332
|
+
if (options.invert) console.log(` Invert: enabled`);
|
|
333
|
+
console.log(chalk.blue('ā'.repeat(60)));
|
|
334
|
+
console.log();
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
const result = await removeBackground(inputPath, outputPath, options);
|
|
338
|
+
|
|
339
|
+
const duration = Date.now() - startTime;
|
|
340
|
+
|
|
341
|
+
console.log(chalk.green('ā Background removed successfully!'));
|
|
342
|
+
console.log(chalk.gray(` Output: ${result.outputPath}`));
|
|
343
|
+
console.log(chalk.gray(` Dimensions: ${result.width}x${result.height}`));
|
|
344
|
+
console.log(chalk.gray(` Duration: ${duration}ms`));
|
|
345
|
+
|
|
346
|
+
if (result.method) {
|
|
347
|
+
console.log(chalk.gray(` Method: ${result.method}`));
|
|
348
|
+
}
|
|
349
|
+
if (result.detectedColor) {
|
|
350
|
+
if (Array.isArray(result.detectedColor)) {
|
|
351
|
+
console.log(chalk.gray(` Detected: RGB(${result.detectedColor.join(', ')})`));
|
|
352
|
+
} else {
|
|
353
|
+
console.log(chalk.gray(` Detected: ${result.detectedColor}`));
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
console.log();
|
|
358
|
+
|
|
359
|
+
} catch (error) {
|
|
360
|
+
console.error(chalk.red('\nā Error during processing:\n'));
|
|
361
|
+
console.error(chalk.red(` ${error.message}`));
|
|
362
|
+
|
|
363
|
+
// AI-friendly suggestions based on error type
|
|
364
|
+
if (error.message.includes('parseColor')) {
|
|
365
|
+
console.error(chalk.yellow('\nš” Color format help:\n'));
|
|
366
|
+
console.error(chalk.yellow(' ⢠Hex: #FFFFFF or #FFF'));
|
|
367
|
+
console.error(chalk.yellow(' ⢠RGB: rgb(255, 255, 255)'));
|
|
368
|
+
console.error(chalk.yellow(' ⢠Named: white, black, red, green, blue, etc.'));
|
|
369
|
+
} else if (error.message.includes('tolerance')) {
|
|
370
|
+
console.error(chalk.yellow('\nš” Tolerance must be a number between 0-255\n'));
|
|
371
|
+
} else if (error.message.includes('EACCES') || error.message.includes('permission')) {
|
|
372
|
+
console.error(chalk.yellow('\nš” Check file permissions. You may need to:\n'));
|
|
373
|
+
console.error(chalk.yellow(' ⢠Use a different output directory'));
|
|
374
|
+
console.error(chalk.yellow(' ⢠Run with appropriate permissions'));
|
|
375
|
+
} else if (error.message.includes('ENOSPC')) {
|
|
376
|
+
console.error(chalk.yellow('\nš” Insufficient disk space. Free up space and try again.\n'));
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
if (options.verbose) {
|
|
380
|
+
console.error(chalk.gray('\nStack trace:'));
|
|
381
|
+
console.error(error.stack);
|
|
382
|
+
}
|
|
383
|
+
process.exit(1);
|
|
384
|
+
}
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
// ============================================================================
|
|
388
|
+
// METHODS COMMAND
|
|
389
|
+
// ============================================================================
|
|
390
|
+
|
|
391
|
+
program
|
|
392
|
+
.command('methods')
|
|
393
|
+
.description(
|
|
394
|
+
'Display detailed information about all available background removal methods.\n' +
|
|
395
|
+
'Each method is suited for different image types and backgrounds.'
|
|
396
|
+
)
|
|
397
|
+
.addHelpText('after', `
|
|
398
|
+
Method Selection Guide:
|
|
399
|
+
⢠Use "auto" for unknown/untested images (default, works well for most)
|
|
400
|
+
⢠Use "color" when you know the exact background color (e.g., white #FFFFFF)
|
|
401
|
+
⢠Use "inferred" when background is solid color but unknown (auto-detects)
|
|
402
|
+
⢠Use "chroma" for green screen / blue screen photos
|
|
403
|
+
⢠Use "flood" when background connects to image edges (e.g., product photos)
|
|
404
|
+
⢠Use "edges" when subject has strong contrast against background
|
|
405
|
+
|
|
406
|
+
Examples:
|
|
407
|
+
$ background-remove methods # Show all methods
|
|
408
|
+
$ background-remove remove img.jpg -m color --color white
|
|
409
|
+
`)
|
|
410
|
+
.action(() => {
|
|
411
|
+
const methods = listMethods();
|
|
412
|
+
|
|
413
|
+
console.log(chalk.bold('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā'));
|
|
414
|
+
console.log(chalk.bold('ā Background Removal Methods Reference Guide ā'));
|
|
415
|
+
console.log(chalk.bold('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā'));
|
|
416
|
+
console.log();
|
|
417
|
+
|
|
418
|
+
methods.forEach(m => {
|
|
419
|
+
console.log(chalk.cyan(`ā¶ ${m.name.toUpperCase()}`));
|
|
420
|
+
console.log(` ${m.description}`);
|
|
421
|
+
console.log();
|
|
422
|
+
console.log(chalk.gray(' Best for:'));
|
|
423
|
+
m.bestFor.forEach(use => {
|
|
424
|
+
console.log(chalk.gray(` ⢠${use}`));
|
|
425
|
+
});
|
|
426
|
+
console.log();
|
|
427
|
+
console.log(chalk.gray(' Key Options:'));
|
|
428
|
+
m.options.forEach(opt => {
|
|
429
|
+
console.log(chalk.gray(` ⢠${opt}`));
|
|
430
|
+
});
|
|
431
|
+
console.log();
|
|
432
|
+
console.log(chalk.gray(' Example:'));
|
|
433
|
+
console.log(chalk.gray(` $ ${m.example}`));
|
|
434
|
+
console.log();
|
|
435
|
+
console.log('ā'.repeat(64));
|
|
436
|
+
console.log();
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
console.log(chalk.yellow('Tip: Use --verbose with remove command to see which method was auto-selected'));
|
|
440
|
+
console.log(chalk.yellow('Tip: Run "background-remove remove --help" for all available options'));
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
// ============================================================================
|
|
444
|
+
// PREVIEW COMMAND
|
|
445
|
+
// ============================================================================
|
|
446
|
+
|
|
447
|
+
program
|
|
448
|
+
.command('preview')
|
|
449
|
+
.alias('mask')
|
|
450
|
+
.description(
|
|
451
|
+
'Generate a mask preview showing what will be kept vs removed.\n' +
|
|
452
|
+
'Creates a black and white image: white = kept, black = removed.\n' +
|
|
453
|
+
'Useful for testing settings before final processing.'
|
|
454
|
+
)
|
|
455
|
+
.argument('<input>',
|
|
456
|
+
'Input image path to generate preview mask for.\n' +
|
|
457
|
+
'Example: photo.jpg'
|
|
458
|
+
)
|
|
459
|
+
.option('-m, --method <method>',
|
|
460
|
+
'Removal method to preview (same as remove command).\n' +
|
|
461
|
+
'See "methods" command for available options.',
|
|
462
|
+
'auto'
|
|
463
|
+
)
|
|
464
|
+
.option('-c, --color <color>',
|
|
465
|
+
'Color to preview removal for (with color method).\n' +
|
|
466
|
+
'Default: #FFFFFF (white)',
|
|
467
|
+
'#FFFFFF'
|
|
468
|
+
)
|
|
469
|
+
.option('-t, --tolerance <number>',
|
|
470
|
+
'Tolerance for preview.\n' +
|
|
471
|
+
'Default: 32',
|
|
472
|
+
parseFloat,
|
|
473
|
+
32
|
|
474
|
+
)
|
|
475
|
+
.option('-o, --output <path>',
|
|
476
|
+
'Custom output path for mask.\n' +
|
|
477
|
+
'Default: <input-name>-mask.png'
|
|
478
|
+
)
|
|
479
|
+
.option('-v, --verbose',
|
|
480
|
+
'Show processing details.',
|
|
481
|
+
false
|
|
482
|
+
)
|
|
483
|
+
.addHelpText('after', `
|
|
484
|
+
Examples:
|
|
485
|
+
# Preview with auto method
|
|
486
|
+
$ background-remove preview photo.jpg
|
|
487
|
+
|
|
488
|
+
# Preview white background removal
|
|
489
|
+
$ background-remove preview logo.png --method color --color white
|
|
490
|
+
|
|
491
|
+
# Preview with custom output
|
|
492
|
+
$ background-remove preview photo.jpg -o test-mask.png --tolerance 50
|
|
493
|
+
|
|
494
|
+
Interpretation:
|
|
495
|
+
⢠White areas = will be KEPT in final output
|
|
496
|
+
⢠Black areas = will be REMOVED (become transparent)
|
|
497
|
+
⢠Use this to tune --tolerance before running remove
|
|
498
|
+
`)
|
|
499
|
+
.action(async (input, options) => {
|
|
500
|
+
try {
|
|
501
|
+
const inputPath = path.resolve(input);
|
|
502
|
+
|
|
503
|
+
if (!options.output) {
|
|
504
|
+
const parsed = path.parse(inputPath);
|
|
505
|
+
options.output = path.join(parsed.dir, `${parsed.name}-mask.png`);
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
const outputPath = path.resolve(options.output);
|
|
509
|
+
|
|
510
|
+
// Validate
|
|
511
|
+
if (!fs.existsSync(inputPath)) {
|
|
512
|
+
console.error(chalk.red(`\nā Input file not found: ${inputPath}\n`));
|
|
513
|
+
console.error(chalk.yellow('š” Make sure the file path is correct and the file exists.\n'));
|
|
514
|
+
process.exit(1);
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
if (options.verbose) {
|
|
518
|
+
console.log(chalk.blue('Generating mask preview...'));
|
|
519
|
+
console.log(chalk.gray(` Input: ${inputPath}`));
|
|
520
|
+
console.log(chalk.gray(` Output: ${outputPath}`));
|
|
521
|
+
console.log(chalk.gray(` Method: ${options.method}`));
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
const { generateMask } = require('../src/index.js');
|
|
525
|
+
const result = await generateMask(inputPath, outputPath, options);
|
|
526
|
+
|
|
527
|
+
console.log(chalk.green('ā Mask preview generated!'));
|
|
528
|
+
console.log(chalk.gray(` Location: ${result.outputPath}`));
|
|
529
|
+
console.log();
|
|
530
|
+
console.log(chalk.white(' Legend:'));
|
|
531
|
+
console.log(chalk.white(' āāāā = White (KEPT in final)'));
|
|
532
|
+
console.log(chalk.gray(' āāāā = Black (REMOVED, becomes transparent)'));
|
|
533
|
+
console.log();
|
|
534
|
+
console.log(chalk.yellow(' If the mask looks wrong, adjust --tolerance or try a different method.'));
|
|
535
|
+
console.log(chalk.gray(` Run: background-remove remove "${input}" --method ${options.method} --tolerance <value>`));
|
|
536
|
+
|
|
537
|
+
} catch (error) {
|
|
538
|
+
console.error(chalk.red(`\nā Error: ${error.message}\n`));
|
|
539
|
+
if (options.verbose) {
|
|
540
|
+
console.error(error.stack);
|
|
541
|
+
}
|
|
542
|
+
process.exit(1);
|
|
543
|
+
}
|
|
544
|
+
});
|
|
545
|
+
|
|
546
|
+
// ============================================================================
|
|
547
|
+
// GLOBAL ERROR HANDLING
|
|
548
|
+
// ============================================================================
|
|
549
|
+
|
|
550
|
+
process.on('unhandledRejection', (error) => {
|
|
551
|
+
console.error(chalk.red('\nā Unexpected error:\n'));
|
|
552
|
+
console.error(chalk.red(` ${error.message}`));
|
|
553
|
+
console.error(chalk.yellow('\nš” This might be a bug. Please report with --verbose output.\n'));
|
|
554
|
+
process.exit(1);
|
|
555
|
+
});
|
|
556
|
+
|
|
557
|
+
// ============================================================================
|
|
558
|
+
// PARSE AND RUN
|
|
559
|
+
// ============================================================================
|
|
560
|
+
|
|
561
|
+
program.parse();
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "background-remove",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"description": "CLI tool for removing backgrounds from images with transparent output",
|
|
5
|
+
"main": "src/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"background-remove": "./bin/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"bin/",
|
|
11
|
+
"src/",
|
|
12
|
+
"README.md",
|
|
13
|
+
"LICENSE"
|
|
14
|
+
],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
17
|
+
},
|
|
18
|
+
"keywords": [
|
|
19
|
+
"background",
|
|
20
|
+
"remove",
|
|
21
|
+
"transparent",
|
|
22
|
+
"image",
|
|
23
|
+
"cli",
|
|
24
|
+
"chroma",
|
|
25
|
+
"greenscreen",
|
|
26
|
+
"alpha",
|
|
27
|
+
"background-removal",
|
|
28
|
+
"remove-background",
|
|
29
|
+
"image-processing",
|
|
30
|
+
"png",
|
|
31
|
+
"transparency"
|
|
32
|
+
],
|
|
33
|
+
"author": "Richard Anaya",
|
|
34
|
+
"license": "MIT",
|
|
35
|
+
"type": "commonjs",
|
|
36
|
+
"engines": {
|
|
37
|
+
"node": ">=14.0.0"
|
|
38
|
+
},
|
|
39
|
+
"repository": {
|
|
40
|
+
"type": "git",
|
|
41
|
+
"url": "git+https://github.com/richardanaya/background-remove.git"
|
|
42
|
+
},
|
|
43
|
+
"bugs": {
|
|
44
|
+
"url": "https://github.com/richardanaya/background-remove/issues"
|
|
45
|
+
},
|
|
46
|
+
"homepage": "https://github.com/richardanaya/background-remove#readme",
|
|
47
|
+
"dependencies": {
|
|
48
|
+
"chalk": "^4.1.2",
|
|
49
|
+
"commander": "^14.0.3",
|
|
50
|
+
"sharp": "^0.34.5"
|
|
51
|
+
}
|
|
52
|
+
}
|