pixeli 0.1.9 → 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/README.md +362 -88
- package/dist/cli/commands/collage/index.d.ts +2 -0
- package/dist/cli/commands/collage/index.js +125 -0
- package/dist/cli/commands/grid/index.d.ts +2 -0
- package/dist/cli/commands/grid/index.js +127 -0
- package/dist/cli/commands/masonry/index.d.ts +2 -0
- package/dist/cli/commands/masonry/index.js +129 -0
- package/dist/cli/commands/template/index.d.ts +2 -0
- package/dist/cli/commands/template/index.js +123 -0
- package/dist/cli/commands/template/presets/artGallery.d.ts +15 -0
- package/dist/cli/commands/template/presets/artGallery.js +15 -0
- package/dist/cli/commands/template/presets/dashboardShot.d.ts +15 -0
- package/dist/cli/commands/template/presets/dashboardShot.js +16 -0
- package/dist/cli/commands/template/presets/horizontalBookSpread.d.ts +15 -0
- package/dist/cli/commands/template/presets/horizontalBookSpread.js +13 -0
- package/dist/cli/commands/template/presets/instagramGrid.d.ts +15 -0
- package/dist/cli/commands/template/presets/instagramGrid.js +16 -0
- package/dist/cli/commands/template/presets/verticalBookSpread.d.ts +15 -0
- package/dist/cli/commands/template/presets/verticalBookSpread.js +13 -0
- package/dist/cli/commands/template/presets.d.ts +73 -0
- package/{lib/merges/collage-merge → dist/cli/commands/template}/presets.js +6 -8
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +24 -0
- package/dist/cli/modules/loadImages.d.ts +15 -0
- package/dist/cli/modules/loadImages.js +74 -0
- package/dist/cli/modules/progressBar.d.ts +10 -0
- package/dist/cli/modules/progressBar.js +34 -0
- package/dist/cli/schemas/collage.d.ts +29 -0
- package/dist/cli/schemas/collage.js +38 -0
- package/dist/cli/schemas/grid.d.ts +34 -0
- package/dist/cli/schemas/grid.js +38 -0
- package/dist/cli/schemas/masonry.d.ts +62 -0
- package/dist/cli/schemas/masonry.js +62 -0
- package/dist/cli/schemas/template.d.ts +31 -0
- package/dist/cli/schemas/template.js +49 -0
- package/dist/cli/utils/buildCommandFromSchema.d.ts +8 -0
- package/dist/cli/utils/buildCommandFromSchema.js +55 -0
- package/dist/cli/utils/configureCommandErrors.d.ts +2 -0
- package/dist/cli/utils/configureCommandErrors.js +22 -0
- package/dist/cli/utils/stringFormatter.d.ts +1 -0
- package/dist/cli/utils/stringFormatter.js +3 -0
- package/dist/cli/utils/toErrorMessage.d.ts +4 -0
- package/dist/cli/utils/toErrorMessage.js +22 -0
- package/dist/core/helpers.d.ts +10 -0
- package/dist/core/helpers.js +42 -0
- package/dist/core/index.d.ts +2 -0
- package/dist/core/index.js +2 -0
- package/dist/core/jobs/batchRunner.d.ts +44 -0
- package/dist/core/jobs/batchRunner.js +90 -0
- package/dist/core/jobs/types.d.ts +10 -0
- package/dist/core/jobs/types.js +1 -0
- package/dist/core/mergeError.d.ts +9 -0
- package/dist/core/mergeError.js +10 -0
- package/dist/core/merges/collage/index.d.ts +9 -0
- package/dist/core/merges/collage/index.js +32 -0
- package/dist/core/merges/collage/steps/calculateImageDimensions.d.ts +12 -0
- package/dist/core/merges/collage/steps/calculateImageDimensions.js +18 -0
- package/dist/core/merges/collage/steps/createComposites.d.ts +8 -0
- package/dist/core/merges/collage/steps/createComposites.js +58 -0
- package/dist/core/merges/collage/steps/resizeAndBorderImages.d.ts +12 -0
- package/dist/core/merges/collage/steps/resizeAndBorderImages.js +26 -0
- package/dist/core/merges/collage/steps/rotateImages.d.ts +7 -0
- package/dist/core/merges/collage/steps/rotateImages.js +9 -0
- package/dist/core/merges/grid/index.d.ts +12 -0
- package/dist/core/merges/grid/index.js +36 -0
- package/dist/core/merges/grid/steps/calculateCanvasDimensions.d.ts +8 -0
- package/dist/core/merges/grid/steps/calculateCanvasDimensions.js +18 -0
- package/dist/core/merges/grid/steps/calculateFontSize.d.ts +7 -0
- package/dist/core/merges/grid/steps/calculateFontSize.js +19 -0
- package/dist/core/merges/grid/steps/calculateImageDimensions.d.ts +8 -0
- package/dist/core/merges/grid/steps/calculateImageDimensions.js +18 -0
- package/dist/core/merges/grid/steps/createComposites.d.ts +10 -0
- package/dist/core/merges/grid/steps/createComposites.js +63 -0
- package/dist/core/merges/grid/steps/prepareImages.d.ts +10 -0
- package/dist/core/merges/grid/steps/prepareImages.js +29 -0
- package/dist/core/merges/grid/steps/shuffleImagesAndCaptions.d.ts +7 -0
- package/dist/core/merges/grid/steps/shuffleImagesAndCaptions.js +17 -0
- package/dist/core/merges/index.d.ts +5 -0
- package/dist/core/merges/index.js +4 -0
- package/dist/core/merges/masonry/index.d.ts +10 -0
- package/dist/core/merges/masonry/index.js +32 -0
- package/dist/core/merges/masonry/steps/calculateCanvasDimensions.d.ts +15 -0
- package/dist/core/merges/masonry/steps/calculateCanvasDimensions.js +17 -0
- package/dist/core/merges/masonry/steps/calculateLaneSize.d.ts +9 -0
- package/dist/core/merges/masonry/steps/calculateLaneSize.js +27 -0
- package/dist/core/merges/masonry/steps/createComposites.d.ts +25 -0
- package/dist/core/merges/masonry/steps/createComposites.js +108 -0
- package/dist/core/merges/masonry/steps/resizeImages.d.ts +7 -0
- package/dist/core/merges/masonry/steps/resizeImages.js +14 -0
- package/dist/core/merges/masonry/steps/splitIntoLanes.d.ts +17 -0
- package/dist/core/merges/masonry/steps/splitIntoLanes.js +78 -0
- package/dist/core/merges/shared-steps/applyComposites.d.ts +2 -0
- package/dist/core/merges/shared-steps/applyComposites.js +16 -0
- package/dist/core/merges/shared-steps/createCanvas.d.ts +11 -0
- package/dist/core/merges/shared-steps/createCanvas.js +17 -0
- package/dist/core/merges/shared-steps/exportCanvas.d.ts +7 -0
- package/dist/core/merges/shared-steps/exportCanvas.js +25 -0
- package/dist/core/merges/shared-steps/finalizeImagePipelines.d.ts +2 -0
- package/dist/core/merges/shared-steps/finalizeImagePipelines.js +9 -0
- package/dist/core/merges/shared-steps/loadImages.d.ts +2 -0
- package/dist/core/merges/shared-steps/loadImages.js +26 -0
- package/dist/core/merges/shared-steps/validateCaptions.d.ts +10 -0
- package/dist/core/merges/shared-steps/validateCaptions.js +17 -0
- package/dist/core/merges/template/index.d.ts +10 -0
- package/dist/core/merges/template/index.js +28 -0
- package/dist/core/merges/template/steps/calculateSlotDimensions.d.ts +9 -0
- package/dist/core/merges/template/steps/calculateSlotDimensions.js +12 -0
- package/dist/core/merges/template/steps/createComposites.d.ts +10 -0
- package/dist/core/merges/template/steps/createComposites.js +25 -0
- package/dist/core/merges/template/steps/getBlocks.d.ts +13 -0
- package/dist/core/merges/template/steps/getBlocks.js +28 -0
- package/dist/core/merges/template/types.d.ts +21 -0
- package/dist/core/merges/template/types.js +1 -0
- package/dist/core/merges/types.d.ts +123 -0
- package/dist/core/merges/types.js +1 -0
- package/dist/core/modules/messages.d.ts +32 -0
- package/dist/core/modules/messages.js +54 -0
- package/dist/core/modules/typedEventEmitter.d.ts +7 -0
- package/dist/core/modules/typedEventEmitter.js +9 -0
- package/dist/core/pipeline/guards.d.ts +4 -0
- package/dist/core/pipeline/guards.js +23 -0
- package/dist/core/pipeline/mergePipeline.d.ts +60 -0
- package/dist/core/pipeline/mergePipeline.js +122 -0
- package/dist/core/schemas/collage.d.ts +26 -0
- package/dist/core/schemas/collage.js +17 -0
- package/dist/core/schemas/grid.d.ts +32 -0
- package/dist/core/schemas/grid.js +29 -0
- package/dist/core/schemas/masonry.d.ts +56 -0
- package/dist/core/schemas/masonry.js +43 -0
- package/dist/core/schemas/mergeJob.d.ts +11 -0
- package/dist/core/schemas/mergeJob.js +6 -0
- package/dist/core/schemas/template.d.ts +34 -0
- package/dist/core/schemas/template.js +88 -0
- package/dist/core/utils/colors/hexToRgba.d.ts +2 -0
- package/dist/core/utils/colors/hexToRgba.js +25 -0
- package/dist/core/utils/colors/rgbaToHex.d.ts +2 -0
- package/dist/core/utils/colors/rgbaToHex.js +15 -0
- package/dist/core/utils/colors/types.d.ts +7 -0
- package/dist/core/utils/colors/types.js +1 -0
- package/dist/core/utils/fonts/getFontSize.d.ts +9 -0
- package/dist/core/utils/fonts/getFontSize.js +40 -0
- package/dist/core/utils/images/addImageBorder.d.ts +13 -0
- package/dist/core/utils/images/addImageBorder.js +33 -0
- package/dist/core/utils/images/getImageHeights.d.ts +2 -0
- package/dist/core/utils/images/getImageHeights.js +9 -0
- package/dist/core/utils/images/getImageWidths.d.ts +2 -0
- package/dist/core/utils/images/getImageWidths.js +9 -0
- package/dist/core/utils/images/getSmallestImageDimensions.d.ts +5 -0
- package/dist/core/utils/images/getSmallestImageDimensions.js +9 -0
- package/dist/core/utils/images/handleImageEdges.d.ts +13 -0
- package/dist/core/utils/images/handleImageEdges.js +39 -0
- package/dist/core/utils/images/isActualImage.d.ts +5 -0
- package/dist/core/utils/images/isActualImage.js +26 -0
- package/dist/core/utils/images/parseAspectRatio.d.ts +1 -0
- package/dist/core/utils/images/parseAspectRatio.js +22 -0
- package/dist/core/utils/images/roundImage.d.ts +9 -0
- package/dist/core/utils/images/roundImage.js +27 -0
- package/dist/core/utils/images/roundImages.d.ts +8 -0
- package/dist/core/utils/images/roundImages.js +19 -0
- package/dist/core/utils/images/scaleImage.d.ts +8 -0
- package/dist/core/utils/images/scaleImage.js +36 -0
- package/dist/core/utils/images/scaleImages.d.ts +8 -0
- package/dist/core/utils/images/scaleImages.js +38 -0
- package/dist/core/utils/math/median.d.ts +1 -0
- package/dist/core/utils/math/median.js +12 -0
- package/dist/core/utils/math/randint.d.ts +6 -0
- package/dist/core/utils/math/randint.js +11 -0
- package/dist/core/utils/math/trimmedMedian.d.ts +1 -0
- package/dist/core/utils/math/trimmedMedian.js +12 -0
- package/dist/core/utils/svg/createSvgTextBuffer.d.ts +9 -0
- package/dist/core/utils/svg/createSvgTextBuffer.js +21 -0
- package/dist/validators/aspectRatio.d.ts +2 -0
- package/dist/validators/aspectRatio.js +15 -0
- package/dist/validators/coercion.d.ts +2 -0
- package/dist/validators/coercion.js +12 -0
- package/dist/validators/format.d.ts +2 -0
- package/dist/validators/format.js +15 -0
- package/dist/validators/hexColor.d.ts +7 -0
- package/dist/validators/hexColor.js +27 -0
- package/dist/validators/index.d.ts +95 -0
- package/dist/validators/index.js +64 -0
- package/dist/validators/outputFile.d.ts +2 -0
- package/dist/validators/outputFile.js +7 -0
- package/dist/validators/path.d.ts +3 -0
- package/dist/validators/path.js +18 -0
- package/dist/validators/sharpImageInput.d.ts +3 -0
- package/dist/validators/sharpImageInput.js +6 -0
- package/dist/validators/template.d.ts +15 -0
- package/dist/validators/template.js +41 -0
- package/package.json +26 -9
- package/bin/pixeli.js +0 -26
- package/commands/merge/collage.js +0 -83
- package/commands/merge/grid.js +0 -71
- package/commands/merge/helpers/utils.js +0 -11
- package/commands/merge/helpers/validations.js +0 -269
- package/commands/merge/index.js +0 -12
- package/commands/merge/masonry.js +0 -72
- package/lib/helpers/loadImages.js +0 -94
- package/lib/helpers/progressBar.js +0 -20
- package/lib/helpers/templateValidator.js +0 -139
- package/lib/helpers/utils.js +0 -208
- package/lib/merges/collage-merge/index.js +0 -110
- package/lib/merges/collage-merge/presets/artGallery.js +0 -17
- package/lib/merges/collage-merge/presets/dashboardShot.js +0 -18
- package/lib/merges/collage-merge/presets/horizontalBookSpread.js +0 -15
- package/lib/merges/collage-merge/presets/instagramGrid.js +0 -18
- package/lib/merges/collage-merge/presets/verticalBookSpread.js +0 -15
- package/lib/merges/grid-merge/index.js +0 -152
- package/lib/merges/masonry-merge/horizontal.js +0 -157
- package/lib/merges/masonry-merge/index.js +0 -57
- package/lib/merges/masonry-merge/vertical.js +0 -152
- package/lib/merges/merge-utils.js +0 -176
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import z from 'zod';
|
|
3
|
+
import { SUPPORTED_OUTPUT_FORMATS } from '../core/helpers.js';
|
|
4
|
+
export const outputFileValidator = z.string().refine((outputPath) => {
|
|
5
|
+
const extension = path.extname(outputPath).replace('.', '');
|
|
6
|
+
return SUPPORTED_OUTPUT_FORMATS.includes(extension);
|
|
7
|
+
}, 'output image format is invalid');
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { stat } from 'node:fs/promises';
|
|
2
|
+
import z from 'zod';
|
|
3
|
+
export const filePathValidator = z.string().refine(async (path) => {
|
|
4
|
+
try {
|
|
5
|
+
return (await stat(path)).isFile();
|
|
6
|
+
}
|
|
7
|
+
catch {
|
|
8
|
+
return false;
|
|
9
|
+
}
|
|
10
|
+
}, { error: 'File path does not exist or is invalid.' });
|
|
11
|
+
export const dirPathValidator = z.string().refine(async (path) => {
|
|
12
|
+
try {
|
|
13
|
+
return (await stat(path)).isDirectory();
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
}, { error: 'Directory path does not exist or is invalid.', abort: true });
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import z from 'zod';
|
|
2
|
+
export declare const sharpImageValidation: z.ZodUnion<readonly [z.ZodString, z.ZodCustom<Buffer<ArrayBufferLike>, Buffer<ArrayBufferLike>>, z.ZodCustom<Uint8Array<ArrayBuffer>, Uint8Array<ArrayBuffer>>]>;
|
|
3
|
+
export type SharpImageInput = z.infer<typeof sharpImageValidation>;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import z from 'zod';
|
|
2
|
+
export declare const templateValidator: z.ZodObject<{
|
|
3
|
+
canvas: z.ZodObject<{
|
|
4
|
+
width: z.ZodNumber;
|
|
5
|
+
height: z.ZodNumber;
|
|
6
|
+
columns: z.ZodNumber;
|
|
7
|
+
rows: z.ZodNumber;
|
|
8
|
+
}, z.z.core.$strip>;
|
|
9
|
+
slots: z.ZodArray<z.ZodObject<{
|
|
10
|
+
col: z.ZodNumber;
|
|
11
|
+
row: z.ZodNumber;
|
|
12
|
+
colSpan: z.ZodNumber;
|
|
13
|
+
rowSpan: z.ZodNumber;
|
|
14
|
+
}, z.z.core.$strip>>;
|
|
15
|
+
}, z.z.core.$strip>;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import z from 'zod';
|
|
2
|
+
export const templateValidator = z
|
|
3
|
+
.object({
|
|
4
|
+
canvas: z.object({
|
|
5
|
+
width: z.number().int().positive(),
|
|
6
|
+
height: z.number().int().positive(),
|
|
7
|
+
columns: z.number().int().positive(),
|
|
8
|
+
rows: z.number().int().positive(),
|
|
9
|
+
}),
|
|
10
|
+
slots: z.array(z.object({
|
|
11
|
+
col: z.number().int().positive(),
|
|
12
|
+
row: z.number().int().positive(),
|
|
13
|
+
colSpan: z.number().int().positive(),
|
|
14
|
+
rowSpan: z.number().int().positive(),
|
|
15
|
+
})),
|
|
16
|
+
})
|
|
17
|
+
.superRefine((opts, ctx) => {
|
|
18
|
+
// Ensure slots do not overlap
|
|
19
|
+
const result = validateSlotOverlaps(opts.slots);
|
|
20
|
+
if (!result.success) {
|
|
21
|
+
const [i, j] = result.overlaps;
|
|
22
|
+
ctx.addIssue({ code: 'custom', message: `slot ${i} overlaps with slot ${j}` });
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
const validateSlotOverlaps = (slots) => {
|
|
26
|
+
for (let i = 0; i < slots.length; i++) {
|
|
27
|
+
const A = slots[i];
|
|
28
|
+
const A_right = A.col + A.colSpan - 1;
|
|
29
|
+
const A_bottom = A.row + A.rowSpan - 1;
|
|
30
|
+
for (let j = i + 1; j < slots.length; j++) {
|
|
31
|
+
const B = slots[j];
|
|
32
|
+
const B_right = B.col + B.colSpan - 1;
|
|
33
|
+
const B_bottom = B.row + B.rowSpan - 1;
|
|
34
|
+
const overlap = A.col <= B_right && A_right >= B.col && A.row <= B_bottom && A_bottom >= B.row;
|
|
35
|
+
if (overlap) {
|
|
36
|
+
return { success: false, overlaps: [i, j] };
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return { success: true };
|
|
41
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pixeli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.4",
|
|
4
4
|
"description": "A lightweight command-line tool for merging multiple images into customizable grid layouts.",
|
|
5
5
|
"homepage": "https://github.com/pakdad-mousavi/pixeli#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -11,16 +11,21 @@
|
|
|
11
11
|
"url": "git+https://github.com/pakdad-mousavi/pixeli.git"
|
|
12
12
|
},
|
|
13
13
|
"bin": {
|
|
14
|
-
"pixeli": "
|
|
14
|
+
"pixeli": "./dist/cli/index.js"
|
|
15
15
|
},
|
|
16
16
|
"license": "MIT",
|
|
17
17
|
"author": "Pakdad Mousavi",
|
|
18
18
|
"type": "module",
|
|
19
|
-
"main": "index.js",
|
|
19
|
+
"main": "./dist/core/index.js",
|
|
20
|
+
"types": "./dist/core/index.d.ts",
|
|
21
|
+
"exports": {
|
|
22
|
+
".": {
|
|
23
|
+
"import": "./dist/core/index.js",
|
|
24
|
+
"types": "./dist/core/index.d.ts"
|
|
25
|
+
}
|
|
26
|
+
},
|
|
20
27
|
"files": [
|
|
21
|
-
"
|
|
22
|
-
"/bin",
|
|
23
|
-
"/commands"
|
|
28
|
+
"dist"
|
|
24
29
|
],
|
|
25
30
|
"keywords": [
|
|
26
31
|
"cli",
|
|
@@ -30,14 +35,26 @@
|
|
|
30
35
|
"image-merge"
|
|
31
36
|
],
|
|
32
37
|
"scripts": {
|
|
33
|
-
"
|
|
38
|
+
"dev": "tsc --watch",
|
|
39
|
+
"build": "tsc",
|
|
40
|
+
"test": "vitest",
|
|
41
|
+
"test:watch": "vitest --watch",
|
|
42
|
+
"test:coverage": "vitest run --coverage"
|
|
34
43
|
},
|
|
35
44
|
"dependencies": {
|
|
36
|
-
"ajv": "^8.17.1",
|
|
37
45
|
"chalk": "^5.6.2",
|
|
38
46
|
"cli-progress": "^3.12.0",
|
|
39
47
|
"commander": "^14.0.2",
|
|
40
48
|
"sharp": "^0.34.5",
|
|
41
|
-
"
|
|
49
|
+
"zod": "^4.1.13"
|
|
50
|
+
},
|
|
51
|
+
"devDependencies": {
|
|
52
|
+
"@types/cli-progress": "^3.11.6",
|
|
53
|
+
"@types/node": "^25.0.0",
|
|
54
|
+
"@vitest/coverage-v8": "^3.2.4",
|
|
55
|
+
"prettier": "^3.8.0",
|
|
56
|
+
"tsx": "^4.21.0",
|
|
57
|
+
"typescript": "^5.9.3",
|
|
58
|
+
"vitest": "^3.2.4"
|
|
42
59
|
}
|
|
43
60
|
}
|
package/bin/pixeli.js
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import { Command } from 'commander';
|
|
4
|
-
import mergeCommand from '../commands/merge/index.js';
|
|
5
|
-
import { configureCommandErrors, handleError } from '../lib/helpers/utils.js';
|
|
6
|
-
|
|
7
|
-
const program = new Command();
|
|
8
|
-
|
|
9
|
-
// Define program
|
|
10
|
-
program
|
|
11
|
-
.name('pixeli')
|
|
12
|
-
.description('A lightweight command-line tool for merging multiple images into customizable grid layouts.')
|
|
13
|
-
.version('1.0.0');
|
|
14
|
-
|
|
15
|
-
// Add subcommands
|
|
16
|
-
program.addCommand(mergeCommand);
|
|
17
|
-
|
|
18
|
-
// Configure errors for all subcommands
|
|
19
|
-
configureCommandErrors(program);
|
|
20
|
-
|
|
21
|
-
// Parse arguments
|
|
22
|
-
try {
|
|
23
|
-
program.parse();
|
|
24
|
-
} catch (e) {
|
|
25
|
-
handleError(e);
|
|
26
|
-
}
|
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
import { Command } from 'commander';
|
|
2
|
-
import chalk from 'chalk';
|
|
3
|
-
import {
|
|
4
|
-
cliConfirm,
|
|
5
|
-
displayInfoMessage,
|
|
6
|
-
displaySuccessMessage,
|
|
7
|
-
displayWarningMessage,
|
|
8
|
-
handleError,
|
|
9
|
-
writeImage,
|
|
10
|
-
} from '../../lib/helpers/utils.js';
|
|
11
|
-
import { validateCollageOptions, validateSharedOptions } from './helpers/validations.js';
|
|
12
|
-
import { loadImages } from '../../lib/helpers/loadImages.js';
|
|
13
|
-
import { addSharedOptions } from './helpers/utils.js';
|
|
14
|
-
import { validateTemplate } from '../../lib/helpers/templateValidator.js';
|
|
15
|
-
import { collageMerge } from '../../lib/merges/collage-merge/index.js';
|
|
16
|
-
|
|
17
|
-
const collageCommand = new Command('collage');
|
|
18
|
-
|
|
19
|
-
collageCommand
|
|
20
|
-
.description('Use JSON layouts to build custom collages.')
|
|
21
|
-
.option('-t, --template <path>', 'The path to the JSON file describing the collage template', null)
|
|
22
|
-
.option('-m, --mapping <json>', 'Inline JSON template override straight from the command line', null)
|
|
23
|
-
.option('-p, --preset <preset-id>', 'Collage preset ID to use. Available collage IDs: ', null)
|
|
24
|
-
.action(async (files, opts) => {
|
|
25
|
-
await main(files, opts);
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
const main = async (files, opts) => {
|
|
29
|
-
// Collect and validate parameters
|
|
30
|
-
try {
|
|
31
|
-
const params = { files, ...opts };
|
|
32
|
-
const sharedOptions = await validateSharedOptions(params);
|
|
33
|
-
const collageOptions = await validateCollageOptions(opts);
|
|
34
|
-
const validatedParams = { ...sharedOptions, ...collageOptions };
|
|
35
|
-
|
|
36
|
-
// Load images, create collage, and write on disk
|
|
37
|
-
await generateAndSaveCollage(validatedParams);
|
|
38
|
-
|
|
39
|
-
// Output success message
|
|
40
|
-
} catch (e) {
|
|
41
|
-
handleError(e);
|
|
42
|
-
}
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
const generateAndSaveCollage = async (validatedParams) => {
|
|
46
|
-
// Replace gap and background if not provided with command line values
|
|
47
|
-
if (validatedParams.template?.canvas?.gap === undefined) {
|
|
48
|
-
validatedParams.template.canvas.gap = validatedParams.gap;
|
|
49
|
-
}
|
|
50
|
-
if (validatedParams.template?.canvas?.background === undefined) {
|
|
51
|
-
validatedParams.template.canvas.background = validatedParams.canvasColor;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// Validate the template and update it
|
|
55
|
-
const validatedTemplate = validateTemplate(validatedParams.template);
|
|
56
|
-
validatedParams.template = validatedTemplate;
|
|
57
|
-
|
|
58
|
-
// Load images from file
|
|
59
|
-
const { images, ignoredFiles } = await loadImages({ ...validatedParams, count: validatedParams.template.slots.length });
|
|
60
|
-
|
|
61
|
-
// Display warnings if needed
|
|
62
|
-
if (ignoredFiles.length) {
|
|
63
|
-
displayWarningMessage('\nThese files will be ignored due to unsupported formats:');
|
|
64
|
-
for (const file of ignoredFiles) {
|
|
65
|
-
displayInfoMessage(file);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
const confirmation = await cliConfirm('\nAre you sure you want to continue?');
|
|
69
|
-
if (!confirmation) return;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
const collage = await collageMerge(images, validatedParams);
|
|
73
|
-
|
|
74
|
-
const success = await writeImage(collage, validatedParams.output);
|
|
75
|
-
|
|
76
|
-
// Display success message
|
|
77
|
-
if (success) {
|
|
78
|
-
displaySuccessMessage(`\nImage has been created successfully: ${chalk.bold(validatedParams.output)}\n`);
|
|
79
|
-
}
|
|
80
|
-
};
|
|
81
|
-
|
|
82
|
-
addSharedOptions(collageCommand);
|
|
83
|
-
export default collageCommand;
|
package/commands/merge/grid.js
DELETED
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
import { Command } from 'commander';
|
|
2
|
-
import chalk from 'chalk';
|
|
3
|
-
import {
|
|
4
|
-
cliConfirm,
|
|
5
|
-
displayInfoMessage,
|
|
6
|
-
displaySuccessMessage,
|
|
7
|
-
displayWarningMessage,
|
|
8
|
-
handleError,
|
|
9
|
-
writeImage,
|
|
10
|
-
} from '../../lib/helpers/utils.js';
|
|
11
|
-
import { addSharedOptions } from './helpers/utils.js';
|
|
12
|
-
import { validateGridOptions, validateSharedOptions } from './helpers/validations.js';
|
|
13
|
-
import { loadImages } from '../../lib/helpers/loadImages.js';
|
|
14
|
-
import { gridMerge } from '../../lib/merges/grid-merge/index.js';
|
|
15
|
-
|
|
16
|
-
const gridCommand = new Command('grid');
|
|
17
|
-
|
|
18
|
-
gridCommand
|
|
19
|
-
.description('Arranges images in an organized grid.')
|
|
20
|
-
.option('--ar, --aspect-ratio <width/height|number>', 'The aspect ratio of all the images (examples: 16/9, 4:3, 1.777)', '1:1')
|
|
21
|
-
.option('-w, --image-width <px>', 'The width of each image, defaults to the smallest image', null)
|
|
22
|
-
.option('-c, --columns <n>', 'The number of columns', 4)
|
|
23
|
-
.option('--ca, --caption', 'Whether to caption each image', false)
|
|
24
|
-
.option('--cc, --caption-color <hex>', 'Image Caption color', '#000000')
|
|
25
|
-
.option('--mcs, --max-caption-size <pt>', 'The maximum allowed caption size', 100)
|
|
26
|
-
.action(async (files, opts) => {
|
|
27
|
-
await main(files, opts);
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
const main = async (files, opts) => {
|
|
31
|
-
// Collect and validate parameters
|
|
32
|
-
try {
|
|
33
|
-
const params = { files, ...opts };
|
|
34
|
-
const sharedOptions = await validateSharedOptions(params);
|
|
35
|
-
const gridOptions = validateGridOptions(sharedOptions, params);
|
|
36
|
-
const validatedParams = { ...sharedOptions, ...gridOptions };
|
|
37
|
-
|
|
38
|
-
// Load images, create grid, and write grid on disk
|
|
39
|
-
await generateAndSaveGrid(validatedParams);
|
|
40
|
-
|
|
41
|
-
// Output success message
|
|
42
|
-
} catch (e) {
|
|
43
|
-
handleError(e);
|
|
44
|
-
}
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
const generateAndSaveGrid = async (validatedParams) => {
|
|
48
|
-
const { files, images, ignoredFiles } = await loadImages(validatedParams);
|
|
49
|
-
|
|
50
|
-
// Display warnings if needed
|
|
51
|
-
if (ignoredFiles.length) {
|
|
52
|
-
displayWarningMessage('\nThese files will be ignored due to unsupported formats:');
|
|
53
|
-
for (const file of ignoredFiles) {
|
|
54
|
-
displayInfoMessage(file);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
const confirmation = await cliConfirm('\nAre you sure you want to continue?');
|
|
58
|
-
if (!confirmation) return;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
const grid = await gridMerge(files, images, validatedParams);
|
|
62
|
-
const success = await writeImage(grid, validatedParams.output);
|
|
63
|
-
|
|
64
|
-
// Display success message
|
|
65
|
-
if (success) {
|
|
66
|
-
displaySuccessMessage(`\nImage has been created successfully: ${chalk.bold(validatedParams.output)}\n`);
|
|
67
|
-
}
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
addSharedOptions(gridCommand);
|
|
71
|
-
export default gridCommand;
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
export const addSharedOptions = (cmd) => {
|
|
2
|
-
return cmd
|
|
3
|
-
.argument('[files...]', 'Image filepaths to merge (use --dir for directories)')
|
|
4
|
-
.option('-d, --dir <path>', 'Directory of images to merge')
|
|
5
|
-
.option('-r, --recursive', 'Recursively include subdirectories', false)
|
|
6
|
-
.option('--sh, --shuffle', 'Shuffle up images to randomize order in the grid', false)
|
|
7
|
-
.option('--cr, --corner-radius <px>', 'How much to round the corners of each image', 0)
|
|
8
|
-
.option('-g, --gap <px>', 'Gap between images', 50)
|
|
9
|
-
.option('--bg, --canvas-color <hex|transparent>', 'Background color for canvas', '#ffffff')
|
|
10
|
-
.option('-o, --output <file>', 'Output file path', './pixeli.png');
|
|
11
|
-
};
|
|
@@ -1,269 +0,0 @@
|
|
|
1
|
-
import fs from 'node:fs/promises';
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
displayWarningMessage,
|
|
5
|
-
handleError,
|
|
6
|
-
isSupportedOutputImage,
|
|
7
|
-
isValidHexadecimal,
|
|
8
|
-
parseAspectRatio,
|
|
9
|
-
SUPPORTED_OUTPUT_FORMATS,
|
|
10
|
-
} from '../../../lib/helpers/utils.js';
|
|
11
|
-
import { isValidPreset, PRESETS } from '../../../lib/merges/collage-merge/presets.js';
|
|
12
|
-
|
|
13
|
-
export const validateSharedOptions = async (sharedOptions) => {
|
|
14
|
-
// Extract params
|
|
15
|
-
const { files, dir, recursive, shuffle, cornerRadius, gap, canvasColor, output } = sharedOptions;
|
|
16
|
-
|
|
17
|
-
// Conduct validations
|
|
18
|
-
if ((!files || !files.length) && !dir) {
|
|
19
|
-
throw new Error('You must specify either [files...] or --dir.');
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
// Ensure dir is a valid dir path
|
|
23
|
-
if (dir.length) {
|
|
24
|
-
let stats;
|
|
25
|
-
|
|
26
|
-
try {
|
|
27
|
-
stats = await fs.stat(dir);
|
|
28
|
-
} catch (e) {
|
|
29
|
-
throw new Error('Path does not exist.');
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
if (!stats.isDirectory()) {
|
|
33
|
-
throw new Error('Path is not a directory.');
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
if (isNaN(gap) || !Number.isInteger(Number(gap)) || gap < 0) {
|
|
38
|
-
throw new Error('--gap must be a positive integer.');
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
if (isNaN(cornerRadius) || !Number.isInteger(Number(cornerRadius)) || cornerRadius < 0) {
|
|
42
|
-
throw new Error('--corner-radius must be a positive integer.');
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
if (canvasColor !== 'transparent' && !isValidHexadecimal(canvasColor)) {
|
|
46
|
-
throw new Error('--canvas-color must be a valid hexadecimal value.');
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
if (!isSupportedOutputImage(output)) {
|
|
50
|
-
throw new Error('Invalid output format. Choose one of the following: ' + SUPPORTED_OUTPUT_FORMATS.join(', '));
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
const formattedParams = {
|
|
54
|
-
files: files || [],
|
|
55
|
-
dir,
|
|
56
|
-
recursive,
|
|
57
|
-
shuffle,
|
|
58
|
-
cornerRadius: Number(cornerRadius),
|
|
59
|
-
gap: Number(gap),
|
|
60
|
-
canvasColor: canvasColor === 'transparent' ? { r: 0, g: 0, b: 0, alpha: 0 } : canvasColor,
|
|
61
|
-
output,
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
return formattedParams;
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
export const validateMasonryOptions = (sharedOptions, masonryOptions) => {
|
|
68
|
-
// Extract params
|
|
69
|
-
const { gap } = sharedOptions;
|
|
70
|
-
const { rowHeight, columnWidth, canvasWidth, canvasHeight, flow, hAlign, vAlign } = masonryOptions;
|
|
71
|
-
|
|
72
|
-
// Define orientations and alignments for validation
|
|
73
|
-
const FLOWS = ['horizontal', 'vertical'];
|
|
74
|
-
const HORIZONTAL_ALIGNMENTS = ['left', 'center', 'right', 'justified'];
|
|
75
|
-
const VERTICAL_ALIGNMENTS = ['top', 'middle', 'bottom', 'justified'];
|
|
76
|
-
|
|
77
|
-
// Define orientation dependent options which are ignored if defined for the wrong orientation
|
|
78
|
-
const IGNORED_FLOW_DEPENDENT_OPTIONS = {
|
|
79
|
-
horizontal: [
|
|
80
|
-
{
|
|
81
|
-
option: '--v-align',
|
|
82
|
-
value: vAlign,
|
|
83
|
-
},
|
|
84
|
-
{
|
|
85
|
-
option: '--canvas-height',
|
|
86
|
-
value: canvasHeight,
|
|
87
|
-
},
|
|
88
|
-
{
|
|
89
|
-
option: '--column-width',
|
|
90
|
-
value: columnWidth,
|
|
91
|
-
},
|
|
92
|
-
],
|
|
93
|
-
vertical: [
|
|
94
|
-
{
|
|
95
|
-
option: '--h-align',
|
|
96
|
-
value: hAlign,
|
|
97
|
-
},
|
|
98
|
-
{
|
|
99
|
-
option: '--canvas-width',
|
|
100
|
-
value: canvasWidth,
|
|
101
|
-
},
|
|
102
|
-
{
|
|
103
|
-
option: '--row-height',
|
|
104
|
-
value: rowHeight,
|
|
105
|
-
},
|
|
106
|
-
],
|
|
107
|
-
};
|
|
108
|
-
|
|
109
|
-
// Validate orientations and alignments
|
|
110
|
-
if (!FLOWS.includes(flow)) {
|
|
111
|
-
throw new Error('Invalid orientation. Choose one of the following: ' + FLOWS.join(', '));
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
if (hAlign && !HORIZONTAL_ALIGNMENTS.includes(hAlign)) {
|
|
115
|
-
throw new Error('Invalid horizontal alignment. Choose one of the following: ' + HORIZONTAL_ALIGNMENTS.join(', '));
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
if (vAlign && !VERTICAL_ALIGNMENTS.includes(vAlign)) {
|
|
119
|
-
throw new Error('Invalid vertical orientation. Choose one of the following: ' + VERTICAL_ALIGNMENTS.join(', '));
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
// Ensure numeric values are positive integers
|
|
123
|
-
if (rowHeight && (isNaN(rowHeight) || !Number.isInteger(Number(rowHeight)) || Number(rowHeight) < 1)) {
|
|
124
|
-
throw new Error('--row-height must be a positive integer.');
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
if (columnWidth && (isNaN(columnWidth) || !Number.isInteger(Number(columnWidth)) || Number(columnWidth) < 1)) {
|
|
128
|
-
throw new Error('--column-width must be a positive integer.');
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
// Ensure canvas width is given
|
|
132
|
-
if (flow === 'horizontal' && !canvasWidth) {
|
|
133
|
-
throw new Error('--canvas-width must be given.');
|
|
134
|
-
}
|
|
135
|
-
// and is a positive integer
|
|
136
|
-
else if (flow === 'horizontal' && (isNaN(canvasWidth) || !Number.isInteger(Number(canvasWidth)) || Number(canvasWidth) < 1)) {
|
|
137
|
-
throw new Error('--canvas-width must be a positive integer.');
|
|
138
|
-
}
|
|
139
|
-
// and it accomodates for the minimum width needed
|
|
140
|
-
else if (flow === 'horizontal' && canvasWidth <= gap * 2) {
|
|
141
|
-
throw new Error(`--canvas-width must be greater than 2 gaps or ${gap * 2}px.`);
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
// Ensure canvas height is given
|
|
145
|
-
if (flow === 'vertical' && !canvasHeight) {
|
|
146
|
-
throw new Error('--canvas-height must be given.');
|
|
147
|
-
}
|
|
148
|
-
// and is a positive integer
|
|
149
|
-
else if (flow === 'vertical' && (isNaN(canvasHeight) || !Number.isInteger(Number(canvasHeight)) || Number(canvasHeight) < 1)) {
|
|
150
|
-
throw new Error('--canvas-height must be a positive integer.');
|
|
151
|
-
}
|
|
152
|
-
// and it accomodates for the minimum height needed
|
|
153
|
-
else if (flow === 'vertical' && canvasHeight <= gap * 2) {
|
|
154
|
-
throw new Error(`--canvas-height must be greater than 2 gaps or ${gap * 2}px.`);
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
// Validate dependent options by showing warnings when incorrect parameters
|
|
158
|
-
// are used with incorrect orientation
|
|
159
|
-
const ignoredFlowOptions = IGNORED_FLOW_DEPENDENT_OPTIONS[flow];
|
|
160
|
-
for (const { option, value } of ignoredFlowOptions) {
|
|
161
|
-
if (value) {
|
|
162
|
-
displayWarningMessage(`"${option}" option is ignored due to ${flow} flow.`);
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
const params = {
|
|
167
|
-
rowHeight: Number(rowHeight) || null,
|
|
168
|
-
columnWidth: Number(columnWidth) || null,
|
|
169
|
-
canvasHeight: Number(canvasHeight) || null,
|
|
170
|
-
canvasWidth: Number(canvasWidth) || null,
|
|
171
|
-
flow,
|
|
172
|
-
hAlign,
|
|
173
|
-
vAlign,
|
|
174
|
-
};
|
|
175
|
-
|
|
176
|
-
return params;
|
|
177
|
-
};
|
|
178
|
-
|
|
179
|
-
export const validateGridOptions = (sharedOptions, gridOptions) => {
|
|
180
|
-
// Extract params
|
|
181
|
-
const { aspectRatio, imageWidth, columns, caption, captionColor, maxCaptionSize } = gridOptions;
|
|
182
|
-
|
|
183
|
-
// Ensure aspect ratio is valid
|
|
184
|
-
const parsedAspectRatio = parseAspectRatio(aspectRatio);
|
|
185
|
-
if (!parsedAspectRatio) {
|
|
186
|
-
throw new Error('--aspect-ratio must be a valid ratio. Examples: 16/9, 2:3, 1x2, 1.77');
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
if (imageWidth && (isNaN(imageWidth) || !Number.isInteger(Number(imageWidth)) || Number(imageWidth) < 1)) {
|
|
190
|
-
throw new Error('--image-width must be a positive integer.');
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
if (isNaN(columns) || !Number.isInteger(Number(columns)) || Number(columns) < 1) {
|
|
194
|
-
throw new Error('--columns must be a positive integer.');
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
if (isNaN(maxCaptionSize) || !Number.isInteger(Number(maxCaptionSize)) || Number(maxCaptionSize) < 2) {
|
|
198
|
-
throw new Error('--max-caption-size must be a positive integer >= 2 (minimum caption size).');
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
if (!isValidHexadecimal(captionColor)) {
|
|
202
|
-
throw new Error('--caption-color must be a valid hexadecimal value.');
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
const formattedParams = {
|
|
206
|
-
aspectRatio: parsedAspectRatio,
|
|
207
|
-
imageWidth: Number(imageWidth) || null,
|
|
208
|
-
columns: Number(columns),
|
|
209
|
-
caption,
|
|
210
|
-
captionColor,
|
|
211
|
-
maxCaptionSize,
|
|
212
|
-
};
|
|
213
|
-
|
|
214
|
-
return formattedParams;
|
|
215
|
-
};
|
|
216
|
-
|
|
217
|
-
export const validateCollageOptions = async (collageOptions) => {
|
|
218
|
-
const { template, mapping, preset } = collageOptions;
|
|
219
|
-
|
|
220
|
-
// If a valid preset is given, use it
|
|
221
|
-
if (preset && isValidPreset(preset)) {
|
|
222
|
-
return { template: PRESETS[preset] };
|
|
223
|
-
}
|
|
224
|
-
// If preset is invalid, throw error
|
|
225
|
-
else if (preset && !isValidPreset(preset)) {
|
|
226
|
-
const validPresets = Object.keys(PRESETS).join(', ');
|
|
227
|
-
throw new Error(`"${preset}" is not a valid preset ID. Choose one of the following: \n${validPresets}`);
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
// Ensure only one of the two are given
|
|
231
|
-
if (!template && !mapping) {
|
|
232
|
-
throw new Error('Either --template, --mapping, or --preset need to be provided.');
|
|
233
|
-
} else if (template && mapping) {
|
|
234
|
-
throw new Error('Either use --template or --mapping, not both.');
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
// Ensure mapping is actually a json string
|
|
238
|
-
if (!template && mapping) {
|
|
239
|
-
try {
|
|
240
|
-
JSON.parse(mapping);
|
|
241
|
-
} catch {
|
|
242
|
-
throw new Error('--mapping should be a valid JSON string.');
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
// Ensure template is a valid dir path
|
|
247
|
-
if (template && !mapping) {
|
|
248
|
-
let stats;
|
|
249
|
-
|
|
250
|
-
try {
|
|
251
|
-
stats = await fs.stat(template);
|
|
252
|
-
} catch (e) {
|
|
253
|
-
throw new Error('Template path does not exist.');
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
// Parse and return respective JSON data
|
|
258
|
-
try {
|
|
259
|
-
if (template) {
|
|
260
|
-
const jsonStr = await fs.readFile(template, 'utf8');
|
|
261
|
-
return { template: JSON.parse(jsonStr) };
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
// mapping is already a JSON string
|
|
265
|
-
return { template: JSON.parse(mapping) };
|
|
266
|
-
} catch (err) {
|
|
267
|
-
throw new Error('Could not read or parse the provided template or mapping JSON.');
|
|
268
|
-
}
|
|
269
|
-
};
|
package/commands/merge/index.js
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import { Command } from 'commander';
|
|
2
|
-
import masonryCommand from './masonry.js';
|
|
3
|
-
import gridCommand from './grid.js';
|
|
4
|
-
import collageCommand from './collage.js';
|
|
5
|
-
|
|
6
|
-
const mergeCommand = new Command('merge').description('Merge images into a grid layout.');
|
|
7
|
-
|
|
8
|
-
mergeCommand.addCommand(masonryCommand);
|
|
9
|
-
mergeCommand.addCommand(gridCommand);
|
|
10
|
-
mergeCommand.addCommand(collageCommand);
|
|
11
|
-
|
|
12
|
-
export default mergeCommand;
|