@spark-apps/piclet 1.0.3 → 1.0.5
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 +493 -192
- package/dist/cli.js.map +1 -1
- package/dist/gui/css/theme.css +24 -10
- package/dist/gui/piclet.html +377 -50
- package/package.json +1 -1
- package/dist/gui/border.html +0 -229
- package/dist/gui/extract-frames.html +0 -156
- package/dist/gui/filter.html +0 -180
- package/dist/gui/iconpack.html +0 -113
- package/dist/gui/makeicon.html +0 -165
- package/dist/gui/recolor.html +0 -243
- package/dist/gui/remove-bg.html +0 -178
- package/dist/gui/rescale.html +0 -195
- package/dist/gui/storepack.html +0 -179
- package/dist/gui/transform.html +0 -202
package/dist/cli.js
CHANGED
|
@@ -40,14 +40,15 @@ ${renderLogo()}`);
|
|
|
40
40
|
import chalk2 from "chalk";
|
|
41
41
|
|
|
42
42
|
// src/tools/border.ts
|
|
43
|
-
import { existsSync as
|
|
43
|
+
import { existsSync as existsSync5, readFileSync as readFileSync3 } from "fs";
|
|
44
44
|
import { tmpdir } from "os";
|
|
45
|
-
import { basename as basename2, join as
|
|
45
|
+
import { basename as basename2, join as join4 } from "path";
|
|
46
46
|
|
|
47
47
|
// src/lib/gui-server.ts
|
|
48
48
|
import { spawn } from "child_process";
|
|
49
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
49
50
|
import { createServer } from "http";
|
|
50
|
-
import { dirname as dirname2, join as join2 } from "path";
|
|
51
|
+
import { dirname as dirname2, extname, join as join2 } from "path";
|
|
51
52
|
import { fileURLToPath } from "url";
|
|
52
53
|
import express from "express";
|
|
53
54
|
|
|
@@ -340,6 +341,58 @@ function startGuiServer(options) {
|
|
|
340
341
|
res.json({ success: false, error: err.message });
|
|
341
342
|
}
|
|
342
343
|
});
|
|
344
|
+
app.post("/api/simplify-gif", async (req, res) => {
|
|
345
|
+
if (!options.onSimplifyGif) {
|
|
346
|
+
res.json({ success: false, error: "GIF simplification not supported" });
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
try {
|
|
350
|
+
const skipFactor = req.body.skipFactor ?? 2;
|
|
351
|
+
const result = await options.onSimplifyGif(skipFactor);
|
|
352
|
+
if (result.success) {
|
|
353
|
+
options.imageInfo.filePath = result.filePath;
|
|
354
|
+
options.imageInfo.fileName = result.fileName;
|
|
355
|
+
options.imageInfo.width = result.width;
|
|
356
|
+
options.imageInfo.height = result.height;
|
|
357
|
+
options.imageInfo.frameCount = result.frameCount;
|
|
358
|
+
}
|
|
359
|
+
res.json(result);
|
|
360
|
+
} catch (err) {
|
|
361
|
+
res.json({ success: false, error: err.message });
|
|
362
|
+
}
|
|
363
|
+
});
|
|
364
|
+
app.post("/api/delete-frame", async (req, res) => {
|
|
365
|
+
if (!options.onDeleteFrame) {
|
|
366
|
+
res.json({ success: false, error: "Frame deletion not supported" });
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
try {
|
|
370
|
+
const frameIndex = req.body.frameIndex;
|
|
371
|
+
const result = await options.onDeleteFrame(frameIndex);
|
|
372
|
+
if (result.success) {
|
|
373
|
+
options.imageInfo.frameCount = result.frameCount;
|
|
374
|
+
}
|
|
375
|
+
res.json(result);
|
|
376
|
+
} catch (err) {
|
|
377
|
+
res.json({ success: false, error: err.message });
|
|
378
|
+
}
|
|
379
|
+
});
|
|
380
|
+
app.post("/api/replace-frame", async (req, res) => {
|
|
381
|
+
if (!options.onReplaceFrame) {
|
|
382
|
+
res.json({ success: false, error: "Frame replacement not supported" });
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
try {
|
|
386
|
+
const { frameIndex, imageData } = req.body;
|
|
387
|
+
const result = await options.onReplaceFrame(frameIndex, imageData);
|
|
388
|
+
if (result.success) {
|
|
389
|
+
options.imageInfo.frameCount = result.frameCount;
|
|
390
|
+
}
|
|
391
|
+
res.json(result);
|
|
392
|
+
} catch (err) {
|
|
393
|
+
res.json({ success: false, error: err.message });
|
|
394
|
+
}
|
|
395
|
+
});
|
|
343
396
|
app.post("/api/preview", async (req, res) => {
|
|
344
397
|
if (!options.onPreview) {
|
|
345
398
|
res.json({ success: false, error: "Preview not supported" });
|
|
@@ -359,6 +412,11 @@ function startGuiServer(options) {
|
|
|
359
412
|
try {
|
|
360
413
|
const result = await options.onProcess(req.body);
|
|
361
414
|
processResult = result.success;
|
|
415
|
+
if (result.outputPath) {
|
|
416
|
+
options.lastOutputPath = result.outputPath;
|
|
417
|
+
} else {
|
|
418
|
+
options.lastOutputPath = void 0;
|
|
419
|
+
}
|
|
362
420
|
res.json(result);
|
|
363
421
|
} catch (err) {
|
|
364
422
|
processResult = false;
|
|
@@ -436,8 +494,39 @@ function startGuiServer(options) {
|
|
|
436
494
|
}).unref();
|
|
437
495
|
res.json({ success: true });
|
|
438
496
|
});
|
|
497
|
+
app.get("/api/output-preview", (_req, res) => {
|
|
498
|
+
const outputPath = options.lastOutputPath;
|
|
499
|
+
if (!outputPath) {
|
|
500
|
+
res.json({ success: false, error: "No output file" });
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
503
|
+
try {
|
|
504
|
+
if (!existsSync2(outputPath)) {
|
|
505
|
+
res.json({ success: false, error: "Output file not found" });
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
508
|
+
const buffer = readFileSync2(outputPath);
|
|
509
|
+
const ext = extname(outputPath).toLowerCase();
|
|
510
|
+
const mimeTypes = {
|
|
511
|
+
".png": "image/png",
|
|
512
|
+
".jpg": "image/jpeg",
|
|
513
|
+
".jpeg": "image/jpeg",
|
|
514
|
+
".gif": "image/gif",
|
|
515
|
+
".ico": "image/x-icon"
|
|
516
|
+
};
|
|
517
|
+
const mimeType = mimeTypes[ext] || "image/png";
|
|
518
|
+
res.json({
|
|
519
|
+
success: true,
|
|
520
|
+
imageData: `data:${mimeType};base64,${buffer.toString("base64")}`,
|
|
521
|
+
isGif: ext === ".gif"
|
|
522
|
+
});
|
|
523
|
+
} catch (err) {
|
|
524
|
+
res.json({ success: false, error: err.message });
|
|
525
|
+
}
|
|
526
|
+
});
|
|
439
527
|
app.post("/api/open-folder", (_req, res) => {
|
|
440
|
-
const
|
|
528
|
+
const outputPath = options.lastOutputPath;
|
|
529
|
+
const filePath = outputPath || options.imageInfo.filePath;
|
|
441
530
|
let winPath = filePath;
|
|
442
531
|
const wslMatch = filePath.match(/^\/mnt\/([a-z])\/(.*)$/);
|
|
443
532
|
if (wslMatch) {
|
|
@@ -445,8 +534,10 @@ function startGuiServer(options) {
|
|
|
445
534
|
const rest = wslMatch[2].replace(/\//g, "\\");
|
|
446
535
|
winPath = `${drive}:\\${rest}`;
|
|
447
536
|
}
|
|
448
|
-
const
|
|
449
|
-
|
|
537
|
+
const lastSep = Math.max(winPath.lastIndexOf("\\"), winPath.lastIndexOf("/"));
|
|
538
|
+
const dir = lastSep > 0 ? winPath.substring(0, lastSep) : winPath;
|
|
539
|
+
const explorerCmd = outputPath ? `explorer.exe /select,"${winPath}"` : `explorer.exe "${dir}"`;
|
|
540
|
+
spawn("powershell.exe", ["-WindowStyle", "Hidden", "-Command", explorerCmd], {
|
|
450
541
|
detached: true,
|
|
451
542
|
stdio: "ignore",
|
|
452
543
|
windowsHide: true
|
|
@@ -534,7 +625,7 @@ function clearLine() {
|
|
|
534
625
|
|
|
535
626
|
// src/lib/magick.ts
|
|
536
627
|
import { exec } from "child_process";
|
|
537
|
-
import { copyFileSync, existsSync as
|
|
628
|
+
import { copyFileSync, existsSync as existsSync3, mkdirSync as mkdirSync2, unlinkSync } from "fs";
|
|
538
629
|
import { dirname as dirname3 } from "path";
|
|
539
630
|
import { promisify } from "util";
|
|
540
631
|
var execAsync = promisify(exec);
|
|
@@ -549,7 +640,7 @@ function isGif(imagePath) {
|
|
|
549
640
|
return imagePath.toLowerCase().endsWith(".gif");
|
|
550
641
|
}
|
|
551
642
|
function getGifOutputSuffix(outputPath) {
|
|
552
|
-
return isGif(outputPath) ? " -layers
|
|
643
|
+
return isGif(outputPath) ? " -dispose Background -layers OptimizePlus" : "";
|
|
553
644
|
}
|
|
554
645
|
function getCoalescePrefix(inputPath) {
|
|
555
646
|
return isGif(inputPath) ? "-coalesce " : "";
|
|
@@ -688,6 +779,20 @@ async function removeBackgroundBorderOnly(inputPath, outputPath, color, fuzz) {
|
|
|
688
779
|
return false;
|
|
689
780
|
}
|
|
690
781
|
}
|
|
782
|
+
async function removeBackgroundEdgeAware(inputPath, outputPath, color, fuzz, featherAmount = 50) {
|
|
783
|
+
if (isGif(inputPath)) {
|
|
784
|
+
return removeBackgroundBorderOnly(inputPath, outputPath, color, fuzz);
|
|
785
|
+
}
|
|
786
|
+
try {
|
|
787
|
+
const featherRadius = 0.5 + featherAmount / 100 * 2.5;
|
|
788
|
+
await execAsync(
|
|
789
|
+
`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}"`
|
|
790
|
+
);
|
|
791
|
+
return true;
|
|
792
|
+
} catch {
|
|
793
|
+
return removeBackgroundBorderOnly(inputPath, outputPath, color, fuzz);
|
|
794
|
+
}
|
|
795
|
+
}
|
|
691
796
|
async function createIco(inputPath, outputPath, sizes = [256, 128, 64, 48, 32, 16]) {
|
|
692
797
|
try {
|
|
693
798
|
const sizeStr = sizes.join(",");
|
|
@@ -710,14 +815,14 @@ async function createIcoFromMultiple(pngPaths, outputPath) {
|
|
|
710
815
|
}
|
|
711
816
|
function ensureDir(filePath) {
|
|
712
817
|
const dir = dirname3(filePath);
|
|
713
|
-
if (!
|
|
818
|
+
if (!existsSync3(dir)) {
|
|
714
819
|
mkdirSync2(dir, { recursive: true });
|
|
715
820
|
}
|
|
716
821
|
}
|
|
717
822
|
function cleanup(...files) {
|
|
718
823
|
for (const file of files) {
|
|
719
824
|
try {
|
|
720
|
-
if (
|
|
825
|
+
if (existsSync3(file)) {
|
|
721
826
|
unlinkSync(file);
|
|
722
827
|
}
|
|
723
828
|
} catch {
|
|
@@ -736,7 +841,13 @@ async function extractFirstFrame(inputPath, outputPath, frameIndex = 0) {
|
|
|
736
841
|
}
|
|
737
842
|
try {
|
|
738
843
|
if (lowerPath.endsWith(".gif")) {
|
|
739
|
-
|
|
844
|
+
if (frameIndex === 0) {
|
|
845
|
+
await execAsync(`convert "${inputPath}[0]" "${outputPath}"`);
|
|
846
|
+
} else {
|
|
847
|
+
await execAsync(
|
|
848
|
+
`convert "${inputPath}[0-${frameIndex}]" -coalesce -delete 0--2 "${outputPath}"`
|
|
849
|
+
);
|
|
850
|
+
}
|
|
740
851
|
} else {
|
|
741
852
|
await execAsync(`convert "${inputPath}[${frameIndex}]" "${outputPath}"`);
|
|
742
853
|
}
|
|
@@ -764,7 +875,7 @@ async function extractAllFrames(inputPath, outputDir, baseName) {
|
|
|
764
875
|
try {
|
|
765
876
|
ensureDir(`${outputDir}/dummy`);
|
|
766
877
|
await execAsync(
|
|
767
|
-
`convert "${inputPath}" -coalesce "${outputDir}/${baseName}-%04d.png"`
|
|
878
|
+
`convert "${inputPath}" -coalesce +adjoin "${outputDir}/${baseName}-%04d.png"`
|
|
768
879
|
);
|
|
769
880
|
const { stdout } = await execAsync(`ls -1 "${outputDir}/${baseName}"-*.png 2>/dev/null || true`);
|
|
770
881
|
const files = stdout.trim().split("\n").filter((f) => f.length > 0);
|
|
@@ -893,9 +1004,97 @@ async function replaceColor(inputPath, outputPath, fromColor, toColor, fuzz) {
|
|
|
893
1004
|
return false;
|
|
894
1005
|
}
|
|
895
1006
|
}
|
|
1007
|
+
async function deleteGifFrame(inputPath, outputPath, frameIndex) {
|
|
1008
|
+
try {
|
|
1009
|
+
const originalCount = await getFrameCount(inputPath);
|
|
1010
|
+
if (originalCount <= 1) {
|
|
1011
|
+
return { success: false };
|
|
1012
|
+
}
|
|
1013
|
+
if (frameIndex < 0 || frameIndex >= originalCount) {
|
|
1014
|
+
return { success: false };
|
|
1015
|
+
}
|
|
1016
|
+
await execAsync(
|
|
1017
|
+
`convert "${inputPath}" -coalesce -delete ${frameIndex} -dispose Background -layers OptimizePlus "${outputPath}"`
|
|
1018
|
+
);
|
|
1019
|
+
const newCount = await getFrameCount(outputPath);
|
|
1020
|
+
return { success: true, frameCount: newCount };
|
|
1021
|
+
} catch {
|
|
1022
|
+
return { success: false };
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
async function replaceGifFrame(inputPath, outputPath, frameIndex, replacementPath) {
|
|
1026
|
+
try {
|
|
1027
|
+
const frameCount = await getFrameCount(inputPath);
|
|
1028
|
+
if (frameIndex < 0 || frameIndex >= frameCount) {
|
|
1029
|
+
return { success: false };
|
|
1030
|
+
}
|
|
1031
|
+
const dims = await getDimensions(inputPath);
|
|
1032
|
+
if (!dims) return { success: false };
|
|
1033
|
+
const tempReplacement = `${outputPath}.tmp.png`;
|
|
1034
|
+
await execAsync(
|
|
1035
|
+
`convert "${replacementPath}" -resize ${dims[0]}x${dims[1]}^ -gravity center -extent ${dims[0]}x${dims[1]} "${tempReplacement}"`
|
|
1036
|
+
);
|
|
1037
|
+
if (frameIndex === 0) {
|
|
1038
|
+
await execAsync(
|
|
1039
|
+
`convert "${tempReplacement}" \\( "${inputPath}" -coalesce \\) -delete 1 -dispose Background -layers OptimizePlus "${outputPath}"`
|
|
1040
|
+
);
|
|
1041
|
+
} else if (frameIndex === frameCount - 1) {
|
|
1042
|
+
await execAsync(
|
|
1043
|
+
`convert \\( "${inputPath}" -coalesce -delete -1 \\) "${tempReplacement}" -dispose Background -layers OptimizePlus "${outputPath}"`
|
|
1044
|
+
);
|
|
1045
|
+
} else {
|
|
1046
|
+
await execAsync(
|
|
1047
|
+
`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"`
|
|
1048
|
+
);
|
|
1049
|
+
}
|
|
1050
|
+
try {
|
|
1051
|
+
unlinkSync(tempReplacement);
|
|
1052
|
+
} catch {
|
|
1053
|
+
}
|
|
1054
|
+
const newCount = await getFrameCount(outputPath);
|
|
1055
|
+
return { success: true, frameCount: newCount };
|
|
1056
|
+
} catch {
|
|
1057
|
+
return { success: false };
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
async function simplifyGif(inputPath, outputPath, skipFactor) {
|
|
1061
|
+
if (skipFactor < 2) {
|
|
1062
|
+
if (inputPath !== outputPath) {
|
|
1063
|
+
try {
|
|
1064
|
+
copyFileSync(inputPath, outputPath);
|
|
1065
|
+
const count2 = await getFrameCount(outputPath);
|
|
1066
|
+
return { success: true, frameCount: count2 };
|
|
1067
|
+
} catch {
|
|
1068
|
+
return { success: false };
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
const count = await getFrameCount(inputPath);
|
|
1072
|
+
return { success: true, frameCount: count };
|
|
1073
|
+
}
|
|
1074
|
+
try {
|
|
1075
|
+
const originalCount = await getFrameCount(inputPath);
|
|
1076
|
+
if (originalCount <= 1) {
|
|
1077
|
+
copyFileSync(inputPath, outputPath);
|
|
1078
|
+
return { success: true, frameCount: 1 };
|
|
1079
|
+
}
|
|
1080
|
+
const frameIndices = [];
|
|
1081
|
+
for (let i = 0; i < originalCount; i += skipFactor) {
|
|
1082
|
+
frameIndices.push(i);
|
|
1083
|
+
}
|
|
1084
|
+
const keepPattern = frameIndices.join(",");
|
|
1085
|
+
await execAsync(
|
|
1086
|
+
`convert "${inputPath}[${keepPattern}]" -coalesce -dispose Background -layers OptimizePlus "${outputPath}"`
|
|
1087
|
+
);
|
|
1088
|
+
const newCount = await getFrameCount(outputPath);
|
|
1089
|
+
return { success: true, frameCount: newCount };
|
|
1090
|
+
} catch {
|
|
1091
|
+
return { success: false };
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
896
1094
|
|
|
897
1095
|
// src/lib/paths.ts
|
|
898
|
-
import {
|
|
1096
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync3 } from "fs";
|
|
1097
|
+
import { basename, dirname as dirname4, extname as extname2, join as join3, resolve } from "path";
|
|
899
1098
|
function windowsToWsl(winPath) {
|
|
900
1099
|
if (winPath.startsWith("/mnt/")) {
|
|
901
1100
|
return winPath;
|
|
@@ -926,7 +1125,7 @@ function normalizePath(inputPath) {
|
|
|
926
1125
|
function getFileInfo(filePath) {
|
|
927
1126
|
const dir = dirname4(filePath);
|
|
928
1127
|
const base = basename(filePath);
|
|
929
|
-
const ext =
|
|
1128
|
+
const ext = extname2(filePath);
|
|
930
1129
|
const name = base.slice(0, -ext.length);
|
|
931
1130
|
return {
|
|
932
1131
|
dirname: dir,
|
|
@@ -935,6 +1134,20 @@ function getFileInfo(filePath) {
|
|
|
935
1134
|
extension: ext
|
|
936
1135
|
};
|
|
937
1136
|
}
|
|
1137
|
+
function getOutputDir(inputPath) {
|
|
1138
|
+
const dir = dirname4(inputPath);
|
|
1139
|
+
if (basename(dir) === "PicLet") {
|
|
1140
|
+
return dir;
|
|
1141
|
+
}
|
|
1142
|
+
return join3(dir, "PicLet");
|
|
1143
|
+
}
|
|
1144
|
+
function ensureOutputDir(inputPath) {
|
|
1145
|
+
const outDir = getOutputDir(inputPath);
|
|
1146
|
+
if (!existsSync4(outDir)) {
|
|
1147
|
+
mkdirSync3(outDir, { recursive: true });
|
|
1148
|
+
}
|
|
1149
|
+
return outDir;
|
|
1150
|
+
}
|
|
938
1151
|
|
|
939
1152
|
// src/lib/prompts.ts
|
|
940
1153
|
import prompts from "prompts";
|
|
@@ -1083,7 +1296,7 @@ async function run(inputRaw) {
|
|
|
1083
1296
|
return false;
|
|
1084
1297
|
}
|
|
1085
1298
|
const input = normalizePath(inputRaw);
|
|
1086
|
-
if (!
|
|
1299
|
+
if (!existsSync5(input)) {
|
|
1087
1300
|
error(`File not found: ${input}`);
|
|
1088
1301
|
await pauseOnError();
|
|
1089
1302
|
return false;
|
|
@@ -1102,7 +1315,7 @@ async function run(inputRaw) {
|
|
|
1102
1315
|
}
|
|
1103
1316
|
async function runGUI(inputRaw) {
|
|
1104
1317
|
const input = normalizePath(inputRaw);
|
|
1105
|
-
if (!
|
|
1318
|
+
if (!existsSync5(input)) {
|
|
1106
1319
|
error(`File not found: ${input}`);
|
|
1107
1320
|
return false;
|
|
1108
1321
|
}
|
|
@@ -1170,8 +1383,8 @@ async function runGUI(inputRaw) {
|
|
|
1170
1383
|
async function generatePreview(input, options) {
|
|
1171
1384
|
const tempDir = tmpdir();
|
|
1172
1385
|
const timestamp = Date.now();
|
|
1173
|
-
const tempSource =
|
|
1174
|
-
const tempOutput =
|
|
1386
|
+
const tempSource = join4(tempDir, `piclet-preview-${timestamp}-src.png`);
|
|
1387
|
+
const tempOutput = join4(tempDir, `piclet-preview-${timestamp}.png`);
|
|
1175
1388
|
try {
|
|
1176
1389
|
let previewInput = input;
|
|
1177
1390
|
if (isMultiFrame(input)) {
|
|
@@ -1185,7 +1398,7 @@ async function generatePreview(input, options) {
|
|
|
1185
1398
|
cleanup(tempSource, tempOutput);
|
|
1186
1399
|
return { success: false, error: "Border failed" };
|
|
1187
1400
|
}
|
|
1188
|
-
const buffer =
|
|
1401
|
+
const buffer = readFileSync3(tempOutput);
|
|
1189
1402
|
const base64 = buffer.toString("base64");
|
|
1190
1403
|
const imageData = `data:image/png;base64,${base64}`;
|
|
1191
1404
|
const dims = await getDimensions(tempOutput);
|
|
@@ -1208,16 +1421,10 @@ var config = {
|
|
|
1208
1421
|
extensions: [".png", ".jpg", ".jpeg", ".gif", ".bmp"]
|
|
1209
1422
|
};
|
|
1210
1423
|
|
|
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
1424
|
// src/tools/extract-frames.ts
|
|
1218
|
-
import { existsSync as
|
|
1425
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync4 } from "fs";
|
|
1219
1426
|
import { tmpdir as tmpdir2 } from "os";
|
|
1220
|
-
import { basename as basename3, join as
|
|
1427
|
+
import { basename as basename3, join as join5 } from "path";
|
|
1221
1428
|
async function run2(inputRaw) {
|
|
1222
1429
|
if (!await checkImageMagick()) {
|
|
1223
1430
|
error("ImageMagick not found. Please install it:");
|
|
@@ -1226,7 +1433,7 @@ async function run2(inputRaw) {
|
|
|
1226
1433
|
return false;
|
|
1227
1434
|
}
|
|
1228
1435
|
const input = normalizePath(inputRaw);
|
|
1229
|
-
if (!
|
|
1436
|
+
if (!existsSync6(input)) {
|
|
1230
1437
|
error(`File not found: ${input}`);
|
|
1231
1438
|
await pauseOnError();
|
|
1232
1439
|
return false;
|
|
@@ -1257,7 +1464,7 @@ async function run2(inputRaw) {
|
|
|
1257
1464
|
const outputDir = `${fileInfo.dirname}/${fileInfo.filename}_frames`;
|
|
1258
1465
|
console.log("");
|
|
1259
1466
|
wip("Extracting frames...");
|
|
1260
|
-
|
|
1467
|
+
mkdirSync4(outputDir, { recursive: true });
|
|
1261
1468
|
const frames = await extractAllFrames(input, outputDir, "frame");
|
|
1262
1469
|
if (frames.length === 0) {
|
|
1263
1470
|
wipDone(false, "Extraction failed");
|
|
@@ -1270,7 +1477,7 @@ async function run2(inputRaw) {
|
|
|
1270
1477
|
}
|
|
1271
1478
|
async function runGUI2(inputRaw) {
|
|
1272
1479
|
const input = normalizePath(inputRaw);
|
|
1273
|
-
if (!
|
|
1480
|
+
if (!existsSync6(input)) {
|
|
1274
1481
|
error(`File not found: ${input}`);
|
|
1275
1482
|
return false;
|
|
1276
1483
|
}
|
|
@@ -1309,7 +1516,7 @@ async function runGUI2(inputRaw) {
|
|
|
1309
1516
|
}
|
|
1310
1517
|
logs.push({ type: "info", message: `Extracting ${frameCount} frames...` });
|
|
1311
1518
|
const outputDir = `${fileInfo.dirname}/${fileInfo.filename}_frames`;
|
|
1312
|
-
|
|
1519
|
+
mkdirSync4(outputDir, { recursive: true });
|
|
1313
1520
|
const frames = await extractAllFrames(input, outputDir, "frame");
|
|
1314
1521
|
if (frames.length > 0) {
|
|
1315
1522
|
logs.push({ type: "success", message: `Extracted ${frames.length} frames` });
|
|
@@ -1327,12 +1534,12 @@ async function runGUI2(inputRaw) {
|
|
|
1327
1534
|
async function generateFramePreview(input, frameIndex) {
|
|
1328
1535
|
const tempDir = tmpdir2();
|
|
1329
1536
|
const timestamp = Date.now();
|
|
1330
|
-
const tempOutput =
|
|
1537
|
+
const tempOutput = join5(tempDir, `piclet-frame-${timestamp}.png`);
|
|
1331
1538
|
try {
|
|
1332
1539
|
if (!await extractFirstFrame(input, tempOutput, frameIndex)) {
|
|
1333
1540
|
return { success: false, error: "Failed to extract frame" };
|
|
1334
1541
|
}
|
|
1335
|
-
const buffer =
|
|
1542
|
+
const buffer = readFileSync4(tempOutput);
|
|
1336
1543
|
const base64 = buffer.toString("base64");
|
|
1337
1544
|
const imageData = `data:image/png;base64,${base64}`;
|
|
1338
1545
|
const dims = await getDimensions(tempOutput);
|
|
@@ -1356,9 +1563,9 @@ var config2 = {
|
|
|
1356
1563
|
};
|
|
1357
1564
|
|
|
1358
1565
|
// src/tools/filter.ts
|
|
1359
|
-
import { existsSync as
|
|
1566
|
+
import { existsSync as existsSync7, readFileSync as readFileSync5 } from "fs";
|
|
1360
1567
|
import { tmpdir as tmpdir3 } from "os";
|
|
1361
|
-
import { basename as basename4, join as
|
|
1568
|
+
import { basename as basename4, join as join6 } from "path";
|
|
1362
1569
|
var FILTER_LABELS = {
|
|
1363
1570
|
"grayscale": "Grayscale",
|
|
1364
1571
|
"sepia": "Sepia",
|
|
@@ -1432,7 +1639,7 @@ async function run3(inputRaw) {
|
|
|
1432
1639
|
return false;
|
|
1433
1640
|
}
|
|
1434
1641
|
const input = normalizePath(inputRaw);
|
|
1435
|
-
if (!
|
|
1642
|
+
if (!existsSync7(input)) {
|
|
1436
1643
|
error(`File not found: ${input}`);
|
|
1437
1644
|
await pauseOnError();
|
|
1438
1645
|
return false;
|
|
@@ -1451,7 +1658,7 @@ async function run3(inputRaw) {
|
|
|
1451
1658
|
}
|
|
1452
1659
|
async function runGUI3(inputRaw) {
|
|
1453
1660
|
const input = normalizePath(inputRaw);
|
|
1454
|
-
if (!
|
|
1661
|
+
if (!existsSync7(input)) {
|
|
1455
1662
|
error(`File not found: ${input}`);
|
|
1456
1663
|
return false;
|
|
1457
1664
|
}
|
|
@@ -1516,8 +1723,8 @@ async function runGUI3(inputRaw) {
|
|
|
1516
1723
|
async function generatePreview2(input, options) {
|
|
1517
1724
|
const tempDir = tmpdir3();
|
|
1518
1725
|
const timestamp = Date.now();
|
|
1519
|
-
const tempSource =
|
|
1520
|
-
const tempOutput =
|
|
1726
|
+
const tempSource = join6(tempDir, `piclet-preview-${timestamp}-src.png`);
|
|
1727
|
+
const tempOutput = join6(tempDir, `piclet-preview-${timestamp}.png`);
|
|
1521
1728
|
try {
|
|
1522
1729
|
let previewInput = input;
|
|
1523
1730
|
if (isMultiFrame(input)) {
|
|
@@ -1531,7 +1738,7 @@ async function generatePreview2(input, options) {
|
|
|
1531
1738
|
cleanup(tempSource, tempOutput);
|
|
1532
1739
|
return { success: false, error: "Filter failed" };
|
|
1533
1740
|
}
|
|
1534
|
-
const buffer =
|
|
1741
|
+
const buffer = readFileSync5(tempOutput);
|
|
1535
1742
|
const base64 = buffer.toString("base64");
|
|
1536
1743
|
const imageData = `data:image/png;base64,${base64}`;
|
|
1537
1744
|
const dims = await getDimensions(tempOutput);
|
|
@@ -1555,7 +1762,7 @@ var config3 = {
|
|
|
1555
1762
|
};
|
|
1556
1763
|
|
|
1557
1764
|
// src/tools/iconpack.ts
|
|
1558
|
-
import { existsSync as
|
|
1765
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync5 } from "fs";
|
|
1559
1766
|
import { basename as basename5, dirname as dirname5 } from "path";
|
|
1560
1767
|
var WEB_ICONS = [
|
|
1561
1768
|
{ filename: "favicon-16x16.png", size: 16 },
|
|
@@ -1606,8 +1813,8 @@ async function generateIcons(outputDir, sourceImg, icons) {
|
|
|
1606
1813
|
current++;
|
|
1607
1814
|
const outputPath = `${outputDir}/${icon.filename}`;
|
|
1608
1815
|
const subdir = dirname5(outputPath);
|
|
1609
|
-
if (!
|
|
1610
|
-
|
|
1816
|
+
if (!existsSync8(subdir)) {
|
|
1817
|
+
mkdirSync5(subdir, { recursive: true });
|
|
1611
1818
|
}
|
|
1612
1819
|
clearLine();
|
|
1613
1820
|
wip(`[${current}/${total}] Generating ${icon.filename}...`);
|
|
@@ -1652,7 +1859,7 @@ async function run4(inputRaw) {
|
|
|
1652
1859
|
return false;
|
|
1653
1860
|
}
|
|
1654
1861
|
const input = normalizePath(inputRaw);
|
|
1655
|
-
if (!
|
|
1862
|
+
if (!existsSync8(input)) {
|
|
1656
1863
|
error(`File not found: ${input}`);
|
|
1657
1864
|
await pauseOnError();
|
|
1658
1865
|
return false;
|
|
@@ -1694,7 +1901,7 @@ async function run4(inputRaw) {
|
|
|
1694
1901
|
const doAndroid = platforms.includes("android");
|
|
1695
1902
|
const doIos = platforms.includes("ios");
|
|
1696
1903
|
const outputDir = `${fileInfo.dirname}/${fileInfo.filename}_icons`;
|
|
1697
|
-
|
|
1904
|
+
mkdirSync5(outputDir, { recursive: true });
|
|
1698
1905
|
info(`Output directory: ${outputDir}`);
|
|
1699
1906
|
console.log("");
|
|
1700
1907
|
wip("Preparing source image...");
|
|
@@ -1716,7 +1923,7 @@ async function run4(inputRaw) {
|
|
|
1716
1923
|
console.log("");
|
|
1717
1924
|
header("Web Icons");
|
|
1718
1925
|
const webDir = `${outputDir}/web`;
|
|
1719
|
-
|
|
1926
|
+
mkdirSync5(webDir, { recursive: true });
|
|
1720
1927
|
if (!await generateFavicon(webDir, tempSource)) {
|
|
1721
1928
|
totalFailed++;
|
|
1722
1929
|
}
|
|
@@ -1726,14 +1933,14 @@ async function run4(inputRaw) {
|
|
|
1726
1933
|
console.log("");
|
|
1727
1934
|
header("Android Icons");
|
|
1728
1935
|
const androidDir = `${outputDir}/android`;
|
|
1729
|
-
|
|
1936
|
+
mkdirSync5(androidDir, { recursive: true });
|
|
1730
1937
|
totalFailed += await generateIcons(androidDir, tempSource, ANDROID_ICONS);
|
|
1731
1938
|
}
|
|
1732
1939
|
if (doIos) {
|
|
1733
1940
|
console.log("");
|
|
1734
1941
|
header("iOS Icons");
|
|
1735
1942
|
const iosDir = `${outputDir}/ios`;
|
|
1736
|
-
|
|
1943
|
+
mkdirSync5(iosDir, { recursive: true });
|
|
1737
1944
|
totalFailed += await generateIcons(iosDir, tempSource, IOS_ICONS);
|
|
1738
1945
|
}
|
|
1739
1946
|
cleanup(tempSource);
|
|
@@ -1759,7 +1966,7 @@ async function run4(inputRaw) {
|
|
|
1759
1966
|
}
|
|
1760
1967
|
async function runGUI4(inputRaw) {
|
|
1761
1968
|
const input = normalizePath(inputRaw);
|
|
1762
|
-
if (!
|
|
1969
|
+
if (!existsSync8(input)) {
|
|
1763
1970
|
error(`File not found: ${input}`);
|
|
1764
1971
|
return false;
|
|
1765
1972
|
}
|
|
@@ -1804,7 +2011,7 @@ async function runGUI4(inputRaw) {
|
|
|
1804
2011
|
};
|
|
1805
2012
|
}
|
|
1806
2013
|
const outputDir = `${fileInfo.dirname}/${fileInfo.filename}_icons`;
|
|
1807
|
-
|
|
2014
|
+
mkdirSync5(outputDir, { recursive: true });
|
|
1808
2015
|
logs.push({ type: "info", message: `Output: ${outputDir}` });
|
|
1809
2016
|
logs.push({ type: "info", message: "Preparing source image..." });
|
|
1810
2017
|
const tempSource = `${outputDir}/.source_1024.png`;
|
|
@@ -1822,7 +2029,7 @@ async function runGUI4(inputRaw) {
|
|
|
1822
2029
|
if (doWeb) {
|
|
1823
2030
|
logs.push({ type: "info", message: "Generating Web icons..." });
|
|
1824
2031
|
const webDir = `${outputDir}/web`;
|
|
1825
|
-
|
|
2032
|
+
mkdirSync5(webDir, { recursive: true });
|
|
1826
2033
|
if (!await generateFaviconSilent(webDir, tempSource)) {
|
|
1827
2034
|
totalFailed++;
|
|
1828
2035
|
}
|
|
@@ -1832,14 +2039,14 @@ async function runGUI4(inputRaw) {
|
|
|
1832
2039
|
if (doAndroid) {
|
|
1833
2040
|
logs.push({ type: "info", message: "Generating Android icons..." });
|
|
1834
2041
|
const androidDir = `${outputDir}/android`;
|
|
1835
|
-
|
|
2042
|
+
mkdirSync5(androidDir, { recursive: true });
|
|
1836
2043
|
totalFailed += await generateIconsSilent(androidDir, tempSource, ANDROID_ICONS, logs);
|
|
1837
2044
|
logs.push({ type: "success", message: `Android: ${ANDROID_ICONS.length} icons` });
|
|
1838
2045
|
}
|
|
1839
2046
|
if (doIos) {
|
|
1840
2047
|
logs.push({ type: "info", message: "Generating iOS icons..." });
|
|
1841
2048
|
const iosDir = `${outputDir}/ios`;
|
|
1842
|
-
|
|
2049
|
+
mkdirSync5(iosDir, { recursive: true });
|
|
1843
2050
|
totalFailed += await generateIconsSilent(iosDir, tempSource, IOS_ICONS, logs);
|
|
1844
2051
|
logs.push({ type: "success", message: `iOS: ${IOS_ICONS.length} icons` });
|
|
1845
2052
|
}
|
|
@@ -1864,8 +2071,8 @@ async function generateIconsSilent(outputDir, sourceImg, icons, _logs) {
|
|
|
1864
2071
|
for (const icon of icons) {
|
|
1865
2072
|
const outputPath = `${outputDir}/${icon.filename}`;
|
|
1866
2073
|
const subdir = dirname5(outputPath);
|
|
1867
|
-
if (!
|
|
1868
|
-
|
|
2074
|
+
if (!existsSync8(subdir)) {
|
|
2075
|
+
mkdirSync5(subdir, { recursive: true });
|
|
1869
2076
|
}
|
|
1870
2077
|
if (!await scaleToSize(sourceImg, outputPath, icon.size)) {
|
|
1871
2078
|
failed++;
|
|
@@ -1895,9 +2102,9 @@ var config4 = {
|
|
|
1895
2102
|
};
|
|
1896
2103
|
|
|
1897
2104
|
// src/tools/makeicon.ts
|
|
1898
|
-
import { existsSync as
|
|
2105
|
+
import { existsSync as existsSync9, readFileSync as readFileSync6 } from "fs";
|
|
1899
2106
|
import { tmpdir as tmpdir4 } from "os";
|
|
1900
|
-
import { basename as basename6, join as
|
|
2107
|
+
import { basename as basename6, join as join7 } from "path";
|
|
1901
2108
|
async function run5(inputRaw) {
|
|
1902
2109
|
if (!await checkImageMagick()) {
|
|
1903
2110
|
error("ImageMagick not found. Please install it:");
|
|
@@ -1906,7 +2113,7 @@ async function run5(inputRaw) {
|
|
|
1906
2113
|
return false;
|
|
1907
2114
|
}
|
|
1908
2115
|
const input = normalizePath(inputRaw);
|
|
1909
|
-
if (!
|
|
2116
|
+
if (!existsSync9(input)) {
|
|
1910
2117
|
error(`File not found: ${input}`);
|
|
1911
2118
|
await pauseOnError();
|
|
1912
2119
|
return false;
|
|
@@ -2004,9 +2211,9 @@ async function processForIcon(input, output, options, logs) {
|
|
|
2004
2211
|
async function generatePreview3(input, options) {
|
|
2005
2212
|
const tempDir = tmpdir4();
|
|
2006
2213
|
const timestamp = Date.now();
|
|
2007
|
-
const tempTrimmed =
|
|
2008
|
-
const tempSquare =
|
|
2009
|
-
const tempOutput =
|
|
2214
|
+
const tempTrimmed = join7(tempDir, `piclet-preview-trimmed-${timestamp}.png`);
|
|
2215
|
+
const tempSquare = join7(tempDir, `piclet-preview-square-${timestamp}.png`);
|
|
2216
|
+
const tempOutput = join7(tempDir, `piclet-preview-${timestamp}.png`);
|
|
2010
2217
|
try {
|
|
2011
2218
|
let currentInput = input;
|
|
2012
2219
|
if (options.trim) {
|
|
@@ -2030,7 +2237,7 @@ async function generatePreview3(input, options) {
|
|
|
2030
2237
|
}
|
|
2031
2238
|
if (currentInput === tempSquare) cleanup(tempSquare);
|
|
2032
2239
|
else if (currentInput === tempTrimmed) cleanup(tempTrimmed);
|
|
2033
|
-
const buffer =
|
|
2240
|
+
const buffer = readFileSync6(tempOutput);
|
|
2034
2241
|
const base64 = buffer.toString("base64");
|
|
2035
2242
|
const imageData = `data:image/png;base64,${base64}`;
|
|
2036
2243
|
const dims = await getDimensions(tempOutput);
|
|
@@ -2048,7 +2255,7 @@ async function generatePreview3(input, options) {
|
|
|
2048
2255
|
}
|
|
2049
2256
|
async function runGUI5(inputRaw) {
|
|
2050
2257
|
const input = normalizePath(inputRaw);
|
|
2051
|
-
if (!
|
|
2258
|
+
if (!existsSync9(input)) {
|
|
2052
2259
|
error(`File not found: ${input}`);
|
|
2053
2260
|
return false;
|
|
2054
2261
|
}
|
|
@@ -2113,16 +2320,16 @@ var config5 = {
|
|
|
2113
2320
|
};
|
|
2114
2321
|
|
|
2115
2322
|
// src/tools/piclet-main.ts
|
|
2116
|
-
import { existsSync as
|
|
2323
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync6, readFileSync as readFileSync7, renameSync, writeFileSync as writeFileSync2 } from "fs";
|
|
2117
2324
|
import { tmpdir as tmpdir5 } from "os";
|
|
2118
|
-
import { basename as basename7, dirname as dirname6, extname as
|
|
2325
|
+
import { basename as basename7, dirname as dirname6, extname as extname3, join as join8 } from "path";
|
|
2119
2326
|
var TOOL_ORDER = ["removebg", "scale", "icons", "storepack"];
|
|
2120
2327
|
async function generateCombinedPreview(input, borderColor, opts) {
|
|
2121
2328
|
const tempDir = tmpdir5();
|
|
2122
2329
|
const ts = Date.now();
|
|
2123
2330
|
const temps = [];
|
|
2124
2331
|
const makeTempPath = (suffix) => {
|
|
2125
|
-
const p =
|
|
2332
|
+
const p = join8(tempDir, `piclet-${ts}-${suffix}.png`);
|
|
2126
2333
|
temps.push(p);
|
|
2127
2334
|
return p;
|
|
2128
2335
|
};
|
|
@@ -2144,7 +2351,7 @@ async function generateCombinedPreview(input, borderColor, opts) {
|
|
|
2144
2351
|
previewPath2 = scaled;
|
|
2145
2352
|
}
|
|
2146
2353
|
}
|
|
2147
|
-
const buffer2 =
|
|
2354
|
+
const buffer2 = readFileSync7(previewPath2);
|
|
2148
2355
|
const finalDims2 = await getDimensions(previewPath2);
|
|
2149
2356
|
cleanup(...temps);
|
|
2150
2357
|
return {
|
|
@@ -2161,7 +2368,9 @@ async function generateCombinedPreview(input, borderColor, opts) {
|
|
|
2161
2368
|
const rbOpts = opts.removebg;
|
|
2162
2369
|
const out = makeTempPath("removebg");
|
|
2163
2370
|
let success2 = false;
|
|
2164
|
-
if (rbOpts.
|
|
2371
|
+
if (rbOpts.edgeDetect && borderColor) {
|
|
2372
|
+
success2 = await removeBackgroundEdgeAware(current, out, borderColor, rbOpts.fuzz, rbOpts.edgeStrength);
|
|
2373
|
+
} else if (rbOpts.preserveInner && borderColor) {
|
|
2165
2374
|
success2 = await removeBackgroundBorderOnly(current, out, borderColor, rbOpts.fuzz);
|
|
2166
2375
|
}
|
|
2167
2376
|
if (!success2 && borderColor) {
|
|
@@ -2227,7 +2436,7 @@ async function generateCombinedPreview(input, borderColor, opts) {
|
|
|
2227
2436
|
previewPath = scaled;
|
|
2228
2437
|
}
|
|
2229
2438
|
}
|
|
2230
|
-
const buffer =
|
|
2439
|
+
const buffer = readFileSync7(previewPath);
|
|
2231
2440
|
const finalDims = await getDimensions(previewPath);
|
|
2232
2441
|
cleanup(...temps);
|
|
2233
2442
|
return {
|
|
@@ -2245,6 +2454,7 @@ async function processCombined(input, borderColor, opts, logs) {
|
|
|
2245
2454
|
const fileInfo = getFileInfo(input);
|
|
2246
2455
|
const outputs = [];
|
|
2247
2456
|
const temps = [];
|
|
2457
|
+
let singleFilePath;
|
|
2248
2458
|
const outputExt = fileInfo.extension.toLowerCase() === ".gif" ? ".gif" : ".png";
|
|
2249
2459
|
const makeTempPath = (suffix) => {
|
|
2250
2460
|
const p = `${fileInfo.dirname}/${fileInfo.filename}_${suffix}${outputExt}`;
|
|
@@ -2260,7 +2470,13 @@ async function processCombined(input, borderColor, opts, logs) {
|
|
|
2260
2470
|
const rbOpts = opts.removebg;
|
|
2261
2471
|
const out = makeTempPath("nobg");
|
|
2262
2472
|
let success2 = false;
|
|
2263
|
-
if (rbOpts.
|
|
2473
|
+
if (rbOpts.edgeDetect && borderColor) {
|
|
2474
|
+
logs.push({ type: "info", message: "Using edge feathering..." });
|
|
2475
|
+
success2 = await removeBackgroundEdgeAware(current, out, borderColor, rbOpts.fuzz, rbOpts.edgeStrength);
|
|
2476
|
+
if (!success2) {
|
|
2477
|
+
logs.push({ type: "warn", message: "Edge feathering failed, trying standard removal" });
|
|
2478
|
+
}
|
|
2479
|
+
} else if (rbOpts.preserveInner && borderColor) {
|
|
2264
2480
|
success2 = await removeBackgroundBorderOnly(current, out, borderColor, rbOpts.fuzz);
|
|
2265
2481
|
if (!success2) {
|
|
2266
2482
|
logs.push({ type: "warn", message: "Border-only failed, trying full removal" });
|
|
@@ -2272,7 +2488,7 @@ async function processCombined(input, borderColor, opts, logs) {
|
|
|
2272
2488
|
if (!success2) {
|
|
2273
2489
|
logs.push({ type: "error", message: "Background removal failed" });
|
|
2274
2490
|
cleanup(...temps);
|
|
2275
|
-
return [];
|
|
2491
|
+
return { outputs: [] };
|
|
2276
2492
|
}
|
|
2277
2493
|
logs.push({ type: "success", message: "Background removed" });
|
|
2278
2494
|
if (rbOpts.trim) {
|
|
@@ -2290,10 +2506,12 @@ async function processCombined(input, borderColor, opts, logs) {
|
|
|
2290
2506
|
current = out;
|
|
2291
2507
|
}
|
|
2292
2508
|
if (activeTools.indexOf(tool) === activeTools.length - 1) {
|
|
2293
|
-
const
|
|
2509
|
+
const outDir = ensureOutputDir(input);
|
|
2510
|
+
const finalOut = join8(outDir, `${fileInfo.filename}_nobg${outputExt}`);
|
|
2294
2511
|
renameSync(current, finalOut);
|
|
2295
2512
|
temps.splice(temps.indexOf(current), 1);
|
|
2296
2513
|
outputs.push(basename7(finalOut));
|
|
2514
|
+
singleFilePath = finalOut;
|
|
2297
2515
|
}
|
|
2298
2516
|
break;
|
|
2299
2517
|
}
|
|
@@ -2311,7 +2529,7 @@ async function processCombined(input, borderColor, opts, logs) {
|
|
|
2311
2529
|
if (!success2) {
|
|
2312
2530
|
logs.push({ type: "error", message: "Scale failed" });
|
|
2313
2531
|
cleanup(...temps);
|
|
2314
|
-
return [];
|
|
2532
|
+
return { outputs: [] };
|
|
2315
2533
|
}
|
|
2316
2534
|
const dims = await getDimensions(out);
|
|
2317
2535
|
logs.push({ type: "success", message: `Scaled to ${dims?.[0]}\xD7${dims?.[1]}` });
|
|
@@ -2321,10 +2539,12 @@ async function processCombined(input, borderColor, opts, logs) {
|
|
|
2321
2539
|
}
|
|
2322
2540
|
current = out;
|
|
2323
2541
|
if (activeTools.indexOf(tool) === activeTools.length - 1) {
|
|
2324
|
-
const
|
|
2542
|
+
const outDir = ensureOutputDir(input);
|
|
2543
|
+
const finalOut = join8(outDir, `${fileInfo.filename}_scaled${outputExt}`);
|
|
2325
2544
|
renameSync(current, finalOut);
|
|
2326
2545
|
temps.splice(temps.indexOf(current), 1);
|
|
2327
2546
|
outputs.push(basename7(finalOut));
|
|
2547
|
+
singleFilePath = finalOut;
|
|
2328
2548
|
}
|
|
2329
2549
|
break;
|
|
2330
2550
|
}
|
|
@@ -2333,7 +2553,7 @@ async function processCombined(input, borderColor, opts, logs) {
|
|
|
2333
2553
|
const icOpts = opts.icons;
|
|
2334
2554
|
if (!icOpts.ico && !icOpts.web && !icOpts.android && !icOpts.ios) {
|
|
2335
2555
|
logs.push({ type: "error", message: "No output format selected" });
|
|
2336
|
-
return [];
|
|
2556
|
+
return { outputs: [] };
|
|
2337
2557
|
}
|
|
2338
2558
|
let iconSource = current;
|
|
2339
2559
|
if (icOpts.trim && current === input) {
|
|
@@ -2358,13 +2578,14 @@ async function processCombined(input, borderColor, opts, logs) {
|
|
|
2358
2578
|
if (!await scaleToSize(iconSource, srcTemp, maxSize)) {
|
|
2359
2579
|
logs.push({ type: "error", message: "Failed to prepare icon source" });
|
|
2360
2580
|
cleanup(...temps);
|
|
2361
|
-
return [];
|
|
2581
|
+
return { outputs: [] };
|
|
2362
2582
|
}
|
|
2363
2583
|
if (iconSource !== current && iconSource !== input) cleanup(iconSource);
|
|
2364
2584
|
let totalCount = 0;
|
|
2365
2585
|
if (icOpts.ico) {
|
|
2366
2586
|
logs.push({ type: "info", message: "Creating ICO file..." });
|
|
2367
|
-
const
|
|
2587
|
+
const outDir = ensureOutputDir(input);
|
|
2588
|
+
const icoOut = join8(outDir, `${fileInfo.filename}.ico`);
|
|
2368
2589
|
if (await createIco(srcTemp, icoOut)) {
|
|
2369
2590
|
logs.push({ type: "success", message: "ICO: 6 sizes (256, 128, 64, 48, 32, 16)" });
|
|
2370
2591
|
outputs.push(basename7(icoOut));
|
|
@@ -2375,12 +2596,13 @@ async function processCombined(input, borderColor, opts, logs) {
|
|
|
2375
2596
|
}
|
|
2376
2597
|
const needsPacks = icOpts.web || icOpts.android || icOpts.ios;
|
|
2377
2598
|
if (needsPacks) {
|
|
2378
|
-
const
|
|
2379
|
-
|
|
2599
|
+
const outDir = ensureOutputDir(input);
|
|
2600
|
+
const outputDir = join8(outDir, `${fileInfo.filename}_icons`);
|
|
2601
|
+
mkdirSync6(outputDir, { recursive: true });
|
|
2380
2602
|
if (icOpts.web) {
|
|
2381
2603
|
logs.push({ type: "info", message: "Generating Web icons..." });
|
|
2382
2604
|
const webDir = `${outputDir}/web`;
|
|
2383
|
-
|
|
2605
|
+
mkdirSync6(webDir, { recursive: true });
|
|
2384
2606
|
const t16 = `${webDir}/.t16.png`, t32 = `${webDir}/.t32.png`, t48 = `${webDir}/.t48.png`;
|
|
2385
2607
|
await scaleToSize(srcTemp, t16, 16);
|
|
2386
2608
|
await scaleToSize(srcTemp, t32, 32);
|
|
@@ -2415,7 +2637,7 @@ async function processCombined(input, borderColor, opts, logs) {
|
|
|
2415
2637
|
];
|
|
2416
2638
|
for (const i of androidIcons) {
|
|
2417
2639
|
const p = `${androidDir}/${i.name}`;
|
|
2418
|
-
|
|
2640
|
+
mkdirSync6(dirname6(p), { recursive: true });
|
|
2419
2641
|
await scaleToSize(srcTemp, p, i.size);
|
|
2420
2642
|
totalCount++;
|
|
2421
2643
|
}
|
|
@@ -2424,7 +2646,7 @@ async function processCombined(input, borderColor, opts, logs) {
|
|
|
2424
2646
|
if (icOpts.ios) {
|
|
2425
2647
|
logs.push({ type: "info", message: "Generating iOS icons..." });
|
|
2426
2648
|
const iosDir = `${outputDir}/ios`;
|
|
2427
|
-
|
|
2649
|
+
mkdirSync6(iosDir, { recursive: true });
|
|
2428
2650
|
const iosSizes = [20, 29, 40, 58, 60, 76, 80, 87, 120, 152, 167, 180, 1024];
|
|
2429
2651
|
for (const s of iosSizes) {
|
|
2430
2652
|
await scaleToSize(srcTemp, `${iosDir}/AppIcon-${s}.png`, s);
|
|
@@ -2443,15 +2665,16 @@ async function processCombined(input, borderColor, opts, logs) {
|
|
|
2443
2665
|
const spOpts = opts.storepack;
|
|
2444
2666
|
if (!spOpts.dimensions || spOpts.dimensions.length === 0) {
|
|
2445
2667
|
logs.push({ type: "error", message: "No dimensions specified" });
|
|
2446
|
-
return [];
|
|
2668
|
+
return { outputs: [] };
|
|
2447
2669
|
}
|
|
2448
2670
|
const folderName = spOpts.presetName || "assets";
|
|
2449
|
-
const
|
|
2450
|
-
|
|
2671
|
+
const outDir = ensureOutputDir(input);
|
|
2672
|
+
const outputDir = join8(outDir, `${fileInfo.filename}_${folderName}`);
|
|
2673
|
+
mkdirSync6(outputDir, { recursive: true });
|
|
2451
2674
|
let count = 0;
|
|
2452
2675
|
for (const dim of spOpts.dimensions) {
|
|
2453
2676
|
const filename = dim.filename || `${dim.width}x${dim.height}.png`;
|
|
2454
|
-
const out =
|
|
2677
|
+
const out = join8(outputDir, filename);
|
|
2455
2678
|
let success2 = false;
|
|
2456
2679
|
switch (spOpts.scaleMode) {
|
|
2457
2680
|
case "fill":
|
|
@@ -2472,11 +2695,11 @@ async function processCombined(input, borderColor, opts, logs) {
|
|
|
2472
2695
|
}
|
|
2473
2696
|
}
|
|
2474
2697
|
cleanup(...temps);
|
|
2475
|
-
return outputs;
|
|
2698
|
+
return { outputs, singleFilePath };
|
|
2476
2699
|
}
|
|
2477
2700
|
async function runGUI6(inputRaw) {
|
|
2478
2701
|
let currentInput = normalizePath(inputRaw);
|
|
2479
|
-
if (!
|
|
2702
|
+
if (!existsSync10(currentInput)) {
|
|
2480
2703
|
error(`File not found: ${currentInput}`);
|
|
2481
2704
|
return false;
|
|
2482
2705
|
}
|
|
@@ -2490,14 +2713,14 @@ async function runGUI6(inputRaw) {
|
|
|
2490
2713
|
const presets = loadPresets();
|
|
2491
2714
|
async function generateFrameThumbnail(frameIndex) {
|
|
2492
2715
|
const tempDir = tmpdir5();
|
|
2493
|
-
const tempOutput =
|
|
2716
|
+
const tempOutput = join8(tempDir, `piclet-frame-${Date.now()}-${frameIndex}.png`);
|
|
2494
2717
|
try {
|
|
2495
2718
|
if (!await extractFirstFrame(currentInput, tempOutput, frameIndex)) {
|
|
2496
2719
|
return { success: false, error: "Failed to extract frame" };
|
|
2497
2720
|
}
|
|
2498
|
-
const thumbOutput =
|
|
2721
|
+
const thumbOutput = join8(tempDir, `piclet-thumb-${Date.now()}-${frameIndex}.png`);
|
|
2499
2722
|
await scaleToSize(tempOutput, thumbOutput, 96);
|
|
2500
|
-
const buffer =
|
|
2723
|
+
const buffer = readFileSync7(thumbOutput);
|
|
2501
2724
|
cleanup(tempOutput, thumbOutput);
|
|
2502
2725
|
return {
|
|
2503
2726
|
success: true,
|
|
@@ -2511,7 +2734,7 @@ async function runGUI6(inputRaw) {
|
|
|
2511
2734
|
async function generateFramePreview2(frameIndex, opts) {
|
|
2512
2735
|
const tempDir = tmpdir5();
|
|
2513
2736
|
const ts = Date.now();
|
|
2514
|
-
const frameFile =
|
|
2737
|
+
const frameFile = join8(tempDir, `piclet-fp-${ts}-${frameIndex}.png`);
|
|
2515
2738
|
const temps = [frameFile];
|
|
2516
2739
|
try {
|
|
2517
2740
|
if (!await extractFirstFrame(currentInput, frameFile, frameIndex)) {
|
|
@@ -2520,7 +2743,7 @@ async function runGUI6(inputRaw) {
|
|
|
2520
2743
|
let current = frameFile;
|
|
2521
2744
|
const activeTools = ["removebg", "scale", "icons"].filter((t) => opts.tools.includes(t));
|
|
2522
2745
|
for (const tool of activeTools) {
|
|
2523
|
-
const tempOut =
|
|
2746
|
+
const tempOut = join8(tempDir, `piclet-fp-${ts}-${frameIndex}-${tool}.png`);
|
|
2524
2747
|
temps.push(tempOut);
|
|
2525
2748
|
switch (tool) {
|
|
2526
2749
|
case "removebg": {
|
|
@@ -2534,7 +2757,7 @@ async function runGUI6(inputRaw) {
|
|
|
2534
2757
|
}
|
|
2535
2758
|
if (success2) {
|
|
2536
2759
|
if (rbOpts.trim) {
|
|
2537
|
-
const trimOut =
|
|
2760
|
+
const trimOut = join8(tempDir, `piclet-fp-${ts}-${frameIndex}-trim.png`);
|
|
2538
2761
|
temps.push(trimOut);
|
|
2539
2762
|
if (await trim(tempOut, trimOut)) {
|
|
2540
2763
|
current = trimOut;
|
|
@@ -2564,7 +2787,7 @@ async function runGUI6(inputRaw) {
|
|
|
2564
2787
|
case "icons": {
|
|
2565
2788
|
const icOpts = opts.icons;
|
|
2566
2789
|
if (icOpts.trim) {
|
|
2567
|
-
const trimOut =
|
|
2790
|
+
const trimOut = join8(tempDir, `piclet-fp-${ts}-${frameIndex}-ictrim.png`);
|
|
2568
2791
|
temps.push(trimOut);
|
|
2569
2792
|
if (await trim(current, trimOut)) {
|
|
2570
2793
|
current = trimOut;
|
|
@@ -2579,10 +2802,10 @@ async function runGUI6(inputRaw) {
|
|
|
2579
2802
|
}
|
|
2580
2803
|
}
|
|
2581
2804
|
}
|
|
2582
|
-
const thumbOut =
|
|
2805
|
+
const thumbOut = join8(tempDir, `piclet-fp-${ts}-${frameIndex}-thumb.png`);
|
|
2583
2806
|
temps.push(thumbOut);
|
|
2584
2807
|
await scaleToSize(current, thumbOut, 96);
|
|
2585
|
-
const buffer =
|
|
2808
|
+
const buffer = readFileSync7(thumbOut);
|
|
2586
2809
|
cleanup(...temps);
|
|
2587
2810
|
return {
|
|
2588
2811
|
success: true,
|
|
@@ -2621,7 +2844,7 @@ async function runGUI6(inputRaw) {
|
|
|
2621
2844
|
if (isMultiFrame(currentInput) && typeof toolOpts.frameIndex === "number") {
|
|
2622
2845
|
const tempDir = tmpdir5();
|
|
2623
2846
|
const ts = Date.now();
|
|
2624
|
-
const frameFile =
|
|
2847
|
+
const frameFile = join8(tempDir, `piclet-prev-${ts}.png`);
|
|
2625
2848
|
if (!await extractFirstFrame(currentInput, frameFile, toolOpts.frameIndex)) {
|
|
2626
2849
|
return { success: false, error: "Failed to extract frame" };
|
|
2627
2850
|
}
|
|
@@ -2647,11 +2870,12 @@ async function runGUI6(inputRaw) {
|
|
|
2647
2870
|
logs: [{ type: "error", message: "ImageMagick not found. Install: sudo apt install imagemagick" }]
|
|
2648
2871
|
};
|
|
2649
2872
|
}
|
|
2650
|
-
const
|
|
2651
|
-
if (outputs.length > 0) {
|
|
2873
|
+
const result = await processCombined(currentInput, currentBorderColor, toolOpts, logs);
|
|
2874
|
+
if (result.outputs.length > 0) {
|
|
2652
2875
|
return {
|
|
2653
2876
|
success: true,
|
|
2654
|
-
output: outputs.join("\n"),
|
|
2877
|
+
output: result.outputs.join("\n"),
|
|
2878
|
+
outputPath: result.singleFilePath,
|
|
2655
2879
|
logs
|
|
2656
2880
|
};
|
|
2657
2881
|
}
|
|
@@ -2659,8 +2883,8 @@ async function runGUI6(inputRaw) {
|
|
|
2659
2883
|
},
|
|
2660
2884
|
onLoadImage: async (data) => {
|
|
2661
2885
|
try {
|
|
2662
|
-
const ext =
|
|
2663
|
-
const tempPath =
|
|
2886
|
+
const ext = extname3(data.fileName) || ".png";
|
|
2887
|
+
const tempPath = join8(tmpdir5(), `piclet-load-${Date.now()}${ext}`);
|
|
2664
2888
|
const buffer = Buffer.from(data.data, "base64");
|
|
2665
2889
|
writeFileSync2(tempPath, buffer);
|
|
2666
2890
|
const newDims = await getDimensions(tempPath);
|
|
@@ -2691,6 +2915,64 @@ async function runGUI6(inputRaw) {
|
|
|
2691
2915
|
const toolOpts = opts;
|
|
2692
2916
|
if (!toolOpts.tools) toolOpts.tools = [];
|
|
2693
2917
|
return generateFramePreview2(frameIndex, toolOpts);
|
|
2918
|
+
},
|
|
2919
|
+
onSimplifyGif: async (skipFactor) => {
|
|
2920
|
+
try {
|
|
2921
|
+
const tempPath = join8(tmpdir5(), `piclet-simplified-${Date.now()}.gif`);
|
|
2922
|
+
const result = await simplifyGif(currentInput, tempPath, skipFactor);
|
|
2923
|
+
if (!result.success) {
|
|
2924
|
+
return { success: false, error: "Failed to simplify GIF" };
|
|
2925
|
+
}
|
|
2926
|
+
const newDims = await getDimensions(tempPath);
|
|
2927
|
+
if (!newDims) {
|
|
2928
|
+
cleanup(tempPath);
|
|
2929
|
+
return { success: false, error: "Failed to read simplified GIF dimensions" };
|
|
2930
|
+
}
|
|
2931
|
+
currentInput = tempPath;
|
|
2932
|
+
currentFrameCount = result.frameCount ?? 1;
|
|
2933
|
+
return {
|
|
2934
|
+
success: true,
|
|
2935
|
+
filePath: tempPath,
|
|
2936
|
+
fileName: basename7(currentInput),
|
|
2937
|
+
width: newDims[0],
|
|
2938
|
+
height: newDims[1],
|
|
2939
|
+
frameCount: currentFrameCount
|
|
2940
|
+
};
|
|
2941
|
+
} catch (err) {
|
|
2942
|
+
return { success: false, error: err.message };
|
|
2943
|
+
}
|
|
2944
|
+
},
|
|
2945
|
+
onDeleteFrame: async (frameIndex) => {
|
|
2946
|
+
try {
|
|
2947
|
+
const tempPath = join8(tmpdir5(), `piclet-edited-${Date.now()}.gif`);
|
|
2948
|
+
const result = await deleteGifFrame(currentInput, tempPath, frameIndex);
|
|
2949
|
+
if (!result.success) {
|
|
2950
|
+
return { success: false, error: "Failed to delete frame" };
|
|
2951
|
+
}
|
|
2952
|
+
currentInput = tempPath;
|
|
2953
|
+
currentFrameCount = result.frameCount ?? 1;
|
|
2954
|
+
return { success: true, frameCount: currentFrameCount };
|
|
2955
|
+
} catch (err) {
|
|
2956
|
+
return { success: false, error: err.message };
|
|
2957
|
+
}
|
|
2958
|
+
},
|
|
2959
|
+
onReplaceFrame: async (frameIndex, imageData) => {
|
|
2960
|
+
try {
|
|
2961
|
+
const buffer = Buffer.from(imageData, "base64");
|
|
2962
|
+
const tempImagePath = join8(tmpdir5(), `piclet-replace-${Date.now()}.png`);
|
|
2963
|
+
writeFileSync2(tempImagePath, buffer);
|
|
2964
|
+
const tempPath = join8(tmpdir5(), `piclet-edited-${Date.now()}.gif`);
|
|
2965
|
+
const result = await replaceGifFrame(currentInput, tempPath, frameIndex, tempImagePath);
|
|
2966
|
+
cleanup(tempImagePath);
|
|
2967
|
+
if (!result.success) {
|
|
2968
|
+
return { success: false, error: "Failed to replace frame" };
|
|
2969
|
+
}
|
|
2970
|
+
currentInput = tempPath;
|
|
2971
|
+
currentFrameCount = result.frameCount ?? currentFrameCount;
|
|
2972
|
+
return { success: true, frameCount: currentFrameCount };
|
|
2973
|
+
} catch (err) {
|
|
2974
|
+
return { success: false, error: err.message };
|
|
2975
|
+
}
|
|
2694
2976
|
}
|
|
2695
2977
|
});
|
|
2696
2978
|
}
|
|
@@ -2709,7 +2991,7 @@ async function processGifExport(input, borderColor, opts, logs) {
|
|
|
2709
2991
|
case "frame": {
|
|
2710
2992
|
const frameIndex = opts.frameIndex ?? 0;
|
|
2711
2993
|
logs.push({ type: "info", message: `Exporting frame ${frameIndex + 1}...` });
|
|
2712
|
-
const frameFile =
|
|
2994
|
+
const frameFile = join8(tempDir, `piclet-export-${ts}.png`);
|
|
2713
2995
|
if (!await extractFirstFrame(input, frameFile, frameIndex)) {
|
|
2714
2996
|
logs.push({ type: "error", message: "Failed to extract frame" });
|
|
2715
2997
|
return { success: false, error: "Failed to extract frame", logs };
|
|
@@ -2717,14 +2999,15 @@ async function processGifExport(input, borderColor, opts, logs) {
|
|
|
2717
2999
|
let outputFile = frameFile;
|
|
2718
3000
|
if (opts.tools && opts.tools.length > 0) {
|
|
2719
3001
|
const processedLogs = [];
|
|
2720
|
-
const
|
|
3002
|
+
const result = await processCombined(frameFile, borderColor, opts, processedLogs);
|
|
2721
3003
|
logs.push(...processedLogs);
|
|
2722
|
-
if (outputs.length === 0) {
|
|
3004
|
+
if (result.outputs.length === 0) {
|
|
2723
3005
|
cleanup(frameFile);
|
|
2724
3006
|
return { success: false, error: "Processing failed", logs };
|
|
2725
3007
|
}
|
|
2726
3008
|
}
|
|
2727
|
-
const
|
|
3009
|
+
const frameOutDir = ensureOutputDir(input);
|
|
3010
|
+
const finalOutput = join8(frameOutDir, `${fileInfo.filename}_frame${frameIndex + 1}.png`);
|
|
2728
3011
|
if (outputFile === frameFile) {
|
|
2729
3012
|
renameSync(frameFile, finalOutput);
|
|
2730
3013
|
}
|
|
@@ -2733,8 +3016,9 @@ async function processGifExport(input, borderColor, opts, logs) {
|
|
|
2733
3016
|
}
|
|
2734
3017
|
case "all-frames": {
|
|
2735
3018
|
logs.push({ type: "info", message: "Extracting all frames..." });
|
|
2736
|
-
const
|
|
2737
|
-
|
|
3019
|
+
const framesOutDir = ensureOutputDir(input);
|
|
3020
|
+
const outputDir = join8(framesOutDir, `${fileInfo.filename}_frames`);
|
|
3021
|
+
mkdirSync6(outputDir, { recursive: true });
|
|
2738
3022
|
const frames = await extractAllFrames(input, outputDir, "frame");
|
|
2739
3023
|
if (frames.length === 0) {
|
|
2740
3024
|
logs.push({ type: "error", message: "Failed to extract frames" });
|
|
@@ -2753,9 +3037,9 @@ async function processGifExport(input, borderColor, opts, logs) {
|
|
|
2753
3037
|
}
|
|
2754
3038
|
case "gif": {
|
|
2755
3039
|
logs.push({ type: "info", message: "Processing GIF..." });
|
|
2756
|
-
const
|
|
2757
|
-
if (outputs.length > 0) {
|
|
2758
|
-
return { success: true, output: outputs.join("\n"), logs };
|
|
3040
|
+
const result = await processCombined(input, borderColor, opts, logs);
|
|
3041
|
+
if (result.outputs.length > 0) {
|
|
3042
|
+
return { success: true, output: result.outputs.join("\n"), outputPath: result.singleFilePath, logs };
|
|
2759
3043
|
}
|
|
2760
3044
|
return { success: false, error: "Processing failed", logs };
|
|
2761
3045
|
}
|
|
@@ -2771,9 +3055,9 @@ var config6 = {
|
|
|
2771
3055
|
};
|
|
2772
3056
|
|
|
2773
3057
|
// src/tools/recolor.ts
|
|
2774
|
-
import { existsSync as
|
|
3058
|
+
import { existsSync as existsSync11, readFileSync as readFileSync8 } from "fs";
|
|
2775
3059
|
import { tmpdir as tmpdir6 } from "os";
|
|
2776
|
-
import { basename as basename8, join as
|
|
3060
|
+
import { basename as basename8, join as join9 } from "path";
|
|
2777
3061
|
async function processImage3(input, options) {
|
|
2778
3062
|
const fileInfo = getFileInfo(input);
|
|
2779
3063
|
const outputExt = fileInfo.extension.toLowerCase() === ".gif" ? ".gif" : ".png";
|
|
@@ -2836,7 +3120,7 @@ async function run6(inputRaw) {
|
|
|
2836
3120
|
return false;
|
|
2837
3121
|
}
|
|
2838
3122
|
const input = normalizePath(inputRaw);
|
|
2839
|
-
if (!
|
|
3123
|
+
if (!existsSync11(input)) {
|
|
2840
3124
|
error(`File not found: ${input}`);
|
|
2841
3125
|
await pauseOnError();
|
|
2842
3126
|
return false;
|
|
@@ -2859,7 +3143,7 @@ async function run6(inputRaw) {
|
|
|
2859
3143
|
}
|
|
2860
3144
|
async function runGUI7(inputRaw) {
|
|
2861
3145
|
const input = normalizePath(inputRaw);
|
|
2862
|
-
if (!
|
|
3146
|
+
if (!existsSync11(input)) {
|
|
2863
3147
|
error(`File not found: ${input}`);
|
|
2864
3148
|
return false;
|
|
2865
3149
|
}
|
|
@@ -2931,8 +3215,8 @@ async function runGUI7(inputRaw) {
|
|
|
2931
3215
|
async function generatePreview4(input, options) {
|
|
2932
3216
|
const tempDir = tmpdir6();
|
|
2933
3217
|
const timestamp = Date.now();
|
|
2934
|
-
const tempSource =
|
|
2935
|
-
const tempOutput =
|
|
3218
|
+
const tempSource = join9(tempDir, `piclet-preview-${timestamp}-src.png`);
|
|
3219
|
+
const tempOutput = join9(tempDir, `piclet-preview-${timestamp}.png`);
|
|
2936
3220
|
try {
|
|
2937
3221
|
let previewInput = input;
|
|
2938
3222
|
if (isMultiFrame(input)) {
|
|
@@ -2952,7 +3236,7 @@ async function generatePreview4(input, options) {
|
|
|
2952
3236
|
cleanup(tempSource, tempOutput);
|
|
2953
3237
|
return { success: false, error: "Color replacement failed" };
|
|
2954
3238
|
}
|
|
2955
|
-
const buffer =
|
|
3239
|
+
const buffer = readFileSync8(tempOutput);
|
|
2956
3240
|
const base64 = buffer.toString("base64");
|
|
2957
3241
|
const imageData = `data:image/png;base64,${base64}`;
|
|
2958
3242
|
const dims = await getDimensions(tempOutput);
|
|
@@ -2976,13 +3260,13 @@ var config7 = {
|
|
|
2976
3260
|
};
|
|
2977
3261
|
|
|
2978
3262
|
// src/tools/remove-bg.ts
|
|
2979
|
-
import { existsSync as
|
|
3263
|
+
import { existsSync as existsSync13, readFileSync as readFileSync10, renameSync as renameSync2 } from "fs";
|
|
2980
3264
|
import { tmpdir as tmpdir7 } from "os";
|
|
2981
|
-
import { basename as basename9, join as
|
|
3265
|
+
import { basename as basename9, join as join11 } from "path";
|
|
2982
3266
|
|
|
2983
3267
|
// src/lib/config.ts
|
|
2984
|
-
import { existsSync as
|
|
2985
|
-
import { dirname as dirname7, join as
|
|
3268
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync7, readFileSync as readFileSync9, writeFileSync as writeFileSync3 } from "fs";
|
|
3269
|
+
import { dirname as dirname7, join as join10 } from "path";
|
|
2986
3270
|
import { homedir as homedir2 } from "os";
|
|
2987
3271
|
var DEFAULT_CONFIG = {
|
|
2988
3272
|
removeBg: {
|
|
@@ -3000,18 +3284,18 @@ var DEFAULT_CONFIG = {
|
|
|
3000
3284
|
}
|
|
3001
3285
|
};
|
|
3002
3286
|
function getConfigDir() {
|
|
3003
|
-
return
|
|
3287
|
+
return join10(homedir2(), ".config", "piclet");
|
|
3004
3288
|
}
|
|
3005
3289
|
function getConfigPath() {
|
|
3006
|
-
return
|
|
3290
|
+
return join10(getConfigDir(), "config.json");
|
|
3007
3291
|
}
|
|
3008
3292
|
function loadConfig() {
|
|
3009
3293
|
const configPath = getConfigPath();
|
|
3010
|
-
if (!
|
|
3294
|
+
if (!existsSync12(configPath)) {
|
|
3011
3295
|
return { ...DEFAULT_CONFIG };
|
|
3012
3296
|
}
|
|
3013
3297
|
try {
|
|
3014
|
-
const content =
|
|
3298
|
+
const content = readFileSync9(configPath, "utf-8");
|
|
3015
3299
|
const loaded = JSON.parse(content);
|
|
3016
3300
|
return {
|
|
3017
3301
|
removeBg: { ...DEFAULT_CONFIG.removeBg, ...loaded.removeBg },
|
|
@@ -3025,8 +3309,8 @@ function loadConfig() {
|
|
|
3025
3309
|
function resetConfig() {
|
|
3026
3310
|
const configPath = getConfigPath();
|
|
3027
3311
|
const configDir = dirname7(configPath);
|
|
3028
|
-
if (!
|
|
3029
|
-
|
|
3312
|
+
if (!existsSync12(configDir)) {
|
|
3313
|
+
mkdirSync7(configDir, { recursive: true });
|
|
3030
3314
|
}
|
|
3031
3315
|
writeFileSync3(configPath, JSON.stringify(DEFAULT_CONFIG, null, 2));
|
|
3032
3316
|
}
|
|
@@ -3129,7 +3413,7 @@ async function run7(inputRaw) {
|
|
|
3129
3413
|
return false;
|
|
3130
3414
|
}
|
|
3131
3415
|
const input = normalizePath(inputRaw);
|
|
3132
|
-
if (!
|
|
3416
|
+
if (!existsSync13(input)) {
|
|
3133
3417
|
error(`File not found: ${input}`);
|
|
3134
3418
|
await pauseOnError();
|
|
3135
3419
|
return false;
|
|
@@ -3152,7 +3436,7 @@ async function run7(inputRaw) {
|
|
|
3152
3436
|
}
|
|
3153
3437
|
async function runGUI8(inputRaw) {
|
|
3154
3438
|
const input = normalizePath(inputRaw);
|
|
3155
|
-
if (!
|
|
3439
|
+
if (!existsSync13(input)) {
|
|
3156
3440
|
error(`File not found: ${input}`);
|
|
3157
3441
|
return false;
|
|
3158
3442
|
}
|
|
@@ -3190,15 +3474,12 @@ async function runGUI8(inputRaw) {
|
|
|
3190
3474
|
return generatePreview5(input, borderColor, options);
|
|
3191
3475
|
},
|
|
3192
3476
|
onProcess: async (opts) => {
|
|
3193
|
-
const logs = [];
|
|
3194
3477
|
if (!await checkImageMagick()) {
|
|
3195
3478
|
return {
|
|
3196
3479
|
success: false,
|
|
3197
|
-
error: "ImageMagick not found"
|
|
3198
|
-
logs: [{ type: "error", message: "ImageMagick not found. Install with: sudo apt install imagemagick" }]
|
|
3480
|
+
error: "ImageMagick not found"
|
|
3199
3481
|
};
|
|
3200
3482
|
}
|
|
3201
|
-
logs.push({ type: "info", message: `Processing ${basename9(input)}...` });
|
|
3202
3483
|
const options = {
|
|
3203
3484
|
fuzz: opts.fuzz ?? defaults.fuzz,
|
|
3204
3485
|
doTrim: opts.trim ?? defaults.trim,
|
|
@@ -3206,22 +3487,25 @@ async function runGUI8(inputRaw) {
|
|
|
3206
3487
|
makeSquare: opts.makeSquare ?? defaults.makeSquare
|
|
3207
3488
|
};
|
|
3208
3489
|
const fileInfo = getFileInfo(input);
|
|
3209
|
-
const
|
|
3210
|
-
|
|
3490
|
+
const outputExt = fileInfo.extension.toLowerCase() === ".gif" ? ".gif" : ".png";
|
|
3491
|
+
const output = `${fileInfo.dirname}/${fileInfo.filename}_nobg${outputExt}`;
|
|
3492
|
+
const logs = [];
|
|
3211
3493
|
const success2 = await processImageSilent(input, borderColor, options, logs);
|
|
3212
|
-
if (success2) {
|
|
3494
|
+
if (success2 && existsSync13(output)) {
|
|
3213
3495
|
const finalDims = await getDimensions(output);
|
|
3214
3496
|
const sizeStr = finalDims ? ` (${finalDims[0]}x${finalDims[1]})` : "";
|
|
3497
|
+
const buffer = readFileSync10(output);
|
|
3498
|
+
const mimeType = outputExt === ".gif" ? "image/gif" : "image/png";
|
|
3499
|
+
const imageData = `data:${mimeType};base64,${buffer.toString("base64")}`;
|
|
3215
3500
|
return {
|
|
3216
3501
|
success: true,
|
|
3217
3502
|
output: `${basename9(output)}${sizeStr}`,
|
|
3218
|
-
|
|
3503
|
+
imageData
|
|
3219
3504
|
};
|
|
3220
3505
|
}
|
|
3221
3506
|
return {
|
|
3222
3507
|
success: false,
|
|
3223
|
-
error: "Processing failed"
|
|
3224
|
-
logs
|
|
3508
|
+
error: "Processing failed"
|
|
3225
3509
|
};
|
|
3226
3510
|
}
|
|
3227
3511
|
});
|
|
@@ -3280,9 +3564,9 @@ async function processImageSilent(input, borderColor, options, logs) {
|
|
|
3280
3564
|
async function generatePreview5(input, borderColor, options) {
|
|
3281
3565
|
const tempDir = tmpdir7();
|
|
3282
3566
|
const timestamp = Date.now();
|
|
3283
|
-
const tempSource =
|
|
3284
|
-
const tempFile =
|
|
3285
|
-
const tempOutput =
|
|
3567
|
+
const tempSource = join11(tempDir, `piclet-preview-${timestamp}-src.png`);
|
|
3568
|
+
const tempFile = join11(tempDir, `piclet-preview-${timestamp}.png`);
|
|
3569
|
+
const tempOutput = join11(tempDir, `piclet-preview-${timestamp}-out.png`);
|
|
3286
3570
|
try {
|
|
3287
3571
|
let previewInput = input;
|
|
3288
3572
|
if (isMultiFrame(input)) {
|
|
@@ -3310,13 +3594,13 @@ async function generatePreview5(input, borderColor, options) {
|
|
|
3310
3594
|
}
|
|
3311
3595
|
}
|
|
3312
3596
|
if (options.makeSquare) {
|
|
3313
|
-
const squareFile =
|
|
3597
|
+
const squareFile = join11(tempDir, `piclet-preview-${timestamp}-sq.png`);
|
|
3314
3598
|
if (await squarify(currentFile, squareFile)) {
|
|
3315
3599
|
cleanup(currentFile);
|
|
3316
3600
|
currentFile = squareFile;
|
|
3317
3601
|
}
|
|
3318
3602
|
}
|
|
3319
|
-
const buffer =
|
|
3603
|
+
const buffer = readFileSync10(currentFile);
|
|
3320
3604
|
const base64 = buffer.toString("base64");
|
|
3321
3605
|
const imageData = `data:image/png;base64,${base64}`;
|
|
3322
3606
|
const dims = await getDimensions(currentFile);
|
|
@@ -3340,9 +3624,9 @@ var config8 = {
|
|
|
3340
3624
|
};
|
|
3341
3625
|
|
|
3342
3626
|
// src/tools/rescale.ts
|
|
3343
|
-
import { existsSync as
|
|
3627
|
+
import { existsSync as existsSync14, readFileSync as readFileSync11 } from "fs";
|
|
3344
3628
|
import { tmpdir as tmpdir8 } from "os";
|
|
3345
|
-
import { basename as basename10, join as
|
|
3629
|
+
import { basename as basename10, join as join12 } from "path";
|
|
3346
3630
|
async function run8(inputRaw) {
|
|
3347
3631
|
if (!await checkImageMagick()) {
|
|
3348
3632
|
error("ImageMagick not found. Please install it:");
|
|
@@ -3351,7 +3635,7 @@ async function run8(inputRaw) {
|
|
|
3351
3635
|
return false;
|
|
3352
3636
|
}
|
|
3353
3637
|
const input = normalizePath(inputRaw);
|
|
3354
|
-
if (!
|
|
3638
|
+
if (!existsSync14(input)) {
|
|
3355
3639
|
error(`File not found: ${input}`);
|
|
3356
3640
|
await pauseOnError();
|
|
3357
3641
|
return false;
|
|
@@ -3412,7 +3696,7 @@ async function run8(inputRaw) {
|
|
|
3412
3696
|
} else {
|
|
3413
3697
|
scaled = await resize(input, output, targetW, targetH);
|
|
3414
3698
|
}
|
|
3415
|
-
if (!scaled || !
|
|
3699
|
+
if (!scaled || !existsSync14(output)) {
|
|
3416
3700
|
wipDone(false, "Scaling failed");
|
|
3417
3701
|
return false;
|
|
3418
3702
|
}
|
|
@@ -3428,7 +3712,7 @@ async function run8(inputRaw) {
|
|
|
3428
3712
|
}
|
|
3429
3713
|
async function runGUI9(inputRaw) {
|
|
3430
3714
|
const input = normalizePath(inputRaw);
|
|
3431
|
-
if (!
|
|
3715
|
+
if (!existsSync14(input)) {
|
|
3432
3716
|
error(`File not found: ${input}`);
|
|
3433
3717
|
return false;
|
|
3434
3718
|
}
|
|
@@ -3482,7 +3766,7 @@ async function runGUI9(inputRaw) {
|
|
|
3482
3766
|
} else {
|
|
3483
3767
|
scaled = await resize(input, output, options.width, options.height);
|
|
3484
3768
|
}
|
|
3485
|
-
if (scaled &&
|
|
3769
|
+
if (scaled && existsSync14(output)) {
|
|
3486
3770
|
const finalDims = await getDimensions(output);
|
|
3487
3771
|
const sizeStr = finalDims ? ` (${finalDims[0]}x${finalDims[1]})` : "";
|
|
3488
3772
|
logs.push({ type: "success", message: "Scaled successfully" });
|
|
@@ -3500,8 +3784,8 @@ async function runGUI9(inputRaw) {
|
|
|
3500
3784
|
async function generatePreview6(input, options) {
|
|
3501
3785
|
const tempDir = tmpdir8();
|
|
3502
3786
|
const timestamp = Date.now();
|
|
3503
|
-
const tempSource =
|
|
3504
|
-
const tempOutput =
|
|
3787
|
+
const tempSource = join12(tempDir, `piclet-preview-${timestamp}-src.png`);
|
|
3788
|
+
const tempOutput = join12(tempDir, `piclet-preview-${timestamp}.png`);
|
|
3505
3789
|
try {
|
|
3506
3790
|
let previewInput = input;
|
|
3507
3791
|
if (isMultiFrame(input)) {
|
|
@@ -3521,11 +3805,11 @@ async function generatePreview6(input, options) {
|
|
|
3521
3805
|
} else {
|
|
3522
3806
|
scaled = await resize(previewInput, tempOutput, targetW, targetH);
|
|
3523
3807
|
}
|
|
3524
|
-
if (!scaled || !
|
|
3808
|
+
if (!scaled || !existsSync14(tempOutput)) {
|
|
3525
3809
|
cleanup(tempSource);
|
|
3526
3810
|
return { success: false, error: "Scaling failed" };
|
|
3527
3811
|
}
|
|
3528
|
-
const buffer =
|
|
3812
|
+
const buffer = readFileSync11(tempOutput);
|
|
3529
3813
|
const base64 = buffer.toString("base64");
|
|
3530
3814
|
const imageData = `data:image/png;base64,${base64}`;
|
|
3531
3815
|
const dims = await getDimensions(tempOutput);
|
|
@@ -3549,8 +3833,8 @@ var config9 = {
|
|
|
3549
3833
|
};
|
|
3550
3834
|
|
|
3551
3835
|
// src/tools/storepack.ts
|
|
3552
|
-
import { existsSync as
|
|
3553
|
-
import { basename as basename11, join as
|
|
3836
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync8 } from "fs";
|
|
3837
|
+
import { basename as basename11, join as join13 } from "path";
|
|
3554
3838
|
async function scaleImage(input, output, width, height, mode) {
|
|
3555
3839
|
switch (mode) {
|
|
3556
3840
|
case "fill":
|
|
@@ -3567,7 +3851,7 @@ async function generatePresetImages(sourceImg, outputDir, preset, scaleMode = "f
|
|
|
3567
3851
|
const total = preset.icons.length;
|
|
3568
3852
|
for (let i = 0; i < total; i++) {
|
|
3569
3853
|
const icon = preset.icons[i];
|
|
3570
|
-
const outputPath =
|
|
3854
|
+
const outputPath = join13(outputDir, icon.filename);
|
|
3571
3855
|
if (logs) {
|
|
3572
3856
|
logs.push({ type: "info", message: `[${i + 1}/${total}] ${icon.filename}` });
|
|
3573
3857
|
} else {
|
|
@@ -3603,7 +3887,7 @@ async function run9(inputRaw) {
|
|
|
3603
3887
|
return false;
|
|
3604
3888
|
}
|
|
3605
3889
|
const input = normalizePath(inputRaw);
|
|
3606
|
-
if (!
|
|
3890
|
+
if (!existsSync15(input)) {
|
|
3607
3891
|
error(`File not found: ${input}`);
|
|
3608
3892
|
await pauseOnError();
|
|
3609
3893
|
return false;
|
|
@@ -3631,11 +3915,11 @@ async function run9(inputRaw) {
|
|
|
3631
3915
|
return false;
|
|
3632
3916
|
}
|
|
3633
3917
|
const outputDir = `${fileInfo.dirname}/${fileInfo.filename}_${preset.id}`;
|
|
3634
|
-
|
|
3918
|
+
mkdirSync8(outputDir, { recursive: true });
|
|
3635
3919
|
info(`Output: ${outputDir}`);
|
|
3636
3920
|
console.log("");
|
|
3637
3921
|
wip("Preparing source...");
|
|
3638
|
-
const tempSource =
|
|
3922
|
+
const tempSource = join13(outputDir, ".source.png");
|
|
3639
3923
|
if (!await squarify(input, tempSource)) {
|
|
3640
3924
|
wipDone(false, "Failed to prepare source");
|
|
3641
3925
|
return false;
|
|
@@ -3656,7 +3940,7 @@ async function run9(inputRaw) {
|
|
|
3656
3940
|
}
|
|
3657
3941
|
async function runGUI10(inputRaw) {
|
|
3658
3942
|
const input = normalizePath(inputRaw);
|
|
3659
|
-
if (!
|
|
3943
|
+
if (!existsSync15(input)) {
|
|
3660
3944
|
error(`File not found: ${input}`);
|
|
3661
3945
|
return false;
|
|
3662
3946
|
}
|
|
@@ -3705,7 +3989,7 @@ async function runGUI10(inputRaw) {
|
|
|
3705
3989
|
};
|
|
3706
3990
|
}
|
|
3707
3991
|
const outputDir = `${fileInfo.dirname}/${fileInfo.filename}_${preset.id}`;
|
|
3708
|
-
|
|
3992
|
+
mkdirSync8(outputDir, { recursive: true });
|
|
3709
3993
|
logs.push({ type: "info", message: `Output: ${outputDir}` });
|
|
3710
3994
|
logs.push({ type: "info", message: `Scale mode: ${scaleMode}` });
|
|
3711
3995
|
logs.push({ type: "info", message: `Generating ${preset.icons.length} images...` });
|
|
@@ -3734,9 +4018,9 @@ var config10 = {
|
|
|
3734
4018
|
};
|
|
3735
4019
|
|
|
3736
4020
|
// src/tools/transform.ts
|
|
3737
|
-
import { existsSync as
|
|
4021
|
+
import { existsSync as existsSync16, readFileSync as readFileSync12 } from "fs";
|
|
3738
4022
|
import { tmpdir as tmpdir9 } from "os";
|
|
3739
|
-
import { basename as basename12, join as
|
|
4023
|
+
import { basename as basename12, join as join14 } from "path";
|
|
3740
4024
|
var TRANSFORM_LABELS = {
|
|
3741
4025
|
"flip-h": "Flip Horizontal",
|
|
3742
4026
|
"flip-v": "Flip Vertical",
|
|
@@ -3812,7 +4096,7 @@ async function run10(inputRaw) {
|
|
|
3812
4096
|
return false;
|
|
3813
4097
|
}
|
|
3814
4098
|
const input = normalizePath(inputRaw);
|
|
3815
|
-
if (!
|
|
4099
|
+
if (!existsSync16(input)) {
|
|
3816
4100
|
error(`File not found: ${input}`);
|
|
3817
4101
|
await pauseOnError();
|
|
3818
4102
|
return false;
|
|
@@ -3831,7 +4115,7 @@ async function run10(inputRaw) {
|
|
|
3831
4115
|
}
|
|
3832
4116
|
async function runGUI11(inputRaw) {
|
|
3833
4117
|
const input = normalizePath(inputRaw);
|
|
3834
|
-
if (!
|
|
4118
|
+
if (!existsSync16(input)) {
|
|
3835
4119
|
error(`File not found: ${input}`);
|
|
3836
4120
|
return false;
|
|
3837
4121
|
}
|
|
@@ -3914,8 +4198,8 @@ async function runGUI11(inputRaw) {
|
|
|
3914
4198
|
async function generatePreview7(input, options) {
|
|
3915
4199
|
const tempDir = tmpdir9();
|
|
3916
4200
|
const timestamp = Date.now();
|
|
3917
|
-
const tempSource =
|
|
3918
|
-
const tempOutput =
|
|
4201
|
+
const tempSource = join14(tempDir, `piclet-preview-${timestamp}-src.png`);
|
|
4202
|
+
const tempOutput = join14(tempDir, `piclet-preview-${timestamp}.png`);
|
|
3919
4203
|
try {
|
|
3920
4204
|
let previewInput = input;
|
|
3921
4205
|
if (isMultiFrame(input)) {
|
|
@@ -3946,7 +4230,7 @@ async function generatePreview7(input, options) {
|
|
|
3946
4230
|
cleanup(tempSource, tempOutput);
|
|
3947
4231
|
return { success: false, error: "Transform failed" };
|
|
3948
4232
|
}
|
|
3949
|
-
const buffer =
|
|
4233
|
+
const buffer = readFileSync12(tempOutput);
|
|
3950
4234
|
const base64 = buffer.toString("base64");
|
|
3951
4235
|
const imageData = `data:image/png;base64,${base64}`;
|
|
3952
4236
|
const dims = await getDimensions(tempOutput);
|
|
@@ -4003,6 +4287,10 @@ function getToolsForExtension(extension) {
|
|
|
4003
4287
|
}
|
|
4004
4288
|
|
|
4005
4289
|
// src/cli/utils.ts
|
|
4290
|
+
import { extname as extname4 } from "path";
|
|
4291
|
+
import { dirname as dirname9 } from "path";
|
|
4292
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
4293
|
+
import chalk from "chalk";
|
|
4006
4294
|
function getDistDir() {
|
|
4007
4295
|
const currentFile = fileURLToPath2(import.meta.url);
|
|
4008
4296
|
return dirname9(currentFile);
|
|
@@ -4014,7 +4302,7 @@ function validateExtensions(files, allowedExtensions) {
|
|
|
4014
4302
|
const valid = [];
|
|
4015
4303
|
const invalid = [];
|
|
4016
4304
|
for (const file of files) {
|
|
4017
|
-
const ext =
|
|
4305
|
+
const ext = extname4(file).toLowerCase();
|
|
4018
4306
|
if (allowedExtensions.includes(ext)) {
|
|
4019
4307
|
valid.push(file);
|
|
4020
4308
|
} else {
|
|
@@ -4074,7 +4362,7 @@ function registerBorderCommand(program2) {
|
|
|
4074
4362
|
if (valid.length === 0) {
|
|
4075
4363
|
process.exit(1);
|
|
4076
4364
|
}
|
|
4077
|
-
const result = await runGUI(valid[0]);
|
|
4365
|
+
const result = await picletTool.runGUI(valid[0]);
|
|
4078
4366
|
process.exit(result ? 0 : 1);
|
|
4079
4367
|
}
|
|
4080
4368
|
const success2 = await runToolOnFiles(
|
|
@@ -4118,7 +4406,7 @@ function registerExtractFramesCommand(program2) {
|
|
|
4118
4406
|
if (valid.length === 0) {
|
|
4119
4407
|
process.exit(1);
|
|
4120
4408
|
}
|
|
4121
|
-
const result = await
|
|
4409
|
+
const result = await picletTool.runGUI(valid[0]);
|
|
4122
4410
|
process.exit(result ? 0 : 1);
|
|
4123
4411
|
}
|
|
4124
4412
|
const success2 = await runToolOnFiles(
|
|
@@ -4145,7 +4433,7 @@ function registerFilterCommand(program2) {
|
|
|
4145
4433
|
if (valid.length === 0) {
|
|
4146
4434
|
process.exit(1);
|
|
4147
4435
|
}
|
|
4148
|
-
const result = await
|
|
4436
|
+
const result = await picletTool.runGUI(valid[0]);
|
|
4149
4437
|
process.exit(result ? 0 : 1);
|
|
4150
4438
|
}
|
|
4151
4439
|
const success2 = await runToolOnFiles(
|
|
@@ -4179,7 +4467,7 @@ function registerIconpackCommand(program2) {
|
|
|
4179
4467
|
if (valid.length === 0) {
|
|
4180
4468
|
process.exit(1);
|
|
4181
4469
|
}
|
|
4182
|
-
const result = await
|
|
4470
|
+
const result = await picletTool.runGUI(valid[0]);
|
|
4183
4471
|
process.exit(result ? 0 : 1);
|
|
4184
4472
|
}
|
|
4185
4473
|
const success2 = await runToolOnFiles(
|
|
@@ -4196,7 +4484,7 @@ import chalk7 from "chalk";
|
|
|
4196
4484
|
|
|
4197
4485
|
// src/lib/registry.ts
|
|
4198
4486
|
import { exec as exec2 } from "child_process";
|
|
4199
|
-
import { existsSync as
|
|
4487
|
+
import { existsSync as existsSync17 } from "fs";
|
|
4200
4488
|
import { dirname as dirname10 } from "path";
|
|
4201
4489
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
4202
4490
|
import { promisify as promisify2 } from "util";
|
|
@@ -4205,7 +4493,7 @@ function isWSL() {
|
|
|
4205
4493
|
return process.platform === "linux" && (process.env.WSL_DISTRO_NAME !== void 0 || process.env.WSLENV !== void 0);
|
|
4206
4494
|
}
|
|
4207
4495
|
function isWSLInteropEnabled() {
|
|
4208
|
-
return
|
|
4496
|
+
return existsSync17("/proc/sys/fs/binfmt_misc/WSLInterop");
|
|
4209
4497
|
}
|
|
4210
4498
|
async function addRegistryKey(keyPath, valueName, value, type = "REG_SZ") {
|
|
4211
4499
|
const valueArg = valueName ? `/v "${valueName}"` : "/ve";
|
|
@@ -4247,7 +4535,7 @@ async function deleteRegistryKey(keyPath) {
|
|
|
4247
4535
|
|
|
4248
4536
|
// src/cli/registry.ts
|
|
4249
4537
|
import { writeFile } from "fs/promises";
|
|
4250
|
-
import { join as
|
|
4538
|
+
import { join as join15 } from "path";
|
|
4251
4539
|
async function registerUnifiedMenu(extension, iconsDir, launcherPath) {
|
|
4252
4540
|
const basePath = `HKCU\\Software\\Classes\\SystemFileAssociations\\${extension}\\shell\\PicLet`;
|
|
4253
4541
|
const iconsDirWin = wslToWindows(iconsDir);
|
|
@@ -4283,8 +4571,8 @@ async function unregisterMenuForExtension(extension) {
|
|
|
4283
4571
|
}
|
|
4284
4572
|
async function registerAllTools() {
|
|
4285
4573
|
const distDir = getDistDir();
|
|
4286
|
-
const iconsDir =
|
|
4287
|
-
const launcherPath =
|
|
4574
|
+
const iconsDir = join15(distDir, "icons");
|
|
4575
|
+
const launcherPath = join15(distDir, "launcher.vbs");
|
|
4288
4576
|
const results = [];
|
|
4289
4577
|
for (const extension of picletTool.config.extensions) {
|
|
4290
4578
|
const result = await registerUnifiedMenu(extension, iconsDir, launcherPath);
|
|
@@ -4360,8 +4648,8 @@ function escapeRegValue(value) {
|
|
|
4360
4648
|
}
|
|
4361
4649
|
function generateRegContent() {
|
|
4362
4650
|
const distDir = getDistDir();
|
|
4363
|
-
const iconsDir =
|
|
4364
|
-
const launcherPath =
|
|
4651
|
+
const iconsDir = join15(distDir, "icons");
|
|
4652
|
+
const launcherPath = join15(distDir, "launcher.vbs");
|
|
4365
4653
|
const iconsDirWin = wslToWindows(iconsDir);
|
|
4366
4654
|
const launcherWin = wslToWindows(launcherPath);
|
|
4367
4655
|
const lines = ["Windows Registry Editor Version 5.00", ""];
|
|
@@ -4398,14 +4686,14 @@ function generateUninstallRegContent() {
|
|
|
4398
4686
|
}
|
|
4399
4687
|
async function generateRegFile() {
|
|
4400
4688
|
const distDir = getDistDir();
|
|
4401
|
-
const regPath =
|
|
4689
|
+
const regPath = join15(distDir, "piclet-install.reg");
|
|
4402
4690
|
const content = generateRegContent();
|
|
4403
4691
|
await writeFile(regPath, content, "utf-8");
|
|
4404
4692
|
return regPath;
|
|
4405
4693
|
}
|
|
4406
4694
|
async function generateUninstallRegFile() {
|
|
4407
4695
|
const distDir = getDistDir();
|
|
4408
|
-
const regPath =
|
|
4696
|
+
const regPath = join15(distDir, "piclet-uninstall.reg");
|
|
4409
4697
|
const content = generateUninstallRegContent();
|
|
4410
4698
|
await writeFile(regPath, content, "utf-8");
|
|
4411
4699
|
return regPath;
|
|
@@ -4458,9 +4746,22 @@ function registerInstallCommand(program2) {
|
|
|
4458
4746
|
chalk7.yellow(`! Registered ${successCount}/${results.length} entries.`)
|
|
4459
4747
|
);
|
|
4460
4748
|
}
|
|
4461
|
-
console.log(chalk7.bold("\
|
|
4749
|
+
console.log(chalk7.bold("\nContext Menu Usage:"));
|
|
4462
4750
|
console.log(" Right-click any supported image in Windows Explorer.");
|
|
4463
4751
|
console.log(" Multi-select supported for batch processing.");
|
|
4752
|
+
console.log(chalk7.bold("\nCLI Usage:"));
|
|
4753
|
+
console.log(chalk7.cyan(" piclet <image>") + chalk7.dim(" Open GUI editor"));
|
|
4754
|
+
console.log(chalk7.cyan(" piclet makeicon <img>") + chalk7.dim(" Convert to .ico"));
|
|
4755
|
+
console.log(chalk7.cyan(" piclet remove-bg <img>") + chalk7.dim(" Remove background"));
|
|
4756
|
+
console.log(chalk7.cyan(" piclet scale <img>") + chalk7.dim(" Resize image"));
|
|
4757
|
+
console.log(chalk7.cyan(" piclet iconpack <img>") + chalk7.dim(" Generate icon pack"));
|
|
4758
|
+
console.log(chalk7.cyan(" piclet storepack <img>") + chalk7.dim(" Generate store assets"));
|
|
4759
|
+
console.log(chalk7.cyan(" piclet transform <img>") + chalk7.dim(" Rotate/flip image"));
|
|
4760
|
+
console.log(chalk7.cyan(" piclet filter <img>") + chalk7.dim(" Apply filters"));
|
|
4761
|
+
console.log(chalk7.cyan(" piclet border <img>") + chalk7.dim(" Add border"));
|
|
4762
|
+
console.log(chalk7.cyan(" piclet recolor <img>") + chalk7.dim(" Replace colors"));
|
|
4763
|
+
console.log(chalk7.cyan(" piclet extract-frames <gif>") + chalk7.dim(" Extract GIF frames"));
|
|
4764
|
+
console.log(chalk7.dim('\n Run "piclet --help" for full documentation.'));
|
|
4464
4765
|
console.log();
|
|
4465
4766
|
});
|
|
4466
4767
|
}
|
|
@@ -4480,7 +4781,7 @@ function registerMakeiconCommand(program2) {
|
|
|
4480
4781
|
if (valid.length === 0) {
|
|
4481
4782
|
process.exit(1);
|
|
4482
4783
|
}
|
|
4483
|
-
const result = await
|
|
4784
|
+
const result = await picletTool.runGUI(valid[0]);
|
|
4484
4785
|
process.exit(result ? 0 : 1);
|
|
4485
4786
|
}
|
|
4486
4787
|
const success2 = await runToolOnFiles(
|
|
@@ -4522,7 +4823,7 @@ function registerRecolorCommand(program2) {
|
|
|
4522
4823
|
if (valid.length === 0) {
|
|
4523
4824
|
process.exit(1);
|
|
4524
4825
|
}
|
|
4525
|
-
const result = await
|
|
4826
|
+
const result = await picletTool.runGUI(valid[0]);
|
|
4526
4827
|
process.exit(result ? 0 : 1);
|
|
4527
4828
|
}
|
|
4528
4829
|
const success2 = await runToolOnFiles(
|
|
@@ -4549,7 +4850,7 @@ function registerRemoveBgCommand(program2) {
|
|
|
4549
4850
|
if (valid.length === 0) {
|
|
4550
4851
|
process.exit(1);
|
|
4551
4852
|
}
|
|
4552
|
-
const result = await
|
|
4853
|
+
const result = await picletTool.runGUI(valid[0]);
|
|
4553
4854
|
process.exit(result ? 0 : 1);
|
|
4554
4855
|
}
|
|
4555
4856
|
if (options.fuzz !== void 0) {
|
|
@@ -4589,7 +4890,7 @@ function registerScaleCommand(program2) {
|
|
|
4589
4890
|
if (valid.length === 0) {
|
|
4590
4891
|
process.exit(1);
|
|
4591
4892
|
}
|
|
4592
|
-
const result = await
|
|
4893
|
+
const result = await picletTool.runGUI(valid[0]);
|
|
4593
4894
|
process.exit(result ? 0 : 1);
|
|
4594
4895
|
}
|
|
4595
4896
|
const success2 = await runToolOnFiles(
|
|
@@ -4616,7 +4917,7 @@ function registerStorepackCommand(program2) {
|
|
|
4616
4917
|
if (valid.length === 0) {
|
|
4617
4918
|
process.exit(1);
|
|
4618
4919
|
}
|
|
4619
|
-
const result = await
|
|
4920
|
+
const result = await picletTool.runGUI(valid[0]);
|
|
4620
4921
|
process.exit(result ? 0 : 1);
|
|
4621
4922
|
}
|
|
4622
4923
|
const success2 = await runToolOnFiles(
|
|
@@ -4643,7 +4944,7 @@ function registerTransformCommand(program2) {
|
|
|
4643
4944
|
if (valid.length === 0) {
|
|
4644
4945
|
process.exit(1);
|
|
4645
4946
|
}
|
|
4646
|
-
const result = await
|
|
4947
|
+
const result = await picletTool.runGUI(valid[0]);
|
|
4647
4948
|
process.exit(result ? 0 : 1);
|
|
4648
4949
|
}
|
|
4649
4950
|
const success2 = await runToolOnFiles(
|