@spark-apps/piclet 1.0.3 → 1.0.4
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/dist/cli.js +311 -23
- package/dist/cli.js.map +1 -1
- package/dist/gui/css/theme.css +16 -10
- package/dist/gui/piclet.html +319 -15
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
3
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
4
|
+
}) : x)(function(x) {
|
|
5
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
6
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
7
|
+
});
|
|
2
8
|
|
|
3
9
|
// src/cli.ts
|
|
4
10
|
import chalk17 from "chalk";
|
|
@@ -340,6 +346,58 @@ function startGuiServer(options) {
|
|
|
340
346
|
res.json({ success: false, error: err.message });
|
|
341
347
|
}
|
|
342
348
|
});
|
|
349
|
+
app.post("/api/simplify-gif", async (req, res) => {
|
|
350
|
+
if (!options.onSimplifyGif) {
|
|
351
|
+
res.json({ success: false, error: "GIF simplification not supported" });
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
try {
|
|
355
|
+
const skipFactor = req.body.skipFactor ?? 2;
|
|
356
|
+
const result = await options.onSimplifyGif(skipFactor);
|
|
357
|
+
if (result.success) {
|
|
358
|
+
options.imageInfo.filePath = result.filePath;
|
|
359
|
+
options.imageInfo.fileName = result.fileName;
|
|
360
|
+
options.imageInfo.width = result.width;
|
|
361
|
+
options.imageInfo.height = result.height;
|
|
362
|
+
options.imageInfo.frameCount = result.frameCount;
|
|
363
|
+
}
|
|
364
|
+
res.json(result);
|
|
365
|
+
} catch (err) {
|
|
366
|
+
res.json({ success: false, error: err.message });
|
|
367
|
+
}
|
|
368
|
+
});
|
|
369
|
+
app.post("/api/delete-frame", async (req, res) => {
|
|
370
|
+
if (!options.onDeleteFrame) {
|
|
371
|
+
res.json({ success: false, error: "Frame deletion not supported" });
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
try {
|
|
375
|
+
const frameIndex = req.body.frameIndex;
|
|
376
|
+
const result = await options.onDeleteFrame(frameIndex);
|
|
377
|
+
if (result.success) {
|
|
378
|
+
options.imageInfo.frameCount = result.frameCount;
|
|
379
|
+
}
|
|
380
|
+
res.json(result);
|
|
381
|
+
} catch (err) {
|
|
382
|
+
res.json({ success: false, error: err.message });
|
|
383
|
+
}
|
|
384
|
+
});
|
|
385
|
+
app.post("/api/replace-frame", async (req, res) => {
|
|
386
|
+
if (!options.onReplaceFrame) {
|
|
387
|
+
res.json({ success: false, error: "Frame replacement not supported" });
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
try {
|
|
391
|
+
const { frameIndex, imageData } = req.body;
|
|
392
|
+
const result = await options.onReplaceFrame(frameIndex, imageData);
|
|
393
|
+
if (result.success) {
|
|
394
|
+
options.imageInfo.frameCount = result.frameCount;
|
|
395
|
+
}
|
|
396
|
+
res.json(result);
|
|
397
|
+
} catch (err) {
|
|
398
|
+
res.json({ success: false, error: err.message });
|
|
399
|
+
}
|
|
400
|
+
});
|
|
343
401
|
app.post("/api/preview", async (req, res) => {
|
|
344
402
|
if (!options.onPreview) {
|
|
345
403
|
res.json({ success: false, error: "Preview not supported" });
|
|
@@ -359,6 +417,11 @@ function startGuiServer(options) {
|
|
|
359
417
|
try {
|
|
360
418
|
const result = await options.onProcess(req.body);
|
|
361
419
|
processResult = result.success;
|
|
420
|
+
if (result.outputPath) {
|
|
421
|
+
options.lastOutputPath = result.outputPath;
|
|
422
|
+
} else {
|
|
423
|
+
options.lastOutputPath = void 0;
|
|
424
|
+
}
|
|
362
425
|
res.json(result);
|
|
363
426
|
} catch (err) {
|
|
364
427
|
processResult = false;
|
|
@@ -436,8 +499,41 @@ function startGuiServer(options) {
|
|
|
436
499
|
}).unref();
|
|
437
500
|
res.json({ success: true });
|
|
438
501
|
});
|
|
502
|
+
app.get("/api/output-preview", (_req, res) => {
|
|
503
|
+
const outputPath = options.lastOutputPath;
|
|
504
|
+
if (!outputPath) {
|
|
505
|
+
res.json({ success: false, error: "No output file" });
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
508
|
+
try {
|
|
509
|
+
const fs = __require("fs");
|
|
510
|
+
const path = __require("path");
|
|
511
|
+
if (!fs.existsSync(outputPath)) {
|
|
512
|
+
res.json({ success: false, error: "Output file not found" });
|
|
513
|
+
return;
|
|
514
|
+
}
|
|
515
|
+
const buffer = fs.readFileSync(outputPath);
|
|
516
|
+
const ext = path.extname(outputPath).toLowerCase();
|
|
517
|
+
const mimeTypes = {
|
|
518
|
+
".png": "image/png",
|
|
519
|
+
".jpg": "image/jpeg",
|
|
520
|
+
".jpeg": "image/jpeg",
|
|
521
|
+
".gif": "image/gif",
|
|
522
|
+
".ico": "image/x-icon"
|
|
523
|
+
};
|
|
524
|
+
const mimeType = mimeTypes[ext] || "image/png";
|
|
525
|
+
res.json({
|
|
526
|
+
success: true,
|
|
527
|
+
imageData: `data:${mimeType};base64,${buffer.toString("base64")}`,
|
|
528
|
+
isGif: ext === ".gif"
|
|
529
|
+
});
|
|
530
|
+
} catch (err) {
|
|
531
|
+
res.json({ success: false, error: err.message });
|
|
532
|
+
}
|
|
533
|
+
});
|
|
439
534
|
app.post("/api/open-folder", (_req, res) => {
|
|
440
|
-
const
|
|
535
|
+
const outputPath = options.lastOutputPath;
|
|
536
|
+
const filePath = outputPath || options.imageInfo.filePath;
|
|
441
537
|
let winPath = filePath;
|
|
442
538
|
const wslMatch = filePath.match(/^\/mnt\/([a-z])\/(.*)$/);
|
|
443
539
|
if (wslMatch) {
|
|
@@ -445,8 +541,10 @@ function startGuiServer(options) {
|
|
|
445
541
|
const rest = wslMatch[2].replace(/\//g, "\\");
|
|
446
542
|
winPath = `${drive}:\\${rest}`;
|
|
447
543
|
}
|
|
448
|
-
const
|
|
449
|
-
|
|
544
|
+
const lastSep = Math.max(winPath.lastIndexOf("\\"), winPath.lastIndexOf("/"));
|
|
545
|
+
const dir = lastSep > 0 ? winPath.substring(0, lastSep) : winPath;
|
|
546
|
+
const explorerCmd = outputPath ? `explorer.exe /select,"${winPath}"` : `explorer.exe "${dir}"`;
|
|
547
|
+
spawn("powershell.exe", ["-WindowStyle", "Hidden", "-Command", explorerCmd], {
|
|
450
548
|
detached: true,
|
|
451
549
|
stdio: "ignore",
|
|
452
550
|
windowsHide: true
|
|
@@ -549,7 +647,7 @@ function isGif(imagePath) {
|
|
|
549
647
|
return imagePath.toLowerCase().endsWith(".gif");
|
|
550
648
|
}
|
|
551
649
|
function getGifOutputSuffix(outputPath) {
|
|
552
|
-
return isGif(outputPath) ? " -layers
|
|
650
|
+
return isGif(outputPath) ? " -dispose Background -layers OptimizePlus" : "";
|
|
553
651
|
}
|
|
554
652
|
function getCoalescePrefix(inputPath) {
|
|
555
653
|
return isGif(inputPath) ? "-coalesce " : "";
|
|
@@ -688,6 +786,20 @@ async function removeBackgroundBorderOnly(inputPath, outputPath, color, fuzz) {
|
|
|
688
786
|
return false;
|
|
689
787
|
}
|
|
690
788
|
}
|
|
789
|
+
async function removeBackgroundEdgeAware(inputPath, outputPath, color, fuzz, featherAmount = 50) {
|
|
790
|
+
if (isGif(inputPath)) {
|
|
791
|
+
return removeBackgroundBorderOnly(inputPath, outputPath, color, fuzz);
|
|
792
|
+
}
|
|
793
|
+
try {
|
|
794
|
+
const featherRadius = 0.5 + featherAmount / 100 * 2.5;
|
|
795
|
+
await execAsync(
|
|
796
|
+
`convert "${inputPath}" -bordercolor "${color}" -border 1x1 -fill none -fuzz ${fuzz}% -draw "matte 0,0 floodfill" -shave 1x1 \\( +clone -alpha extract -blur 0x${featherRadius} \\) -compose CopyOpacity -composite "${outputPath}"`
|
|
797
|
+
);
|
|
798
|
+
return true;
|
|
799
|
+
} catch {
|
|
800
|
+
return removeBackgroundBorderOnly(inputPath, outputPath, color, fuzz);
|
|
801
|
+
}
|
|
802
|
+
}
|
|
691
803
|
async function createIco(inputPath, outputPath, sizes = [256, 128, 64, 48, 32, 16]) {
|
|
692
804
|
try {
|
|
693
805
|
const sizeStr = sizes.join(",");
|
|
@@ -736,7 +848,13 @@ async function extractFirstFrame(inputPath, outputPath, frameIndex = 0) {
|
|
|
736
848
|
}
|
|
737
849
|
try {
|
|
738
850
|
if (lowerPath.endsWith(".gif")) {
|
|
739
|
-
|
|
851
|
+
if (frameIndex === 0) {
|
|
852
|
+
await execAsync(`convert "${inputPath}[0]" "${outputPath}"`);
|
|
853
|
+
} else {
|
|
854
|
+
await execAsync(
|
|
855
|
+
`convert "${inputPath}[0-${frameIndex}]" -coalesce -delete 0--2 "${outputPath}"`
|
|
856
|
+
);
|
|
857
|
+
}
|
|
740
858
|
} else {
|
|
741
859
|
await execAsync(`convert "${inputPath}[${frameIndex}]" "${outputPath}"`);
|
|
742
860
|
}
|
|
@@ -764,7 +882,7 @@ async function extractAllFrames(inputPath, outputDir, baseName) {
|
|
|
764
882
|
try {
|
|
765
883
|
ensureDir(`${outputDir}/dummy`);
|
|
766
884
|
await execAsync(
|
|
767
|
-
`convert "${inputPath}" -coalesce "${outputDir}/${baseName}-%04d.png"`
|
|
885
|
+
`convert "${inputPath}" -coalesce +adjoin "${outputDir}/${baseName}-%04d.png"`
|
|
768
886
|
);
|
|
769
887
|
const { stdout } = await execAsync(`ls -1 "${outputDir}/${baseName}"-*.png 2>/dev/null || true`);
|
|
770
888
|
const files = stdout.trim().split("\n").filter((f) => f.length > 0);
|
|
@@ -893,6 +1011,93 @@ async function replaceColor(inputPath, outputPath, fromColor, toColor, fuzz) {
|
|
|
893
1011
|
return false;
|
|
894
1012
|
}
|
|
895
1013
|
}
|
|
1014
|
+
async function deleteGifFrame(inputPath, outputPath, frameIndex) {
|
|
1015
|
+
try {
|
|
1016
|
+
const originalCount = await getFrameCount(inputPath);
|
|
1017
|
+
if (originalCount <= 1) {
|
|
1018
|
+
return { success: false };
|
|
1019
|
+
}
|
|
1020
|
+
if (frameIndex < 0 || frameIndex >= originalCount) {
|
|
1021
|
+
return { success: false };
|
|
1022
|
+
}
|
|
1023
|
+
await execAsync(
|
|
1024
|
+
`convert "${inputPath}" -coalesce -delete ${frameIndex} -dispose Background -layers OptimizePlus "${outputPath}"`
|
|
1025
|
+
);
|
|
1026
|
+
const newCount = await getFrameCount(outputPath);
|
|
1027
|
+
return { success: true, frameCount: newCount };
|
|
1028
|
+
} catch {
|
|
1029
|
+
return { success: false };
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
async function replaceGifFrame(inputPath, outputPath, frameIndex, replacementPath) {
|
|
1033
|
+
try {
|
|
1034
|
+
const frameCount = await getFrameCount(inputPath);
|
|
1035
|
+
if (frameIndex < 0 || frameIndex >= frameCount) {
|
|
1036
|
+
return { success: false };
|
|
1037
|
+
}
|
|
1038
|
+
const dims = await getDimensions(inputPath);
|
|
1039
|
+
if (!dims) return { success: false };
|
|
1040
|
+
const tempReplacement = `${outputPath}.tmp.png`;
|
|
1041
|
+
await execAsync(
|
|
1042
|
+
`convert "${replacementPath}" -resize ${dims[0]}x${dims[1]}^ -gravity center -extent ${dims[0]}x${dims[1]} "${tempReplacement}"`
|
|
1043
|
+
);
|
|
1044
|
+
if (frameIndex === 0) {
|
|
1045
|
+
await execAsync(
|
|
1046
|
+
`convert "${tempReplacement}" \\( "${inputPath}" -coalesce \\) -delete 1 -dispose Background -layers OptimizePlus "${outputPath}"`
|
|
1047
|
+
);
|
|
1048
|
+
} else if (frameIndex === frameCount - 1) {
|
|
1049
|
+
await execAsync(
|
|
1050
|
+
`convert \\( "${inputPath}" -coalesce -delete -1 \\) "${tempReplacement}" -dispose Background -layers OptimizePlus "${outputPath}"`
|
|
1051
|
+
);
|
|
1052
|
+
} else {
|
|
1053
|
+
await execAsync(
|
|
1054
|
+
`convert \\( "${inputPath}" -coalesce \\) -delete ${frameIndex} "${outputPath}.frames.gif" && convert "${outputPath}.frames.gif[0-${frameIndex - 1}]" "${tempReplacement}" "${outputPath}.frames.gif[${frameIndex}-]" -dispose Background -layers OptimizePlus "${outputPath}" && rm -f "${outputPath}.frames.gif"`
|
|
1055
|
+
);
|
|
1056
|
+
}
|
|
1057
|
+
try {
|
|
1058
|
+
unlinkSync(tempReplacement);
|
|
1059
|
+
} catch {
|
|
1060
|
+
}
|
|
1061
|
+
const newCount = await getFrameCount(outputPath);
|
|
1062
|
+
return { success: true, frameCount: newCount };
|
|
1063
|
+
} catch {
|
|
1064
|
+
return { success: false };
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
async function simplifyGif(inputPath, outputPath, skipFactor) {
|
|
1068
|
+
if (skipFactor < 2) {
|
|
1069
|
+
if (inputPath !== outputPath) {
|
|
1070
|
+
try {
|
|
1071
|
+
copyFileSync(inputPath, outputPath);
|
|
1072
|
+
const count2 = await getFrameCount(outputPath);
|
|
1073
|
+
return { success: true, frameCount: count2 };
|
|
1074
|
+
} catch {
|
|
1075
|
+
return { success: false };
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
const count = await getFrameCount(inputPath);
|
|
1079
|
+
return { success: true, frameCount: count };
|
|
1080
|
+
}
|
|
1081
|
+
try {
|
|
1082
|
+
const originalCount = await getFrameCount(inputPath);
|
|
1083
|
+
if (originalCount <= 1) {
|
|
1084
|
+
copyFileSync(inputPath, outputPath);
|
|
1085
|
+
return { success: true, frameCount: 1 };
|
|
1086
|
+
}
|
|
1087
|
+
const frameIndices = [];
|
|
1088
|
+
for (let i = 0; i < originalCount; i += skipFactor) {
|
|
1089
|
+
frameIndices.push(i);
|
|
1090
|
+
}
|
|
1091
|
+
const keepPattern = frameIndices.join(",");
|
|
1092
|
+
await execAsync(
|
|
1093
|
+
`convert "${inputPath}[${keepPattern}]" -coalesce -dispose Background -layers OptimizePlus "${outputPath}"`
|
|
1094
|
+
);
|
|
1095
|
+
const newCount = await getFrameCount(outputPath);
|
|
1096
|
+
return { success: true, frameCount: newCount };
|
|
1097
|
+
} catch {
|
|
1098
|
+
return { success: false };
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
896
1101
|
|
|
897
1102
|
// src/lib/paths.ts
|
|
898
1103
|
import { basename, dirname as dirname4, extname, resolve } from "path";
|
|
@@ -2161,7 +2366,9 @@ async function generateCombinedPreview(input, borderColor, opts) {
|
|
|
2161
2366
|
const rbOpts = opts.removebg;
|
|
2162
2367
|
const out = makeTempPath("removebg");
|
|
2163
2368
|
let success2 = false;
|
|
2164
|
-
if (rbOpts.
|
|
2369
|
+
if (rbOpts.edgeDetect && borderColor) {
|
|
2370
|
+
success2 = await removeBackgroundEdgeAware(current, out, borderColor, rbOpts.fuzz, rbOpts.edgeStrength);
|
|
2371
|
+
} else if (rbOpts.preserveInner && borderColor) {
|
|
2165
2372
|
success2 = await removeBackgroundBorderOnly(current, out, borderColor, rbOpts.fuzz);
|
|
2166
2373
|
}
|
|
2167
2374
|
if (!success2 && borderColor) {
|
|
@@ -2245,6 +2452,7 @@ async function processCombined(input, borderColor, opts, logs) {
|
|
|
2245
2452
|
const fileInfo = getFileInfo(input);
|
|
2246
2453
|
const outputs = [];
|
|
2247
2454
|
const temps = [];
|
|
2455
|
+
let singleFilePath;
|
|
2248
2456
|
const outputExt = fileInfo.extension.toLowerCase() === ".gif" ? ".gif" : ".png";
|
|
2249
2457
|
const makeTempPath = (suffix) => {
|
|
2250
2458
|
const p = `${fileInfo.dirname}/${fileInfo.filename}_${suffix}${outputExt}`;
|
|
@@ -2260,7 +2468,13 @@ async function processCombined(input, borderColor, opts, logs) {
|
|
|
2260
2468
|
const rbOpts = opts.removebg;
|
|
2261
2469
|
const out = makeTempPath("nobg");
|
|
2262
2470
|
let success2 = false;
|
|
2263
|
-
if (rbOpts.
|
|
2471
|
+
if (rbOpts.edgeDetect && borderColor) {
|
|
2472
|
+
logs.push({ type: "info", message: "Using edge feathering..." });
|
|
2473
|
+
success2 = await removeBackgroundEdgeAware(current, out, borderColor, rbOpts.fuzz, rbOpts.edgeStrength);
|
|
2474
|
+
if (!success2) {
|
|
2475
|
+
logs.push({ type: "warn", message: "Edge feathering failed, trying standard removal" });
|
|
2476
|
+
}
|
|
2477
|
+
} else if (rbOpts.preserveInner && borderColor) {
|
|
2264
2478
|
success2 = await removeBackgroundBorderOnly(current, out, borderColor, rbOpts.fuzz);
|
|
2265
2479
|
if (!success2) {
|
|
2266
2480
|
logs.push({ type: "warn", message: "Border-only failed, trying full removal" });
|
|
@@ -2272,7 +2486,7 @@ async function processCombined(input, borderColor, opts, logs) {
|
|
|
2272
2486
|
if (!success2) {
|
|
2273
2487
|
logs.push({ type: "error", message: "Background removal failed" });
|
|
2274
2488
|
cleanup(...temps);
|
|
2275
|
-
return [];
|
|
2489
|
+
return { outputs: [] };
|
|
2276
2490
|
}
|
|
2277
2491
|
logs.push({ type: "success", message: "Background removed" });
|
|
2278
2492
|
if (rbOpts.trim) {
|
|
@@ -2294,6 +2508,7 @@ async function processCombined(input, borderColor, opts, logs) {
|
|
|
2294
2508
|
renameSync(current, finalOut);
|
|
2295
2509
|
temps.splice(temps.indexOf(current), 1);
|
|
2296
2510
|
outputs.push(basename7(finalOut));
|
|
2511
|
+
singleFilePath = finalOut;
|
|
2297
2512
|
}
|
|
2298
2513
|
break;
|
|
2299
2514
|
}
|
|
@@ -2311,7 +2526,7 @@ async function processCombined(input, borderColor, opts, logs) {
|
|
|
2311
2526
|
if (!success2) {
|
|
2312
2527
|
logs.push({ type: "error", message: "Scale failed" });
|
|
2313
2528
|
cleanup(...temps);
|
|
2314
|
-
return [];
|
|
2529
|
+
return { outputs: [] };
|
|
2315
2530
|
}
|
|
2316
2531
|
const dims = await getDimensions(out);
|
|
2317
2532
|
logs.push({ type: "success", message: `Scaled to ${dims?.[0]}\xD7${dims?.[1]}` });
|
|
@@ -2325,6 +2540,7 @@ async function processCombined(input, borderColor, opts, logs) {
|
|
|
2325
2540
|
renameSync(current, finalOut);
|
|
2326
2541
|
temps.splice(temps.indexOf(current), 1);
|
|
2327
2542
|
outputs.push(basename7(finalOut));
|
|
2543
|
+
singleFilePath = finalOut;
|
|
2328
2544
|
}
|
|
2329
2545
|
break;
|
|
2330
2546
|
}
|
|
@@ -2333,7 +2549,7 @@ async function processCombined(input, borderColor, opts, logs) {
|
|
|
2333
2549
|
const icOpts = opts.icons;
|
|
2334
2550
|
if (!icOpts.ico && !icOpts.web && !icOpts.android && !icOpts.ios) {
|
|
2335
2551
|
logs.push({ type: "error", message: "No output format selected" });
|
|
2336
|
-
return [];
|
|
2552
|
+
return { outputs: [] };
|
|
2337
2553
|
}
|
|
2338
2554
|
let iconSource = current;
|
|
2339
2555
|
if (icOpts.trim && current === input) {
|
|
@@ -2358,7 +2574,7 @@ async function processCombined(input, borderColor, opts, logs) {
|
|
|
2358
2574
|
if (!await scaleToSize(iconSource, srcTemp, maxSize)) {
|
|
2359
2575
|
logs.push({ type: "error", message: "Failed to prepare icon source" });
|
|
2360
2576
|
cleanup(...temps);
|
|
2361
|
-
return [];
|
|
2577
|
+
return { outputs: [] };
|
|
2362
2578
|
}
|
|
2363
2579
|
if (iconSource !== current && iconSource !== input) cleanup(iconSource);
|
|
2364
2580
|
let totalCount = 0;
|
|
@@ -2443,7 +2659,7 @@ async function processCombined(input, borderColor, opts, logs) {
|
|
|
2443
2659
|
const spOpts = opts.storepack;
|
|
2444
2660
|
if (!spOpts.dimensions || spOpts.dimensions.length === 0) {
|
|
2445
2661
|
logs.push({ type: "error", message: "No dimensions specified" });
|
|
2446
|
-
return [];
|
|
2662
|
+
return { outputs: [] };
|
|
2447
2663
|
}
|
|
2448
2664
|
const folderName = spOpts.presetName || "assets";
|
|
2449
2665
|
const outputDir = `${fileInfo.dirname}/${fileInfo.filename}_${folderName}`;
|
|
@@ -2472,7 +2688,7 @@ async function processCombined(input, borderColor, opts, logs) {
|
|
|
2472
2688
|
}
|
|
2473
2689
|
}
|
|
2474
2690
|
cleanup(...temps);
|
|
2475
|
-
return outputs;
|
|
2691
|
+
return { outputs, singleFilePath };
|
|
2476
2692
|
}
|
|
2477
2693
|
async function runGUI6(inputRaw) {
|
|
2478
2694
|
let currentInput = normalizePath(inputRaw);
|
|
@@ -2647,11 +2863,12 @@ async function runGUI6(inputRaw) {
|
|
|
2647
2863
|
logs: [{ type: "error", message: "ImageMagick not found. Install: sudo apt install imagemagick" }]
|
|
2648
2864
|
};
|
|
2649
2865
|
}
|
|
2650
|
-
const
|
|
2651
|
-
if (outputs.length > 0) {
|
|
2866
|
+
const result = await processCombined(currentInput, currentBorderColor, toolOpts, logs);
|
|
2867
|
+
if (result.outputs.length > 0) {
|
|
2652
2868
|
return {
|
|
2653
2869
|
success: true,
|
|
2654
|
-
output: outputs.join("\n"),
|
|
2870
|
+
output: result.outputs.join("\n"),
|
|
2871
|
+
outputPath: result.singleFilePath,
|
|
2655
2872
|
logs
|
|
2656
2873
|
};
|
|
2657
2874
|
}
|
|
@@ -2691,6 +2908,64 @@ async function runGUI6(inputRaw) {
|
|
|
2691
2908
|
const toolOpts = opts;
|
|
2692
2909
|
if (!toolOpts.tools) toolOpts.tools = [];
|
|
2693
2910
|
return generateFramePreview2(frameIndex, toolOpts);
|
|
2911
|
+
},
|
|
2912
|
+
onSimplifyGif: async (skipFactor) => {
|
|
2913
|
+
try {
|
|
2914
|
+
const tempPath = join7(tmpdir5(), `piclet-simplified-${Date.now()}.gif`);
|
|
2915
|
+
const result = await simplifyGif(currentInput, tempPath, skipFactor);
|
|
2916
|
+
if (!result.success) {
|
|
2917
|
+
return { success: false, error: "Failed to simplify GIF" };
|
|
2918
|
+
}
|
|
2919
|
+
const newDims = await getDimensions(tempPath);
|
|
2920
|
+
if (!newDims) {
|
|
2921
|
+
cleanup(tempPath);
|
|
2922
|
+
return { success: false, error: "Failed to read simplified GIF dimensions" };
|
|
2923
|
+
}
|
|
2924
|
+
currentInput = tempPath;
|
|
2925
|
+
currentFrameCount = result.frameCount ?? 1;
|
|
2926
|
+
return {
|
|
2927
|
+
success: true,
|
|
2928
|
+
filePath: tempPath,
|
|
2929
|
+
fileName: basename7(currentInput),
|
|
2930
|
+
width: newDims[0],
|
|
2931
|
+
height: newDims[1],
|
|
2932
|
+
frameCount: currentFrameCount
|
|
2933
|
+
};
|
|
2934
|
+
} catch (err) {
|
|
2935
|
+
return { success: false, error: err.message };
|
|
2936
|
+
}
|
|
2937
|
+
},
|
|
2938
|
+
onDeleteFrame: async (frameIndex) => {
|
|
2939
|
+
try {
|
|
2940
|
+
const tempPath = join7(tmpdir5(), `piclet-edited-${Date.now()}.gif`);
|
|
2941
|
+
const result = await deleteGifFrame(currentInput, tempPath, frameIndex);
|
|
2942
|
+
if (!result.success) {
|
|
2943
|
+
return { success: false, error: "Failed to delete frame" };
|
|
2944
|
+
}
|
|
2945
|
+
currentInput = tempPath;
|
|
2946
|
+
currentFrameCount = result.frameCount ?? 1;
|
|
2947
|
+
return { success: true, frameCount: currentFrameCount };
|
|
2948
|
+
} catch (err) {
|
|
2949
|
+
return { success: false, error: err.message };
|
|
2950
|
+
}
|
|
2951
|
+
},
|
|
2952
|
+
onReplaceFrame: async (frameIndex, imageData) => {
|
|
2953
|
+
try {
|
|
2954
|
+
const buffer = Buffer.from(imageData, "base64");
|
|
2955
|
+
const tempImagePath = join7(tmpdir5(), `piclet-replace-${Date.now()}.png`);
|
|
2956
|
+
writeFileSync2(tempImagePath, buffer);
|
|
2957
|
+
const tempPath = join7(tmpdir5(), `piclet-edited-${Date.now()}.gif`);
|
|
2958
|
+
const result = await replaceGifFrame(currentInput, tempPath, frameIndex, tempImagePath);
|
|
2959
|
+
cleanup(tempImagePath);
|
|
2960
|
+
if (!result.success) {
|
|
2961
|
+
return { success: false, error: "Failed to replace frame" };
|
|
2962
|
+
}
|
|
2963
|
+
currentInput = tempPath;
|
|
2964
|
+
currentFrameCount = result.frameCount ?? currentFrameCount;
|
|
2965
|
+
return { success: true, frameCount: currentFrameCount };
|
|
2966
|
+
} catch (err) {
|
|
2967
|
+
return { success: false, error: err.message };
|
|
2968
|
+
}
|
|
2694
2969
|
}
|
|
2695
2970
|
});
|
|
2696
2971
|
}
|
|
@@ -2717,9 +2992,9 @@ async function processGifExport(input, borderColor, opts, logs) {
|
|
|
2717
2992
|
let outputFile = frameFile;
|
|
2718
2993
|
if (opts.tools && opts.tools.length > 0) {
|
|
2719
2994
|
const processedLogs = [];
|
|
2720
|
-
const
|
|
2995
|
+
const result = await processCombined(frameFile, borderColor, opts, processedLogs);
|
|
2721
2996
|
logs.push(...processedLogs);
|
|
2722
|
-
if (outputs.length === 0) {
|
|
2997
|
+
if (result.outputs.length === 0) {
|
|
2723
2998
|
cleanup(frameFile);
|
|
2724
2999
|
return { success: false, error: "Processing failed", logs };
|
|
2725
3000
|
}
|
|
@@ -2753,9 +3028,9 @@ async function processGifExport(input, borderColor, opts, logs) {
|
|
|
2753
3028
|
}
|
|
2754
3029
|
case "gif": {
|
|
2755
3030
|
logs.push({ type: "info", message: "Processing GIF..." });
|
|
2756
|
-
const
|
|
2757
|
-
if (outputs.length > 0) {
|
|
2758
|
-
return { success: true, output: outputs.join("\n"), logs };
|
|
3031
|
+
const result = await processCombined(input, borderColor, opts, logs);
|
|
3032
|
+
if (result.outputs.length > 0) {
|
|
3033
|
+
return { success: true, output: result.outputs.join("\n"), outputPath: result.singleFilePath, logs };
|
|
2759
3034
|
}
|
|
2760
3035
|
return { success: false, error: "Processing failed", logs };
|
|
2761
3036
|
}
|
|
@@ -4458,9 +4733,22 @@ function registerInstallCommand(program2) {
|
|
|
4458
4733
|
chalk7.yellow(`! Registered ${successCount}/${results.length} entries.`)
|
|
4459
4734
|
);
|
|
4460
4735
|
}
|
|
4461
|
-
console.log(chalk7.bold("\
|
|
4736
|
+
console.log(chalk7.bold("\nContext Menu Usage:"));
|
|
4462
4737
|
console.log(" Right-click any supported image in Windows Explorer.");
|
|
4463
4738
|
console.log(" Multi-select supported for batch processing.");
|
|
4739
|
+
console.log(chalk7.bold("\nCLI Usage:"));
|
|
4740
|
+
console.log(chalk7.cyan(" piclet <image>") + chalk7.dim(" Open GUI editor"));
|
|
4741
|
+
console.log(chalk7.cyan(" piclet makeicon <img>") + chalk7.dim(" Convert to .ico"));
|
|
4742
|
+
console.log(chalk7.cyan(" piclet remove-bg <img>") + chalk7.dim(" Remove background"));
|
|
4743
|
+
console.log(chalk7.cyan(" piclet scale <img>") + chalk7.dim(" Resize image"));
|
|
4744
|
+
console.log(chalk7.cyan(" piclet iconpack <img>") + chalk7.dim(" Generate icon pack"));
|
|
4745
|
+
console.log(chalk7.cyan(" piclet storepack <img>") + chalk7.dim(" Generate store assets"));
|
|
4746
|
+
console.log(chalk7.cyan(" piclet transform <img>") + chalk7.dim(" Rotate/flip image"));
|
|
4747
|
+
console.log(chalk7.cyan(" piclet filter <img>") + chalk7.dim(" Apply filters"));
|
|
4748
|
+
console.log(chalk7.cyan(" piclet border <img>") + chalk7.dim(" Add border"));
|
|
4749
|
+
console.log(chalk7.cyan(" piclet recolor <img>") + chalk7.dim(" Replace colors"));
|
|
4750
|
+
console.log(chalk7.cyan(" piclet extract-frames <gif>") + chalk7.dim(" Extract GIF frames"));
|
|
4751
|
+
console.log(chalk7.dim('\n Run "piclet --help" for full documentation.'));
|
|
4464
4752
|
console.log();
|
|
4465
4753
|
});
|
|
4466
4754
|
}
|