pixeli 0.1.8 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (204) hide show
  1. package/README.md +341 -88
  2. package/dist/cli/commands/collage/index.d.ts +2 -0
  3. package/dist/cli/commands/collage/index.js +125 -0
  4. package/dist/cli/commands/grid/index.d.ts +2 -0
  5. package/dist/cli/commands/grid/index.js +127 -0
  6. package/dist/cli/commands/masonry/index.d.ts +2 -0
  7. package/dist/cli/commands/masonry/index.js +129 -0
  8. package/dist/cli/commands/template/index.d.ts +2 -0
  9. package/dist/cli/commands/template/index.js +123 -0
  10. package/dist/cli/commands/template/presets/artGallery.d.ts +15 -0
  11. package/dist/cli/commands/template/presets/artGallery.js +15 -0
  12. package/dist/cli/commands/template/presets/dashboardShot.d.ts +15 -0
  13. package/dist/cli/commands/template/presets/dashboardShot.js +16 -0
  14. package/dist/cli/commands/template/presets/horizontalBookSpread.d.ts +15 -0
  15. package/dist/cli/commands/template/presets/horizontalBookSpread.js +13 -0
  16. package/dist/cli/commands/template/presets/instagramGrid.d.ts +15 -0
  17. package/dist/cli/commands/template/presets/instagramGrid.js +16 -0
  18. package/dist/cli/commands/template/presets/verticalBookSpread.d.ts +15 -0
  19. package/dist/cli/commands/template/presets/verticalBookSpread.js +13 -0
  20. package/dist/cli/commands/template/presets.d.ts +73 -0
  21. package/{lib/merges/collage-merge → dist/cli/commands/template}/presets.js +6 -8
  22. package/dist/cli/index.d.ts +2 -0
  23. package/dist/cli/index.js +24 -0
  24. package/dist/cli/modules/loadImages.d.ts +15 -0
  25. package/dist/cli/modules/loadImages.js +74 -0
  26. package/dist/cli/modules/progressBar.d.ts +10 -0
  27. package/dist/cli/modules/progressBar.js +34 -0
  28. package/dist/cli/schemas/collage.d.ts +29 -0
  29. package/dist/cli/schemas/collage.js +38 -0
  30. package/dist/cli/schemas/grid.d.ts +34 -0
  31. package/dist/cli/schemas/grid.js +38 -0
  32. package/dist/cli/schemas/masonry.d.ts +62 -0
  33. package/dist/cli/schemas/masonry.js +62 -0
  34. package/dist/cli/schemas/template.d.ts +31 -0
  35. package/dist/cli/schemas/template.js +49 -0
  36. package/dist/cli/utils/buildCommandFromSchema.d.ts +8 -0
  37. package/dist/cli/utils/buildCommandFromSchema.js +55 -0
  38. package/dist/cli/utils/configureCommandErrors.d.ts +2 -0
  39. package/dist/cli/utils/configureCommandErrors.js +22 -0
  40. package/dist/cli/utils/stringFormatter.d.ts +1 -0
  41. package/dist/cli/utils/stringFormatter.js +3 -0
  42. package/dist/cli/utils/toErrorMessage.d.ts +4 -0
  43. package/dist/cli/utils/toErrorMessage.js +22 -0
  44. package/dist/core/helpers.d.ts +10 -0
  45. package/dist/core/helpers.js +42 -0
  46. package/dist/core/index.d.ts +1 -0
  47. package/dist/core/index.js +1 -0
  48. package/dist/core/mergeError.d.ts +9 -0
  49. package/dist/core/mergeError.js +10 -0
  50. package/dist/core/merges/collage/index.d.ts +9 -0
  51. package/dist/core/merges/collage/index.js +32 -0
  52. package/dist/core/merges/collage/steps/calculateImageDimensions.d.ts +12 -0
  53. package/dist/core/merges/collage/steps/calculateImageDimensions.js +18 -0
  54. package/dist/core/merges/collage/steps/createComposites.d.ts +8 -0
  55. package/dist/core/merges/collage/steps/createComposites.js +58 -0
  56. package/dist/core/merges/collage/steps/resizeAndBorderImages.d.ts +12 -0
  57. package/dist/core/merges/collage/steps/resizeAndBorderImages.js +26 -0
  58. package/dist/core/merges/collage/steps/rotateImages.d.ts +7 -0
  59. package/dist/core/merges/collage/steps/rotateImages.js +9 -0
  60. package/dist/core/merges/grid/index.d.ts +12 -0
  61. package/dist/core/merges/grid/index.js +36 -0
  62. package/dist/core/merges/grid/steps/calculateCanvasDimensions.d.ts +8 -0
  63. package/dist/core/merges/grid/steps/calculateCanvasDimensions.js +18 -0
  64. package/dist/core/merges/grid/steps/calculateFontSize.d.ts +7 -0
  65. package/dist/core/merges/grid/steps/calculateFontSize.js +19 -0
  66. package/dist/core/merges/grid/steps/calculateImageDimensions.d.ts +8 -0
  67. package/dist/core/merges/grid/steps/calculateImageDimensions.js +18 -0
  68. package/dist/core/merges/grid/steps/createComposites.d.ts +10 -0
  69. package/dist/core/merges/grid/steps/createComposites.js +63 -0
  70. package/dist/core/merges/grid/steps/prepareImages.d.ts +10 -0
  71. package/dist/core/merges/grid/steps/prepareImages.js +29 -0
  72. package/dist/core/merges/grid/steps/shuffleImagesAndCaptions.d.ts +7 -0
  73. package/dist/core/merges/grid/steps/shuffleImagesAndCaptions.js +17 -0
  74. package/dist/core/merges/index.d.ts +3 -0
  75. package/dist/core/merges/index.js +3 -0
  76. package/dist/core/merges/masonry/index.d.ts +10 -0
  77. package/dist/core/merges/masonry/index.js +32 -0
  78. package/dist/core/merges/masonry/steps/calculateCanvasDimensions.d.ts +15 -0
  79. package/dist/core/merges/masonry/steps/calculateCanvasDimensions.js +17 -0
  80. package/dist/core/merges/masonry/steps/calculateLaneSize.d.ts +9 -0
  81. package/dist/core/merges/masonry/steps/calculateLaneSize.js +27 -0
  82. package/dist/core/merges/masonry/steps/createComposites.d.ts +25 -0
  83. package/dist/core/merges/masonry/steps/createComposites.js +108 -0
  84. package/dist/core/merges/masonry/steps/resizeImages.d.ts +7 -0
  85. package/dist/core/merges/masonry/steps/resizeImages.js +14 -0
  86. package/dist/core/merges/masonry/steps/splitIntoLanes.d.ts +17 -0
  87. package/dist/core/merges/masonry/steps/splitIntoLanes.js +78 -0
  88. package/dist/core/merges/shared-steps/applyComposites.d.ts +2 -0
  89. package/dist/core/merges/shared-steps/applyComposites.js +16 -0
  90. package/dist/core/merges/shared-steps/createCanvas.d.ts +11 -0
  91. package/dist/core/merges/shared-steps/createCanvas.js +17 -0
  92. package/dist/core/merges/shared-steps/exportCanvas.d.ts +7 -0
  93. package/dist/core/merges/shared-steps/exportCanvas.js +25 -0
  94. package/dist/core/merges/shared-steps/finalizeImagePipelines.d.ts +2 -0
  95. package/dist/core/merges/shared-steps/finalizeImagePipelines.js +9 -0
  96. package/dist/core/merges/shared-steps/loadImages.d.ts +2 -0
  97. package/dist/core/merges/shared-steps/loadImages.js +26 -0
  98. package/dist/core/merges/shared-steps/validateCaptions.d.ts +10 -0
  99. package/dist/core/merges/shared-steps/validateCaptions.js +17 -0
  100. package/dist/core/merges/template/index.d.ts +10 -0
  101. package/dist/core/merges/template/index.js +28 -0
  102. package/dist/core/merges/template/steps/calculateSlotDimensions.d.ts +9 -0
  103. package/dist/core/merges/template/steps/calculateSlotDimensions.js +12 -0
  104. package/dist/core/merges/template/steps/createComposites.d.ts +10 -0
  105. package/dist/core/merges/template/steps/createComposites.js +25 -0
  106. package/dist/core/merges/template/steps/getBlocks.d.ts +13 -0
  107. package/dist/core/merges/template/steps/getBlocks.js +28 -0
  108. package/dist/core/merges/template/types.d.ts +21 -0
  109. package/dist/core/merges/template/types.js +1 -0
  110. package/dist/core/merges/types.d.ts +102 -0
  111. package/dist/core/merges/types.js +1 -0
  112. package/dist/core/modules/messages.d.ts +32 -0
  113. package/dist/core/modules/messages.js +54 -0
  114. package/dist/core/pipeline/guards.d.ts +4 -0
  115. package/dist/core/pipeline/guards.js +23 -0
  116. package/dist/core/pipeline/mergePipeline.d.ts +60 -0
  117. package/dist/core/pipeline/mergePipeline.js +122 -0
  118. package/dist/core/schemas/collage.d.ts +26 -0
  119. package/dist/core/schemas/collage.js +17 -0
  120. package/dist/core/schemas/grid.d.ts +32 -0
  121. package/dist/core/schemas/grid.js +29 -0
  122. package/dist/core/schemas/masonry.d.ts +56 -0
  123. package/dist/core/schemas/masonry.js +43 -0
  124. package/dist/core/schemas/template.d.ts +34 -0
  125. package/dist/core/schemas/template.js +88 -0
  126. package/dist/core/utils/colors/hexToRgba.d.ts +2 -0
  127. package/dist/core/utils/colors/hexToRgba.js +25 -0
  128. package/dist/core/utils/colors/rgbaToHex.d.ts +2 -0
  129. package/dist/core/utils/colors/rgbaToHex.js +15 -0
  130. package/dist/core/utils/colors/types.d.ts +7 -0
  131. package/dist/core/utils/colors/types.js +1 -0
  132. package/dist/core/utils/fonts/getFontSize.d.ts +9 -0
  133. package/dist/core/utils/fonts/getFontSize.js +40 -0
  134. package/dist/core/utils/images/addImageBorder.d.ts +13 -0
  135. package/dist/core/utils/images/addImageBorder.js +33 -0
  136. package/dist/core/utils/images/getImageHeights.d.ts +2 -0
  137. package/dist/core/utils/images/getImageHeights.js +9 -0
  138. package/dist/core/utils/images/getImageWidths.d.ts +2 -0
  139. package/dist/core/utils/images/getImageWidths.js +9 -0
  140. package/dist/core/utils/images/getSmallestImageDimensions.d.ts +5 -0
  141. package/dist/core/utils/images/getSmallestImageDimensions.js +9 -0
  142. package/dist/core/utils/images/handleImageEdges.d.ts +13 -0
  143. package/dist/core/utils/images/handleImageEdges.js +39 -0
  144. package/dist/core/utils/images/isActualImage.d.ts +5 -0
  145. package/dist/core/utils/images/isActualImage.js +26 -0
  146. package/dist/core/utils/images/parseAspectRatio.d.ts +1 -0
  147. package/dist/core/utils/images/parseAspectRatio.js +22 -0
  148. package/dist/core/utils/images/roundImage.d.ts +9 -0
  149. package/dist/core/utils/images/roundImage.js +27 -0
  150. package/dist/core/utils/images/roundImages.d.ts +8 -0
  151. package/dist/core/utils/images/roundImages.js +19 -0
  152. package/dist/core/utils/images/scaleImage.d.ts +8 -0
  153. package/dist/core/utils/images/scaleImage.js +36 -0
  154. package/dist/core/utils/images/scaleImages.d.ts +8 -0
  155. package/dist/core/utils/images/scaleImages.js +38 -0
  156. package/dist/core/utils/math/median.d.ts +1 -0
  157. package/dist/core/utils/math/median.js +12 -0
  158. package/dist/core/utils/math/randint.d.ts +6 -0
  159. package/dist/core/utils/math/randint.js +11 -0
  160. package/dist/core/utils/math/trimmedMedian.d.ts +1 -0
  161. package/dist/core/utils/math/trimmedMedian.js +12 -0
  162. package/dist/core/utils/svg/createSvgTextBuffer.d.ts +9 -0
  163. package/dist/core/utils/svg/createSvgTextBuffer.js +21 -0
  164. package/dist/validators/aspectRatio.d.ts +2 -0
  165. package/dist/validators/aspectRatio.js +15 -0
  166. package/dist/validators/coercion.d.ts +2 -0
  167. package/dist/validators/coercion.js +12 -0
  168. package/dist/validators/format.d.ts +2 -0
  169. package/dist/validators/format.js +15 -0
  170. package/dist/validators/hexColor.d.ts +7 -0
  171. package/dist/validators/hexColor.js +27 -0
  172. package/dist/validators/index.d.ts +95 -0
  173. package/dist/validators/index.js +64 -0
  174. package/dist/validators/outputFile.d.ts +2 -0
  175. package/dist/validators/outputFile.js +7 -0
  176. package/dist/validators/path.d.ts +3 -0
  177. package/dist/validators/path.js +18 -0
  178. package/dist/validators/sharpImageInput.d.ts +3 -0
  179. package/dist/validators/sharpImageInput.js +6 -0
  180. package/dist/validators/template.d.ts +15 -0
  181. package/dist/validators/template.js +41 -0
  182. package/package.json +26 -9
  183. package/bin/pixeli.js +0 -26
  184. package/commands/merge/collage.js +0 -83
  185. package/commands/merge/grid.js +0 -71
  186. package/commands/merge/helpers/utils.js +0 -11
  187. package/commands/merge/helpers/validations.js +0 -269
  188. package/commands/merge/index.js +0 -12
  189. package/commands/merge/masonry.js +0 -72
  190. package/lib/helpers/loadImages.js +0 -94
  191. package/lib/helpers/progressBar.js +0 -20
  192. package/lib/helpers/templateValidator.js +0 -139
  193. package/lib/helpers/utils.js +0 -208
  194. package/lib/merges/collage-merge/index.js +0 -110
  195. package/lib/merges/collage-merge/presets/artGallery.js +0 -17
  196. package/lib/merges/collage-merge/presets/dashboardShot.js +0 -18
  197. package/lib/merges/collage-merge/presets/horizontalBookSpread.js +0 -15
  198. package/lib/merges/collage-merge/presets/instagramGrid.js +0 -18
  199. package/lib/merges/collage-merge/presets/verticalBookSpread.js +0 -15
  200. package/lib/merges/grid-merge/index.js +0 -152
  201. package/lib/merges/masonry-merge/horizontal.js +0 -157
  202. package/lib/merges/masonry-merge/index.js +0 -57
  203. package/lib/merges/masonry-merge/vertical.js +0 -152
  204. package/lib/merges/merge-utils.js +0 -176
@@ -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;
@@ -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
- };
@@ -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;
@@ -1,72 +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 { validateMasonryOptions, validateSharedOptions } from './helpers/validations.js';
13
- import { loadImages } from '../../lib/helpers/loadImages.js';
14
- import { masonryMerge } from '../../lib/merges/masonry-merge/index.js';
15
-
16
- const masonryCommand = new Command('masonry');
17
-
18
- masonryCommand
19
- .description("Use a ragged-grid layout, preserves images' aspect ratios")
20
- .option('--rh, --row-height <px>', 'The height of each row, defaults to the smallest image height', null)
21
- .option('--cw, --column-width <px>', 'The width of each column, defaults to the smallest image width', null)
22
- .option('--cvw, --canvas-width <px>', 'The width of the canvas', null)
23
- .option('--cvh, --canvas-height <px>', 'The height of the canvas', null)
24
- .option('-f, --flow <horizontal|vertical>', 'The flow of the masonry layout', 'horizontal')
25
- .option('--ha, --h-align <left|center|right|justified>', 'Horizontal alignment of the grid (for horizontal flows)', null)
26
- .option('--va, --v-align <top|middle|bottom|justified>', 'Vertical alignment of the grid (for vertical flows)', null)
27
- .action((files, opts) => {
28
- main(files, opts);
29
- });
30
-
31
- const main = async (files, opts) => {
32
- try {
33
- // Collect and validate parameters
34
- const params = { files, ...opts };
35
- const sharedOptions = await validateSharedOptions(params);
36
- const masonryOptions = validateMasonryOptions(sharedOptions, opts);
37
- const validatedParams = { ...sharedOptions, ...masonryOptions };
38
-
39
- // Load images, create grid, and write grid on disk
40
- generateAndSaveGrid(validatedParams);
41
-
42
- // Output success message
43
- } catch (e) {
44
- handleError(e);
45
- }
46
- };
47
-
48
- const generateAndSaveGrid = async (validatedParams) => {
49
- const { images, ignoredFiles } = await loadImages(validatedParams);
50
-
51
- // Display warnings if needed
52
- if (ignoredFiles.length) {
53
- displayWarningMessage('\nThese files will be ignored due to unsupported formats:');
54
- for (const file of ignoredFiles) {
55
- displayInfoMessage(file);
56
- }
57
-
58
- const confirmation = await cliConfirm('\nAre you sure you want to continue?');
59
- if (!confirmation) return;
60
- }
61
-
62
- const grid = await masonryMerge(images, validatedParams);
63
- const success = await writeImage(grid, validatedParams.output);
64
-
65
- // Display success message
66
- if (success) {
67
- displaySuccessMessage(`\nImage has been created successfully: ${chalk.bold(validatedParams.output)}\n`);
68
- }
69
- };
70
-
71
- addSharedOptions(masonryCommand);
72
- export default masonryCommand;
@@ -1,94 +0,0 @@
1
- import sharp from 'sharp';
2
- import fs from 'node:fs/promises';
3
- import path from 'node:path';
4
- import { isSupportedInputImage, shuffleTogether } from './utils.js';
5
-
6
- const MAX_RECURSION_DEPTH = 10;
7
-
8
- export const loadImages = async ({ files, dir, recursive, shuffle, count }) => {
9
- let ignoredFiles = [];
10
- let filepaths = files;
11
- let images = [];
12
-
13
- if (files && files.length) {
14
- // Load directly from provided file list
15
- images = await loadFromFiles(files, count);
16
- } else {
17
- // Get all files from directory
18
- const { skippedFiles, paths } = await getFilesFromDirectory(dir, recursive);
19
- filepaths = paths;
20
- ignoredFiles = skippedFiles;
21
- images = await loadFromFiles(filepaths, count);
22
- }
23
-
24
- // Ensure filepaths and images match
25
- if (images.length !== filepaths.length) {
26
- filepaths = filepaths.slice(0, images.length);
27
- }
28
-
29
- // Optional: shuffle filepaths and images together
30
- if (shuffle) {
31
- [filepaths, images] = shuffleTogether(filepaths, images);
32
- }
33
-
34
- return { images, files: filepaths, ignoredFiles };
35
- };
36
-
37
- const loadFromFiles = async (files, count) => {
38
- const images = [];
39
- const total = count || files.length;
40
- for (let i = 0; i < total; i++) {
41
- // End the loop if count is higher than number of available files
42
- if (i >= files.length) break;
43
-
44
- // Load images
45
- const filepath = files[i];
46
- let image;
47
-
48
- if (filepath.endsWith('.svg')) {
49
- const svgBuffer = await fs.readFile(filepath);
50
- image = sharp(svgBuffer);
51
- } else {
52
- image = sharp(filepath);
53
- }
54
-
55
- images.push(image);
56
- }
57
-
58
- return images;
59
- };
60
-
61
- const getFilesFromDirectory = async (dir, recursive, depth = 0) => {
62
- // Use to collect warnings
63
- const skippedFiles = [];
64
-
65
- // Ensure recursiveness ends at the max recursion depth
66
- if (depth >= MAX_RECURSION_DEPTH) return { paths: [], skippedFiles: [] };
67
-
68
- // Get entries
69
- const entries = await fs.readdir(dir, { withFileTypes: true });
70
- const files = [];
71
-
72
- for (const entry of entries) {
73
- const file = path.join(entry.parentPath, entry.name);
74
-
75
- // If the entry is a valid image file, add it to the list
76
- if (entry.isFile() && isSupportedInputImage(entry.name)) {
77
- files.push(file);
78
- }
79
- // If it is an invalid file format, add to skipped files
80
- else if (entry.isFile() && !isSupportedInputImage(entry.name) && entry.name !== '.DS_Store') {
81
- skippedFiles.push(entry.name);
82
- }
83
- // If it's a directory AND the recursive option is true,
84
- // recursively get all the files
85
- else if (recursive && entry.isDirectory() && !entry.isSymbolicLink()) {
86
- const dirpath = path.join(entry.parentPath, entry.name);
87
- const dirObj = await getFilesFromDirectory(dirpath, recursive, depth + 1);
88
- files.push(...dirObj?.paths);
89
- skippedFiles.push(...dirObj.skippedFiles);
90
- }
91
- }
92
-
93
- return { paths: files, skippedFiles };
94
- };
@@ -1,20 +0,0 @@
1
- import { SingleBar } from 'cli-progress';
2
- import chalk from 'chalk';
3
-
4
- const title = chalk.gray('Creating Image:');
5
- const bar = chalk.blue('{bar}');
6
- const percentage = chalk.yellow('{percentage}%');
7
- const eta = chalk.blue('ETA: ') + chalk.yellow('{eta_formatted}');
8
- const stage = chalk.gray('{stage}...');
9
- const divider = chalk.blue('|');
10
-
11
- export const WRITING_TO_FILE_PERCENTAGE = 0.05;
12
-
13
- export const progressBar = new SingleBar({
14
- format: `${title} ${divider}${bar}${divider} ${percentage} ${divider} ${eta} ${divider} ${stage} `,
15
- barCompleteChar: '\u2588',
16
- barIncompleteChar: '\u2591',
17
- stopOnComplete: true,
18
- barsize: 40,
19
- etaBuffer: 50,
20
- });