@spark-apps/piclet 1.0.1 → 1.0.3
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/{Readme.md → README.md} +28 -4
- package/dist/cli.js +2135 -478
- package/dist/cli.js.map +1 -1
- package/dist/gui/border.html +229 -0
- package/dist/gui/css/theme.css +88 -88
- package/dist/gui/extract-frames.html +156 -0
- package/dist/gui/filter.html +180 -0
- package/dist/gui/iconpack.html +113 -113
- package/dist/gui/loading.hta +3 -5
- package/dist/gui/piclet.html +1529 -1004
- package/dist/gui/recolor.html +243 -0
- package/dist/gui/remove-bg.html +178 -178
- package/dist/gui/storepack.html +179 -179
- package/dist/gui/transform.html +202 -0
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/cli.ts
|
|
4
|
-
import
|
|
4
|
+
import chalk17 from "chalk";
|
|
5
5
|
|
|
6
6
|
// src/cli/index.ts
|
|
7
|
-
import
|
|
7
|
+
import chalk16 from "chalk";
|
|
8
8
|
import { Command } from "commander";
|
|
9
9
|
|
|
10
10
|
// src/lib/banner.ts
|
|
@@ -36,101 +36,25 @@ ${renderLogo()}`);
|
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
// src/cli/commands/
|
|
40
|
-
import
|
|
41
|
-
|
|
42
|
-
// src/lib/config.ts
|
|
43
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
44
|
-
import { dirname, join } from "path";
|
|
45
|
-
import { homedir } from "os";
|
|
46
|
-
var DEFAULT_CONFIG = {
|
|
47
|
-
removeBg: {
|
|
48
|
-
fuzz: 10,
|
|
49
|
-
trim: true,
|
|
50
|
-
preserveInner: false,
|
|
51
|
-
makeSquare: false
|
|
52
|
-
},
|
|
53
|
-
rescale: {
|
|
54
|
-
defaultScale: 50,
|
|
55
|
-
makeSquare: false
|
|
56
|
-
},
|
|
57
|
-
iconpack: {
|
|
58
|
-
platforms: ["web", "android", "ios"]
|
|
59
|
-
}
|
|
60
|
-
};
|
|
61
|
-
function getConfigDir() {
|
|
62
|
-
return join(homedir(), ".config", "piclet");
|
|
63
|
-
}
|
|
64
|
-
function getConfigPath() {
|
|
65
|
-
return join(getConfigDir(), "config.json");
|
|
66
|
-
}
|
|
67
|
-
function loadConfig() {
|
|
68
|
-
const configPath = getConfigPath();
|
|
69
|
-
if (!existsSync(configPath)) {
|
|
70
|
-
return { ...DEFAULT_CONFIG };
|
|
71
|
-
}
|
|
72
|
-
try {
|
|
73
|
-
const content = readFileSync(configPath, "utf-8");
|
|
74
|
-
const loaded = JSON.parse(content);
|
|
75
|
-
return {
|
|
76
|
-
removeBg: { ...DEFAULT_CONFIG.removeBg, ...loaded.removeBg },
|
|
77
|
-
rescale: { ...DEFAULT_CONFIG.rescale, ...loaded.rescale },
|
|
78
|
-
iconpack: { ...DEFAULT_CONFIG.iconpack, ...loaded.iconpack }
|
|
79
|
-
};
|
|
80
|
-
} catch {
|
|
81
|
-
return { ...DEFAULT_CONFIG };
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
function resetConfig() {
|
|
85
|
-
const configPath = getConfigPath();
|
|
86
|
-
const configDir = dirname(configPath);
|
|
87
|
-
if (!existsSync(configDir)) {
|
|
88
|
-
mkdirSync(configDir, { recursive: true });
|
|
89
|
-
}
|
|
90
|
-
writeFileSync(configPath, JSON.stringify(DEFAULT_CONFIG, null, 2));
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// src/cli/commands/config.ts
|
|
94
|
-
function registerConfigCommand(program2) {
|
|
95
|
-
const configCmd = program2.command("config").description("Display current settings").action(() => {
|
|
96
|
-
const config7 = loadConfig();
|
|
97
|
-
console.log(chalk.white.bold("\n PicLet Configuration"));
|
|
98
|
-
console.log(chalk.gray(` ${getConfigPath()}
|
|
99
|
-
`));
|
|
100
|
-
console.log(JSON.stringify(config7, null, 2));
|
|
101
|
-
console.log();
|
|
102
|
-
});
|
|
103
|
-
configCmd.command("reset").description("Restore defaults").action(() => {
|
|
104
|
-
resetConfig();
|
|
105
|
-
console.log(chalk.green("Configuration reset to defaults."));
|
|
106
|
-
});
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
// src/cli/commands/help.ts
|
|
110
|
-
function registerHelpCommand(program2) {
|
|
111
|
-
program2.command("help").description("Show help").action(() => {
|
|
112
|
-
showHelp();
|
|
113
|
-
});
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// src/cli/commands/iconpack.ts
|
|
117
|
-
import chalk3 from "chalk";
|
|
39
|
+
// src/cli/commands/border.ts
|
|
40
|
+
import chalk2 from "chalk";
|
|
118
41
|
|
|
119
|
-
// src/tools/
|
|
120
|
-
import { existsSync as
|
|
121
|
-
import {
|
|
42
|
+
// src/tools/border.ts
|
|
43
|
+
import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
|
|
44
|
+
import { tmpdir } from "os";
|
|
45
|
+
import { basename as basename2, join as join3 } from "path";
|
|
122
46
|
|
|
123
47
|
// src/lib/gui-server.ts
|
|
124
48
|
import { spawn } from "child_process";
|
|
125
49
|
import { createServer } from "http";
|
|
126
|
-
import { dirname as
|
|
50
|
+
import { dirname as dirname2, join as join2 } from "path";
|
|
127
51
|
import { fileURLToPath } from "url";
|
|
128
52
|
import express from "express";
|
|
129
53
|
|
|
130
54
|
// src/lib/presets.ts
|
|
131
|
-
import { existsSync
|
|
132
|
-
import { dirname
|
|
133
|
-
import { homedir
|
|
55
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
56
|
+
import { dirname, join } from "path";
|
|
57
|
+
import { homedir } from "os";
|
|
134
58
|
var BUILT_IN_PRESETS = [
|
|
135
59
|
// ── Game Asset Stores ──
|
|
136
60
|
{
|
|
@@ -258,24 +182,24 @@ var BUILT_IN_PRESETS = [
|
|
|
258
182
|
}
|
|
259
183
|
];
|
|
260
184
|
function getPresetsPath() {
|
|
261
|
-
const picletDir =
|
|
262
|
-
return
|
|
185
|
+
const picletDir = join(homedir(), ".piclet");
|
|
186
|
+
return join(picletDir, "presets.json");
|
|
263
187
|
}
|
|
264
188
|
function ensurePresetsDir() {
|
|
265
189
|
const presetsPath = getPresetsPath();
|
|
266
|
-
const dir =
|
|
267
|
-
if (!
|
|
268
|
-
|
|
190
|
+
const dir = dirname(presetsPath);
|
|
191
|
+
if (!existsSync(dir)) {
|
|
192
|
+
mkdirSync(dir, { recursive: true });
|
|
269
193
|
}
|
|
270
194
|
}
|
|
271
195
|
function loadPresets() {
|
|
272
196
|
const presetsPath = getPresetsPath();
|
|
273
197
|
let userPresets = [];
|
|
274
|
-
if (
|
|
198
|
+
if (existsSync(presetsPath)) {
|
|
275
199
|
try {
|
|
276
|
-
const data =
|
|
277
|
-
const
|
|
278
|
-
userPresets =
|
|
200
|
+
const data = readFileSync(presetsPath, "utf-8");
|
|
201
|
+
const config12 = JSON.parse(data);
|
|
202
|
+
userPresets = config12.presets || [];
|
|
279
203
|
} catch {
|
|
280
204
|
}
|
|
281
205
|
}
|
|
@@ -290,20 +214,20 @@ function loadPresets() {
|
|
|
290
214
|
}
|
|
291
215
|
function savePresets(presets) {
|
|
292
216
|
ensurePresetsDir();
|
|
293
|
-
const
|
|
217
|
+
const config12 = {
|
|
294
218
|
version: 1,
|
|
295
219
|
presets
|
|
296
220
|
};
|
|
297
|
-
|
|
221
|
+
writeFileSync(getPresetsPath(), JSON.stringify(config12, null, 2));
|
|
298
222
|
}
|
|
299
223
|
function savePreset(preset) {
|
|
300
224
|
const presetsPath = getPresetsPath();
|
|
301
225
|
let userPresets = [];
|
|
302
|
-
if (
|
|
226
|
+
if (existsSync(presetsPath)) {
|
|
303
227
|
try {
|
|
304
|
-
const data =
|
|
305
|
-
const
|
|
306
|
-
userPresets =
|
|
228
|
+
const data = readFileSync(presetsPath, "utf-8");
|
|
229
|
+
const config12 = JSON.parse(data);
|
|
230
|
+
userPresets = config12.presets || [];
|
|
307
231
|
} catch {
|
|
308
232
|
}
|
|
309
233
|
}
|
|
@@ -320,13 +244,13 @@ function deletePreset(id) {
|
|
|
320
244
|
return { success: false, error: "Cannot delete built-in presets" };
|
|
321
245
|
}
|
|
322
246
|
const presetsPath = getPresetsPath();
|
|
323
|
-
if (!
|
|
247
|
+
if (!existsSync(presetsPath)) {
|
|
324
248
|
return { success: false, error: "Preset not found" };
|
|
325
249
|
}
|
|
326
250
|
try {
|
|
327
|
-
const data =
|
|
328
|
-
const
|
|
329
|
-
const userPresets =
|
|
251
|
+
const data = readFileSync(presetsPath, "utf-8");
|
|
252
|
+
const config12 = JSON.parse(data);
|
|
253
|
+
const userPresets = config12.presets || [];
|
|
330
254
|
const idx = userPresets.findIndex((p) => p.id === id);
|
|
331
255
|
if (idx < 0) {
|
|
332
256
|
return { success: false, error: "Preset not found" };
|
|
@@ -340,7 +264,7 @@ function deletePreset(id) {
|
|
|
340
264
|
}
|
|
341
265
|
|
|
342
266
|
// src/lib/gui-server.ts
|
|
343
|
-
var __dirname2 =
|
|
267
|
+
var __dirname2 = dirname2(fileURLToPath(import.meta.url));
|
|
344
268
|
function signalReady() {
|
|
345
269
|
spawn("powershell.exe", ["-WindowStyle", "Hidden", "-Command", "New-Item -Path $env:TEMP\\piclet-ready.tmp -ItemType File -Force | Out-Null"], {
|
|
346
270
|
stdio: "ignore",
|
|
@@ -358,7 +282,7 @@ function openAppWindow(url) {
|
|
|
358
282
|
stdio: "ignore",
|
|
359
283
|
windowsHide: true
|
|
360
284
|
}).unref();
|
|
361
|
-
|
|
285
|
+
signalReady();
|
|
362
286
|
}
|
|
363
287
|
function startGuiServer(options) {
|
|
364
288
|
return new Promise((resolve2) => {
|
|
@@ -373,12 +297,12 @@ function startGuiServer(options) {
|
|
|
373
297
|
});
|
|
374
298
|
let processResult = null;
|
|
375
299
|
let server = null;
|
|
376
|
-
const guiDir =
|
|
377
|
-
const iconsDir =
|
|
300
|
+
const guiDir = join2(__dirname2, "gui");
|
|
301
|
+
const iconsDir = join2(__dirname2, "icons");
|
|
378
302
|
app.use(express.static(guiDir));
|
|
379
303
|
app.use("/icons", express.static(iconsDir));
|
|
380
304
|
app.get("/favicon.ico", (_req, res) => {
|
|
381
|
-
res.sendFile(
|
|
305
|
+
res.sendFile(join2(iconsDir, "banana.ico"));
|
|
382
306
|
});
|
|
383
307
|
app.get("/api/info", (_req, res) => {
|
|
384
308
|
res.json({
|
|
@@ -386,9 +310,36 @@ function startGuiServer(options) {
|
|
|
386
310
|
width: options.imageInfo.width,
|
|
387
311
|
height: options.imageInfo.height,
|
|
388
312
|
borderColor: options.imageInfo.borderColor,
|
|
313
|
+
frameCount: options.imageInfo.frameCount || 1,
|
|
389
314
|
defaults: options.defaults
|
|
390
315
|
});
|
|
391
316
|
});
|
|
317
|
+
app.post("/api/frame-thumbnail", async (req, res) => {
|
|
318
|
+
if (!options.onFrameThumbnail) {
|
|
319
|
+
res.json({ success: false, error: "Frame thumbnails not supported" });
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
try {
|
|
323
|
+
const frameIndex = req.body.frameIndex ?? 0;
|
|
324
|
+
const result = await options.onFrameThumbnail(frameIndex);
|
|
325
|
+
res.json(result);
|
|
326
|
+
} catch (err) {
|
|
327
|
+
res.json({ success: false, error: err.message });
|
|
328
|
+
}
|
|
329
|
+
});
|
|
330
|
+
app.post("/api/frame-preview", async (req, res) => {
|
|
331
|
+
if (!options.onFramePreview) {
|
|
332
|
+
res.json({ success: false, error: "Frame preview not supported" });
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
try {
|
|
336
|
+
const { frameIndex, ...opts } = req.body;
|
|
337
|
+
const result = await options.onFramePreview(frameIndex ?? 0, opts);
|
|
338
|
+
res.json(result);
|
|
339
|
+
} catch (err) {
|
|
340
|
+
res.json({ success: false, error: err.message });
|
|
341
|
+
}
|
|
342
|
+
});
|
|
392
343
|
app.post("/api/preview", async (req, res) => {
|
|
393
344
|
if (!options.onPreview) {
|
|
394
345
|
res.json({ success: false, error: "Preview not supported" });
|
|
@@ -485,6 +436,23 @@ function startGuiServer(options) {
|
|
|
485
436
|
}).unref();
|
|
486
437
|
res.json({ success: true });
|
|
487
438
|
});
|
|
439
|
+
app.post("/api/open-folder", (_req, res) => {
|
|
440
|
+
const filePath = options.imageInfo.filePath;
|
|
441
|
+
let winPath = filePath;
|
|
442
|
+
const wslMatch = filePath.match(/^\/mnt\/([a-z])\/(.*)$/);
|
|
443
|
+
if (wslMatch) {
|
|
444
|
+
const drive = wslMatch[1].toUpperCase();
|
|
445
|
+
const rest = wslMatch[2].replace(/\//g, "\\");
|
|
446
|
+
winPath = `${drive}:\\${rest}`;
|
|
447
|
+
}
|
|
448
|
+
const dir = winPath.substring(0, winPath.lastIndexOf("\\")) || winPath.substring(0, winPath.lastIndexOf("/"));
|
|
449
|
+
spawn("powershell.exe", ["-WindowStyle", "Hidden", "-Command", `explorer.exe "${dir}"`], {
|
|
450
|
+
detached: true,
|
|
451
|
+
stdio: "ignore",
|
|
452
|
+
windowsHide: true
|
|
453
|
+
}).unref();
|
|
454
|
+
res.json({ success: true });
|
|
455
|
+
});
|
|
488
456
|
function shutdown() {
|
|
489
457
|
setTimeout(() => {
|
|
490
458
|
server?.close();
|
|
@@ -566,17 +534,26 @@ function clearLine() {
|
|
|
566
534
|
|
|
567
535
|
// src/lib/magick.ts
|
|
568
536
|
import { exec } from "child_process";
|
|
569
|
-
import { copyFileSync, existsSync as
|
|
570
|
-
import { dirname as
|
|
537
|
+
import { copyFileSync, existsSync as existsSync2, mkdirSync as mkdirSync2, unlinkSync } from "fs";
|
|
538
|
+
import { dirname as dirname3 } from "path";
|
|
571
539
|
import { promisify } from "util";
|
|
572
540
|
var execAsync = promisify(exec);
|
|
573
|
-
function
|
|
541
|
+
function getPreviewInputSelector(imagePath) {
|
|
574
542
|
const lowerPath = imagePath.toLowerCase();
|
|
575
|
-
if (lowerPath.endsWith(".ico")) {
|
|
543
|
+
if (lowerPath.endsWith(".ico") || lowerPath.endsWith(".gif")) {
|
|
576
544
|
return `"${imagePath}[0]"`;
|
|
577
545
|
}
|
|
578
546
|
return `"${imagePath}"`;
|
|
579
547
|
}
|
|
548
|
+
function isGif(imagePath) {
|
|
549
|
+
return imagePath.toLowerCase().endsWith(".gif");
|
|
550
|
+
}
|
|
551
|
+
function getGifOutputSuffix(outputPath) {
|
|
552
|
+
return isGif(outputPath) ? " -layers Optimize" : "";
|
|
553
|
+
}
|
|
554
|
+
function getCoalescePrefix(inputPath) {
|
|
555
|
+
return isGif(inputPath) ? "-coalesce " : "";
|
|
556
|
+
}
|
|
580
557
|
async function checkImageMagick() {
|
|
581
558
|
try {
|
|
582
559
|
await execAsync("command -v convert");
|
|
@@ -587,7 +564,7 @@ async function checkImageMagick() {
|
|
|
587
564
|
}
|
|
588
565
|
async function getDimensions(imagePath) {
|
|
589
566
|
try {
|
|
590
|
-
const input =
|
|
567
|
+
const input = getPreviewInputSelector(imagePath);
|
|
591
568
|
const { stdout } = await execAsync(
|
|
592
569
|
`convert ${input} -ping -format "%w %h" info:`
|
|
593
570
|
);
|
|
@@ -600,7 +577,7 @@ async function getDimensions(imagePath) {
|
|
|
600
577
|
}
|
|
601
578
|
async function getBorderColor(imagePath) {
|
|
602
579
|
try {
|
|
603
|
-
const input =
|
|
580
|
+
const input = getPreviewInputSelector(imagePath);
|
|
604
581
|
const { stdout } = await execAsync(
|
|
605
582
|
`convert ${input} -format "%[pixel:u.p{0,0}]" info:`
|
|
606
583
|
);
|
|
@@ -611,14 +588,15 @@ async function getBorderColor(imagePath) {
|
|
|
611
588
|
}
|
|
612
589
|
async function trim(inputPath, outputPath) {
|
|
613
590
|
try {
|
|
614
|
-
const
|
|
615
|
-
|
|
591
|
+
const coalesce = getCoalescePrefix(inputPath);
|
|
592
|
+
const gifSuffix = getGifOutputSuffix(outputPath);
|
|
593
|
+
await execAsync(`convert "${inputPath}" ${coalesce}-trim +repage${gifSuffix} "${outputPath}"`);
|
|
616
594
|
return true;
|
|
617
595
|
} catch {
|
|
618
596
|
return false;
|
|
619
597
|
}
|
|
620
598
|
}
|
|
621
|
-
async function
|
|
599
|
+
async function squarify(inputPath, outputPath) {
|
|
622
600
|
const dims = await getDimensions(inputPath);
|
|
623
601
|
if (!dims) return false;
|
|
624
602
|
const [width, height] = dims;
|
|
@@ -629,10 +607,11 @@ async function squarify2(inputPath, outputPath) {
|
|
|
629
607
|
return true;
|
|
630
608
|
}
|
|
631
609
|
const size = Math.max(width, height);
|
|
632
|
-
const
|
|
610
|
+
const coalesce = getCoalescePrefix(inputPath);
|
|
611
|
+
const gifSuffix = getGifOutputSuffix(outputPath);
|
|
633
612
|
try {
|
|
634
613
|
await execAsync(
|
|
635
|
-
`convert ${
|
|
614
|
+
`convert "${inputPath}" ${coalesce}-background none -gravity center -extent ${size}x${size}${gifSuffix} "${outputPath}"`
|
|
636
615
|
);
|
|
637
616
|
return true;
|
|
638
617
|
} catch {
|
|
@@ -651,8 +630,10 @@ async function scaleToSize(inputPath, outputPath, size) {
|
|
|
651
630
|
}
|
|
652
631
|
async function scaleWithPadding(inputPath, outputPath, width, height) {
|
|
653
632
|
try {
|
|
633
|
+
const coalesce = getCoalescePrefix(inputPath);
|
|
634
|
+
const gifSuffix = getGifOutputSuffix(outputPath);
|
|
654
635
|
await execAsync(
|
|
655
|
-
`convert "${inputPath}" -resize ${width}x${height} -background none -gravity center -extent ${width}x${height} "${outputPath}"`
|
|
636
|
+
`convert "${inputPath}" ${coalesce}-resize ${width}x${height} -background none -gravity center -extent ${width}x${height}${gifSuffix} "${outputPath}"`
|
|
656
637
|
);
|
|
657
638
|
return true;
|
|
658
639
|
} catch {
|
|
@@ -661,8 +642,10 @@ async function scaleWithPadding(inputPath, outputPath, width, height) {
|
|
|
661
642
|
}
|
|
662
643
|
async function resize(inputPath, outputPath, width, height) {
|
|
663
644
|
try {
|
|
645
|
+
const coalesce = getCoalescePrefix(inputPath);
|
|
646
|
+
const gifSuffix = getGifOutputSuffix(outputPath);
|
|
664
647
|
await execAsync(
|
|
665
|
-
`convert "${inputPath}" -resize ${width}x${height}
|
|
648
|
+
`convert "${inputPath}" ${coalesce}-resize ${width}x${height}!${gifSuffix} "${outputPath}"`
|
|
666
649
|
);
|
|
667
650
|
return true;
|
|
668
651
|
} catch {
|
|
@@ -671,8 +654,10 @@ async function resize(inputPath, outputPath, width, height) {
|
|
|
671
654
|
}
|
|
672
655
|
async function scaleFillCrop(inputPath, outputPath, width, height) {
|
|
673
656
|
try {
|
|
657
|
+
const coalesce = getCoalescePrefix(inputPath);
|
|
658
|
+
const gifSuffix = getGifOutputSuffix(outputPath);
|
|
674
659
|
await execAsync(
|
|
675
|
-
`convert "${inputPath}" -resize ${width}x${height}^ -background none -gravity center -extent ${width}x${height} "${outputPath}"`
|
|
660
|
+
`convert "${inputPath}" ${coalesce}-resize ${width}x${height}^ -background none -gravity center -extent ${width}x${height}${gifSuffix} "${outputPath}"`
|
|
676
661
|
);
|
|
677
662
|
return true;
|
|
678
663
|
} catch {
|
|
@@ -681,9 +666,10 @@ async function scaleFillCrop(inputPath, outputPath, width, height) {
|
|
|
681
666
|
}
|
|
682
667
|
async function removeBackground(inputPath, outputPath, color, fuzz) {
|
|
683
668
|
try {
|
|
684
|
-
const
|
|
669
|
+
const coalesce = getCoalescePrefix(inputPath);
|
|
670
|
+
const gifSuffix = getGifOutputSuffix(outputPath);
|
|
685
671
|
await execAsync(
|
|
686
|
-
`convert ${
|
|
672
|
+
`convert "${inputPath}" ${coalesce}-fuzz ${fuzz}% -transparent "${color}"${gifSuffix} "${outputPath}"`
|
|
687
673
|
);
|
|
688
674
|
return true;
|
|
689
675
|
} catch {
|
|
@@ -692,9 +678,10 @@ async function removeBackground(inputPath, outputPath, color, fuzz) {
|
|
|
692
678
|
}
|
|
693
679
|
async function removeBackgroundBorderOnly(inputPath, outputPath, color, fuzz) {
|
|
694
680
|
try {
|
|
695
|
-
const
|
|
681
|
+
const coalesce = getCoalescePrefix(inputPath);
|
|
682
|
+
const gifSuffix = getGifOutputSuffix(outputPath);
|
|
696
683
|
await execAsync(
|
|
697
|
-
`convert ${
|
|
684
|
+
`convert "${inputPath}" ${coalesce}-bordercolor "${color}" -border 1x1 -fill none -fuzz ${fuzz}% -draw "matte 0,0 floodfill" -shave 1x1${gifSuffix} "${outputPath}"`
|
|
698
685
|
);
|
|
699
686
|
return true;
|
|
700
687
|
} catch {
|
|
@@ -721,19 +708,194 @@ async function createIcoFromMultiple(pngPaths, outputPath) {
|
|
|
721
708
|
return false;
|
|
722
709
|
}
|
|
723
710
|
}
|
|
711
|
+
function ensureDir(filePath) {
|
|
712
|
+
const dir = dirname3(filePath);
|
|
713
|
+
if (!existsSync2(dir)) {
|
|
714
|
+
mkdirSync2(dir, { recursive: true });
|
|
715
|
+
}
|
|
716
|
+
}
|
|
724
717
|
function cleanup(...files) {
|
|
725
718
|
for (const file of files) {
|
|
726
719
|
try {
|
|
727
|
-
if (
|
|
720
|
+
if (existsSync2(file)) {
|
|
728
721
|
unlinkSync(file);
|
|
729
722
|
}
|
|
730
723
|
} catch {
|
|
731
724
|
}
|
|
732
725
|
}
|
|
733
726
|
}
|
|
727
|
+
async function extractFirstFrame(inputPath, outputPath, frameIndex = 0) {
|
|
728
|
+
const lowerPath = inputPath.toLowerCase();
|
|
729
|
+
if (!lowerPath.endsWith(".gif") && !lowerPath.endsWith(".ico")) {
|
|
730
|
+
try {
|
|
731
|
+
copyFileSync(inputPath, outputPath);
|
|
732
|
+
return true;
|
|
733
|
+
} catch {
|
|
734
|
+
return false;
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
try {
|
|
738
|
+
if (lowerPath.endsWith(".gif")) {
|
|
739
|
+
await execAsync(`convert "${inputPath}" -coalesce miff:- | convert "miff:-[${frameIndex}]" "${outputPath}"`);
|
|
740
|
+
} else {
|
|
741
|
+
await execAsync(`convert "${inputPath}[${frameIndex}]" "${outputPath}"`);
|
|
742
|
+
}
|
|
743
|
+
return true;
|
|
744
|
+
} catch {
|
|
745
|
+
return false;
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
function isMultiFrame(imagePath) {
|
|
749
|
+
const lowerPath = imagePath.toLowerCase();
|
|
750
|
+
return lowerPath.endsWith(".gif") || lowerPath.endsWith(".ico");
|
|
751
|
+
}
|
|
752
|
+
async function getFrameCount(imagePath) {
|
|
753
|
+
try {
|
|
754
|
+
const { stdout } = await execAsync(
|
|
755
|
+
`identify -format "%n\\n" "${imagePath}" | head -1`
|
|
756
|
+
);
|
|
757
|
+
const count = parseInt(stdout.trim(), 10);
|
|
758
|
+
return Number.isNaN(count) ? 1 : count;
|
|
759
|
+
} catch {
|
|
760
|
+
return 1;
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
async function extractAllFrames(inputPath, outputDir, baseName) {
|
|
764
|
+
try {
|
|
765
|
+
ensureDir(`${outputDir}/dummy`);
|
|
766
|
+
await execAsync(
|
|
767
|
+
`convert "${inputPath}" -coalesce "${outputDir}/${baseName}-%04d.png"`
|
|
768
|
+
);
|
|
769
|
+
const { stdout } = await execAsync(`ls -1 "${outputDir}/${baseName}"-*.png 2>/dev/null || true`);
|
|
770
|
+
const files = stdout.trim().split("\n").filter((f) => f.length > 0);
|
|
771
|
+
return files;
|
|
772
|
+
} catch {
|
|
773
|
+
return [];
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
async function flipHorizontal(inputPath, outputPath) {
|
|
777
|
+
try {
|
|
778
|
+
const coalesce = getCoalescePrefix(inputPath);
|
|
779
|
+
const gifSuffix = getGifOutputSuffix(outputPath);
|
|
780
|
+
await execAsync(
|
|
781
|
+
`convert "${inputPath}" ${coalesce}-flop${gifSuffix} "${outputPath}"`
|
|
782
|
+
);
|
|
783
|
+
return true;
|
|
784
|
+
} catch {
|
|
785
|
+
return false;
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
async function flipVertical(inputPath, outputPath) {
|
|
789
|
+
try {
|
|
790
|
+
const coalesce = getCoalescePrefix(inputPath);
|
|
791
|
+
const gifSuffix = getGifOutputSuffix(outputPath);
|
|
792
|
+
await execAsync(
|
|
793
|
+
`convert "${inputPath}" ${coalesce}-flip${gifSuffix} "${outputPath}"`
|
|
794
|
+
);
|
|
795
|
+
return true;
|
|
796
|
+
} catch {
|
|
797
|
+
return false;
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
async function rotate(inputPath, outputPath, degrees) {
|
|
801
|
+
try {
|
|
802
|
+
const coalesce = getCoalescePrefix(inputPath);
|
|
803
|
+
const gifSuffix = getGifOutputSuffix(outputPath);
|
|
804
|
+
await execAsync(
|
|
805
|
+
`convert "${inputPath}" ${coalesce}-rotate ${degrees} -background none${gifSuffix} "${outputPath}"`
|
|
806
|
+
);
|
|
807
|
+
return true;
|
|
808
|
+
} catch {
|
|
809
|
+
return false;
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
async function filterGrayscale(inputPath, outputPath) {
|
|
813
|
+
try {
|
|
814
|
+
const coalesce = getCoalescePrefix(inputPath);
|
|
815
|
+
const gifSuffix = getGifOutputSuffix(outputPath);
|
|
816
|
+
await execAsync(
|
|
817
|
+
`convert "${inputPath}" ${coalesce}-colorspace Gray${gifSuffix} "${outputPath}"`
|
|
818
|
+
);
|
|
819
|
+
return true;
|
|
820
|
+
} catch {
|
|
821
|
+
return false;
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
async function filterSepia(inputPath, outputPath, intensity = 80) {
|
|
825
|
+
try {
|
|
826
|
+
const coalesce = getCoalescePrefix(inputPath);
|
|
827
|
+
const gifSuffix = getGifOutputSuffix(outputPath);
|
|
828
|
+
await execAsync(
|
|
829
|
+
`convert "${inputPath}" ${coalesce}-sepia-tone ${intensity}%${gifSuffix} "${outputPath}"`
|
|
830
|
+
);
|
|
831
|
+
return true;
|
|
832
|
+
} catch {
|
|
833
|
+
return false;
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
async function filterInvert(inputPath, outputPath) {
|
|
837
|
+
try {
|
|
838
|
+
const coalesce = getCoalescePrefix(inputPath);
|
|
839
|
+
const gifSuffix = getGifOutputSuffix(outputPath);
|
|
840
|
+
await execAsync(
|
|
841
|
+
`convert "${inputPath}" ${coalesce}-negate${gifSuffix} "${outputPath}"`
|
|
842
|
+
);
|
|
843
|
+
return true;
|
|
844
|
+
} catch {
|
|
845
|
+
return false;
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
async function filterVintage(inputPath, outputPath) {
|
|
849
|
+
try {
|
|
850
|
+
const coalesce = getCoalescePrefix(inputPath);
|
|
851
|
+
const gifSuffix = getGifOutputSuffix(outputPath);
|
|
852
|
+
await execAsync(
|
|
853
|
+
`convert "${inputPath}" ${coalesce}-modulate 100,70,100 -fill "#704214" -colorize 15%${gifSuffix} "${outputPath}"`
|
|
854
|
+
);
|
|
855
|
+
return true;
|
|
856
|
+
} catch {
|
|
857
|
+
return false;
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
async function filterVivid(inputPath, outputPath) {
|
|
861
|
+
try {
|
|
862
|
+
const coalesce = getCoalescePrefix(inputPath);
|
|
863
|
+
const gifSuffix = getGifOutputSuffix(outputPath);
|
|
864
|
+
await execAsync(
|
|
865
|
+
`convert "${inputPath}" ${coalesce}-modulate 100,130,100${gifSuffix} "${outputPath}"`
|
|
866
|
+
);
|
|
867
|
+
return true;
|
|
868
|
+
} catch {
|
|
869
|
+
return false;
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
async function addBorder(inputPath, outputPath, width, color) {
|
|
873
|
+
try {
|
|
874
|
+
const coalesce = getCoalescePrefix(inputPath);
|
|
875
|
+
const gifSuffix = getGifOutputSuffix(outputPath);
|
|
876
|
+
await execAsync(
|
|
877
|
+
`convert "${inputPath}" ${coalesce}-bordercolor "${color}" -border ${width}${gifSuffix} "${outputPath}"`
|
|
878
|
+
);
|
|
879
|
+
return true;
|
|
880
|
+
} catch {
|
|
881
|
+
return false;
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
async function replaceColor(inputPath, outputPath, fromColor, toColor, fuzz) {
|
|
885
|
+
try {
|
|
886
|
+
const coalesce = getCoalescePrefix(inputPath);
|
|
887
|
+
const gifSuffix = getGifOutputSuffix(outputPath);
|
|
888
|
+
await execAsync(
|
|
889
|
+
`convert "${inputPath}" ${coalesce}-fuzz ${fuzz}% -fill "${toColor}" -opaque "${fromColor}"${gifSuffix} "${outputPath}"`
|
|
890
|
+
);
|
|
891
|
+
return true;
|
|
892
|
+
} catch {
|
|
893
|
+
return false;
|
|
894
|
+
}
|
|
895
|
+
}
|
|
734
896
|
|
|
735
897
|
// src/lib/paths.ts
|
|
736
|
-
import { basename, dirname as
|
|
898
|
+
import { basename, dirname as dirname4, extname, resolve } from "path";
|
|
737
899
|
function windowsToWsl(winPath) {
|
|
738
900
|
if (winPath.startsWith("/mnt/")) {
|
|
739
901
|
return winPath;
|
|
@@ -762,7 +924,7 @@ function normalizePath(inputPath) {
|
|
|
762
924
|
return resolve(inputPath);
|
|
763
925
|
}
|
|
764
926
|
function getFileInfo(filePath) {
|
|
765
|
-
const dir =
|
|
927
|
+
const dir = dirname4(filePath);
|
|
766
928
|
const base = basename(filePath);
|
|
767
929
|
const ext = extname(filePath);
|
|
768
930
|
const name = base.slice(0, -ext.length);
|
|
@@ -798,6 +960,16 @@ function getOverride(message) {
|
|
|
798
960
|
}
|
|
799
961
|
return void 0;
|
|
800
962
|
}
|
|
963
|
+
async function text(message, defaultValue) {
|
|
964
|
+
if (useDefaults) return defaultValue ?? "";
|
|
965
|
+
const response = await prompts({
|
|
966
|
+
type: "text",
|
|
967
|
+
name: "value",
|
|
968
|
+
message,
|
|
969
|
+
initial: defaultValue
|
|
970
|
+
});
|
|
971
|
+
return response.value ?? defaultValue ?? "";
|
|
972
|
+
}
|
|
801
973
|
async function number(message, defaultValue, min, max) {
|
|
802
974
|
const override = getOverride(message);
|
|
803
975
|
if (override !== void 0) return Number(override);
|
|
@@ -865,38 +1037,557 @@ async function pauseOnError(message = "Press Enter to close...") {
|
|
|
865
1037
|
});
|
|
866
1038
|
}
|
|
867
1039
|
|
|
868
|
-
// src/tools/
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
{
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
{
|
|
893
|
-
{
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
1040
|
+
// src/tools/border.ts
|
|
1041
|
+
async function processImage(input, options) {
|
|
1042
|
+
const fileInfo = getFileInfo(input);
|
|
1043
|
+
const outputExt = fileInfo.extension.toLowerCase() === ".gif" ? ".gif" : ".png";
|
|
1044
|
+
const output = `${fileInfo.dirname}/${fileInfo.filename}_border${outputExt}`;
|
|
1045
|
+
wip(`Adding ${options.width}px ${options.color} border...`);
|
|
1046
|
+
const success2 = await addBorder(input, output, options.width, options.color);
|
|
1047
|
+
if (!success2) {
|
|
1048
|
+
wipDone(false, "Border failed");
|
|
1049
|
+
return false;
|
|
1050
|
+
}
|
|
1051
|
+
wipDone(true, "Border added");
|
|
1052
|
+
const finalDims = await getDimensions(output);
|
|
1053
|
+
console.log("");
|
|
1054
|
+
if (finalDims) {
|
|
1055
|
+
console.log(`Output: ${output} (${finalDims[0]}x${finalDims[1]})`);
|
|
1056
|
+
} else {
|
|
1057
|
+
console.log(`Output: ${output}`);
|
|
1058
|
+
}
|
|
1059
|
+
return true;
|
|
1060
|
+
}
|
|
1061
|
+
async function collectOptionsCLI() {
|
|
1062
|
+
console.log("");
|
|
1063
|
+
console.log(`${BOLD}Border Settings:${RESET}`);
|
|
1064
|
+
console.log(` ${DIM}Width: How thick the border should be (in pixels)${RESET}`);
|
|
1065
|
+
console.log(` ${DIM}Color: Hex color (#fff), named color (white), or rgb(...)${RESET}`);
|
|
1066
|
+
console.log("");
|
|
1067
|
+
let width = await number("Border width (px)", 10, 1, 200);
|
|
1068
|
+
if (width < 1) {
|
|
1069
|
+
width = 10;
|
|
1070
|
+
}
|
|
1071
|
+
console.log("");
|
|
1072
|
+
let color = await text("Border color", "#ffffff");
|
|
1073
|
+
if (!color) {
|
|
1074
|
+
color = "#ffffff";
|
|
1075
|
+
}
|
|
1076
|
+
return { width, color };
|
|
1077
|
+
}
|
|
1078
|
+
async function run(inputRaw) {
|
|
1079
|
+
if (!await checkImageMagick()) {
|
|
1080
|
+
error("ImageMagick not found. Please install it:");
|
|
1081
|
+
console.log(" sudo apt update && sudo apt install imagemagick");
|
|
1082
|
+
await pauseOnError();
|
|
1083
|
+
return false;
|
|
1084
|
+
}
|
|
1085
|
+
const input = normalizePath(inputRaw);
|
|
1086
|
+
if (!existsSync3(input)) {
|
|
1087
|
+
error(`File not found: ${input}`);
|
|
1088
|
+
await pauseOnError();
|
|
1089
|
+
return false;
|
|
1090
|
+
}
|
|
1091
|
+
header("PicLet Border");
|
|
1092
|
+
wip("Analyzing image...");
|
|
1093
|
+
const dims = await getDimensions(input);
|
|
1094
|
+
if (!dims) {
|
|
1095
|
+
wipDone(false, "Failed to read image");
|
|
1096
|
+
return false;
|
|
1097
|
+
}
|
|
1098
|
+
wipDone(true, `Size: ${dims[0]}x${dims[1]}`);
|
|
1099
|
+
const options = await collectOptionsCLI();
|
|
1100
|
+
console.log("");
|
|
1101
|
+
return processImage(input, options);
|
|
1102
|
+
}
|
|
1103
|
+
async function runGUI(inputRaw) {
|
|
1104
|
+
const input = normalizePath(inputRaw);
|
|
1105
|
+
if (!existsSync3(input)) {
|
|
1106
|
+
error(`File not found: ${input}`);
|
|
1107
|
+
return false;
|
|
1108
|
+
}
|
|
1109
|
+
const dims = await getDimensions(input);
|
|
1110
|
+
if (!dims) {
|
|
1111
|
+
error("Failed to read image dimensions");
|
|
1112
|
+
return false;
|
|
1113
|
+
}
|
|
1114
|
+
return startGuiServer({
|
|
1115
|
+
htmlFile: "border.html",
|
|
1116
|
+
title: "PicLet - Border",
|
|
1117
|
+
imageInfo: {
|
|
1118
|
+
filePath: input,
|
|
1119
|
+
fileName: basename2(input),
|
|
1120
|
+
width: dims[0],
|
|
1121
|
+
height: dims[1],
|
|
1122
|
+
borderColor: null
|
|
1123
|
+
},
|
|
1124
|
+
defaults: {
|
|
1125
|
+
width: 10,
|
|
1126
|
+
color: "#ffffff"
|
|
1127
|
+
},
|
|
1128
|
+
onPreview: async (opts) => {
|
|
1129
|
+
const width = opts.width ?? 10;
|
|
1130
|
+
const color = opts.color ?? "#ffffff";
|
|
1131
|
+
return generatePreview(input, { width, color });
|
|
1132
|
+
},
|
|
1133
|
+
onProcess: async (opts) => {
|
|
1134
|
+
const logs = [];
|
|
1135
|
+
if (!await checkImageMagick()) {
|
|
1136
|
+
return {
|
|
1137
|
+
success: false,
|
|
1138
|
+
error: "ImageMagick not found",
|
|
1139
|
+
logs: [{ type: "error", message: "ImageMagick not found. Install with: sudo apt install imagemagick" }]
|
|
1140
|
+
};
|
|
1141
|
+
}
|
|
1142
|
+
logs.push({ type: "info", message: `Processing ${basename2(input)}...` });
|
|
1143
|
+
const width = opts.width ?? 10;
|
|
1144
|
+
const color = opts.color ?? "#ffffff";
|
|
1145
|
+
const options = { width, color };
|
|
1146
|
+
const fileInfo = getFileInfo(input);
|
|
1147
|
+
const outputExt = fileInfo.extension.toLowerCase() === ".gif" ? ".gif" : ".png";
|
|
1148
|
+
const output = `${fileInfo.dirname}/${fileInfo.filename}_border${outputExt}`;
|
|
1149
|
+
logs.push({ type: "info", message: `Adding ${width}px border...` });
|
|
1150
|
+
const success2 = await addBorder(input, output, width, color);
|
|
1151
|
+
if (success2) {
|
|
1152
|
+
logs.push({ type: "success", message: "Border added" });
|
|
1153
|
+
const finalDims = await getDimensions(output);
|
|
1154
|
+
const sizeStr = finalDims ? ` (${finalDims[0]}x${finalDims[1]})` : "";
|
|
1155
|
+
return {
|
|
1156
|
+
success: true,
|
|
1157
|
+
output: `${basename2(output)}${sizeStr}`,
|
|
1158
|
+
logs
|
|
1159
|
+
};
|
|
1160
|
+
}
|
|
1161
|
+
logs.push({ type: "error", message: "Border failed" });
|
|
1162
|
+
return {
|
|
1163
|
+
success: false,
|
|
1164
|
+
error: "Processing failed",
|
|
1165
|
+
logs
|
|
1166
|
+
};
|
|
1167
|
+
}
|
|
1168
|
+
});
|
|
1169
|
+
}
|
|
1170
|
+
async function generatePreview(input, options) {
|
|
1171
|
+
const tempDir = tmpdir();
|
|
1172
|
+
const timestamp = Date.now();
|
|
1173
|
+
const tempSource = join3(tempDir, `piclet-preview-${timestamp}-src.png`);
|
|
1174
|
+
const tempOutput = join3(tempDir, `piclet-preview-${timestamp}.png`);
|
|
1175
|
+
try {
|
|
1176
|
+
let previewInput = input;
|
|
1177
|
+
if (isMultiFrame(input)) {
|
|
1178
|
+
if (!await extractFirstFrame(input, tempSource)) {
|
|
1179
|
+
return { success: false, error: "Failed to extract frame" };
|
|
1180
|
+
}
|
|
1181
|
+
previewInput = tempSource;
|
|
1182
|
+
}
|
|
1183
|
+
const success2 = await addBorder(previewInput, tempOutput, options.width, options.color);
|
|
1184
|
+
if (!success2) {
|
|
1185
|
+
cleanup(tempSource, tempOutput);
|
|
1186
|
+
return { success: false, error: "Border failed" };
|
|
1187
|
+
}
|
|
1188
|
+
const buffer = readFileSync2(tempOutput);
|
|
1189
|
+
const base64 = buffer.toString("base64");
|
|
1190
|
+
const imageData = `data:image/png;base64,${base64}`;
|
|
1191
|
+
const dims = await getDimensions(tempOutput);
|
|
1192
|
+
cleanup(tempSource, tempOutput);
|
|
1193
|
+
return {
|
|
1194
|
+
success: true,
|
|
1195
|
+
imageData,
|
|
1196
|
+
width: dims?.[0],
|
|
1197
|
+
height: dims?.[1]
|
|
1198
|
+
};
|
|
1199
|
+
} catch (err) {
|
|
1200
|
+
cleanup(tempSource, tempOutput);
|
|
1201
|
+
return { success: false, error: err.message };
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
1204
|
+
var config = {
|
|
1205
|
+
id: "border",
|
|
1206
|
+
name: "Add Border",
|
|
1207
|
+
icon: "border.ico",
|
|
1208
|
+
extensions: [".png", ".jpg", ".jpeg", ".gif", ".bmp"]
|
|
1209
|
+
};
|
|
1210
|
+
|
|
1211
|
+
// src/cli/utils.ts
|
|
1212
|
+
import { extname as extname3 } from "path";
|
|
1213
|
+
import { dirname as dirname9 } from "path";
|
|
1214
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
1215
|
+
import chalk from "chalk";
|
|
1216
|
+
|
|
1217
|
+
// src/tools/extract-frames.ts
|
|
1218
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync3 } from "fs";
|
|
1219
|
+
import { tmpdir as tmpdir2 } from "os";
|
|
1220
|
+
import { basename as basename3, join as join4 } from "path";
|
|
1221
|
+
async function run2(inputRaw) {
|
|
1222
|
+
if (!await checkImageMagick()) {
|
|
1223
|
+
error("ImageMagick not found. Please install it:");
|
|
1224
|
+
console.log(" sudo apt update && sudo apt install imagemagick");
|
|
1225
|
+
await pauseOnError();
|
|
1226
|
+
return false;
|
|
1227
|
+
}
|
|
1228
|
+
const input = normalizePath(inputRaw);
|
|
1229
|
+
if (!existsSync4(input)) {
|
|
1230
|
+
error(`File not found: ${input}`);
|
|
1231
|
+
await pauseOnError();
|
|
1232
|
+
return false;
|
|
1233
|
+
}
|
|
1234
|
+
header("PicLet Extract Frames");
|
|
1235
|
+
wip("Analyzing GIF...");
|
|
1236
|
+
const dims = await getDimensions(input);
|
|
1237
|
+
if (!dims) {
|
|
1238
|
+
wipDone(false, "Failed to read image");
|
|
1239
|
+
return false;
|
|
1240
|
+
}
|
|
1241
|
+
const frameCount = await getFrameCount(input);
|
|
1242
|
+
wipDone(true, `Size: ${dims[0]}x${dims[1]}, ${frameCount} frames`);
|
|
1243
|
+
if (frameCount <= 1) {
|
|
1244
|
+
info("Image has only 1 frame, nothing to extract");
|
|
1245
|
+
return true;
|
|
1246
|
+
}
|
|
1247
|
+
console.log("");
|
|
1248
|
+
console.log(`${BOLD}Extract Frames:${RESET}`);
|
|
1249
|
+
console.log(` ${DIM}This will create ${frameCount} PNG files${RESET}`);
|
|
1250
|
+
console.log("");
|
|
1251
|
+
const proceed = await confirm(`Extract ${frameCount} frames?`, true);
|
|
1252
|
+
if (!proceed) {
|
|
1253
|
+
info("Cancelled");
|
|
1254
|
+
return false;
|
|
1255
|
+
}
|
|
1256
|
+
const fileInfo = getFileInfo(input);
|
|
1257
|
+
const outputDir = `${fileInfo.dirname}/${fileInfo.filename}_frames`;
|
|
1258
|
+
console.log("");
|
|
1259
|
+
wip("Extracting frames...");
|
|
1260
|
+
mkdirSync3(outputDir, { recursive: true });
|
|
1261
|
+
const frames = await extractAllFrames(input, outputDir, "frame");
|
|
1262
|
+
if (frames.length === 0) {
|
|
1263
|
+
wipDone(false, "Extraction failed");
|
|
1264
|
+
return false;
|
|
1265
|
+
}
|
|
1266
|
+
wipDone(true, `Extracted ${frames.length} frames`);
|
|
1267
|
+
console.log("");
|
|
1268
|
+
success(`Output: ${outputDir}/`);
|
|
1269
|
+
return true;
|
|
1270
|
+
}
|
|
1271
|
+
async function runGUI2(inputRaw) {
|
|
1272
|
+
const input = normalizePath(inputRaw);
|
|
1273
|
+
if (!existsSync4(input)) {
|
|
1274
|
+
error(`File not found: ${input}`);
|
|
1275
|
+
return false;
|
|
1276
|
+
}
|
|
1277
|
+
const dims = await getDimensions(input);
|
|
1278
|
+
if (!dims) {
|
|
1279
|
+
error("Failed to read image dimensions");
|
|
1280
|
+
return false;
|
|
1281
|
+
}
|
|
1282
|
+
const frameCount = await getFrameCount(input);
|
|
1283
|
+
const fileInfo = getFileInfo(input);
|
|
1284
|
+
return startGuiServer({
|
|
1285
|
+
htmlFile: "extract-frames.html",
|
|
1286
|
+
title: "PicLet - Extract Frames",
|
|
1287
|
+
imageInfo: {
|
|
1288
|
+
filePath: input,
|
|
1289
|
+
fileName: basename3(input),
|
|
1290
|
+
width: dims[0],
|
|
1291
|
+
height: dims[1],
|
|
1292
|
+
borderColor: null
|
|
1293
|
+
},
|
|
1294
|
+
defaults: {
|
|
1295
|
+
frameCount
|
|
1296
|
+
},
|
|
1297
|
+
onPreview: async (opts) => {
|
|
1298
|
+
const frameIndex = opts.frameIndex ?? 0;
|
|
1299
|
+
return generateFramePreview(input, frameIndex);
|
|
1300
|
+
},
|
|
1301
|
+
onProcess: async () => {
|
|
1302
|
+
const logs = [];
|
|
1303
|
+
if (!await checkImageMagick()) {
|
|
1304
|
+
return {
|
|
1305
|
+
success: false,
|
|
1306
|
+
error: "ImageMagick not found",
|
|
1307
|
+
logs: [{ type: "error", message: "ImageMagick not found. Install with: sudo apt install imagemagick" }]
|
|
1308
|
+
};
|
|
1309
|
+
}
|
|
1310
|
+
logs.push({ type: "info", message: `Extracting ${frameCount} frames...` });
|
|
1311
|
+
const outputDir = `${fileInfo.dirname}/${fileInfo.filename}_frames`;
|
|
1312
|
+
mkdirSync3(outputDir, { recursive: true });
|
|
1313
|
+
const frames = await extractAllFrames(input, outputDir, "frame");
|
|
1314
|
+
if (frames.length > 0) {
|
|
1315
|
+
logs.push({ type: "success", message: `Extracted ${frames.length} frames` });
|
|
1316
|
+
return {
|
|
1317
|
+
success: true,
|
|
1318
|
+
output: `${frames.length} frames -> ${fileInfo.filename}_frames/`,
|
|
1319
|
+
logs
|
|
1320
|
+
};
|
|
1321
|
+
}
|
|
1322
|
+
logs.push({ type: "error", message: "Extraction failed" });
|
|
1323
|
+
return { success: false, error: "Extraction failed", logs };
|
|
1324
|
+
}
|
|
1325
|
+
});
|
|
1326
|
+
}
|
|
1327
|
+
async function generateFramePreview(input, frameIndex) {
|
|
1328
|
+
const tempDir = tmpdir2();
|
|
1329
|
+
const timestamp = Date.now();
|
|
1330
|
+
const tempOutput = join4(tempDir, `piclet-frame-${timestamp}.png`);
|
|
1331
|
+
try {
|
|
1332
|
+
if (!await extractFirstFrame(input, tempOutput, frameIndex)) {
|
|
1333
|
+
return { success: false, error: "Failed to extract frame" };
|
|
1334
|
+
}
|
|
1335
|
+
const buffer = readFileSync3(tempOutput);
|
|
1336
|
+
const base64 = buffer.toString("base64");
|
|
1337
|
+
const imageData = `data:image/png;base64,${base64}`;
|
|
1338
|
+
const dims = await getDimensions(tempOutput);
|
|
1339
|
+
cleanup(tempOutput);
|
|
1340
|
+
return {
|
|
1341
|
+
success: true,
|
|
1342
|
+
imageData,
|
|
1343
|
+
width: dims?.[0],
|
|
1344
|
+
height: dims?.[1]
|
|
1345
|
+
};
|
|
1346
|
+
} catch (err) {
|
|
1347
|
+
cleanup(tempOutput);
|
|
1348
|
+
return { success: false, error: err.message };
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1351
|
+
var config2 = {
|
|
1352
|
+
id: "extract-frames",
|
|
1353
|
+
name: "Extract Frames",
|
|
1354
|
+
icon: "extract.ico",
|
|
1355
|
+
extensions: [".gif"]
|
|
1356
|
+
};
|
|
1357
|
+
|
|
1358
|
+
// src/tools/filter.ts
|
|
1359
|
+
import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
|
|
1360
|
+
import { tmpdir as tmpdir3 } from "os";
|
|
1361
|
+
import { basename as basename4, join as join5 } from "path";
|
|
1362
|
+
var FILTER_LABELS = {
|
|
1363
|
+
"grayscale": "Grayscale",
|
|
1364
|
+
"sepia": "Sepia",
|
|
1365
|
+
"invert": "Invert",
|
|
1366
|
+
"vintage": "Vintage",
|
|
1367
|
+
"vivid": "Vivid"
|
|
1368
|
+
};
|
|
1369
|
+
async function applyFilter(input, output, filter) {
|
|
1370
|
+
switch (filter) {
|
|
1371
|
+
case "grayscale":
|
|
1372
|
+
return filterGrayscale(input, output);
|
|
1373
|
+
case "sepia":
|
|
1374
|
+
return filterSepia(input, output);
|
|
1375
|
+
case "invert":
|
|
1376
|
+
return filterInvert(input, output);
|
|
1377
|
+
case "vintage":
|
|
1378
|
+
return filterVintage(input, output);
|
|
1379
|
+
case "vivid":
|
|
1380
|
+
return filterVivid(input, output);
|
|
1381
|
+
default:
|
|
1382
|
+
return false;
|
|
1383
|
+
}
|
|
1384
|
+
}
|
|
1385
|
+
async function processImage2(input, options) {
|
|
1386
|
+
const fileInfo = getFileInfo(input);
|
|
1387
|
+
const outputExt = fileInfo.extension.toLowerCase() === ".gif" ? ".gif" : ".png";
|
|
1388
|
+
const output = `${fileInfo.dirname}/${fileInfo.filename}_${options.filter}${outputExt}`;
|
|
1389
|
+
wip(`Applying ${FILTER_LABELS[options.filter]} filter...`);
|
|
1390
|
+
const success2 = await applyFilter(input, output, options.filter);
|
|
1391
|
+
if (!success2) {
|
|
1392
|
+
wipDone(false, "Filter failed");
|
|
1393
|
+
return false;
|
|
1394
|
+
}
|
|
1395
|
+
wipDone(true, FILTER_LABELS[options.filter]);
|
|
1396
|
+
const finalDims = await getDimensions(output);
|
|
1397
|
+
console.log("");
|
|
1398
|
+
if (finalDims) {
|
|
1399
|
+
console.log(`Output: ${output} (${finalDims[0]}x${finalDims[1]})`);
|
|
1400
|
+
} else {
|
|
1401
|
+
console.log(`Output: ${output}`);
|
|
1402
|
+
}
|
|
1403
|
+
return true;
|
|
1404
|
+
}
|
|
1405
|
+
async function collectOptionsCLI2() {
|
|
1406
|
+
console.log("");
|
|
1407
|
+
console.log(`${BOLD}Filter Options:${RESET}`);
|
|
1408
|
+
console.log(` ${DIM}1. Grayscale - Black and white${RESET}`);
|
|
1409
|
+
console.log(` ${DIM}2. Sepia - Warm brownish tint${RESET}`);
|
|
1410
|
+
console.log(` ${DIM}3. Invert - Negative colors${RESET}`);
|
|
1411
|
+
console.log(` ${DIM}4. Vintage - Desaturated warm tone${RESET}`);
|
|
1412
|
+
console.log(` ${DIM}5. Vivid - Increased saturation${RESET}`);
|
|
1413
|
+
console.log("");
|
|
1414
|
+
const filter = await select(
|
|
1415
|
+
"Select filter",
|
|
1416
|
+
[
|
|
1417
|
+
{ value: "grayscale", title: "Grayscale" },
|
|
1418
|
+
{ value: "sepia", title: "Sepia" },
|
|
1419
|
+
{ value: "invert", title: "Invert" },
|
|
1420
|
+
{ value: "vintage", title: "Vintage" },
|
|
1421
|
+
{ value: "vivid", title: "Vivid" }
|
|
1422
|
+
],
|
|
1423
|
+
"grayscale"
|
|
1424
|
+
);
|
|
1425
|
+
return { filter: filter ?? "grayscale" };
|
|
1426
|
+
}
|
|
1427
|
+
async function run3(inputRaw) {
|
|
1428
|
+
if (!await checkImageMagick()) {
|
|
1429
|
+
error("ImageMagick not found. Please install it:");
|
|
1430
|
+
console.log(" sudo apt update && sudo apt install imagemagick");
|
|
1431
|
+
await pauseOnError();
|
|
1432
|
+
return false;
|
|
1433
|
+
}
|
|
1434
|
+
const input = normalizePath(inputRaw);
|
|
1435
|
+
if (!existsSync5(input)) {
|
|
1436
|
+
error(`File not found: ${input}`);
|
|
1437
|
+
await pauseOnError();
|
|
1438
|
+
return false;
|
|
1439
|
+
}
|
|
1440
|
+
header("PicLet Filter");
|
|
1441
|
+
wip("Analyzing image...");
|
|
1442
|
+
const dims = await getDimensions(input);
|
|
1443
|
+
if (!dims) {
|
|
1444
|
+
wipDone(false, "Failed to read image");
|
|
1445
|
+
return false;
|
|
1446
|
+
}
|
|
1447
|
+
wipDone(true, `Size: ${dims[0]}x${dims[1]}`);
|
|
1448
|
+
const options = await collectOptionsCLI2();
|
|
1449
|
+
console.log("");
|
|
1450
|
+
return processImage2(input, options);
|
|
1451
|
+
}
|
|
1452
|
+
async function runGUI3(inputRaw) {
|
|
1453
|
+
const input = normalizePath(inputRaw);
|
|
1454
|
+
if (!existsSync5(input)) {
|
|
1455
|
+
error(`File not found: ${input}`);
|
|
1456
|
+
return false;
|
|
1457
|
+
}
|
|
1458
|
+
const dims = await getDimensions(input);
|
|
1459
|
+
if (!dims) {
|
|
1460
|
+
error("Failed to read image dimensions");
|
|
1461
|
+
return false;
|
|
1462
|
+
}
|
|
1463
|
+
return startGuiServer({
|
|
1464
|
+
htmlFile: "filter.html",
|
|
1465
|
+
title: "PicLet - Filter",
|
|
1466
|
+
imageInfo: {
|
|
1467
|
+
filePath: input,
|
|
1468
|
+
fileName: basename4(input),
|
|
1469
|
+
width: dims[0],
|
|
1470
|
+
height: dims[1],
|
|
1471
|
+
borderColor: null
|
|
1472
|
+
},
|
|
1473
|
+
defaults: {
|
|
1474
|
+
filter: "grayscale"
|
|
1475
|
+
},
|
|
1476
|
+
onPreview: async (opts) => {
|
|
1477
|
+
const filter = opts.filter ?? "grayscale";
|
|
1478
|
+
return generatePreview2(input, { filter });
|
|
1479
|
+
},
|
|
1480
|
+
onProcess: async (opts) => {
|
|
1481
|
+
const logs = [];
|
|
1482
|
+
if (!await checkImageMagick()) {
|
|
1483
|
+
return {
|
|
1484
|
+
success: false,
|
|
1485
|
+
error: "ImageMagick not found",
|
|
1486
|
+
logs: [{ type: "error", message: "ImageMagick not found. Install with: sudo apt install imagemagick" }]
|
|
1487
|
+
};
|
|
1488
|
+
}
|
|
1489
|
+
logs.push({ type: "info", message: `Processing ${basename4(input)}...` });
|
|
1490
|
+
const filter = opts.filter ?? "grayscale";
|
|
1491
|
+
const options = { filter };
|
|
1492
|
+
const fileInfo = getFileInfo(input);
|
|
1493
|
+
const outputExt = fileInfo.extension.toLowerCase() === ".gif" ? ".gif" : ".png";
|
|
1494
|
+
const output = `${fileInfo.dirname}/${fileInfo.filename}_${filter}${outputExt}`;
|
|
1495
|
+
logs.push({ type: "info", message: `Applying ${FILTER_LABELS[filter]} filter...` });
|
|
1496
|
+
const success2 = await applyFilter(input, output, filter);
|
|
1497
|
+
if (success2) {
|
|
1498
|
+
logs.push({ type: "success", message: "Filter applied" });
|
|
1499
|
+
const finalDims = await getDimensions(output);
|
|
1500
|
+
const sizeStr = finalDims ? ` (${finalDims[0]}x${finalDims[1]})` : "";
|
|
1501
|
+
return {
|
|
1502
|
+
success: true,
|
|
1503
|
+
output: `${basename4(output)}${sizeStr}`,
|
|
1504
|
+
logs
|
|
1505
|
+
};
|
|
1506
|
+
}
|
|
1507
|
+
logs.push({ type: "error", message: "Filter failed" });
|
|
1508
|
+
return {
|
|
1509
|
+
success: false,
|
|
1510
|
+
error: "Processing failed",
|
|
1511
|
+
logs
|
|
1512
|
+
};
|
|
1513
|
+
}
|
|
1514
|
+
});
|
|
1515
|
+
}
|
|
1516
|
+
async function generatePreview2(input, options) {
|
|
1517
|
+
const tempDir = tmpdir3();
|
|
1518
|
+
const timestamp = Date.now();
|
|
1519
|
+
const tempSource = join5(tempDir, `piclet-preview-${timestamp}-src.png`);
|
|
1520
|
+
const tempOutput = join5(tempDir, `piclet-preview-${timestamp}.png`);
|
|
1521
|
+
try {
|
|
1522
|
+
let previewInput = input;
|
|
1523
|
+
if (isMultiFrame(input)) {
|
|
1524
|
+
if (!await extractFirstFrame(input, tempSource)) {
|
|
1525
|
+
return { success: false, error: "Failed to extract frame" };
|
|
1526
|
+
}
|
|
1527
|
+
previewInput = tempSource;
|
|
1528
|
+
}
|
|
1529
|
+
const success2 = await applyFilter(previewInput, tempOutput, options.filter);
|
|
1530
|
+
if (!success2) {
|
|
1531
|
+
cleanup(tempSource, tempOutput);
|
|
1532
|
+
return { success: false, error: "Filter failed" };
|
|
1533
|
+
}
|
|
1534
|
+
const buffer = readFileSync4(tempOutput);
|
|
1535
|
+
const base64 = buffer.toString("base64");
|
|
1536
|
+
const imageData = `data:image/png;base64,${base64}`;
|
|
1537
|
+
const dims = await getDimensions(tempOutput);
|
|
1538
|
+
cleanup(tempSource, tempOutput);
|
|
1539
|
+
return {
|
|
1540
|
+
success: true,
|
|
1541
|
+
imageData,
|
|
1542
|
+
width: dims?.[0],
|
|
1543
|
+
height: dims?.[1]
|
|
1544
|
+
};
|
|
1545
|
+
} catch (err) {
|
|
1546
|
+
cleanup(tempSource, tempOutput);
|
|
1547
|
+
return { success: false, error: err.message };
|
|
1548
|
+
}
|
|
1549
|
+
}
|
|
1550
|
+
var config3 = {
|
|
1551
|
+
id: "filter",
|
|
1552
|
+
name: "Filter",
|
|
1553
|
+
icon: "filter.ico",
|
|
1554
|
+
extensions: [".png", ".jpg", ".jpeg", ".gif", ".bmp"]
|
|
1555
|
+
};
|
|
1556
|
+
|
|
1557
|
+
// src/tools/iconpack.ts
|
|
1558
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync4 } from "fs";
|
|
1559
|
+
import { basename as basename5, dirname as dirname5 } from "path";
|
|
1560
|
+
var WEB_ICONS = [
|
|
1561
|
+
{ filename: "favicon-16x16.png", size: 16 },
|
|
1562
|
+
{ filename: "favicon-32x32.png", size: 32 },
|
|
1563
|
+
{ filename: "favicon-48x48.png", size: 48 },
|
|
1564
|
+
{ filename: "apple-touch-icon.png", size: 180 },
|
|
1565
|
+
{ filename: "android-chrome-192x192.png", size: 192 },
|
|
1566
|
+
{ filename: "android-chrome-512x512.png", size: 512 },
|
|
1567
|
+
{ filename: "mstile-150x150.png", size: 150 }
|
|
1568
|
+
];
|
|
1569
|
+
var ANDROID_ICONS = [
|
|
1570
|
+
{ filename: "mipmap-mdpi/ic_launcher.png", size: 48 },
|
|
1571
|
+
{ filename: "mipmap-hdpi/ic_launcher.png", size: 72 },
|
|
1572
|
+
{ filename: "mipmap-xhdpi/ic_launcher.png", size: 96 },
|
|
1573
|
+
{ filename: "mipmap-xxhdpi/ic_launcher.png", size: 144 },
|
|
1574
|
+
{ filename: "mipmap-xxxhdpi/ic_launcher.png", size: 192 },
|
|
1575
|
+
{ filename: "playstore-icon.png", size: 512 }
|
|
1576
|
+
];
|
|
1577
|
+
var IOS_ICONS = [
|
|
1578
|
+
// 20pt - Notifications
|
|
1579
|
+
{ filename: "AppIcon-20.png", size: 20 },
|
|
1580
|
+
{ filename: "AppIcon-20@2x.png", size: 40 },
|
|
1581
|
+
{ filename: "AppIcon-20@3x.png", size: 60 },
|
|
1582
|
+
// 29pt - Settings
|
|
1583
|
+
{ filename: "AppIcon-29.png", size: 29 },
|
|
1584
|
+
{ filename: "AppIcon-29@2x.png", size: 58 },
|
|
1585
|
+
{ filename: "AppIcon-29@3x.png", size: 87 },
|
|
1586
|
+
// 40pt - Spotlight
|
|
1587
|
+
{ filename: "AppIcon-40.png", size: 40 },
|
|
1588
|
+
{ filename: "AppIcon-40@2x.png", size: 80 },
|
|
1589
|
+
{ filename: "AppIcon-40@3x.png", size: 120 },
|
|
1590
|
+
// 60pt - iPhone App
|
|
900
1591
|
{ filename: "AppIcon-60@2x.png", size: 120 },
|
|
901
1592
|
{ filename: "AppIcon-60@3x.png", size: 180 },
|
|
902
1593
|
// 76pt - iPad App
|
|
@@ -914,8 +1605,8 @@ async function generateIcons(outputDir, sourceImg, icons) {
|
|
|
914
1605
|
for (const icon of icons) {
|
|
915
1606
|
current++;
|
|
916
1607
|
const outputPath = `${outputDir}/${icon.filename}`;
|
|
917
|
-
const subdir =
|
|
918
|
-
if (!
|
|
1608
|
+
const subdir = dirname5(outputPath);
|
|
1609
|
+
if (!existsSync6(subdir)) {
|
|
919
1610
|
mkdirSync4(subdir, { recursive: true });
|
|
920
1611
|
}
|
|
921
1612
|
clearLine();
|
|
@@ -953,7 +1644,7 @@ async function generateFavicon(outputDir, sourceImg) {
|
|
|
953
1644
|
wipDone(false, "favicon.ico failed");
|
|
954
1645
|
return false;
|
|
955
1646
|
}
|
|
956
|
-
async function
|
|
1647
|
+
async function run4(inputRaw) {
|
|
957
1648
|
if (!await checkImageMagick()) {
|
|
958
1649
|
error("ImageMagick not found. Please install it:");
|
|
959
1650
|
console.log(" sudo apt update && sudo apt install imagemagick");
|
|
@@ -961,7 +1652,7 @@ async function run(inputRaw) {
|
|
|
961
1652
|
return false;
|
|
962
1653
|
}
|
|
963
1654
|
const input = normalizePath(inputRaw);
|
|
964
|
-
if (!
|
|
1655
|
+
if (!existsSync6(input)) {
|
|
965
1656
|
error(`File not found: ${input}`);
|
|
966
1657
|
await pauseOnError();
|
|
967
1658
|
return false;
|
|
@@ -1009,7 +1700,7 @@ async function run(inputRaw) {
|
|
|
1009
1700
|
wip("Preparing source image...");
|
|
1010
1701
|
const tempSource = `${outputDir}/.source_1024.png`;
|
|
1011
1702
|
const tempSquare = `${outputDir}/.temp_square.png`;
|
|
1012
|
-
if (!await
|
|
1703
|
+
if (!await squarify(input, tempSquare)) {
|
|
1013
1704
|
wipDone(false, "Failed to prepare source");
|
|
1014
1705
|
return false;
|
|
1015
1706
|
}
|
|
@@ -1066,9 +1757,9 @@ async function run(inputRaw) {
|
|
|
1066
1757
|
}
|
|
1067
1758
|
return totalFailed === 0;
|
|
1068
1759
|
}
|
|
1069
|
-
async function
|
|
1760
|
+
async function runGUI4(inputRaw) {
|
|
1070
1761
|
const input = normalizePath(inputRaw);
|
|
1071
|
-
if (!
|
|
1762
|
+
if (!existsSync6(input)) {
|
|
1072
1763
|
error(`File not found: ${input}`);
|
|
1073
1764
|
return false;
|
|
1074
1765
|
}
|
|
@@ -1083,7 +1774,7 @@ async function runGUI(inputRaw) {
|
|
|
1083
1774
|
title: "PicLet - Icon Pack",
|
|
1084
1775
|
imageInfo: {
|
|
1085
1776
|
filePath: input,
|
|
1086
|
-
fileName:
|
|
1777
|
+
fileName: basename5(input),
|
|
1087
1778
|
width: dims[0],
|
|
1088
1779
|
height: dims[1],
|
|
1089
1780
|
borderColor: null
|
|
@@ -1118,7 +1809,7 @@ async function runGUI(inputRaw) {
|
|
|
1118
1809
|
logs.push({ type: "info", message: "Preparing source image..." });
|
|
1119
1810
|
const tempSource = `${outputDir}/.source_1024.png`;
|
|
1120
1811
|
const tempSquare = `${outputDir}/.temp_square.png`;
|
|
1121
|
-
if (!await
|
|
1812
|
+
if (!await squarify(input, tempSquare)) {
|
|
1122
1813
|
return { success: false, error: "Failed to prepare source", logs };
|
|
1123
1814
|
}
|
|
1124
1815
|
if (!await scaleToSize(tempSquare, tempSource, 1024)) {
|
|
@@ -1172,8 +1863,8 @@ async function generateIconsSilent(outputDir, sourceImg, icons, _logs) {
|
|
|
1172
1863
|
let failed = 0;
|
|
1173
1864
|
for (const icon of icons) {
|
|
1174
1865
|
const outputPath = `${outputDir}/${icon.filename}`;
|
|
1175
|
-
const subdir =
|
|
1176
|
-
if (!
|
|
1866
|
+
const subdir = dirname5(outputPath);
|
|
1867
|
+
if (!existsSync6(subdir)) {
|
|
1177
1868
|
mkdirSync4(subdir, { recursive: true });
|
|
1178
1869
|
}
|
|
1179
1870
|
if (!await scaleToSize(sourceImg, outputPath, icon.size)) {
|
|
@@ -1196,24 +1887,18 @@ async function generateFaviconSilent(outputDir, sourceImg) {
|
|
|
1196
1887
|
cleanup(temp16, temp32, temp48);
|
|
1197
1888
|
return result;
|
|
1198
1889
|
}
|
|
1199
|
-
var
|
|
1890
|
+
var config4 = {
|
|
1200
1891
|
id: "iconpack",
|
|
1201
1892
|
name: "Icon Pack",
|
|
1202
1893
|
icon: "iconpack.ico",
|
|
1203
|
-
extensions: [".png", ".jpg", ".jpeg"]
|
|
1894
|
+
extensions: [".png", ".jpg", ".jpeg", ".gif"]
|
|
1204
1895
|
};
|
|
1205
1896
|
|
|
1206
|
-
// src/cli/utils.ts
|
|
1207
|
-
import { extname as extname3 } from "path";
|
|
1208
|
-
import { dirname as dirname9 } from "path";
|
|
1209
|
-
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
1210
|
-
import chalk2 from "chalk";
|
|
1211
|
-
|
|
1212
1897
|
// src/tools/makeicon.ts
|
|
1213
|
-
import { existsSync as
|
|
1214
|
-
import { tmpdir } from "os";
|
|
1215
|
-
import { basename as
|
|
1216
|
-
async function
|
|
1898
|
+
import { existsSync as existsSync7, readFileSync as readFileSync5 } from "fs";
|
|
1899
|
+
import { tmpdir as tmpdir4 } from "os";
|
|
1900
|
+
import { basename as basename6, join as join6 } from "path";
|
|
1901
|
+
async function run5(inputRaw) {
|
|
1217
1902
|
if (!await checkImageMagick()) {
|
|
1218
1903
|
error("ImageMagick not found. Please install it:");
|
|
1219
1904
|
console.log(" sudo apt update && sudo apt install imagemagick");
|
|
@@ -1221,7 +1906,7 @@ async function run2(inputRaw) {
|
|
|
1221
1906
|
return false;
|
|
1222
1907
|
}
|
|
1223
1908
|
const input = normalizePath(inputRaw);
|
|
1224
|
-
if (!
|
|
1909
|
+
if (!existsSync7(input)) {
|
|
1225
1910
|
error(`File not found: ${input}`);
|
|
1226
1911
|
await pauseOnError();
|
|
1227
1912
|
return false;
|
|
@@ -1247,7 +1932,7 @@ async function run2(inputRaw) {
|
|
|
1247
1932
|
}
|
|
1248
1933
|
wipDone(true, "Trimmed");
|
|
1249
1934
|
wip("Making square...");
|
|
1250
|
-
if (!await
|
|
1935
|
+
if (!await squarify(tempTrimmed, tempSquare)) {
|
|
1251
1936
|
wipDone(false, "Square padding failed");
|
|
1252
1937
|
cleanup(tempTrimmed, tempSquare);
|
|
1253
1938
|
return false;
|
|
@@ -1291,7 +1976,7 @@ async function processForIcon(input, output, options, logs) {
|
|
|
1291
1976
|
}
|
|
1292
1977
|
if (options.makeSquare) {
|
|
1293
1978
|
logs?.push({ type: "info", message: "Making square..." });
|
|
1294
|
-
if (!await
|
|
1979
|
+
if (!await squarify(currentInput, tempSquare)) {
|
|
1295
1980
|
cleanup(tempTrimmed, tempSquare);
|
|
1296
1981
|
return false;
|
|
1297
1982
|
}
|
|
@@ -1316,12 +2001,12 @@ async function processForIcon(input, output, options, logs) {
|
|
|
1316
2001
|
logs?.push({ type: "success", message: "Icon created" });
|
|
1317
2002
|
return true;
|
|
1318
2003
|
}
|
|
1319
|
-
async function
|
|
1320
|
-
const tempDir =
|
|
2004
|
+
async function generatePreview3(input, options) {
|
|
2005
|
+
const tempDir = tmpdir4();
|
|
1321
2006
|
const timestamp = Date.now();
|
|
1322
|
-
const tempTrimmed =
|
|
1323
|
-
const tempSquare =
|
|
1324
|
-
const tempOutput =
|
|
2007
|
+
const tempTrimmed = join6(tempDir, `piclet-preview-trimmed-${timestamp}.png`);
|
|
2008
|
+
const tempSquare = join6(tempDir, `piclet-preview-square-${timestamp}.png`);
|
|
2009
|
+
const tempOutput = join6(tempDir, `piclet-preview-${timestamp}.png`);
|
|
1325
2010
|
try {
|
|
1326
2011
|
let currentInput = input;
|
|
1327
2012
|
if (options.trim) {
|
|
@@ -1332,7 +2017,7 @@ async function generatePreview(input, options) {
|
|
|
1332
2017
|
currentInput = tempTrimmed;
|
|
1333
2018
|
}
|
|
1334
2019
|
if (options.makeSquare) {
|
|
1335
|
-
if (!await
|
|
2020
|
+
if (!await squarify(currentInput, tempSquare)) {
|
|
1336
2021
|
cleanup(tempTrimmed, tempSquare);
|
|
1337
2022
|
return { success: false, error: "Square failed" };
|
|
1338
2023
|
}
|
|
@@ -1345,7 +2030,7 @@ async function generatePreview(input, options) {
|
|
|
1345
2030
|
}
|
|
1346
2031
|
if (currentInput === tempSquare) cleanup(tempSquare);
|
|
1347
2032
|
else if (currentInput === tempTrimmed) cleanup(tempTrimmed);
|
|
1348
|
-
const buffer =
|
|
2033
|
+
const buffer = readFileSync5(tempOutput);
|
|
1349
2034
|
const base64 = buffer.toString("base64");
|
|
1350
2035
|
const imageData = `data:image/png;base64,${base64}`;
|
|
1351
2036
|
const dims = await getDimensions(tempOutput);
|
|
@@ -1361,9 +2046,9 @@ async function generatePreview(input, options) {
|
|
|
1361
2046
|
return { success: false, error: err.message };
|
|
1362
2047
|
}
|
|
1363
2048
|
}
|
|
1364
|
-
async function
|
|
2049
|
+
async function runGUI5(inputRaw) {
|
|
1365
2050
|
const input = normalizePath(inputRaw);
|
|
1366
|
-
if (!
|
|
2051
|
+
if (!existsSync7(input)) {
|
|
1367
2052
|
error(`File not found: ${input}`);
|
|
1368
2053
|
return false;
|
|
1369
2054
|
}
|
|
@@ -1378,7 +2063,7 @@ async function runGUI2(inputRaw) {
|
|
|
1378
2063
|
title: "PicLet - Make Icon",
|
|
1379
2064
|
imageInfo: {
|
|
1380
2065
|
filePath: input,
|
|
1381
|
-
fileName:
|
|
2066
|
+
fileName: basename6(input),
|
|
1382
2067
|
width: dims[0],
|
|
1383
2068
|
height: dims[1],
|
|
1384
2069
|
borderColor: null
|
|
@@ -1392,7 +2077,7 @@ async function runGUI2(inputRaw) {
|
|
|
1392
2077
|
trim: opts.trim ?? true,
|
|
1393
2078
|
makeSquare: opts.makeSquare ?? true
|
|
1394
2079
|
};
|
|
1395
|
-
return
|
|
2080
|
+
return generatePreview3(input, options);
|
|
1396
2081
|
},
|
|
1397
2082
|
onProcess: async (opts) => {
|
|
1398
2083
|
const logs = [];
|
|
@@ -1420,40 +2105,46 @@ async function runGUI2(inputRaw) {
|
|
|
1420
2105
|
}
|
|
1421
2106
|
});
|
|
1422
2107
|
}
|
|
1423
|
-
var
|
|
2108
|
+
var config5 = {
|
|
1424
2109
|
id: "makeicon",
|
|
1425
2110
|
name: "Make Icon",
|
|
1426
2111
|
icon: "makeicon.ico",
|
|
1427
|
-
extensions: [".png"]
|
|
2112
|
+
extensions: [".png", ".gif"]
|
|
1428
2113
|
};
|
|
1429
2114
|
|
|
1430
2115
|
// src/tools/piclet-main.ts
|
|
1431
|
-
import { existsSync as
|
|
1432
|
-
import { tmpdir as
|
|
1433
|
-
import { basename as
|
|
2116
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync6, renameSync, writeFileSync as writeFileSync2 } from "fs";
|
|
2117
|
+
import { tmpdir as tmpdir5 } from "os";
|
|
2118
|
+
import { basename as basename7, dirname as dirname6, extname as extname2, join as join7 } from "path";
|
|
1434
2119
|
var TOOL_ORDER = ["removebg", "scale", "icons", "storepack"];
|
|
1435
2120
|
async function generateCombinedPreview(input, borderColor, opts) {
|
|
1436
|
-
const tempDir =
|
|
2121
|
+
const tempDir = tmpdir5();
|
|
1437
2122
|
const ts = Date.now();
|
|
1438
2123
|
const temps = [];
|
|
1439
2124
|
const makeTempPath = (suffix) => {
|
|
1440
|
-
const p =
|
|
2125
|
+
const p = join7(tempDir, `piclet-${ts}-${suffix}.png`);
|
|
1441
2126
|
temps.push(p);
|
|
1442
2127
|
return p;
|
|
1443
2128
|
};
|
|
1444
2129
|
try {
|
|
1445
2130
|
let current = input;
|
|
2131
|
+
if (isMultiFrame(input)) {
|
|
2132
|
+
const frameTemp = makeTempPath("frame");
|
|
2133
|
+
if (await extractFirstFrame(input, frameTemp)) {
|
|
2134
|
+
current = frameTemp;
|
|
2135
|
+
}
|
|
2136
|
+
}
|
|
1446
2137
|
if (opts.original || opts.tools.length === 0) {
|
|
1447
|
-
const dims2 = await getDimensions(
|
|
1448
|
-
let previewPath2 =
|
|
2138
|
+
const dims2 = await getDimensions(current);
|
|
2139
|
+
let previewPath2 = current;
|
|
1449
2140
|
if (dims2 && (dims2[0] > 512 || dims2[1] > 512)) {
|
|
1450
2141
|
const scaled = makeTempPath("orig-preview");
|
|
1451
2142
|
const targetSize = Math.min(512, Math.max(dims2[0], dims2[1]));
|
|
1452
|
-
if (await scaleToSize(
|
|
2143
|
+
if (await scaleToSize(current, scaled, targetSize)) {
|
|
1453
2144
|
previewPath2 = scaled;
|
|
1454
2145
|
}
|
|
1455
2146
|
}
|
|
1456
|
-
const buffer2 =
|
|
2147
|
+
const buffer2 = readFileSync6(previewPath2);
|
|
1457
2148
|
const finalDims2 = await getDimensions(previewPath2);
|
|
1458
2149
|
cleanup(...temps);
|
|
1459
2150
|
return {
|
|
@@ -1519,7 +2210,7 @@ async function generateCombinedPreview(input, borderColor, opts) {
|
|
|
1519
2210
|
}
|
|
1520
2211
|
if (icOpts.makeSquare) {
|
|
1521
2212
|
const sqOut = makeTempPath("ic-sq");
|
|
1522
|
-
if (await
|
|
2213
|
+
if (await squarify(current, sqOut)) {
|
|
1523
2214
|
current = sqOut;
|
|
1524
2215
|
}
|
|
1525
2216
|
}
|
|
@@ -1536,7 +2227,7 @@ async function generateCombinedPreview(input, borderColor, opts) {
|
|
|
1536
2227
|
previewPath = scaled;
|
|
1537
2228
|
}
|
|
1538
2229
|
}
|
|
1539
|
-
const buffer =
|
|
2230
|
+
const buffer = readFileSync6(previewPath);
|
|
1540
2231
|
const finalDims = await getDimensions(previewPath);
|
|
1541
2232
|
cleanup(...temps);
|
|
1542
2233
|
return {
|
|
@@ -1554,8 +2245,9 @@ async function processCombined(input, borderColor, opts, logs) {
|
|
|
1554
2245
|
const fileInfo = getFileInfo(input);
|
|
1555
2246
|
const outputs = [];
|
|
1556
2247
|
const temps = [];
|
|
2248
|
+
const outputExt = fileInfo.extension.toLowerCase() === ".gif" ? ".gif" : ".png";
|
|
1557
2249
|
const makeTempPath = (suffix) => {
|
|
1558
|
-
const p = `${fileInfo.dirname}/${fileInfo.filename}_${suffix}
|
|
2250
|
+
const p = `${fileInfo.dirname}/${fileInfo.filename}_${suffix}${outputExt}`;
|
|
1559
2251
|
temps.push(p);
|
|
1560
2252
|
return p;
|
|
1561
2253
|
};
|
|
@@ -1598,10 +2290,10 @@ async function processCombined(input, borderColor, opts, logs) {
|
|
|
1598
2290
|
current = out;
|
|
1599
2291
|
}
|
|
1600
2292
|
if (activeTools.indexOf(tool) === activeTools.length - 1) {
|
|
1601
|
-
const finalOut = `${fileInfo.dirname}/${fileInfo.filename}_nobg
|
|
2293
|
+
const finalOut = `${fileInfo.dirname}/${fileInfo.filename}_nobg${outputExt}`;
|
|
1602
2294
|
renameSync(current, finalOut);
|
|
1603
2295
|
temps.splice(temps.indexOf(current), 1);
|
|
1604
|
-
outputs.push(
|
|
2296
|
+
outputs.push(basename7(finalOut));
|
|
1605
2297
|
}
|
|
1606
2298
|
break;
|
|
1607
2299
|
}
|
|
@@ -1629,10 +2321,10 @@ async function processCombined(input, borderColor, opts, logs) {
|
|
|
1629
2321
|
}
|
|
1630
2322
|
current = out;
|
|
1631
2323
|
if (activeTools.indexOf(tool) === activeTools.length - 1) {
|
|
1632
|
-
const finalOut = `${fileInfo.dirname}/${fileInfo.filename}_scaled${
|
|
2324
|
+
const finalOut = `${fileInfo.dirname}/${fileInfo.filename}_scaled${outputExt}`;
|
|
1633
2325
|
renameSync(current, finalOut);
|
|
1634
2326
|
temps.splice(temps.indexOf(current), 1);
|
|
1635
|
-
outputs.push(
|
|
2327
|
+
outputs.push(basename7(finalOut));
|
|
1636
2328
|
}
|
|
1637
2329
|
break;
|
|
1638
2330
|
}
|
|
@@ -1655,7 +2347,7 @@ async function processCombined(input, borderColor, opts, logs) {
|
|
|
1655
2347
|
if (icOpts.makeSquare) {
|
|
1656
2348
|
logs.push({ type: "info", message: "Making square..." });
|
|
1657
2349
|
const sqOut = makeTempPath("ic-sq");
|
|
1658
|
-
if (await
|
|
2350
|
+
if (await squarify(iconSource, sqOut)) {
|
|
1659
2351
|
if (iconSource !== current && iconSource !== input) cleanup(iconSource);
|
|
1660
2352
|
iconSource = sqOut;
|
|
1661
2353
|
logs.push({ type: "success", message: "Made square" });
|
|
@@ -1675,7 +2367,7 @@ async function processCombined(input, borderColor, opts, logs) {
|
|
|
1675
2367
|
const icoOut = `${fileInfo.dirname}/${fileInfo.filename}.ico`;
|
|
1676
2368
|
if (await createIco(srcTemp, icoOut)) {
|
|
1677
2369
|
logs.push({ type: "success", message: "ICO: 6 sizes (256, 128, 64, 48, 32, 16)" });
|
|
1678
|
-
outputs.push(
|
|
2370
|
+
outputs.push(basename7(icoOut));
|
|
1679
2371
|
totalCount += 6;
|
|
1680
2372
|
} else {
|
|
1681
2373
|
logs.push({ type: "warn", message: "ICO creation failed" });
|
|
@@ -1723,7 +2415,7 @@ async function processCombined(input, borderColor, opts, logs) {
|
|
|
1723
2415
|
];
|
|
1724
2416
|
for (const i of androidIcons) {
|
|
1725
2417
|
const p = `${androidDir}/${i.name}`;
|
|
1726
|
-
mkdirSync5(
|
|
2418
|
+
mkdirSync5(dirname6(p), { recursive: true });
|
|
1727
2419
|
await scaleToSize(srcTemp, p, i.size);
|
|
1728
2420
|
totalCount++;
|
|
1729
2421
|
}
|
|
@@ -1759,7 +2451,7 @@ async function processCombined(input, borderColor, opts, logs) {
|
|
|
1759
2451
|
let count = 0;
|
|
1760
2452
|
for (const dim of spOpts.dimensions) {
|
|
1761
2453
|
const filename = dim.filename || `${dim.width}x${dim.height}.png`;
|
|
1762
|
-
const out =
|
|
2454
|
+
const out = join7(outputDir, filename);
|
|
1763
2455
|
let success2 = false;
|
|
1764
2456
|
switch (spOpts.scaleMode) {
|
|
1765
2457
|
case "fill":
|
|
@@ -1782,9 +2474,9 @@ async function processCombined(input, borderColor, opts, logs) {
|
|
|
1782
2474
|
cleanup(...temps);
|
|
1783
2475
|
return outputs;
|
|
1784
2476
|
}
|
|
1785
|
-
async function
|
|
2477
|
+
async function runGUI6(inputRaw) {
|
|
1786
2478
|
let currentInput = normalizePath(inputRaw);
|
|
1787
|
-
if (!
|
|
2479
|
+
if (!existsSync8(currentInput)) {
|
|
1788
2480
|
error(`File not found: ${currentInput}`);
|
|
1789
2481
|
return false;
|
|
1790
2482
|
}
|
|
@@ -1794,99 +2486,557 @@ async function runGUI3(inputRaw) {
|
|
|
1794
2486
|
return false;
|
|
1795
2487
|
}
|
|
1796
2488
|
let currentBorderColor = await getBorderColor(currentInput);
|
|
2489
|
+
let currentFrameCount = isMultiFrame(currentInput) ? await getFrameCount(currentInput) : 1;
|
|
1797
2490
|
const presets = loadPresets();
|
|
2491
|
+
async function generateFrameThumbnail(frameIndex) {
|
|
2492
|
+
const tempDir = tmpdir5();
|
|
2493
|
+
const tempOutput = join7(tempDir, `piclet-frame-${Date.now()}-${frameIndex}.png`);
|
|
2494
|
+
try {
|
|
2495
|
+
if (!await extractFirstFrame(currentInput, tempOutput, frameIndex)) {
|
|
2496
|
+
return { success: false, error: "Failed to extract frame" };
|
|
2497
|
+
}
|
|
2498
|
+
const thumbOutput = join7(tempDir, `piclet-thumb-${Date.now()}-${frameIndex}.png`);
|
|
2499
|
+
await scaleToSize(tempOutput, thumbOutput, 96);
|
|
2500
|
+
const buffer = readFileSync6(thumbOutput);
|
|
2501
|
+
cleanup(tempOutput, thumbOutput);
|
|
2502
|
+
return {
|
|
2503
|
+
success: true,
|
|
2504
|
+
imageData: `data:image/png;base64,${buffer.toString("base64")}`
|
|
2505
|
+
};
|
|
2506
|
+
} catch (err) {
|
|
2507
|
+
cleanup(tempOutput);
|
|
2508
|
+
return { success: false, error: err.message };
|
|
2509
|
+
}
|
|
2510
|
+
}
|
|
2511
|
+
async function generateFramePreview2(frameIndex, opts) {
|
|
2512
|
+
const tempDir = tmpdir5();
|
|
2513
|
+
const ts = Date.now();
|
|
2514
|
+
const frameFile = join7(tempDir, `piclet-fp-${ts}-${frameIndex}.png`);
|
|
2515
|
+
const temps = [frameFile];
|
|
2516
|
+
try {
|
|
2517
|
+
if (!await extractFirstFrame(currentInput, frameFile, frameIndex)) {
|
|
2518
|
+
return { success: false, error: "Failed to extract frame" };
|
|
2519
|
+
}
|
|
2520
|
+
let current = frameFile;
|
|
2521
|
+
const activeTools = ["removebg", "scale", "icons"].filter((t) => opts.tools.includes(t));
|
|
2522
|
+
for (const tool of activeTools) {
|
|
2523
|
+
const tempOut = join7(tempDir, `piclet-fp-${ts}-${frameIndex}-${tool}.png`);
|
|
2524
|
+
temps.push(tempOut);
|
|
2525
|
+
switch (tool) {
|
|
2526
|
+
case "removebg": {
|
|
2527
|
+
const rbOpts = opts.removebg;
|
|
2528
|
+
let success2 = false;
|
|
2529
|
+
if (rbOpts.preserveInner && currentBorderColor) {
|
|
2530
|
+
success2 = await removeBackgroundBorderOnly(current, tempOut, currentBorderColor, rbOpts.fuzz);
|
|
2531
|
+
}
|
|
2532
|
+
if (!success2 && currentBorderColor) {
|
|
2533
|
+
success2 = await removeBackground(current, tempOut, currentBorderColor, rbOpts.fuzz);
|
|
2534
|
+
}
|
|
2535
|
+
if (success2) {
|
|
2536
|
+
if (rbOpts.trim) {
|
|
2537
|
+
const trimOut = join7(tempDir, `piclet-fp-${ts}-${frameIndex}-trim.png`);
|
|
2538
|
+
temps.push(trimOut);
|
|
2539
|
+
if (await trim(tempOut, trimOut)) {
|
|
2540
|
+
current = trimOut;
|
|
2541
|
+
} else {
|
|
2542
|
+
current = tempOut;
|
|
2543
|
+
}
|
|
2544
|
+
} else {
|
|
2545
|
+
current = tempOut;
|
|
2546
|
+
}
|
|
2547
|
+
}
|
|
2548
|
+
break;
|
|
2549
|
+
}
|
|
2550
|
+
case "scale": {
|
|
2551
|
+
const scOpts = opts.scale;
|
|
2552
|
+
if (scOpts.makeSquare) {
|
|
2553
|
+
const max = Math.max(scOpts.width, scOpts.height);
|
|
2554
|
+
if (await scaleWithPadding(current, tempOut, max, max)) {
|
|
2555
|
+
current = tempOut;
|
|
2556
|
+
}
|
|
2557
|
+
} else {
|
|
2558
|
+
if (await resize(current, tempOut, scOpts.width, scOpts.height)) {
|
|
2559
|
+
current = tempOut;
|
|
2560
|
+
}
|
|
2561
|
+
}
|
|
2562
|
+
break;
|
|
2563
|
+
}
|
|
2564
|
+
case "icons": {
|
|
2565
|
+
const icOpts = opts.icons;
|
|
2566
|
+
if (icOpts.trim) {
|
|
2567
|
+
const trimOut = join7(tempDir, `piclet-fp-${ts}-${frameIndex}-ictrim.png`);
|
|
2568
|
+
temps.push(trimOut);
|
|
2569
|
+
if (await trim(current, trimOut)) {
|
|
2570
|
+
current = trimOut;
|
|
2571
|
+
}
|
|
2572
|
+
}
|
|
2573
|
+
if (icOpts.makeSquare) {
|
|
2574
|
+
if (await squarify(current, tempOut)) {
|
|
2575
|
+
current = tempOut;
|
|
2576
|
+
}
|
|
2577
|
+
}
|
|
2578
|
+
break;
|
|
2579
|
+
}
|
|
2580
|
+
}
|
|
2581
|
+
}
|
|
2582
|
+
const thumbOut = join7(tempDir, `piclet-fp-${ts}-${frameIndex}-thumb.png`);
|
|
2583
|
+
temps.push(thumbOut);
|
|
2584
|
+
await scaleToSize(current, thumbOut, 96);
|
|
2585
|
+
const buffer = readFileSync6(thumbOut);
|
|
2586
|
+
cleanup(...temps);
|
|
2587
|
+
return {
|
|
2588
|
+
success: true,
|
|
2589
|
+
imageData: `data:image/png;base64,${buffer.toString("base64")}`
|
|
2590
|
+
};
|
|
2591
|
+
} catch (err) {
|
|
2592
|
+
cleanup(...temps);
|
|
2593
|
+
return { success: false, error: err.message };
|
|
2594
|
+
}
|
|
2595
|
+
}
|
|
1798
2596
|
return startGuiServer({
|
|
1799
2597
|
htmlFile: "piclet.html",
|
|
1800
2598
|
title: "PicLet",
|
|
1801
2599
|
imageInfo: {
|
|
1802
|
-
filePath: currentInput,
|
|
1803
|
-
fileName:
|
|
2600
|
+
filePath: currentInput,
|
|
2601
|
+
fileName: basename7(currentInput),
|
|
2602
|
+
width: dims[0],
|
|
2603
|
+
height: dims[1],
|
|
2604
|
+
borderColor: currentBorderColor,
|
|
2605
|
+
frameCount: currentFrameCount
|
|
2606
|
+
},
|
|
2607
|
+
defaults: {
|
|
2608
|
+
// Return full preset data for the UI
|
|
2609
|
+
presets: presets.map((p) => ({
|
|
2610
|
+
id: p.id,
|
|
2611
|
+
name: p.name,
|
|
2612
|
+
description: p.description,
|
|
2613
|
+
icons: p.icons
|
|
2614
|
+
}))
|
|
2615
|
+
},
|
|
2616
|
+
onPreview: async (opts) => {
|
|
2617
|
+
const toolOpts = opts;
|
|
2618
|
+
if (!toolOpts.tools) {
|
|
2619
|
+
toolOpts.tools = [];
|
|
2620
|
+
}
|
|
2621
|
+
if (isMultiFrame(currentInput) && typeof toolOpts.frameIndex === "number") {
|
|
2622
|
+
const tempDir = tmpdir5();
|
|
2623
|
+
const ts = Date.now();
|
|
2624
|
+
const frameFile = join7(tempDir, `piclet-prev-${ts}.png`);
|
|
2625
|
+
if (!await extractFirstFrame(currentInput, frameFile, toolOpts.frameIndex)) {
|
|
2626
|
+
return { success: false, error: "Failed to extract frame" };
|
|
2627
|
+
}
|
|
2628
|
+
const result = await generateCombinedPreview(frameFile, currentBorderColor, toolOpts);
|
|
2629
|
+
cleanup(frameFile);
|
|
2630
|
+
return result;
|
|
2631
|
+
}
|
|
2632
|
+
return generateCombinedPreview(currentInput, currentBorderColor, toolOpts);
|
|
2633
|
+
},
|
|
2634
|
+
onProcess: async (opts) => {
|
|
2635
|
+
const logs = [];
|
|
2636
|
+
const toolOpts = opts;
|
|
2637
|
+
if (toolOpts.exportMode && isMultiFrame(currentInput)) {
|
|
2638
|
+
return processGifExport(currentInput, currentBorderColor, toolOpts, logs);
|
|
2639
|
+
}
|
|
2640
|
+
if (!toolOpts.tools || toolOpts.tools.length === 0) {
|
|
2641
|
+
return { success: false, error: "No tools selected", logs };
|
|
2642
|
+
}
|
|
2643
|
+
if (!await checkImageMagick()) {
|
|
2644
|
+
return {
|
|
2645
|
+
success: false,
|
|
2646
|
+
error: "ImageMagick not found",
|
|
2647
|
+
logs: [{ type: "error", message: "ImageMagick not found. Install: sudo apt install imagemagick" }]
|
|
2648
|
+
};
|
|
2649
|
+
}
|
|
2650
|
+
const outputs = await processCombined(currentInput, currentBorderColor, toolOpts, logs);
|
|
2651
|
+
if (outputs.length > 0) {
|
|
2652
|
+
return {
|
|
2653
|
+
success: true,
|
|
2654
|
+
output: outputs.join("\n"),
|
|
2655
|
+
logs
|
|
2656
|
+
};
|
|
2657
|
+
}
|
|
2658
|
+
return { success: false, error: "Processing failed", logs };
|
|
2659
|
+
},
|
|
2660
|
+
onLoadImage: async (data) => {
|
|
2661
|
+
try {
|
|
2662
|
+
const ext = extname2(data.fileName) || ".png";
|
|
2663
|
+
const tempPath = join7(tmpdir5(), `piclet-load-${Date.now()}${ext}`);
|
|
2664
|
+
const buffer = Buffer.from(data.data, "base64");
|
|
2665
|
+
writeFileSync2(tempPath, buffer);
|
|
2666
|
+
const newDims = await getDimensions(tempPath);
|
|
2667
|
+
if (!newDims) {
|
|
2668
|
+
cleanup(tempPath);
|
|
2669
|
+
return { success: false, error: "Failed to read image dimensions" };
|
|
2670
|
+
}
|
|
2671
|
+
const newBorderColor = await getBorderColor(tempPath);
|
|
2672
|
+
const newFrameCount = isMultiFrame(tempPath) ? await getFrameCount(tempPath) : 1;
|
|
2673
|
+
currentInput = tempPath;
|
|
2674
|
+
currentBorderColor = newBorderColor;
|
|
2675
|
+
currentFrameCount = newFrameCount;
|
|
2676
|
+
return {
|
|
2677
|
+
success: true,
|
|
2678
|
+
filePath: tempPath,
|
|
2679
|
+
fileName: data.fileName,
|
|
2680
|
+
width: newDims[0],
|
|
2681
|
+
height: newDims[1],
|
|
2682
|
+
borderColor: newBorderColor,
|
|
2683
|
+
frameCount: newFrameCount
|
|
2684
|
+
};
|
|
2685
|
+
} catch (err) {
|
|
2686
|
+
return { success: false, error: err.message };
|
|
2687
|
+
}
|
|
2688
|
+
},
|
|
2689
|
+
onFrameThumbnail: generateFrameThumbnail,
|
|
2690
|
+
onFramePreview: async (frameIndex, opts) => {
|
|
2691
|
+
const toolOpts = opts;
|
|
2692
|
+
if (!toolOpts.tools) toolOpts.tools = [];
|
|
2693
|
+
return generateFramePreview2(frameIndex, toolOpts);
|
|
2694
|
+
}
|
|
2695
|
+
});
|
|
2696
|
+
}
|
|
2697
|
+
async function processGifExport(input, borderColor, opts, logs) {
|
|
2698
|
+
if (!await checkImageMagick()) {
|
|
2699
|
+
return {
|
|
2700
|
+
success: false,
|
|
2701
|
+
error: "ImageMagick not found",
|
|
2702
|
+
logs: [{ type: "error", message: "ImageMagick not found" }]
|
|
2703
|
+
};
|
|
2704
|
+
}
|
|
2705
|
+
const fileInfo = getFileInfo(input);
|
|
2706
|
+
const tempDir = tmpdir5();
|
|
2707
|
+
const ts = Date.now();
|
|
2708
|
+
switch (opts.exportMode) {
|
|
2709
|
+
case "frame": {
|
|
2710
|
+
const frameIndex = opts.frameIndex ?? 0;
|
|
2711
|
+
logs.push({ type: "info", message: `Exporting frame ${frameIndex + 1}...` });
|
|
2712
|
+
const frameFile = join7(tempDir, `piclet-export-${ts}.png`);
|
|
2713
|
+
if (!await extractFirstFrame(input, frameFile, frameIndex)) {
|
|
2714
|
+
logs.push({ type: "error", message: "Failed to extract frame" });
|
|
2715
|
+
return { success: false, error: "Failed to extract frame", logs };
|
|
2716
|
+
}
|
|
2717
|
+
let outputFile = frameFile;
|
|
2718
|
+
if (opts.tools && opts.tools.length > 0) {
|
|
2719
|
+
const processedLogs = [];
|
|
2720
|
+
const outputs = await processCombined(frameFile, borderColor, opts, processedLogs);
|
|
2721
|
+
logs.push(...processedLogs);
|
|
2722
|
+
if (outputs.length === 0) {
|
|
2723
|
+
cleanup(frameFile);
|
|
2724
|
+
return { success: false, error: "Processing failed", logs };
|
|
2725
|
+
}
|
|
2726
|
+
}
|
|
2727
|
+
const finalOutput = `${fileInfo.dirname}/${fileInfo.filename}_frame${frameIndex + 1}.png`;
|
|
2728
|
+
if (outputFile === frameFile) {
|
|
2729
|
+
renameSync(frameFile, finalOutput);
|
|
2730
|
+
}
|
|
2731
|
+
logs.push({ type: "success", message: `Exported frame ${frameIndex + 1}` });
|
|
2732
|
+
return { success: true, output: basename7(finalOutput), logs };
|
|
2733
|
+
}
|
|
2734
|
+
case "all-frames": {
|
|
2735
|
+
logs.push({ type: "info", message: "Extracting all frames..." });
|
|
2736
|
+
const outputDir = `${fileInfo.dirname}/${fileInfo.filename}_frames`;
|
|
2737
|
+
mkdirSync5(outputDir, { recursive: true });
|
|
2738
|
+
const frames = await extractAllFrames(input, outputDir, "frame");
|
|
2739
|
+
if (frames.length === 0) {
|
|
2740
|
+
logs.push({ type: "error", message: "Failed to extract frames" });
|
|
2741
|
+
return { success: false, error: "Failed to extract frames", logs };
|
|
2742
|
+
}
|
|
2743
|
+
if (opts.tools && opts.tools.length > 0) {
|
|
2744
|
+
logs.push({ type: "info", message: `Processing ${frames.length} frames...` });
|
|
2745
|
+
for (let i = 0; i < frames.length; i++) {
|
|
2746
|
+
const frameLogs = [];
|
|
2747
|
+
await processCombined(frames[i], borderColor, opts, frameLogs);
|
|
2748
|
+
}
|
|
2749
|
+
logs.push({ type: "success", message: `Processed ${frames.length} frames` });
|
|
2750
|
+
}
|
|
2751
|
+
logs.push({ type: "success", message: `Exported ${frames.length} frames` });
|
|
2752
|
+
return { success: true, output: `${frames.length} frames -> ${fileInfo.filename}_frames/`, logs };
|
|
2753
|
+
}
|
|
2754
|
+
case "gif": {
|
|
2755
|
+
logs.push({ type: "info", message: "Processing GIF..." });
|
|
2756
|
+
const outputs = await processCombined(input, borderColor, opts, logs);
|
|
2757
|
+
if (outputs.length > 0) {
|
|
2758
|
+
return { success: true, output: outputs.join("\n"), logs };
|
|
2759
|
+
}
|
|
2760
|
+
return { success: false, error: "Processing failed", logs };
|
|
2761
|
+
}
|
|
2762
|
+
default:
|
|
2763
|
+
return { success: false, error: "Unknown export mode", logs };
|
|
2764
|
+
}
|
|
2765
|
+
}
|
|
2766
|
+
var config6 = {
|
|
2767
|
+
id: "piclet",
|
|
2768
|
+
name: "PicLet",
|
|
2769
|
+
icon: "banana.ico",
|
|
2770
|
+
extensions: [".png", ".jpg", ".jpeg", ".gif", ".bmp", ".ico"]
|
|
2771
|
+
};
|
|
2772
|
+
|
|
2773
|
+
// src/tools/recolor.ts
|
|
2774
|
+
import { existsSync as existsSync9, readFileSync as readFileSync7 } from "fs";
|
|
2775
|
+
import { tmpdir as tmpdir6 } from "os";
|
|
2776
|
+
import { basename as basename8, join as join8 } from "path";
|
|
2777
|
+
async function processImage3(input, options) {
|
|
2778
|
+
const fileInfo = getFileInfo(input);
|
|
2779
|
+
const outputExt = fileInfo.extension.toLowerCase() === ".gif" ? ".gif" : ".png";
|
|
2780
|
+
const output = `${fileInfo.dirname}/${fileInfo.filename}_recolor${outputExt}`;
|
|
2781
|
+
wip(`Replacing ${options.fromColor} with ${options.toColor}...`);
|
|
2782
|
+
const success2 = await replaceColor(
|
|
2783
|
+
input,
|
|
2784
|
+
output,
|
|
2785
|
+
options.fromColor,
|
|
2786
|
+
options.toColor,
|
|
2787
|
+
options.fuzz
|
|
2788
|
+
);
|
|
2789
|
+
if (!success2) {
|
|
2790
|
+
wipDone(false, "Color replacement failed");
|
|
2791
|
+
return false;
|
|
2792
|
+
}
|
|
2793
|
+
wipDone(true, "Color replaced");
|
|
2794
|
+
const finalDims = await getDimensions(output);
|
|
2795
|
+
console.log("");
|
|
2796
|
+
if (finalDims) {
|
|
2797
|
+
console.log(`Output: ${output} (${finalDims[0]}x${finalDims[1]})`);
|
|
2798
|
+
} else {
|
|
2799
|
+
console.log(`Output: ${output}`);
|
|
2800
|
+
}
|
|
2801
|
+
return true;
|
|
2802
|
+
}
|
|
2803
|
+
async function collectOptionsCLI3(detectedColor) {
|
|
2804
|
+
console.log("");
|
|
2805
|
+
console.log(`${BOLD}Color Replacement:${RESET}`);
|
|
2806
|
+
console.log(` ${DIM}Replace one color with another${RESET}`);
|
|
2807
|
+
console.log(` ${DIM}Colors can be hex (#fff), named (white), or rgb(...)${RESET}`);
|
|
2808
|
+
console.log("");
|
|
2809
|
+
const defaultFrom = detectedColor || "#ffffff";
|
|
2810
|
+
let fromColor = await text("Color to replace", defaultFrom);
|
|
2811
|
+
if (!fromColor) {
|
|
2812
|
+
fromColor = defaultFrom;
|
|
2813
|
+
}
|
|
2814
|
+
console.log("");
|
|
2815
|
+
let toColor = await text("New color", "#000000");
|
|
2816
|
+
if (!toColor) {
|
|
2817
|
+
toColor = "#000000";
|
|
2818
|
+
}
|
|
2819
|
+
console.log("");
|
|
2820
|
+
console.log(`${BOLD}Fuzz Value:${RESET} Controls color matching sensitivity`);
|
|
2821
|
+
console.log(` ${DIM}0-10% = Exact match only${RESET}`);
|
|
2822
|
+
console.log(` ${DIM}10-30% = Similar colors${RESET}`);
|
|
2823
|
+
console.log(` ${DIM}30-50% = Wider range${RESET}`);
|
|
2824
|
+
console.log("");
|
|
2825
|
+
let fuzz = await number("Fuzz value (0-100)", 10, 0, 100);
|
|
2826
|
+
if (fuzz < 0 || fuzz > 100) {
|
|
2827
|
+
fuzz = 10;
|
|
2828
|
+
}
|
|
2829
|
+
return { fromColor, toColor, fuzz };
|
|
2830
|
+
}
|
|
2831
|
+
async function run6(inputRaw) {
|
|
2832
|
+
if (!await checkImageMagick()) {
|
|
2833
|
+
error("ImageMagick not found. Please install it:");
|
|
2834
|
+
console.log(" sudo apt update && sudo apt install imagemagick");
|
|
2835
|
+
await pauseOnError();
|
|
2836
|
+
return false;
|
|
2837
|
+
}
|
|
2838
|
+
const input = normalizePath(inputRaw);
|
|
2839
|
+
if (!existsSync9(input)) {
|
|
2840
|
+
error(`File not found: ${input}`);
|
|
2841
|
+
await pauseOnError();
|
|
2842
|
+
return false;
|
|
2843
|
+
}
|
|
2844
|
+
header("PicLet Recolor");
|
|
2845
|
+
wip("Analyzing image...");
|
|
2846
|
+
const dims = await getDimensions(input);
|
|
2847
|
+
if (!dims) {
|
|
2848
|
+
wipDone(false, "Failed to read image");
|
|
2849
|
+
return false;
|
|
2850
|
+
}
|
|
2851
|
+
const borderColor = await getBorderColor(input);
|
|
2852
|
+
wipDone(true, `Size: ${dims[0]}x${dims[1]}`);
|
|
2853
|
+
if (borderColor) {
|
|
2854
|
+
info(`Detected corner color: ${borderColor}`);
|
|
2855
|
+
}
|
|
2856
|
+
const options = await collectOptionsCLI3(borderColor);
|
|
2857
|
+
console.log("");
|
|
2858
|
+
return processImage3(input, options);
|
|
2859
|
+
}
|
|
2860
|
+
async function runGUI7(inputRaw) {
|
|
2861
|
+
const input = normalizePath(inputRaw);
|
|
2862
|
+
if (!existsSync9(input)) {
|
|
2863
|
+
error(`File not found: ${input}`);
|
|
2864
|
+
return false;
|
|
2865
|
+
}
|
|
2866
|
+
const dims = await getDimensions(input);
|
|
2867
|
+
if (!dims) {
|
|
2868
|
+
error("Failed to read image dimensions");
|
|
2869
|
+
return false;
|
|
2870
|
+
}
|
|
2871
|
+
const borderColor = await getBorderColor(input);
|
|
2872
|
+
return startGuiServer({
|
|
2873
|
+
htmlFile: "recolor.html",
|
|
2874
|
+
title: "PicLet - Recolor",
|
|
2875
|
+
imageInfo: {
|
|
2876
|
+
filePath: input,
|
|
2877
|
+
fileName: basename8(input),
|
|
1804
2878
|
width: dims[0],
|
|
1805
2879
|
height: dims[1],
|
|
1806
|
-
borderColor
|
|
2880
|
+
borderColor
|
|
1807
2881
|
},
|
|
1808
2882
|
defaults: {
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
name: p.name,
|
|
1813
|
-
description: p.description,
|
|
1814
|
-
icons: p.icons
|
|
1815
|
-
}))
|
|
2883
|
+
fromColor: borderColor || "#ffffff",
|
|
2884
|
+
toColor: "#000000",
|
|
2885
|
+
fuzz: 10
|
|
1816
2886
|
},
|
|
1817
2887
|
onPreview: async (opts) => {
|
|
1818
|
-
const
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
}
|
|
1822
|
-
return generateCombinedPreview(currentInput, currentBorderColor, toolOpts);
|
|
2888
|
+
const fromColor = opts.fromColor ?? "#ffffff";
|
|
2889
|
+
const toColor = opts.toColor ?? "#000000";
|
|
2890
|
+
const fuzz = opts.fuzz ?? 10;
|
|
2891
|
+
return generatePreview4(input, { fromColor, toColor, fuzz });
|
|
1823
2892
|
},
|
|
1824
2893
|
onProcess: async (opts) => {
|
|
1825
2894
|
const logs = [];
|
|
1826
|
-
const toolOpts = opts;
|
|
1827
|
-
if (!toolOpts.tools || toolOpts.tools.length === 0) {
|
|
1828
|
-
return { success: false, error: "No tools selected", logs };
|
|
1829
|
-
}
|
|
1830
2895
|
if (!await checkImageMagick()) {
|
|
1831
2896
|
return {
|
|
1832
2897
|
success: false,
|
|
1833
2898
|
error: "ImageMagick not found",
|
|
1834
|
-
logs: [{ type: "error", message: "ImageMagick not found. Install: sudo apt install imagemagick" }]
|
|
2899
|
+
logs: [{ type: "error", message: "ImageMagick not found. Install with: sudo apt install imagemagick" }]
|
|
1835
2900
|
};
|
|
1836
2901
|
}
|
|
1837
|
-
|
|
1838
|
-
|
|
2902
|
+
logs.push({ type: "info", message: `Processing ${basename8(input)}...` });
|
|
2903
|
+
const fromColor = opts.fromColor ?? "#ffffff";
|
|
2904
|
+
const toColor = opts.toColor ?? "#000000";
|
|
2905
|
+
const fuzz = opts.fuzz ?? 10;
|
|
2906
|
+
const options = { fromColor, toColor, fuzz };
|
|
2907
|
+
const fileInfo = getFileInfo(input);
|
|
2908
|
+
const outputExt = fileInfo.extension.toLowerCase() === ".gif" ? ".gif" : ".png";
|
|
2909
|
+
const output = `${fileInfo.dirname}/${fileInfo.filename}_recolor${outputExt}`;
|
|
2910
|
+
logs.push({ type: "info", message: `Replacing ${fromColor} \u2192 ${toColor}...` });
|
|
2911
|
+
const success2 = await replaceColor(input, output, fromColor, toColor, fuzz);
|
|
2912
|
+
if (success2) {
|
|
2913
|
+
logs.push({ type: "success", message: "Color replaced" });
|
|
2914
|
+
const finalDims = await getDimensions(output);
|
|
2915
|
+
const sizeStr = finalDims ? ` (${finalDims[0]}x${finalDims[1]})` : "";
|
|
1839
2916
|
return {
|
|
1840
2917
|
success: true,
|
|
1841
|
-
output:
|
|
2918
|
+
output: `${basename8(output)}${sizeStr}`,
|
|
1842
2919
|
logs
|
|
1843
2920
|
};
|
|
1844
2921
|
}
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
const buffer = Buffer.from(data.data, "base64");
|
|
1852
|
-
writeFileSync3(tempPath, buffer);
|
|
1853
|
-
const newDims = await getDimensions(tempPath);
|
|
1854
|
-
if (!newDims) {
|
|
1855
|
-
cleanup(tempPath);
|
|
1856
|
-
return { success: false, error: "Failed to read image dimensions" };
|
|
1857
|
-
}
|
|
1858
|
-
const newBorderColor = await getBorderColor(tempPath);
|
|
1859
|
-
currentInput = tempPath;
|
|
1860
|
-
currentBorderColor = newBorderColor;
|
|
1861
|
-
return {
|
|
1862
|
-
success: true,
|
|
1863
|
-
filePath: tempPath,
|
|
1864
|
-
fileName: data.fileName,
|
|
1865
|
-
width: newDims[0],
|
|
1866
|
-
height: newDims[1],
|
|
1867
|
-
borderColor: newBorderColor
|
|
1868
|
-
};
|
|
1869
|
-
} catch (err) {
|
|
1870
|
-
return { success: false, error: err.message };
|
|
1871
|
-
}
|
|
2922
|
+
logs.push({ type: "error", message: "Color replacement failed" });
|
|
2923
|
+
return {
|
|
2924
|
+
success: false,
|
|
2925
|
+
error: "Processing failed",
|
|
2926
|
+
logs
|
|
2927
|
+
};
|
|
1872
2928
|
}
|
|
1873
2929
|
});
|
|
1874
2930
|
}
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
2931
|
+
async function generatePreview4(input, options) {
|
|
2932
|
+
const tempDir = tmpdir6();
|
|
2933
|
+
const timestamp = Date.now();
|
|
2934
|
+
const tempSource = join8(tempDir, `piclet-preview-${timestamp}-src.png`);
|
|
2935
|
+
const tempOutput = join8(tempDir, `piclet-preview-${timestamp}.png`);
|
|
2936
|
+
try {
|
|
2937
|
+
let previewInput = input;
|
|
2938
|
+
if (isMultiFrame(input)) {
|
|
2939
|
+
if (!await extractFirstFrame(input, tempSource)) {
|
|
2940
|
+
return { success: false, error: "Failed to extract frame" };
|
|
2941
|
+
}
|
|
2942
|
+
previewInput = tempSource;
|
|
2943
|
+
}
|
|
2944
|
+
const success2 = await replaceColor(
|
|
2945
|
+
previewInput,
|
|
2946
|
+
tempOutput,
|
|
2947
|
+
options.fromColor,
|
|
2948
|
+
options.toColor,
|
|
2949
|
+
options.fuzz
|
|
2950
|
+
);
|
|
2951
|
+
if (!success2) {
|
|
2952
|
+
cleanup(tempSource, tempOutput);
|
|
2953
|
+
return { success: false, error: "Color replacement failed" };
|
|
2954
|
+
}
|
|
2955
|
+
const buffer = readFileSync7(tempOutput);
|
|
2956
|
+
const base64 = buffer.toString("base64");
|
|
2957
|
+
const imageData = `data:image/png;base64,${base64}`;
|
|
2958
|
+
const dims = await getDimensions(tempOutput);
|
|
2959
|
+
cleanup(tempSource, tempOutput);
|
|
2960
|
+
return {
|
|
2961
|
+
success: true,
|
|
2962
|
+
imageData,
|
|
2963
|
+
width: dims?.[0],
|
|
2964
|
+
height: dims?.[1]
|
|
2965
|
+
};
|
|
2966
|
+
} catch (err) {
|
|
2967
|
+
cleanup(tempSource, tempOutput);
|
|
2968
|
+
return { success: false, error: err.message };
|
|
2969
|
+
}
|
|
2970
|
+
}
|
|
2971
|
+
var config7 = {
|
|
2972
|
+
id: "recolor",
|
|
2973
|
+
name: "Recolor",
|
|
2974
|
+
icon: "recolor.ico",
|
|
2975
|
+
extensions: [".png", ".jpg", ".jpeg", ".gif", ".bmp"]
|
|
1880
2976
|
};
|
|
1881
2977
|
|
|
1882
2978
|
// src/tools/remove-bg.ts
|
|
1883
|
-
import { existsSync as
|
|
1884
|
-
import { tmpdir as
|
|
1885
|
-
import { basename as
|
|
1886
|
-
|
|
2979
|
+
import { existsSync as existsSync11, readFileSync as readFileSync9, renameSync as renameSync2 } from "fs";
|
|
2980
|
+
import { tmpdir as tmpdir7 } from "os";
|
|
2981
|
+
import { basename as basename9, join as join10 } from "path";
|
|
2982
|
+
|
|
2983
|
+
// src/lib/config.ts
|
|
2984
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync6, readFileSync as readFileSync8, writeFileSync as writeFileSync3 } from "fs";
|
|
2985
|
+
import { dirname as dirname7, join as join9 } from "path";
|
|
2986
|
+
import { homedir as homedir2 } from "os";
|
|
2987
|
+
var DEFAULT_CONFIG = {
|
|
2988
|
+
removeBg: {
|
|
2989
|
+
fuzz: 10,
|
|
2990
|
+
trim: true,
|
|
2991
|
+
preserveInner: false,
|
|
2992
|
+
makeSquare: false
|
|
2993
|
+
},
|
|
2994
|
+
rescale: {
|
|
2995
|
+
defaultScale: 50,
|
|
2996
|
+
makeSquare: false
|
|
2997
|
+
},
|
|
2998
|
+
iconpack: {
|
|
2999
|
+
platforms: ["web", "android", "ios"]
|
|
3000
|
+
}
|
|
3001
|
+
};
|
|
3002
|
+
function getConfigDir() {
|
|
3003
|
+
return join9(homedir2(), ".config", "piclet");
|
|
3004
|
+
}
|
|
3005
|
+
function getConfigPath() {
|
|
3006
|
+
return join9(getConfigDir(), "config.json");
|
|
3007
|
+
}
|
|
3008
|
+
function loadConfig() {
|
|
3009
|
+
const configPath = getConfigPath();
|
|
3010
|
+
if (!existsSync10(configPath)) {
|
|
3011
|
+
return { ...DEFAULT_CONFIG };
|
|
3012
|
+
}
|
|
3013
|
+
try {
|
|
3014
|
+
const content = readFileSync8(configPath, "utf-8");
|
|
3015
|
+
const loaded = JSON.parse(content);
|
|
3016
|
+
return {
|
|
3017
|
+
removeBg: { ...DEFAULT_CONFIG.removeBg, ...loaded.removeBg },
|
|
3018
|
+
rescale: { ...DEFAULT_CONFIG.rescale, ...loaded.rescale },
|
|
3019
|
+
iconpack: { ...DEFAULT_CONFIG.iconpack, ...loaded.iconpack }
|
|
3020
|
+
};
|
|
3021
|
+
} catch {
|
|
3022
|
+
return { ...DEFAULT_CONFIG };
|
|
3023
|
+
}
|
|
3024
|
+
}
|
|
3025
|
+
function resetConfig() {
|
|
3026
|
+
const configPath = getConfigPath();
|
|
3027
|
+
const configDir = dirname7(configPath);
|
|
3028
|
+
if (!existsSync10(configDir)) {
|
|
3029
|
+
mkdirSync6(configDir, { recursive: true });
|
|
3030
|
+
}
|
|
3031
|
+
writeFileSync3(configPath, JSON.stringify(DEFAULT_CONFIG, null, 2));
|
|
3032
|
+
}
|
|
3033
|
+
|
|
3034
|
+
// src/tools/remove-bg.ts
|
|
3035
|
+
async function processImage4(input, borderColor, options) {
|
|
1887
3036
|
const fileInfo = getFileInfo(input);
|
|
1888
|
-
const
|
|
1889
|
-
const
|
|
3037
|
+
const outputExt = fileInfo.extension.toLowerCase() === ".gif" ? ".gif" : ".png";
|
|
3038
|
+
const output = `${fileInfo.dirname}/${fileInfo.filename}_nobg${outputExt}`;
|
|
3039
|
+
const tempFile = `${fileInfo.dirname}/${fileInfo.filename}_temp${outputExt}`;
|
|
1890
3040
|
wip("Removing background...");
|
|
1891
3041
|
let bgRemoved = false;
|
|
1892
3042
|
if (options.preserveInner && borderColor) {
|
|
@@ -1923,8 +3073,8 @@ async function processImage(input, borderColor, options) {
|
|
|
1923
3073
|
}
|
|
1924
3074
|
if (options.makeSquare) {
|
|
1925
3075
|
wip("Making square...");
|
|
1926
|
-
const tempSquare = `${fileInfo.dirname}/${fileInfo.filename}_square_temp
|
|
1927
|
-
if (await
|
|
3076
|
+
const tempSquare = `${fileInfo.dirname}/${fileInfo.filename}_square_temp${outputExt}`;
|
|
3077
|
+
if (await squarify(output, tempSquare)) {
|
|
1928
3078
|
renameSync2(tempSquare, output);
|
|
1929
3079
|
wipDone(true, "Made square");
|
|
1930
3080
|
} else {
|
|
@@ -1941,7 +3091,7 @@ async function processImage(input, borderColor, options) {
|
|
|
1941
3091
|
}
|
|
1942
3092
|
return true;
|
|
1943
3093
|
}
|
|
1944
|
-
async function
|
|
3094
|
+
async function collectOptionsCLI4() {
|
|
1945
3095
|
console.log("");
|
|
1946
3096
|
console.log(`${BOLD}Fuzz Value:${RESET} Controls color matching strictness`);
|
|
1947
3097
|
console.log(` ${DIM}0-10% = Only exact or very similar colors${RESET}`);
|
|
@@ -1971,7 +3121,7 @@ async function collectOptionsCLI() {
|
|
|
1971
3121
|
);
|
|
1972
3122
|
return { fuzz, doTrim, preserveInner, makeSquare };
|
|
1973
3123
|
}
|
|
1974
|
-
async function
|
|
3124
|
+
async function run7(inputRaw) {
|
|
1975
3125
|
if (!await checkImageMagick()) {
|
|
1976
3126
|
error("ImageMagick not found. Please install it:");
|
|
1977
3127
|
console.log(" sudo apt update && sudo apt install imagemagick");
|
|
@@ -1979,7 +3129,7 @@ async function run3(inputRaw) {
|
|
|
1979
3129
|
return false;
|
|
1980
3130
|
}
|
|
1981
3131
|
const input = normalizePath(inputRaw);
|
|
1982
|
-
if (!
|
|
3132
|
+
if (!existsSync11(input)) {
|
|
1983
3133
|
error(`File not found: ${input}`);
|
|
1984
3134
|
await pauseOnError();
|
|
1985
3135
|
return false;
|
|
@@ -1996,13 +3146,13 @@ async function run3(inputRaw) {
|
|
|
1996
3146
|
if (borderColor) {
|
|
1997
3147
|
info(`Detected border color: ${borderColor}`);
|
|
1998
3148
|
}
|
|
1999
|
-
const options = await
|
|
3149
|
+
const options = await collectOptionsCLI4();
|
|
2000
3150
|
console.log("");
|
|
2001
|
-
return
|
|
3151
|
+
return processImage4(input, borderColor, options);
|
|
2002
3152
|
}
|
|
2003
|
-
async function
|
|
3153
|
+
async function runGUI8(inputRaw) {
|
|
2004
3154
|
const input = normalizePath(inputRaw);
|
|
2005
|
-
if (!
|
|
3155
|
+
if (!existsSync11(input)) {
|
|
2006
3156
|
error(`File not found: ${input}`);
|
|
2007
3157
|
return false;
|
|
2008
3158
|
}
|
|
@@ -2012,14 +3162,14 @@ async function runGUI4(inputRaw) {
|
|
|
2012
3162
|
return false;
|
|
2013
3163
|
}
|
|
2014
3164
|
const borderColor = await getBorderColor(input);
|
|
2015
|
-
const
|
|
2016
|
-
const defaults =
|
|
3165
|
+
const config12 = loadConfig();
|
|
3166
|
+
const defaults = config12.removeBg;
|
|
2017
3167
|
return startGuiServer({
|
|
2018
3168
|
htmlFile: "remove-bg.html",
|
|
2019
3169
|
title: "PicLet - Remove Background",
|
|
2020
3170
|
imageInfo: {
|
|
2021
3171
|
filePath: input,
|
|
2022
|
-
fileName:
|
|
3172
|
+
fileName: basename9(input),
|
|
2023
3173
|
width: dims[0],
|
|
2024
3174
|
height: dims[1],
|
|
2025
3175
|
borderColor
|
|
@@ -2037,7 +3187,7 @@ async function runGUI4(inputRaw) {
|
|
|
2037
3187
|
preserveInner: opts.preserveInner ?? defaults.preserveInner,
|
|
2038
3188
|
makeSquare: opts.makeSquare ?? defaults.makeSquare
|
|
2039
3189
|
};
|
|
2040
|
-
return
|
|
3190
|
+
return generatePreview5(input, borderColor, options);
|
|
2041
3191
|
},
|
|
2042
3192
|
onProcess: async (opts) => {
|
|
2043
3193
|
const logs = [];
|
|
@@ -2048,7 +3198,7 @@ async function runGUI4(inputRaw) {
|
|
|
2048
3198
|
logs: [{ type: "error", message: "ImageMagick not found. Install with: sudo apt install imagemagick" }]
|
|
2049
3199
|
};
|
|
2050
3200
|
}
|
|
2051
|
-
logs.push({ type: "info", message: `Processing ${
|
|
3201
|
+
logs.push({ type: "info", message: `Processing ${basename9(input)}...` });
|
|
2052
3202
|
const options = {
|
|
2053
3203
|
fuzz: opts.fuzz ?? defaults.fuzz,
|
|
2054
3204
|
doTrim: opts.trim ?? defaults.trim,
|
|
@@ -2064,7 +3214,7 @@ async function runGUI4(inputRaw) {
|
|
|
2064
3214
|
const sizeStr = finalDims ? ` (${finalDims[0]}x${finalDims[1]})` : "";
|
|
2065
3215
|
return {
|
|
2066
3216
|
success: true,
|
|
2067
|
-
output: `${
|
|
3217
|
+
output: `${basename9(output)}${sizeStr}`,
|
|
2068
3218
|
logs
|
|
2069
3219
|
};
|
|
2070
3220
|
}
|
|
@@ -2078,8 +3228,9 @@ async function runGUI4(inputRaw) {
|
|
|
2078
3228
|
}
|
|
2079
3229
|
async function processImageSilent(input, borderColor, options, logs) {
|
|
2080
3230
|
const fileInfo = getFileInfo(input);
|
|
2081
|
-
const
|
|
2082
|
-
const
|
|
3231
|
+
const outputExt = fileInfo.extension.toLowerCase() === ".gif" ? ".gif" : ".png";
|
|
3232
|
+
const output = `${fileInfo.dirname}/${fileInfo.filename}_nobg${outputExt}`;
|
|
3233
|
+
const tempFile = `${fileInfo.dirname}/${fileInfo.filename}_temp${outputExt}`;
|
|
2083
3234
|
let bgRemoved = false;
|
|
2084
3235
|
if (options.preserveInner && borderColor) {
|
|
2085
3236
|
bgRemoved = await removeBackgroundBorderOnly(
|
|
@@ -2115,8 +3266,8 @@ async function processImageSilent(input, borderColor, options, logs) {
|
|
|
2115
3266
|
}
|
|
2116
3267
|
if (options.makeSquare) {
|
|
2117
3268
|
logs.push({ type: "info", message: "Making square..." });
|
|
2118
|
-
const tempSquare = `${fileInfo.dirname}/${fileInfo.filename}_square_temp
|
|
2119
|
-
if (await
|
|
3269
|
+
const tempSquare = `${fileInfo.dirname}/${fileInfo.filename}_square_temp${outputExt}`;
|
|
3270
|
+
if (await squarify(output, tempSquare)) {
|
|
2120
3271
|
renameSync2(tempSquare, output);
|
|
2121
3272
|
logs.push({ type: "success", message: "Made square" });
|
|
2122
3273
|
} else {
|
|
@@ -2126,20 +3277,29 @@ async function processImageSilent(input, borderColor, options, logs) {
|
|
|
2126
3277
|
}
|
|
2127
3278
|
return true;
|
|
2128
3279
|
}
|
|
2129
|
-
async function
|
|
2130
|
-
const tempDir =
|
|
3280
|
+
async function generatePreview5(input, borderColor, options) {
|
|
3281
|
+
const tempDir = tmpdir7();
|
|
2131
3282
|
const timestamp = Date.now();
|
|
2132
|
-
const
|
|
2133
|
-
const
|
|
3283
|
+
const tempSource = join10(tempDir, `piclet-preview-${timestamp}-src.png`);
|
|
3284
|
+
const tempFile = join10(tempDir, `piclet-preview-${timestamp}.png`);
|
|
3285
|
+
const tempOutput = join10(tempDir, `piclet-preview-${timestamp}-out.png`);
|
|
2134
3286
|
try {
|
|
3287
|
+
let previewInput = input;
|
|
3288
|
+
if (isMultiFrame(input)) {
|
|
3289
|
+
if (!await extractFirstFrame(input, tempSource)) {
|
|
3290
|
+
return { success: false, error: "Failed to extract frame" };
|
|
3291
|
+
}
|
|
3292
|
+
previewInput = tempSource;
|
|
3293
|
+
}
|
|
2135
3294
|
let bgRemoved = false;
|
|
2136
3295
|
if (options.preserveInner && borderColor) {
|
|
2137
|
-
bgRemoved = await removeBackgroundBorderOnly(
|
|
3296
|
+
bgRemoved = await removeBackgroundBorderOnly(previewInput, tempFile, borderColor, options.fuzz);
|
|
2138
3297
|
}
|
|
2139
3298
|
if (!bgRemoved && borderColor) {
|
|
2140
|
-
bgRemoved = await removeBackground(
|
|
3299
|
+
bgRemoved = await removeBackground(previewInput, tempFile, borderColor, options.fuzz);
|
|
2141
3300
|
}
|
|
2142
3301
|
if (!bgRemoved) {
|
|
3302
|
+
cleanup(tempSource);
|
|
2143
3303
|
return { success: false, error: "Background removal failed" };
|
|
2144
3304
|
}
|
|
2145
3305
|
let currentFile = tempFile;
|
|
@@ -2150,17 +3310,17 @@ async function generatePreview2(input, borderColor, options) {
|
|
|
2150
3310
|
}
|
|
2151
3311
|
}
|
|
2152
3312
|
if (options.makeSquare) {
|
|
2153
|
-
const squareFile =
|
|
2154
|
-
if (await
|
|
3313
|
+
const squareFile = join10(tempDir, `piclet-preview-${timestamp}-sq.png`);
|
|
3314
|
+
if (await squarify(currentFile, squareFile)) {
|
|
2155
3315
|
cleanup(currentFile);
|
|
2156
3316
|
currentFile = squareFile;
|
|
2157
3317
|
}
|
|
2158
3318
|
}
|
|
2159
|
-
const buffer =
|
|
3319
|
+
const buffer = readFileSync9(currentFile);
|
|
2160
3320
|
const base64 = buffer.toString("base64");
|
|
2161
3321
|
const imageData = `data:image/png;base64,${base64}`;
|
|
2162
3322
|
const dims = await getDimensions(currentFile);
|
|
2163
|
-
cleanup(currentFile, tempFile, tempOutput);
|
|
3323
|
+
cleanup(currentFile, tempFile, tempOutput, tempSource);
|
|
2164
3324
|
return {
|
|
2165
3325
|
success: true,
|
|
2166
3326
|
imageData,
|
|
@@ -2168,22 +3328,22 @@ async function generatePreview2(input, borderColor, options) {
|
|
|
2168
3328
|
height: dims?.[1]
|
|
2169
3329
|
};
|
|
2170
3330
|
} catch (err) {
|
|
2171
|
-
cleanup(tempFile, tempOutput);
|
|
3331
|
+
cleanup(tempFile, tempOutput, tempSource);
|
|
2172
3332
|
return { success: false, error: err.message };
|
|
2173
3333
|
}
|
|
2174
3334
|
}
|
|
2175
|
-
var
|
|
3335
|
+
var config8 = {
|
|
2176
3336
|
id: "remove-bg",
|
|
2177
3337
|
name: "Remove Background",
|
|
2178
3338
|
icon: "removebg.ico",
|
|
2179
|
-
extensions: [".png", ".jpg", ".jpeg", ".ico"]
|
|
3339
|
+
extensions: [".png", ".jpg", ".jpeg", ".ico", ".gif"]
|
|
2180
3340
|
};
|
|
2181
3341
|
|
|
2182
3342
|
// src/tools/rescale.ts
|
|
2183
|
-
import { existsSync as
|
|
2184
|
-
import { tmpdir as
|
|
2185
|
-
import { basename as
|
|
2186
|
-
async function
|
|
3343
|
+
import { existsSync as existsSync12, readFileSync as readFileSync10 } from "fs";
|
|
3344
|
+
import { tmpdir as tmpdir8 } from "os";
|
|
3345
|
+
import { basename as basename10, join as join11 } from "path";
|
|
3346
|
+
async function run8(inputRaw) {
|
|
2187
3347
|
if (!await checkImageMagick()) {
|
|
2188
3348
|
error("ImageMagick not found. Please install it:");
|
|
2189
3349
|
console.log(" sudo apt update && sudo apt install imagemagick");
|
|
@@ -2191,7 +3351,7 @@ async function run4(inputRaw) {
|
|
|
2191
3351
|
return false;
|
|
2192
3352
|
}
|
|
2193
3353
|
const input = normalizePath(inputRaw);
|
|
2194
|
-
if (!
|
|
3354
|
+
if (!existsSync12(input)) {
|
|
2195
3355
|
error(`File not found: ${input}`);
|
|
2196
3356
|
await pauseOnError();
|
|
2197
3357
|
return false;
|
|
@@ -2252,7 +3412,7 @@ async function run4(inputRaw) {
|
|
|
2252
3412
|
} else {
|
|
2253
3413
|
scaled = await resize(input, output, targetW, targetH);
|
|
2254
3414
|
}
|
|
2255
|
-
if (!scaled || !
|
|
3415
|
+
if (!scaled || !existsSync12(output)) {
|
|
2256
3416
|
wipDone(false, "Scaling failed");
|
|
2257
3417
|
return false;
|
|
2258
3418
|
}
|
|
@@ -2266,9 +3426,9 @@ async function run4(inputRaw) {
|
|
|
2266
3426
|
success(`Output: ${output}`);
|
|
2267
3427
|
return true;
|
|
2268
3428
|
}
|
|
2269
|
-
async function
|
|
3429
|
+
async function runGUI9(inputRaw) {
|
|
2270
3430
|
const input = normalizePath(inputRaw);
|
|
2271
|
-
if (!
|
|
3431
|
+
if (!existsSync12(input)) {
|
|
2272
3432
|
error(`File not found: ${input}`);
|
|
2273
3433
|
return false;
|
|
2274
3434
|
}
|
|
@@ -2283,7 +3443,7 @@ async function runGUI5(inputRaw) {
|
|
|
2283
3443
|
title: "PicLet - Scale Image",
|
|
2284
3444
|
imageInfo: {
|
|
2285
3445
|
filePath: input,
|
|
2286
|
-
fileName:
|
|
3446
|
+
fileName: basename10(input),
|
|
2287
3447
|
width: dims[0],
|
|
2288
3448
|
height: dims[1],
|
|
2289
3449
|
borderColor: null
|
|
@@ -2297,7 +3457,7 @@ async function runGUI5(inputRaw) {
|
|
|
2297
3457
|
height: opts.height ?? Math.round(dims[1] / 2),
|
|
2298
3458
|
makeSquare: opts.makeSquare ?? false
|
|
2299
3459
|
};
|
|
2300
|
-
return
|
|
3460
|
+
return generatePreview6(input, options);
|
|
2301
3461
|
},
|
|
2302
3462
|
onProcess: async (opts) => {
|
|
2303
3463
|
const logs = [];
|
|
@@ -2322,13 +3482,13 @@ async function runGUI5(inputRaw) {
|
|
|
2322
3482
|
} else {
|
|
2323
3483
|
scaled = await resize(input, output, options.width, options.height);
|
|
2324
3484
|
}
|
|
2325
|
-
if (scaled &&
|
|
3485
|
+
if (scaled && existsSync12(output)) {
|
|
2326
3486
|
const finalDims = await getDimensions(output);
|
|
2327
3487
|
const sizeStr = finalDims ? ` (${finalDims[0]}x${finalDims[1]})` : "";
|
|
2328
3488
|
logs.push({ type: "success", message: "Scaled successfully" });
|
|
2329
3489
|
return {
|
|
2330
3490
|
success: true,
|
|
2331
|
-
output: `${
|
|
3491
|
+
output: `${basename10(output)}${sizeStr}`,
|
|
2332
3492
|
logs
|
|
2333
3493
|
};
|
|
2334
3494
|
}
|
|
@@ -2337,11 +3497,19 @@ async function runGUI5(inputRaw) {
|
|
|
2337
3497
|
}
|
|
2338
3498
|
});
|
|
2339
3499
|
}
|
|
2340
|
-
async function
|
|
2341
|
-
const tempDir =
|
|
3500
|
+
async function generatePreview6(input, options) {
|
|
3501
|
+
const tempDir = tmpdir8();
|
|
2342
3502
|
const timestamp = Date.now();
|
|
2343
|
-
const
|
|
3503
|
+
const tempSource = join11(tempDir, `piclet-preview-${timestamp}-src.png`);
|
|
3504
|
+
const tempOutput = join11(tempDir, `piclet-preview-${timestamp}.png`);
|
|
2344
3505
|
try {
|
|
3506
|
+
let previewInput = input;
|
|
3507
|
+
if (isMultiFrame(input)) {
|
|
3508
|
+
if (!await extractFirstFrame(input, tempSource)) {
|
|
3509
|
+
return { success: false, error: "Failed to extract frame" };
|
|
3510
|
+
}
|
|
3511
|
+
previewInput = tempSource;
|
|
3512
|
+
}
|
|
2345
3513
|
let scaled = false;
|
|
2346
3514
|
let targetW = options.width;
|
|
2347
3515
|
let targetH = options.height;
|
|
@@ -2349,18 +3517,19 @@ async function generatePreview3(input, options) {
|
|
|
2349
3517
|
const maxDim = Math.max(targetW, targetH);
|
|
2350
3518
|
targetW = maxDim;
|
|
2351
3519
|
targetH = maxDim;
|
|
2352
|
-
scaled = await scaleWithPadding(
|
|
3520
|
+
scaled = await scaleWithPadding(previewInput, tempOutput, targetW, targetH);
|
|
2353
3521
|
} else {
|
|
2354
|
-
scaled = await resize(
|
|
3522
|
+
scaled = await resize(previewInput, tempOutput, targetW, targetH);
|
|
2355
3523
|
}
|
|
2356
|
-
if (!scaled || !
|
|
3524
|
+
if (!scaled || !existsSync12(tempOutput)) {
|
|
3525
|
+
cleanup(tempSource);
|
|
2357
3526
|
return { success: false, error: "Scaling failed" };
|
|
2358
3527
|
}
|
|
2359
|
-
const buffer =
|
|
3528
|
+
const buffer = readFileSync10(tempOutput);
|
|
2360
3529
|
const base64 = buffer.toString("base64");
|
|
2361
3530
|
const imageData = `data:image/png;base64,${base64}`;
|
|
2362
3531
|
const dims = await getDimensions(tempOutput);
|
|
2363
|
-
cleanup(tempOutput);
|
|
3532
|
+
cleanup(tempSource, tempOutput);
|
|
2364
3533
|
return {
|
|
2365
3534
|
success: true,
|
|
2366
3535
|
imageData,
|
|
@@ -2368,11 +3537,11 @@ async function generatePreview3(input, options) {
|
|
|
2368
3537
|
height: dims?.[1]
|
|
2369
3538
|
};
|
|
2370
3539
|
} catch (err) {
|
|
2371
|
-
cleanup(tempOutput);
|
|
3540
|
+
cleanup(tempSource, tempOutput);
|
|
2372
3541
|
return { success: false, error: err.message };
|
|
2373
3542
|
}
|
|
2374
3543
|
}
|
|
2375
|
-
var
|
|
3544
|
+
var config9 = {
|
|
2376
3545
|
id: "rescale",
|
|
2377
3546
|
name: "Scale Image",
|
|
2378
3547
|
icon: "rescale.ico",
|
|
@@ -2380,8 +3549,8 @@ var config5 = {
|
|
|
2380
3549
|
};
|
|
2381
3550
|
|
|
2382
3551
|
// src/tools/storepack.ts
|
|
2383
|
-
import { existsSync as
|
|
2384
|
-
import { basename as
|
|
3552
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync7 } from "fs";
|
|
3553
|
+
import { basename as basename11, join as join12 } from "path";
|
|
2385
3554
|
async function scaleImage(input, output, width, height, mode) {
|
|
2386
3555
|
switch (mode) {
|
|
2387
3556
|
case "fill":
|
|
@@ -2398,7 +3567,7 @@ async function generatePresetImages(sourceImg, outputDir, preset, scaleMode = "f
|
|
|
2398
3567
|
const total = preset.icons.length;
|
|
2399
3568
|
for (let i = 0; i < total; i++) {
|
|
2400
3569
|
const icon = preset.icons[i];
|
|
2401
|
-
const outputPath =
|
|
3570
|
+
const outputPath = join12(outputDir, icon.filename);
|
|
2402
3571
|
if (logs) {
|
|
2403
3572
|
logs.push({ type: "info", message: `[${i + 1}/${total}] ${icon.filename}` });
|
|
2404
3573
|
} else {
|
|
@@ -2426,7 +3595,7 @@ async function generatePresetImages(sourceImg, outputDir, preset, scaleMode = "f
|
|
|
2426
3595
|
}
|
|
2427
3596
|
return failed;
|
|
2428
3597
|
}
|
|
2429
|
-
async function
|
|
3598
|
+
async function run9(inputRaw) {
|
|
2430
3599
|
if (!await checkImageMagick()) {
|
|
2431
3600
|
error("ImageMagick not found. Please install it:");
|
|
2432
3601
|
console.log(" sudo apt update && sudo apt install imagemagick");
|
|
@@ -2434,7 +3603,7 @@ async function run5(inputRaw) {
|
|
|
2434
3603
|
return false;
|
|
2435
3604
|
}
|
|
2436
3605
|
const input = normalizePath(inputRaw);
|
|
2437
|
-
if (!
|
|
3606
|
+
if (!existsSync13(input)) {
|
|
2438
3607
|
error(`File not found: ${input}`);
|
|
2439
3608
|
await pauseOnError();
|
|
2440
3609
|
return false;
|
|
@@ -2462,11 +3631,11 @@ async function run5(inputRaw) {
|
|
|
2462
3631
|
return false;
|
|
2463
3632
|
}
|
|
2464
3633
|
const outputDir = `${fileInfo.dirname}/${fileInfo.filename}_${preset.id}`;
|
|
2465
|
-
|
|
3634
|
+
mkdirSync7(outputDir, { recursive: true });
|
|
2466
3635
|
info(`Output: ${outputDir}`);
|
|
2467
3636
|
console.log("");
|
|
2468
3637
|
wip("Preparing source...");
|
|
2469
|
-
const tempSource =
|
|
3638
|
+
const tempSource = join12(outputDir, ".source.png");
|
|
2470
3639
|
if (!await squarify(input, tempSource)) {
|
|
2471
3640
|
wipDone(false, "Failed to prepare source");
|
|
2472
3641
|
return false;
|
|
@@ -2482,12 +3651,187 @@ async function run5(inputRaw) {
|
|
|
2482
3651
|
} else {
|
|
2483
3652
|
error(`${failed}/${preset.icons.length} images failed`);
|
|
2484
3653
|
}
|
|
2485
|
-
info(`Output: ${outputDir}`);
|
|
2486
|
-
return failed === 0;
|
|
3654
|
+
info(`Output: ${outputDir}`);
|
|
3655
|
+
return failed === 0;
|
|
3656
|
+
}
|
|
3657
|
+
async function runGUI10(inputRaw) {
|
|
3658
|
+
const input = normalizePath(inputRaw);
|
|
3659
|
+
if (!existsSync13(input)) {
|
|
3660
|
+
error(`File not found: ${input}`);
|
|
3661
|
+
return false;
|
|
3662
|
+
}
|
|
3663
|
+
const dims = await getDimensions(input);
|
|
3664
|
+
if (!dims) {
|
|
3665
|
+
error("Failed to read image dimensions");
|
|
3666
|
+
return false;
|
|
3667
|
+
}
|
|
3668
|
+
const fileInfo = getFileInfo(input);
|
|
3669
|
+
const presets = loadPresets();
|
|
3670
|
+
return startGuiServer({
|
|
3671
|
+
htmlFile: "storepack.html",
|
|
3672
|
+
title: "PicLet - Store Pack",
|
|
3673
|
+
imageInfo: {
|
|
3674
|
+
filePath: input,
|
|
3675
|
+
fileName: basename11(input),
|
|
3676
|
+
width: dims[0],
|
|
3677
|
+
height: dims[1],
|
|
3678
|
+
borderColor: null
|
|
3679
|
+
},
|
|
3680
|
+
defaults: {
|
|
3681
|
+
presets: presets.map((p) => ({
|
|
3682
|
+
id: p.id,
|
|
3683
|
+
name: p.name,
|
|
3684
|
+
description: p.description,
|
|
3685
|
+
icons: p.icons
|
|
3686
|
+
}))
|
|
3687
|
+
},
|
|
3688
|
+
onProcess: async (opts) => {
|
|
3689
|
+
const logs = [];
|
|
3690
|
+
if (!await checkImageMagick()) {
|
|
3691
|
+
return {
|
|
3692
|
+
success: false,
|
|
3693
|
+
error: "ImageMagick not found",
|
|
3694
|
+
logs: [{ type: "error", message: "ImageMagick not found" }]
|
|
3695
|
+
};
|
|
3696
|
+
}
|
|
3697
|
+
const presetId = opts.preset;
|
|
3698
|
+
const scaleMode = opts.scaleMode || "fit";
|
|
3699
|
+
const preset = presets.find((p) => p.id === presetId);
|
|
3700
|
+
if (!preset) {
|
|
3701
|
+
return {
|
|
3702
|
+
success: false,
|
|
3703
|
+
error: "Preset not found",
|
|
3704
|
+
logs: [{ type: "error", message: "No preset selected" }]
|
|
3705
|
+
};
|
|
3706
|
+
}
|
|
3707
|
+
const outputDir = `${fileInfo.dirname}/${fileInfo.filename}_${preset.id}`;
|
|
3708
|
+
mkdirSync7(outputDir, { recursive: true });
|
|
3709
|
+
logs.push({ type: "info", message: `Output: ${outputDir}` });
|
|
3710
|
+
logs.push({ type: "info", message: `Scale mode: ${scaleMode}` });
|
|
3711
|
+
logs.push({ type: "info", message: `Generating ${preset.icons.length} images...` });
|
|
3712
|
+
const failed = await generatePresetImages(input, outputDir, preset, scaleMode, logs);
|
|
3713
|
+
if (failed === 0) {
|
|
3714
|
+
logs.push({ type: "success", message: `All ${preset.icons.length} images generated` });
|
|
3715
|
+
return {
|
|
3716
|
+
success: true,
|
|
3717
|
+
output: `${preset.icons.length} images saved to ${fileInfo.filename}_${preset.id}/`,
|
|
3718
|
+
logs
|
|
3719
|
+
};
|
|
3720
|
+
}
|
|
3721
|
+
return {
|
|
3722
|
+
success: false,
|
|
3723
|
+
error: `${failed} image(s) failed`,
|
|
3724
|
+
logs
|
|
3725
|
+
};
|
|
3726
|
+
}
|
|
3727
|
+
});
|
|
3728
|
+
}
|
|
3729
|
+
var config10 = {
|
|
3730
|
+
id: "storepack",
|
|
3731
|
+
name: "Store Pack",
|
|
3732
|
+
icon: "storepack.ico",
|
|
3733
|
+
extensions: [".png", ".jpg", ".jpeg", ".gif"]
|
|
3734
|
+
};
|
|
3735
|
+
|
|
3736
|
+
// src/tools/transform.ts
|
|
3737
|
+
import { existsSync as existsSync14, readFileSync as readFileSync11 } from "fs";
|
|
3738
|
+
import { tmpdir as tmpdir9 } from "os";
|
|
3739
|
+
import { basename as basename12, join as join13 } from "path";
|
|
3740
|
+
var TRANSFORM_LABELS = {
|
|
3741
|
+
"flip-h": "Flip Horizontal",
|
|
3742
|
+
"flip-v": "Flip Vertical",
|
|
3743
|
+
"rotate-90": "Rotate 90\xB0",
|
|
3744
|
+
"rotate-180": "Rotate 180\xB0",
|
|
3745
|
+
"rotate-270": "Rotate 270\xB0"
|
|
3746
|
+
};
|
|
3747
|
+
async function processImage5(input, options) {
|
|
3748
|
+
const fileInfo = getFileInfo(input);
|
|
3749
|
+
const outputExt = fileInfo.extension.toLowerCase() === ".gif" ? ".gif" : ".png";
|
|
3750
|
+
const suffix = options.transform.replace("-", "");
|
|
3751
|
+
const output = `${fileInfo.dirname}/${fileInfo.filename}_${suffix}${outputExt}`;
|
|
3752
|
+
wip(`Applying ${TRANSFORM_LABELS[options.transform]}...`);
|
|
3753
|
+
let success2 = false;
|
|
3754
|
+
switch (options.transform) {
|
|
3755
|
+
case "flip-h":
|
|
3756
|
+
success2 = await flipHorizontal(input, output);
|
|
3757
|
+
break;
|
|
3758
|
+
case "flip-v":
|
|
3759
|
+
success2 = await flipVertical(input, output);
|
|
3760
|
+
break;
|
|
3761
|
+
case "rotate-90":
|
|
3762
|
+
success2 = await rotate(input, output, 90);
|
|
3763
|
+
break;
|
|
3764
|
+
case "rotate-180":
|
|
3765
|
+
success2 = await rotate(input, output, 180);
|
|
3766
|
+
break;
|
|
3767
|
+
case "rotate-270":
|
|
3768
|
+
success2 = await rotate(input, output, 270);
|
|
3769
|
+
break;
|
|
3770
|
+
}
|
|
3771
|
+
if (!success2) {
|
|
3772
|
+
wipDone(false, "Transform failed");
|
|
3773
|
+
return false;
|
|
3774
|
+
}
|
|
3775
|
+
wipDone(true, TRANSFORM_LABELS[options.transform]);
|
|
3776
|
+
const finalDims = await getDimensions(output);
|
|
3777
|
+
console.log("");
|
|
3778
|
+
if (finalDims) {
|
|
3779
|
+
console.log(`Output: ${output} (${finalDims[0]}x${finalDims[1]})`);
|
|
3780
|
+
} else {
|
|
3781
|
+
console.log(`Output: ${output}`);
|
|
3782
|
+
}
|
|
3783
|
+
return true;
|
|
3784
|
+
}
|
|
3785
|
+
async function collectOptionsCLI5() {
|
|
3786
|
+
console.log("");
|
|
3787
|
+
console.log(`${BOLD}Transform Options:${RESET}`);
|
|
3788
|
+
console.log(` ${DIM}1. Flip Horizontal (mirror)${RESET}`);
|
|
3789
|
+
console.log(` ${DIM}2. Flip Vertical${RESET}`);
|
|
3790
|
+
console.log(` ${DIM}3. Rotate 90\xB0 clockwise${RESET}`);
|
|
3791
|
+
console.log(` ${DIM}4. Rotate 180\xB0${RESET}`);
|
|
3792
|
+
console.log(` ${DIM}5. Rotate 270\xB0 clockwise${RESET}`);
|
|
3793
|
+
console.log("");
|
|
3794
|
+
const transform = await select(
|
|
3795
|
+
"Select transform",
|
|
3796
|
+
[
|
|
3797
|
+
{ value: "flip-h", title: "Flip Horizontal" },
|
|
3798
|
+
{ value: "flip-v", title: "Flip Vertical" },
|
|
3799
|
+
{ value: "rotate-90", title: "Rotate 90\xB0" },
|
|
3800
|
+
{ value: "rotate-180", title: "Rotate 180\xB0" },
|
|
3801
|
+
{ value: "rotate-270", title: "Rotate 270\xB0" }
|
|
3802
|
+
],
|
|
3803
|
+
"flip-h"
|
|
3804
|
+
);
|
|
3805
|
+
return { transform: transform ?? "flip-h" };
|
|
3806
|
+
}
|
|
3807
|
+
async function run10(inputRaw) {
|
|
3808
|
+
if (!await checkImageMagick()) {
|
|
3809
|
+
error("ImageMagick not found. Please install it:");
|
|
3810
|
+
console.log(" sudo apt update && sudo apt install imagemagick");
|
|
3811
|
+
await pauseOnError();
|
|
3812
|
+
return false;
|
|
3813
|
+
}
|
|
3814
|
+
const input = normalizePath(inputRaw);
|
|
3815
|
+
if (!existsSync14(input)) {
|
|
3816
|
+
error(`File not found: ${input}`);
|
|
3817
|
+
await pauseOnError();
|
|
3818
|
+
return false;
|
|
3819
|
+
}
|
|
3820
|
+
header("PicLet Transform");
|
|
3821
|
+
wip("Analyzing image...");
|
|
3822
|
+
const dims = await getDimensions(input);
|
|
3823
|
+
if (!dims) {
|
|
3824
|
+
wipDone(false, "Failed to read image");
|
|
3825
|
+
return false;
|
|
3826
|
+
}
|
|
3827
|
+
wipDone(true, `Size: ${dims[0]}x${dims[1]}`);
|
|
3828
|
+
const options = await collectOptionsCLI5();
|
|
3829
|
+
console.log("");
|
|
3830
|
+
return processImage5(input, options);
|
|
2487
3831
|
}
|
|
2488
|
-
async function
|
|
3832
|
+
async function runGUI11(inputRaw) {
|
|
2489
3833
|
const input = normalizePath(inputRaw);
|
|
2490
|
-
if (!
|
|
3834
|
+
if (!existsSync14(input)) {
|
|
2491
3835
|
error(`File not found: ${input}`);
|
|
2492
3836
|
return false;
|
|
2493
3837
|
}
|
|
@@ -2496,25 +3840,22 @@ async function runGUI6(inputRaw) {
|
|
|
2496
3840
|
error("Failed to read image dimensions");
|
|
2497
3841
|
return false;
|
|
2498
3842
|
}
|
|
2499
|
-
const fileInfo = getFileInfo(input);
|
|
2500
|
-
const presets = loadPresets();
|
|
2501
3843
|
return startGuiServer({
|
|
2502
|
-
htmlFile: "
|
|
2503
|
-
title: "PicLet -
|
|
3844
|
+
htmlFile: "transform.html",
|
|
3845
|
+
title: "PicLet - Transform",
|
|
2504
3846
|
imageInfo: {
|
|
2505
3847
|
filePath: input,
|
|
2506
|
-
fileName:
|
|
3848
|
+
fileName: basename12(input),
|
|
2507
3849
|
width: dims[0],
|
|
2508
3850
|
height: dims[1],
|
|
2509
3851
|
borderColor: null
|
|
2510
3852
|
},
|
|
2511
3853
|
defaults: {
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
}))
|
|
3854
|
+
transform: "flip-h"
|
|
3855
|
+
},
|
|
3856
|
+
onPreview: async (opts) => {
|
|
3857
|
+
const transform = opts.transform ?? "flip-h";
|
|
3858
|
+
return generatePreview7(input, { transform });
|
|
2518
3859
|
},
|
|
2519
3860
|
onProcess: async (opts) => {
|
|
2520
3861
|
const logs = [];
|
|
@@ -2522,67 +3863,136 @@ async function runGUI6(inputRaw) {
|
|
|
2522
3863
|
return {
|
|
2523
3864
|
success: false,
|
|
2524
3865
|
error: "ImageMagick not found",
|
|
2525
|
-
logs: [{ type: "error", message: "ImageMagick not found" }]
|
|
3866
|
+
logs: [{ type: "error", message: "ImageMagick not found. Install with: sudo apt install imagemagick" }]
|
|
2526
3867
|
};
|
|
2527
3868
|
}
|
|
2528
|
-
|
|
2529
|
-
const
|
|
2530
|
-
const
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
3869
|
+
logs.push({ type: "info", message: `Processing ${basename12(input)}...` });
|
|
3870
|
+
const transform = opts.transform ?? "flip-h";
|
|
3871
|
+
const options = { transform };
|
|
3872
|
+
const fileInfo = getFileInfo(input);
|
|
3873
|
+
const outputExt = fileInfo.extension.toLowerCase() === ".gif" ? ".gif" : ".png";
|
|
3874
|
+
const suffix = transform.replace("-", "");
|
|
3875
|
+
const output = `${fileInfo.dirname}/${fileInfo.filename}_${suffix}${outputExt}`;
|
|
3876
|
+
logs.push({ type: "info", message: `Applying ${TRANSFORM_LABELS[transform]}...` });
|
|
3877
|
+
let success2 = false;
|
|
3878
|
+
switch (transform) {
|
|
3879
|
+
case "flip-h":
|
|
3880
|
+
success2 = await flipHorizontal(input, output);
|
|
3881
|
+
break;
|
|
3882
|
+
case "flip-v":
|
|
3883
|
+
success2 = await flipVertical(input, output);
|
|
3884
|
+
break;
|
|
3885
|
+
case "rotate-90":
|
|
3886
|
+
success2 = await rotate(input, output, 90);
|
|
3887
|
+
break;
|
|
3888
|
+
case "rotate-180":
|
|
3889
|
+
success2 = await rotate(input, output, 180);
|
|
3890
|
+
break;
|
|
3891
|
+
case "rotate-270":
|
|
3892
|
+
success2 = await rotate(input, output, 270);
|
|
3893
|
+
break;
|
|
2537
3894
|
}
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
logs.push({ type: "info", message: `Generating ${preset.icons.length} images...` });
|
|
2543
|
-
const failed = await generatePresetImages(input, outputDir, preset, scaleMode, logs);
|
|
2544
|
-
if (failed === 0) {
|
|
2545
|
-
logs.push({ type: "success", message: `All ${preset.icons.length} images generated` });
|
|
3895
|
+
if (success2) {
|
|
3896
|
+
logs.push({ type: "success", message: "Transform applied" });
|
|
3897
|
+
const finalDims = await getDimensions(output);
|
|
3898
|
+
const sizeStr = finalDims ? ` (${finalDims[0]}x${finalDims[1]})` : "";
|
|
2546
3899
|
return {
|
|
2547
3900
|
success: true,
|
|
2548
|
-
output: `${
|
|
3901
|
+
output: `${basename12(output)}${sizeStr}`,
|
|
2549
3902
|
logs
|
|
2550
3903
|
};
|
|
2551
3904
|
}
|
|
3905
|
+
logs.push({ type: "error", message: "Transform failed" });
|
|
2552
3906
|
return {
|
|
2553
3907
|
success: false,
|
|
2554
|
-
error:
|
|
3908
|
+
error: "Processing failed",
|
|
2555
3909
|
logs
|
|
2556
3910
|
};
|
|
2557
3911
|
}
|
|
2558
3912
|
});
|
|
2559
3913
|
}
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
3914
|
+
async function generatePreview7(input, options) {
|
|
3915
|
+
const tempDir = tmpdir9();
|
|
3916
|
+
const timestamp = Date.now();
|
|
3917
|
+
const tempSource = join13(tempDir, `piclet-preview-${timestamp}-src.png`);
|
|
3918
|
+
const tempOutput = join13(tempDir, `piclet-preview-${timestamp}.png`);
|
|
3919
|
+
try {
|
|
3920
|
+
let previewInput = input;
|
|
3921
|
+
if (isMultiFrame(input)) {
|
|
3922
|
+
if (!await extractFirstFrame(input, tempSource)) {
|
|
3923
|
+
return { success: false, error: "Failed to extract frame" };
|
|
3924
|
+
}
|
|
3925
|
+
previewInput = tempSource;
|
|
3926
|
+
}
|
|
3927
|
+
let success2 = false;
|
|
3928
|
+
switch (options.transform) {
|
|
3929
|
+
case "flip-h":
|
|
3930
|
+
success2 = await flipHorizontal(previewInput, tempOutput);
|
|
3931
|
+
break;
|
|
3932
|
+
case "flip-v":
|
|
3933
|
+
success2 = await flipVertical(previewInput, tempOutput);
|
|
3934
|
+
break;
|
|
3935
|
+
case "rotate-90":
|
|
3936
|
+
success2 = await rotate(previewInput, tempOutput, 90);
|
|
3937
|
+
break;
|
|
3938
|
+
case "rotate-180":
|
|
3939
|
+
success2 = await rotate(previewInput, tempOutput, 180);
|
|
3940
|
+
break;
|
|
3941
|
+
case "rotate-270":
|
|
3942
|
+
success2 = await rotate(previewInput, tempOutput, 270);
|
|
3943
|
+
break;
|
|
3944
|
+
}
|
|
3945
|
+
if (!success2) {
|
|
3946
|
+
cleanup(tempSource, tempOutput);
|
|
3947
|
+
return { success: false, error: "Transform failed" };
|
|
3948
|
+
}
|
|
3949
|
+
const buffer = readFileSync11(tempOutput);
|
|
3950
|
+
const base64 = buffer.toString("base64");
|
|
3951
|
+
const imageData = `data:image/png;base64,${base64}`;
|
|
3952
|
+
const dims = await getDimensions(tempOutput);
|
|
3953
|
+
cleanup(tempSource, tempOutput);
|
|
3954
|
+
return {
|
|
3955
|
+
success: true,
|
|
3956
|
+
imageData,
|
|
3957
|
+
width: dims?.[0],
|
|
3958
|
+
height: dims?.[1]
|
|
3959
|
+
};
|
|
3960
|
+
} catch (err) {
|
|
3961
|
+
cleanup(tempSource, tempOutput);
|
|
3962
|
+
return { success: false, error: err.message };
|
|
3963
|
+
}
|
|
3964
|
+
}
|
|
3965
|
+
var config11 = {
|
|
3966
|
+
id: "transform",
|
|
3967
|
+
name: "Transform",
|
|
3968
|
+
icon: "transform.ico",
|
|
3969
|
+
extensions: [".png", ".jpg", ".jpeg", ".gif", ".bmp", ".ico"]
|
|
2565
3970
|
};
|
|
2566
3971
|
|
|
2567
3972
|
// src/cli/tools.ts
|
|
2568
3973
|
var tools = [
|
|
2569
|
-
{ config:
|
|
2570
|
-
{ config:
|
|
2571
|
-
{ config:
|
|
3974
|
+
{ config: config5, run: run5, runGUI: runGUI5 },
|
|
3975
|
+
{ config: config8, run: run7, runGUI: runGUI8 },
|
|
3976
|
+
{ config: config9, run: run8, runGUI: runGUI9 },
|
|
3977
|
+
{ config: config4, run: run4, runGUI: runGUI4 },
|
|
3978
|
+
{ config: config10, run: run9, runGUI: runGUI10 },
|
|
3979
|
+
{ config: config11, run: run10, runGUI: runGUI11 },
|
|
3980
|
+
{ config: config3, run: run3, runGUI: runGUI3 },
|
|
2572
3981
|
{ config, run, runGUI },
|
|
2573
|
-
{ config:
|
|
3982
|
+
{ config: config7, run: run6, runGUI: runGUI7 },
|
|
3983
|
+
{ config: config2, run: run2, runGUI: runGUI2 }
|
|
2574
3984
|
];
|
|
2575
3985
|
var picletTool = {
|
|
2576
|
-
config:
|
|
2577
|
-
runGUI:
|
|
3986
|
+
config: config6,
|
|
3987
|
+
runGUI: runGUI6
|
|
2578
3988
|
};
|
|
2579
3989
|
function getTool(id) {
|
|
2580
3990
|
return tools.find((t) => t.config.id === id);
|
|
2581
3991
|
}
|
|
2582
3992
|
function getAllExtensions() {
|
|
2583
3993
|
const extensions = /* @__PURE__ */ new Set();
|
|
2584
|
-
for (const { config:
|
|
2585
|
-
for (const ext of
|
|
3994
|
+
for (const { config: config12 } of tools) {
|
|
3995
|
+
for (const ext of config12.extensions) {
|
|
2586
3996
|
extensions.add(ext);
|
|
2587
3997
|
}
|
|
2588
3998
|
}
|
|
@@ -2616,17 +4026,17 @@ function validateExtensions(files, allowedExtensions) {
|
|
|
2616
4026
|
async function runToolOnFiles(toolId, files, useYes) {
|
|
2617
4027
|
const tool = getTool(toolId);
|
|
2618
4028
|
if (!tool) {
|
|
2619
|
-
console.error(
|
|
4029
|
+
console.error(chalk.red(`Tool not found: ${toolId}`));
|
|
2620
4030
|
return false;
|
|
2621
4031
|
}
|
|
2622
4032
|
const { valid, invalid } = validateExtensions(files, tool.config.extensions);
|
|
2623
4033
|
if (invalid.length > 0) {
|
|
2624
|
-
console.error(
|
|
4034
|
+
console.error(chalk.red("Invalid file types:"));
|
|
2625
4035
|
for (const file of invalid) {
|
|
2626
|
-
console.error(
|
|
4036
|
+
console.error(chalk.red(` \u2717 ${file}`));
|
|
2627
4037
|
}
|
|
2628
4038
|
console.error(
|
|
2629
|
-
|
|
4039
|
+
chalk.yellow(
|
|
2630
4040
|
`
|
|
2631
4041
|
Supported extensions: ${tool.config.extensions.join(", ")}`
|
|
2632
4042
|
)
|
|
@@ -2641,7 +4051,7 @@ Supported extensions: ${tool.config.extensions.join(", ")}`
|
|
|
2641
4051
|
for (let i = 0; i < valid.length; i++) {
|
|
2642
4052
|
const file = valid[i];
|
|
2643
4053
|
if (valid.length > 1) {
|
|
2644
|
-
console.log(
|
|
4054
|
+
console.log(chalk.cyan(`
|
|
2645
4055
|
[${i + 1}/${valid.length}] ${file}`));
|
|
2646
4056
|
}
|
|
2647
4057
|
const success2 = await tool.run(file);
|
|
@@ -2650,21 +4060,126 @@ Supported extensions: ${tool.config.extensions.join(", ")}`
|
|
|
2650
4060
|
return allSuccess;
|
|
2651
4061
|
}
|
|
2652
4062
|
|
|
4063
|
+
// src/cli/commands/border.ts
|
|
4064
|
+
function registerBorderCommand(program2) {
|
|
4065
|
+
program2.command("border <files...>").description("Add solid color border to images").option("-y, --yes", "Use defaults, skip prompts").option("-g, --gui", "Use GUI for options").action(async (files, options) => {
|
|
4066
|
+
if (options.gui) {
|
|
4067
|
+
const { valid, invalid } = validateExtensions(files, config.extensions);
|
|
4068
|
+
if (invalid.length > 0) {
|
|
4069
|
+
console.error(chalk2.red("Invalid file types:"));
|
|
4070
|
+
for (const file of invalid) {
|
|
4071
|
+
console.error(chalk2.red(` - ${file}`));
|
|
4072
|
+
}
|
|
4073
|
+
}
|
|
4074
|
+
if (valid.length === 0) {
|
|
4075
|
+
process.exit(1);
|
|
4076
|
+
}
|
|
4077
|
+
const result = await runGUI(valid[0]);
|
|
4078
|
+
process.exit(result ? 0 : 1);
|
|
4079
|
+
}
|
|
4080
|
+
const success2 = await runToolOnFiles(
|
|
4081
|
+
"border",
|
|
4082
|
+
files,
|
|
4083
|
+
options.yes ?? false
|
|
4084
|
+
);
|
|
4085
|
+
process.exit(success2 ? 0 : 1);
|
|
4086
|
+
});
|
|
4087
|
+
}
|
|
4088
|
+
|
|
4089
|
+
// src/cli/commands/config.ts
|
|
4090
|
+
import chalk3 from "chalk";
|
|
4091
|
+
function registerConfigCommand(program2) {
|
|
4092
|
+
const configCmd = program2.command("config").description("Display current settings").action(() => {
|
|
4093
|
+
const config12 = loadConfig();
|
|
4094
|
+
console.log(chalk3.white.bold("\n PicLet Configuration"));
|
|
4095
|
+
console.log(chalk3.gray(` ${getConfigPath()}
|
|
4096
|
+
`));
|
|
4097
|
+
console.log(JSON.stringify(config12, null, 2));
|
|
4098
|
+
console.log();
|
|
4099
|
+
});
|
|
4100
|
+
configCmd.command("reset").description("Restore defaults").action(() => {
|
|
4101
|
+
resetConfig();
|
|
4102
|
+
console.log(chalk3.green("Configuration reset to defaults."));
|
|
4103
|
+
});
|
|
4104
|
+
}
|
|
4105
|
+
|
|
4106
|
+
// src/cli/commands/extract-frames.ts
|
|
4107
|
+
import chalk4 from "chalk";
|
|
4108
|
+
function registerExtractFramesCommand(program2) {
|
|
4109
|
+
program2.command("extract-frames <files...>").alias("frames").description("Extract frames from animated GIF").option("-y, --yes", "Use defaults, skip prompts").option("-g, --gui", "Use GUI for options").action(async (files, options) => {
|
|
4110
|
+
if (options.gui) {
|
|
4111
|
+
const { valid, invalid } = validateExtensions(files, config2.extensions);
|
|
4112
|
+
if (invalid.length > 0) {
|
|
4113
|
+
console.error(chalk4.red("Invalid file types:"));
|
|
4114
|
+
for (const file of invalid) {
|
|
4115
|
+
console.error(chalk4.red(` - ${file}`));
|
|
4116
|
+
}
|
|
4117
|
+
}
|
|
4118
|
+
if (valid.length === 0) {
|
|
4119
|
+
process.exit(1);
|
|
4120
|
+
}
|
|
4121
|
+
const result = await runGUI2(valid[0]);
|
|
4122
|
+
process.exit(result ? 0 : 1);
|
|
4123
|
+
}
|
|
4124
|
+
const success2 = await runToolOnFiles(
|
|
4125
|
+
"extract-frames",
|
|
4126
|
+
files,
|
|
4127
|
+
options.yes ?? false
|
|
4128
|
+
);
|
|
4129
|
+
process.exit(success2 ? 0 : 1);
|
|
4130
|
+
});
|
|
4131
|
+
}
|
|
4132
|
+
|
|
4133
|
+
// src/cli/commands/filter.ts
|
|
4134
|
+
import chalk5 from "chalk";
|
|
4135
|
+
function registerFilterCommand(program2) {
|
|
4136
|
+
program2.command("filter <files...>").description("Apply color filters (grayscale, sepia, etc.)").option("-y, --yes", "Use defaults, skip prompts").option("-g, --gui", "Use GUI for options").action(async (files, options) => {
|
|
4137
|
+
if (options.gui) {
|
|
4138
|
+
const { valid, invalid } = validateExtensions(files, config3.extensions);
|
|
4139
|
+
if (invalid.length > 0) {
|
|
4140
|
+
console.error(chalk5.red("Invalid file types:"));
|
|
4141
|
+
for (const file of invalid) {
|
|
4142
|
+
console.error(chalk5.red(` - ${file}`));
|
|
4143
|
+
}
|
|
4144
|
+
}
|
|
4145
|
+
if (valid.length === 0) {
|
|
4146
|
+
process.exit(1);
|
|
4147
|
+
}
|
|
4148
|
+
const result = await runGUI3(valid[0]);
|
|
4149
|
+
process.exit(result ? 0 : 1);
|
|
4150
|
+
}
|
|
4151
|
+
const success2 = await runToolOnFiles(
|
|
4152
|
+
"filter",
|
|
4153
|
+
files,
|
|
4154
|
+
options.yes ?? false
|
|
4155
|
+
);
|
|
4156
|
+
process.exit(success2 ? 0 : 1);
|
|
4157
|
+
});
|
|
4158
|
+
}
|
|
4159
|
+
|
|
4160
|
+
// src/cli/commands/help.ts
|
|
4161
|
+
function registerHelpCommand(program2) {
|
|
4162
|
+
program2.command("help").description("Show help").action(() => {
|
|
4163
|
+
showHelp();
|
|
4164
|
+
});
|
|
4165
|
+
}
|
|
4166
|
+
|
|
2653
4167
|
// src/cli/commands/iconpack.ts
|
|
4168
|
+
import chalk6 from "chalk";
|
|
2654
4169
|
function registerIconpackCommand(program2) {
|
|
2655
4170
|
program2.command("iconpack <files...>").description("Generate icon sets for Web, Android, iOS").option("-y, --yes", "Use defaults, skip prompts").option("-g, --gui", "Use GUI for options").action(async (files, options) => {
|
|
2656
4171
|
if (options.gui) {
|
|
2657
|
-
const { valid, invalid } = validateExtensions(files,
|
|
4172
|
+
const { valid, invalid } = validateExtensions(files, config4.extensions);
|
|
2658
4173
|
if (invalid.length > 0) {
|
|
2659
|
-
console.error(
|
|
4174
|
+
console.error(chalk6.red("Invalid file types:"));
|
|
2660
4175
|
for (const file of invalid) {
|
|
2661
|
-
console.error(
|
|
4176
|
+
console.error(chalk6.red(` - ${file}`));
|
|
2662
4177
|
}
|
|
2663
4178
|
}
|
|
2664
4179
|
if (valid.length === 0) {
|
|
2665
4180
|
process.exit(1);
|
|
2666
4181
|
}
|
|
2667
|
-
const result = await
|
|
4182
|
+
const result = await runGUI4(valid[0]);
|
|
2668
4183
|
process.exit(result ? 0 : 1);
|
|
2669
4184
|
}
|
|
2670
4185
|
const success2 = await runToolOnFiles(
|
|
@@ -2677,11 +4192,11 @@ function registerIconpackCommand(program2) {
|
|
|
2677
4192
|
}
|
|
2678
4193
|
|
|
2679
4194
|
// src/cli/commands/install.ts
|
|
2680
|
-
import
|
|
4195
|
+
import chalk7 from "chalk";
|
|
2681
4196
|
|
|
2682
4197
|
// src/lib/registry.ts
|
|
2683
4198
|
import { exec as exec2 } from "child_process";
|
|
2684
|
-
import { existsSync as
|
|
4199
|
+
import { existsSync as existsSync15 } from "fs";
|
|
2685
4200
|
import { dirname as dirname10 } from "path";
|
|
2686
4201
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
2687
4202
|
import { promisify as promisify2 } from "util";
|
|
@@ -2690,7 +4205,7 @@ function isWSL() {
|
|
|
2690
4205
|
return process.platform === "linux" && (process.env.WSL_DISTRO_NAME !== void 0 || process.env.WSLENV !== void 0);
|
|
2691
4206
|
}
|
|
2692
4207
|
function isWSLInteropEnabled() {
|
|
2693
|
-
return
|
|
4208
|
+
return existsSync15("/proc/sys/fs/binfmt_misc/WSLInterop");
|
|
2694
4209
|
}
|
|
2695
4210
|
async function addRegistryKey(keyPath, valueName, value, type = "REG_SZ") {
|
|
2696
4211
|
const valueArg = valueName ? `/v "${valueName}"` : "/ve";
|
|
@@ -2732,7 +4247,7 @@ async function deleteRegistryKey(keyPath) {
|
|
|
2732
4247
|
|
|
2733
4248
|
// src/cli/registry.ts
|
|
2734
4249
|
import { writeFile } from "fs/promises";
|
|
2735
|
-
import { join as
|
|
4250
|
+
import { join as join14 } from "path";
|
|
2736
4251
|
async function registerUnifiedMenu(extension, iconsDir, launcherPath) {
|
|
2737
4252
|
const basePath = `HKCU\\Software\\Classes\\SystemFileAssociations\\${extension}\\shell\\PicLet`;
|
|
2738
4253
|
const iconsDirWin = wslToWindows(iconsDir);
|
|
@@ -2752,13 +4267,13 @@ async function unregisterMenuForExtension(extension) {
|
|
|
2752
4267
|
const results = [];
|
|
2753
4268
|
const basePath = getMenuBasePath(extension);
|
|
2754
4269
|
const extensionTools = getToolsForExtension(extension);
|
|
2755
|
-
for (const { config:
|
|
2756
|
-
const toolPath = `${basePath}\\shell\\${
|
|
4270
|
+
for (const { config: config12 } of extensionTools) {
|
|
4271
|
+
const toolPath = `${basePath}\\shell\\${config12.id}`;
|
|
2757
4272
|
await deleteRegistryKey(`${toolPath}\\command`);
|
|
2758
4273
|
const success2 = await deleteRegistryKey(toolPath);
|
|
2759
4274
|
results.push({
|
|
2760
4275
|
extension,
|
|
2761
|
-
toolName:
|
|
4276
|
+
toolName: config12.name,
|
|
2762
4277
|
success: success2
|
|
2763
4278
|
});
|
|
2764
4279
|
}
|
|
@@ -2768,8 +4283,8 @@ async function unregisterMenuForExtension(extension) {
|
|
|
2768
4283
|
}
|
|
2769
4284
|
async function registerAllTools() {
|
|
2770
4285
|
const distDir = getDistDir();
|
|
2771
|
-
const iconsDir =
|
|
2772
|
-
const launcherPath =
|
|
4286
|
+
const iconsDir = join14(distDir, "icons");
|
|
4287
|
+
const launcherPath = join14(distDir, "launcher.vbs");
|
|
2773
4288
|
const results = [];
|
|
2774
4289
|
for (const extension of picletTool.config.extensions) {
|
|
2775
4290
|
const result = await registerUnifiedMenu(extension, iconsDir, launcherPath);
|
|
@@ -2777,6 +4292,57 @@ async function registerAllTools() {
|
|
|
2777
4292
|
}
|
|
2778
4293
|
return results;
|
|
2779
4294
|
}
|
|
4295
|
+
var LEGACY_TOOL_NAMES = [
|
|
4296
|
+
// Old direct menu items (various naming conventions)
|
|
4297
|
+
"Scale Image",
|
|
4298
|
+
"Resize Image",
|
|
4299
|
+
"Eale Image",
|
|
4300
|
+
// Corrupted entry
|
|
4301
|
+
"Remove Background",
|
|
4302
|
+
"Remove BG",
|
|
4303
|
+
"RemoveBG",
|
|
4304
|
+
"Make Icon",
|
|
4305
|
+
"MakeIcon",
|
|
4306
|
+
"Icon Pack",
|
|
4307
|
+
"IconPack",
|
|
4308
|
+
"Store Pack",
|
|
4309
|
+
"StorePack",
|
|
4310
|
+
"PicLet - Scale",
|
|
4311
|
+
"PicLet - Remove BG",
|
|
4312
|
+
"PicLet - Make Icon",
|
|
4313
|
+
"PicLet - Icon Pack",
|
|
4314
|
+
"PicLet Scale",
|
|
4315
|
+
"PicLet RemoveBG",
|
|
4316
|
+
// Tool IDs that might have been used as menu names
|
|
4317
|
+
"makeicon",
|
|
4318
|
+
"remove-bg",
|
|
4319
|
+
"removebg",
|
|
4320
|
+
"rescale",
|
|
4321
|
+
"scale",
|
|
4322
|
+
"iconpack",
|
|
4323
|
+
"storepack",
|
|
4324
|
+
"transform",
|
|
4325
|
+
"filter",
|
|
4326
|
+
"border",
|
|
4327
|
+
"recolor"
|
|
4328
|
+
];
|
|
4329
|
+
var ALL_IMAGE_EXTENSIONS = [".png", ".jpg", ".jpeg", ".gif", ".bmp", ".ico", ".webp", ".tiff", ".tif"];
|
|
4330
|
+
async function cleanupLegacyEntries() {
|
|
4331
|
+
const removed = [];
|
|
4332
|
+
const failed = [];
|
|
4333
|
+
for (const ext of ALL_IMAGE_EXTENSIONS) {
|
|
4334
|
+
const shellBase = `HKCU\\Software\\Classes\\SystemFileAssociations\\${ext}\\shell`;
|
|
4335
|
+
for (const legacyName of LEGACY_TOOL_NAMES) {
|
|
4336
|
+
const keyPath = `${shellBase}\\${legacyName}`;
|
|
4337
|
+
await deleteRegistryKey(`${keyPath}\\command`);
|
|
4338
|
+
const success2 = await deleteRegistryKey(keyPath);
|
|
4339
|
+
if (success2) {
|
|
4340
|
+
removed.push(`${ext} \u2192 ${legacyName}`);
|
|
4341
|
+
}
|
|
4342
|
+
}
|
|
4343
|
+
}
|
|
4344
|
+
return { removed, failed };
|
|
4345
|
+
}
|
|
2780
4346
|
async function unregisterAllTools() {
|
|
2781
4347
|
const results = [];
|
|
2782
4348
|
const allExts = /* @__PURE__ */ new Set([...getAllExtensions(), ...picletTool.config.extensions]);
|
|
@@ -2794,8 +4360,8 @@ function escapeRegValue(value) {
|
|
|
2794
4360
|
}
|
|
2795
4361
|
function generateRegContent() {
|
|
2796
4362
|
const distDir = getDistDir();
|
|
2797
|
-
const iconsDir =
|
|
2798
|
-
const launcherPath =
|
|
4363
|
+
const iconsDir = join14(distDir, "icons");
|
|
4364
|
+
const launcherPath = join14(distDir, "launcher.vbs");
|
|
2799
4365
|
const iconsDirWin = wslToWindows(iconsDir);
|
|
2800
4366
|
const launcherWin = wslToWindows(launcherPath);
|
|
2801
4367
|
const lines = ["Windows Registry Editor Version 5.00", ""];
|
|
@@ -2821,18 +4387,25 @@ function generateUninstallRegContent() {
|
|
|
2821
4387
|
lines.push(`[-${basePath}]`);
|
|
2822
4388
|
lines.push("");
|
|
2823
4389
|
}
|
|
4390
|
+
for (const ext of ALL_IMAGE_EXTENSIONS) {
|
|
4391
|
+
for (const legacyName of LEGACY_TOOL_NAMES) {
|
|
4392
|
+
const keyPath = `HKEY_CURRENT_USER\\Software\\Classes\\SystemFileAssociations\\${ext}\\shell\\${legacyName}`;
|
|
4393
|
+
lines.push(`[-${keyPath}]`);
|
|
4394
|
+
}
|
|
4395
|
+
lines.push("");
|
|
4396
|
+
}
|
|
2824
4397
|
return lines.join("\r\n");
|
|
2825
4398
|
}
|
|
2826
4399
|
async function generateRegFile() {
|
|
2827
4400
|
const distDir = getDistDir();
|
|
2828
|
-
const regPath =
|
|
4401
|
+
const regPath = join14(distDir, "piclet-install.reg");
|
|
2829
4402
|
const content = generateRegContent();
|
|
2830
4403
|
await writeFile(regPath, content, "utf-8");
|
|
2831
4404
|
return regPath;
|
|
2832
4405
|
}
|
|
2833
4406
|
async function generateUninstallRegFile() {
|
|
2834
4407
|
const distDir = getDistDir();
|
|
2835
|
-
const regPath =
|
|
4408
|
+
const regPath = join14(distDir, "piclet-uninstall.reg");
|
|
2836
4409
|
const content = generateUninstallRegContent();
|
|
2837
4410
|
await writeFile(regPath, content, "utf-8");
|
|
2838
4411
|
return regPath;
|
|
@@ -2842,50 +4415,50 @@ async function generateUninstallRegFile() {
|
|
|
2842
4415
|
function registerInstallCommand(program2) {
|
|
2843
4416
|
program2.command("install").description("Install Windows shell context menu integration").action(async () => {
|
|
2844
4417
|
showBanner();
|
|
2845
|
-
console.log(
|
|
4418
|
+
console.log(chalk7.bold("Installing...\n"));
|
|
2846
4419
|
if (!isWSL()) {
|
|
2847
4420
|
console.log(
|
|
2848
|
-
|
|
4421
|
+
chalk7.yellow("! Not running in WSL. Registry integration skipped.")
|
|
2849
4422
|
);
|
|
2850
4423
|
console.log(
|
|
2851
|
-
|
|
4424
|
+
chalk7.yellow('! Run "piclet install" from WSL to add context menu.')
|
|
2852
4425
|
);
|
|
2853
4426
|
return;
|
|
2854
4427
|
}
|
|
2855
4428
|
if (!isWSLInteropEnabled()) {
|
|
2856
|
-
console.log(
|
|
4429
|
+
console.log(chalk7.yellow("WSL Interop not available. Generating registry file...\n"));
|
|
2857
4430
|
const regPath = await generateRegFile();
|
|
2858
4431
|
const winPath = wslToWindows(regPath);
|
|
2859
|
-
console.log(
|
|
2860
|
-
console.log(
|
|
4432
|
+
console.log(chalk7.green("\u2713 Generated registry file:"));
|
|
4433
|
+
console.log(chalk7.cyan(` ${winPath}
|
|
2861
4434
|
`));
|
|
2862
|
-
console.log(
|
|
2863
|
-
console.log(
|
|
2864
|
-
console.log(
|
|
4435
|
+
console.log(chalk7.bold("To install, either:"));
|
|
4436
|
+
console.log(chalk7.dim(" 1. Double-click the .reg file in Windows Explorer"));
|
|
4437
|
+
console.log(chalk7.dim(` 2. Run in elevated PowerShell: reg import "${winPath}"`));
|
|
2865
4438
|
console.log();
|
|
2866
4439
|
return;
|
|
2867
4440
|
}
|
|
2868
|
-
console.log(
|
|
4441
|
+
console.log(chalk7.dim("Removing old entries..."));
|
|
2869
4442
|
await unregisterAllTools();
|
|
2870
4443
|
console.log();
|
|
2871
4444
|
const results = await registerAllTools();
|
|
2872
4445
|
const allSuccess = results.every((r) => r.success);
|
|
2873
|
-
for (const { config:
|
|
2874
|
-
const extList =
|
|
2875
|
-
console.log(`${
|
|
4446
|
+
for (const { config: config12 } of tools) {
|
|
4447
|
+
const extList = config12.extensions.join(", ");
|
|
4448
|
+
console.log(`${chalk7.green("\u2713")} ${config12.name} ${chalk7.dim(`[${extList}]`)}`);
|
|
2876
4449
|
}
|
|
2877
4450
|
console.log();
|
|
2878
4451
|
if (allSuccess) {
|
|
2879
4452
|
console.log(
|
|
2880
|
-
|
|
4453
|
+
chalk7.green(`\u2713 Registered ${tools.length} tools for context menu.`)
|
|
2881
4454
|
);
|
|
2882
4455
|
} else {
|
|
2883
4456
|
const successCount = results.filter((r) => r.success).length;
|
|
2884
4457
|
console.log(
|
|
2885
|
-
|
|
4458
|
+
chalk7.yellow(`! Registered ${successCount}/${results.length} entries.`)
|
|
2886
4459
|
);
|
|
2887
4460
|
}
|
|
2888
|
-
console.log(
|
|
4461
|
+
console.log(chalk7.bold("\nUsage:"));
|
|
2889
4462
|
console.log(" Right-click any supported image in Windows Explorer.");
|
|
2890
4463
|
console.log(" Multi-select supported for batch processing.");
|
|
2891
4464
|
console.log();
|
|
@@ -2893,21 +4466,21 @@ function registerInstallCommand(program2) {
|
|
|
2893
4466
|
}
|
|
2894
4467
|
|
|
2895
4468
|
// src/cli/commands/makeicon.ts
|
|
2896
|
-
import
|
|
4469
|
+
import chalk8 from "chalk";
|
|
2897
4470
|
function registerMakeiconCommand(program2) {
|
|
2898
4471
|
program2.command("makeicon <files...>").description("Convert PNG to multi-resolution ICO file").option("-y, --yes", "Use defaults, skip prompts").option("-g, --gui", "Use GUI for confirmation").action(async (files, options) => {
|
|
2899
4472
|
if (options.gui) {
|
|
2900
|
-
const { valid, invalid } = validateExtensions(files,
|
|
4473
|
+
const { valid, invalid } = validateExtensions(files, config5.extensions);
|
|
2901
4474
|
if (invalid.length > 0) {
|
|
2902
|
-
console.error(
|
|
4475
|
+
console.error(chalk8.red("Invalid file types:"));
|
|
2903
4476
|
for (const file of invalid) {
|
|
2904
|
-
console.error(
|
|
4477
|
+
console.error(chalk8.red(` - ${file}`));
|
|
2905
4478
|
}
|
|
2906
4479
|
}
|
|
2907
4480
|
if (valid.length === 0) {
|
|
2908
4481
|
process.exit(1);
|
|
2909
4482
|
}
|
|
2910
|
-
const result = await
|
|
4483
|
+
const result = await runGUI5(valid[0]);
|
|
2911
4484
|
process.exit(result ? 0 : 1);
|
|
2912
4485
|
}
|
|
2913
4486
|
const success2 = await runToolOnFiles(
|
|
@@ -2920,13 +4493,13 @@ function registerMakeiconCommand(program2) {
|
|
|
2920
4493
|
}
|
|
2921
4494
|
|
|
2922
4495
|
// src/cli/commands/piclet.ts
|
|
2923
|
-
import
|
|
4496
|
+
import chalk9 from "chalk";
|
|
2924
4497
|
function registerPicletCommand(program2) {
|
|
2925
4498
|
program2.command("piclet <file>").description("Open unified PicLet window with all tools").option("-g, --gui", "Use GUI (default)").action(async (file) => {
|
|
2926
4499
|
const { valid, invalid } = validateExtensions([file], picletTool.config.extensions);
|
|
2927
4500
|
if (invalid.length > 0) {
|
|
2928
|
-
console.error(
|
|
2929
|
-
console.error(
|
|
4501
|
+
console.error(chalk9.red(`Invalid file type: ${file}`));
|
|
4502
|
+
console.error(chalk9.yellow(`Supported: ${picletTool.config.extensions.join(", ")}`));
|
|
2930
4503
|
process.exit(1);
|
|
2931
4504
|
}
|
|
2932
4505
|
const result = await picletTool.runGUI(valid[0]);
|
|
@@ -2934,22 +4507,49 @@ function registerPicletCommand(program2) {
|
|
|
2934
4507
|
});
|
|
2935
4508
|
}
|
|
2936
4509
|
|
|
4510
|
+
// src/cli/commands/recolor.ts
|
|
4511
|
+
import chalk10 from "chalk";
|
|
4512
|
+
function registerRecolorCommand(program2) {
|
|
4513
|
+
program2.command("recolor <files...>").alias("replace-color").description("Replace one color with another").option("-y, --yes", "Use defaults, skip prompts").option("-g, --gui", "Use GUI for options").action(async (files, options) => {
|
|
4514
|
+
if (options.gui) {
|
|
4515
|
+
const { valid, invalid } = validateExtensions(files, config7.extensions);
|
|
4516
|
+
if (invalid.length > 0) {
|
|
4517
|
+
console.error(chalk10.red("Invalid file types:"));
|
|
4518
|
+
for (const file of invalid) {
|
|
4519
|
+
console.error(chalk10.red(` - ${file}`));
|
|
4520
|
+
}
|
|
4521
|
+
}
|
|
4522
|
+
if (valid.length === 0) {
|
|
4523
|
+
process.exit(1);
|
|
4524
|
+
}
|
|
4525
|
+
const result = await runGUI7(valid[0]);
|
|
4526
|
+
process.exit(result ? 0 : 1);
|
|
4527
|
+
}
|
|
4528
|
+
const success2 = await runToolOnFiles(
|
|
4529
|
+
"recolor",
|
|
4530
|
+
files,
|
|
4531
|
+
options.yes ?? false
|
|
4532
|
+
);
|
|
4533
|
+
process.exit(success2 ? 0 : 1);
|
|
4534
|
+
});
|
|
4535
|
+
}
|
|
4536
|
+
|
|
2937
4537
|
// src/cli/commands/remove-bg.ts
|
|
2938
|
-
import
|
|
4538
|
+
import chalk11 from "chalk";
|
|
2939
4539
|
function registerRemoveBgCommand(program2) {
|
|
2940
4540
|
program2.command("remove-bg <files...>").alias("removebg").description("Remove solid background from image").option("-y, --yes", "Use defaults, skip prompts").option("-g, --gui", "Use TUI (terminal GUI) for options").option("-f, --fuzz <percent>", "Fuzz tolerance 0-100 (default: 10)").option("-t, --trim", "Trim transparent edges (default: true)").option("--no-trim", "Do not trim transparent edges").option("-p, --preserve-inner", "Preserve inner areas of same color").option("-s, --square", "Make output square with padding").action(async (files, options) => {
|
|
2941
4541
|
if (options.gui) {
|
|
2942
|
-
const { valid, invalid } = validateExtensions(files,
|
|
4542
|
+
const { valid, invalid } = validateExtensions(files, config8.extensions);
|
|
2943
4543
|
if (invalid.length > 0) {
|
|
2944
|
-
console.error(
|
|
4544
|
+
console.error(chalk11.red("Invalid file types:"));
|
|
2945
4545
|
for (const file of invalid) {
|
|
2946
|
-
console.error(
|
|
4546
|
+
console.error(chalk11.red(` - ${file}`));
|
|
2947
4547
|
}
|
|
2948
4548
|
}
|
|
2949
4549
|
if (valid.length === 0) {
|
|
2950
4550
|
process.exit(1);
|
|
2951
4551
|
}
|
|
2952
|
-
const result = await
|
|
4552
|
+
const result = await runGUI8(valid[0]);
|
|
2953
4553
|
process.exit(result ? 0 : 1);
|
|
2954
4554
|
}
|
|
2955
4555
|
if (options.fuzz !== void 0) {
|
|
@@ -2975,21 +4575,21 @@ function registerRemoveBgCommand(program2) {
|
|
|
2975
4575
|
}
|
|
2976
4576
|
|
|
2977
4577
|
// src/cli/commands/scale.ts
|
|
2978
|
-
import
|
|
4578
|
+
import chalk12 from "chalk";
|
|
2979
4579
|
function registerScaleCommand(program2) {
|
|
2980
4580
|
program2.command("scale <files...>").alias("rescale").description("Resize image with optional padding").option("-y, --yes", "Use defaults, skip prompts").option("-g, --gui", "Use GUI for options").action(async (files, options) => {
|
|
2981
4581
|
if (options.gui) {
|
|
2982
|
-
const { valid, invalid } = validateExtensions(files,
|
|
4582
|
+
const { valid, invalid } = validateExtensions(files, config9.extensions);
|
|
2983
4583
|
if (invalid.length > 0) {
|
|
2984
|
-
console.error(
|
|
4584
|
+
console.error(chalk12.red("Invalid file types:"));
|
|
2985
4585
|
for (const file of invalid) {
|
|
2986
|
-
console.error(
|
|
4586
|
+
console.error(chalk12.red(` - ${file}`));
|
|
2987
4587
|
}
|
|
2988
4588
|
}
|
|
2989
4589
|
if (valid.length === 0) {
|
|
2990
4590
|
process.exit(1);
|
|
2991
4591
|
}
|
|
2992
|
-
const result = await
|
|
4592
|
+
const result = await runGUI9(valid[0]);
|
|
2993
4593
|
process.exit(result ? 0 : 1);
|
|
2994
4594
|
}
|
|
2995
4595
|
const success2 = await runToolOnFiles(
|
|
@@ -3002,21 +4602,21 @@ function registerScaleCommand(program2) {
|
|
|
3002
4602
|
}
|
|
3003
4603
|
|
|
3004
4604
|
// src/cli/commands/storepack.ts
|
|
3005
|
-
import
|
|
4605
|
+
import chalk13 from "chalk";
|
|
3006
4606
|
function registerStorepackCommand(program2) {
|
|
3007
4607
|
program2.command("storepack <files...>").description("Generate assets for app stores (Windows, Unity, Steam, etc.)").option("-y, --yes", "Use defaults, skip prompts").option("-g, --gui", "Use GUI for options").action(async (files, options) => {
|
|
3008
4608
|
if (options.gui) {
|
|
3009
|
-
const { valid, invalid } = validateExtensions(files,
|
|
4609
|
+
const { valid, invalid } = validateExtensions(files, config10.extensions);
|
|
3010
4610
|
if (invalid.length > 0) {
|
|
3011
|
-
console.error(
|
|
4611
|
+
console.error(chalk13.red("Invalid file types:"));
|
|
3012
4612
|
for (const file of invalid) {
|
|
3013
|
-
console.error(
|
|
4613
|
+
console.error(chalk13.red(` - ${file}`));
|
|
3014
4614
|
}
|
|
3015
4615
|
}
|
|
3016
4616
|
if (valid.length === 0) {
|
|
3017
4617
|
process.exit(1);
|
|
3018
4618
|
}
|
|
3019
|
-
const result = await
|
|
4619
|
+
const result = await runGUI10(valid[0]);
|
|
3020
4620
|
process.exit(result ? 0 : 1);
|
|
3021
4621
|
}
|
|
3022
4622
|
const success2 = await runToolOnFiles(
|
|
@@ -3028,67 +4628,104 @@ function registerStorepackCommand(program2) {
|
|
|
3028
4628
|
});
|
|
3029
4629
|
}
|
|
3030
4630
|
|
|
4631
|
+
// src/cli/commands/transform.ts
|
|
4632
|
+
import chalk14 from "chalk";
|
|
4633
|
+
function registerTransformCommand(program2) {
|
|
4634
|
+
program2.command("transform <files...>").alias("flip").description("Flip or rotate images").option("-y, --yes", "Use defaults, skip prompts").option("-g, --gui", "Use GUI for options").action(async (files, options) => {
|
|
4635
|
+
if (options.gui) {
|
|
4636
|
+
const { valid, invalid } = validateExtensions(files, config11.extensions);
|
|
4637
|
+
if (invalid.length > 0) {
|
|
4638
|
+
console.error(chalk14.red("Invalid file types:"));
|
|
4639
|
+
for (const file of invalid) {
|
|
4640
|
+
console.error(chalk14.red(` - ${file}`));
|
|
4641
|
+
}
|
|
4642
|
+
}
|
|
4643
|
+
if (valid.length === 0) {
|
|
4644
|
+
process.exit(1);
|
|
4645
|
+
}
|
|
4646
|
+
const result = await runGUI11(valid[0]);
|
|
4647
|
+
process.exit(result ? 0 : 1);
|
|
4648
|
+
}
|
|
4649
|
+
const success2 = await runToolOnFiles(
|
|
4650
|
+
"transform",
|
|
4651
|
+
files,
|
|
4652
|
+
options.yes ?? false
|
|
4653
|
+
);
|
|
4654
|
+
process.exit(success2 ? 0 : 1);
|
|
4655
|
+
});
|
|
4656
|
+
}
|
|
4657
|
+
|
|
3031
4658
|
// src/cli/commands/uninstall.ts
|
|
3032
|
-
import
|
|
4659
|
+
import chalk15 from "chalk";
|
|
3033
4660
|
function registerUninstallCommand(program2) {
|
|
3034
4661
|
program2.command("uninstall").description("Remove Windows shell context menu integration").action(async () => {
|
|
3035
4662
|
showBanner();
|
|
3036
|
-
console.log(
|
|
4663
|
+
console.log(chalk15.bold("Uninstalling...\n"));
|
|
3037
4664
|
if (!isWSL()) {
|
|
3038
4665
|
console.log(
|
|
3039
|
-
|
|
4666
|
+
chalk15.yellow("! Not running in WSL. Registry cleanup skipped.")
|
|
3040
4667
|
);
|
|
3041
4668
|
console.log(
|
|
3042
|
-
|
|
4669
|
+
chalk15.yellow(
|
|
3043
4670
|
'! Run "piclet uninstall" from WSL to remove context menu.'
|
|
3044
4671
|
)
|
|
3045
4672
|
);
|
|
3046
4673
|
return;
|
|
3047
4674
|
}
|
|
3048
4675
|
if (!isWSLInteropEnabled()) {
|
|
3049
|
-
console.log(
|
|
4676
|
+
console.log(chalk15.yellow("WSL Interop not available. Generating registry file...\n"));
|
|
3050
4677
|
const regPath = await generateUninstallRegFile();
|
|
3051
4678
|
const winPath = wslToWindows(regPath);
|
|
3052
|
-
console.log(
|
|
3053
|
-
console.log(
|
|
4679
|
+
console.log(chalk15.green("\u2713 Generated uninstall registry file:"));
|
|
4680
|
+
console.log(chalk15.cyan(` ${winPath}
|
|
3054
4681
|
`));
|
|
3055
|
-
console.log(
|
|
3056
|
-
console.log(
|
|
3057
|
-
console.log(
|
|
4682
|
+
console.log(chalk15.bold("To uninstall, either:"));
|
|
4683
|
+
console.log(chalk15.dim(" 1. Double-click the .reg file in Windows Explorer"));
|
|
4684
|
+
console.log(chalk15.dim(` 2. Run in elevated PowerShell: reg import "${winPath}"`));
|
|
3058
4685
|
console.log();
|
|
3059
4686
|
return;
|
|
3060
4687
|
}
|
|
4688
|
+
console.log(chalk15.dim("Cleaning up legacy entries...\n"));
|
|
4689
|
+
const legacyResult = await cleanupLegacyEntries();
|
|
4690
|
+
if (legacyResult.removed.length > 0) {
|
|
4691
|
+
console.log(chalk15.yellow(`Removed ${legacyResult.removed.length} legacy entries:`));
|
|
4692
|
+
for (const entry of legacyResult.removed) {
|
|
4693
|
+
console.log(` ${chalk15.green("\u2713")} ${entry}`);
|
|
4694
|
+
}
|
|
4695
|
+
console.log();
|
|
4696
|
+
}
|
|
3061
4697
|
const results = await unregisterAllTools();
|
|
3062
4698
|
const removedCount = results.filter((r) => r.success).length;
|
|
3063
4699
|
for (const result of results) {
|
|
3064
4700
|
if (result.success) {
|
|
3065
4701
|
console.log(
|
|
3066
|
-
`${
|
|
4702
|
+
`${chalk15.green("\u2713")} Removed: ${result.extension} \u2192 ${result.toolName}`
|
|
3067
4703
|
);
|
|
3068
4704
|
} else {
|
|
3069
4705
|
console.log(
|
|
3070
|
-
`${
|
|
4706
|
+
`${chalk15.gray("-")} Skipped: ${result.extension} \u2192 ${result.toolName}`
|
|
3071
4707
|
);
|
|
3072
4708
|
}
|
|
3073
4709
|
}
|
|
4710
|
+
const totalRemoved = removedCount + legacyResult.removed.length;
|
|
3074
4711
|
console.log();
|
|
3075
4712
|
console.log(
|
|
3076
|
-
|
|
3077
|
-
`\u2713 Cleanup complete. Removed ${
|
|
4713
|
+
chalk15.green(
|
|
4714
|
+
`\u2713 Cleanup complete. Removed ${totalRemoved} entries total.`
|
|
3078
4715
|
)
|
|
3079
4716
|
);
|
|
3080
|
-
console.log(
|
|
4717
|
+
console.log(chalk15.dim("\nThanks for using PicLet!\n"));
|
|
3081
4718
|
});
|
|
3082
4719
|
}
|
|
3083
4720
|
|
|
3084
4721
|
// src/cli/index.ts
|
|
3085
4722
|
function showHelp() {
|
|
3086
4723
|
showBanner();
|
|
3087
|
-
const dim =
|
|
3088
|
-
const cmd =
|
|
3089
|
-
const arg =
|
|
3090
|
-
const opt =
|
|
3091
|
-
const head =
|
|
4724
|
+
const dim = chalk16.gray;
|
|
4725
|
+
const cmd = chalk16.cyan;
|
|
4726
|
+
const arg = chalk16.yellow;
|
|
4727
|
+
const opt = chalk16.green;
|
|
4728
|
+
const head = chalk16.white.bold;
|
|
3092
4729
|
console.log(
|
|
3093
4730
|
` ${head("Usage:")} piclet ${cmd("<command>")} ${arg("<file>")} ${opt("[options]")}`
|
|
3094
4731
|
);
|
|
@@ -3108,6 +4745,21 @@ function showHelp() {
|
|
|
3108
4745
|
console.log(
|
|
3109
4746
|
` ${cmd("scale")} ${arg("<file>")} Resize image with optional padding`
|
|
3110
4747
|
);
|
|
4748
|
+
console.log(
|
|
4749
|
+
` ${cmd("transform")} ${arg("<file>")} Flip or rotate images`
|
|
4750
|
+
);
|
|
4751
|
+
console.log(
|
|
4752
|
+
` ${cmd("filter")} ${arg("<file>")} Apply color filters (grayscale, sepia)`
|
|
4753
|
+
);
|
|
4754
|
+
console.log(
|
|
4755
|
+
` ${cmd("border")} ${arg("<file>")} Add solid color border`
|
|
4756
|
+
);
|
|
4757
|
+
console.log(
|
|
4758
|
+
` ${cmd("recolor")} ${arg("<file>")} Replace one color with another`
|
|
4759
|
+
);
|
|
4760
|
+
console.log(
|
|
4761
|
+
` ${cmd("frames")} ${arg("<file>")} Extract frames from animated GIF`
|
|
4762
|
+
);
|
|
3111
4763
|
console.log(
|
|
3112
4764
|
` ${cmd("iconpack")} ${arg("<file>")} Generate icon sets for Web/Android/iOS`
|
|
3113
4765
|
);
|
|
@@ -3152,6 +4804,11 @@ function createProgram() {
|
|
|
3152
4804
|
registerMakeiconCommand(program2);
|
|
3153
4805
|
registerRemoveBgCommand(program2);
|
|
3154
4806
|
registerScaleCommand(program2);
|
|
4807
|
+
registerTransformCommand(program2);
|
|
4808
|
+
registerFilterCommand(program2);
|
|
4809
|
+
registerBorderCommand(program2);
|
|
4810
|
+
registerRecolorCommand(program2);
|
|
4811
|
+
registerExtractFramesCommand(program2);
|
|
3155
4812
|
registerIconpackCommand(program2);
|
|
3156
4813
|
registerStorepackCommand(program2);
|
|
3157
4814
|
registerPicletCommand(program2);
|
|
@@ -3162,7 +4819,7 @@ function createProgram() {
|
|
|
3162
4819
|
// src/cli.ts
|
|
3163
4820
|
var program = createProgram();
|
|
3164
4821
|
program.parseAsync(process.argv).catch((error2) => {
|
|
3165
|
-
console.error(
|
|
4822
|
+
console.error(chalk17.red(`Error: ${error2.message}`));
|
|
3166
4823
|
process.exit(1);
|
|
3167
4824
|
});
|
|
3168
4825
|
//# sourceMappingURL=cli.js.map
|