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.
Files changed (212) hide show
  1. package/README.md +362 -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 +2 -0
  47. package/dist/core/index.js +2 -0
  48. package/dist/core/jobs/batchRunner.d.ts +44 -0
  49. package/dist/core/jobs/batchRunner.js +90 -0
  50. package/dist/core/jobs/types.d.ts +10 -0
  51. package/dist/core/jobs/types.js +1 -0
  52. package/dist/core/mergeError.d.ts +9 -0
  53. package/dist/core/mergeError.js +10 -0
  54. package/dist/core/merges/collage/index.d.ts +9 -0
  55. package/dist/core/merges/collage/index.js +32 -0
  56. package/dist/core/merges/collage/steps/calculateImageDimensions.d.ts +12 -0
  57. package/dist/core/merges/collage/steps/calculateImageDimensions.js +18 -0
  58. package/dist/core/merges/collage/steps/createComposites.d.ts +8 -0
  59. package/dist/core/merges/collage/steps/createComposites.js +58 -0
  60. package/dist/core/merges/collage/steps/resizeAndBorderImages.d.ts +12 -0
  61. package/dist/core/merges/collage/steps/resizeAndBorderImages.js +26 -0
  62. package/dist/core/merges/collage/steps/rotateImages.d.ts +7 -0
  63. package/dist/core/merges/collage/steps/rotateImages.js +9 -0
  64. package/dist/core/merges/grid/index.d.ts +12 -0
  65. package/dist/core/merges/grid/index.js +36 -0
  66. package/dist/core/merges/grid/steps/calculateCanvasDimensions.d.ts +8 -0
  67. package/dist/core/merges/grid/steps/calculateCanvasDimensions.js +18 -0
  68. package/dist/core/merges/grid/steps/calculateFontSize.d.ts +7 -0
  69. package/dist/core/merges/grid/steps/calculateFontSize.js +19 -0
  70. package/dist/core/merges/grid/steps/calculateImageDimensions.d.ts +8 -0
  71. package/dist/core/merges/grid/steps/calculateImageDimensions.js +18 -0
  72. package/dist/core/merges/grid/steps/createComposites.d.ts +10 -0
  73. package/dist/core/merges/grid/steps/createComposites.js +63 -0
  74. package/dist/core/merges/grid/steps/prepareImages.d.ts +10 -0
  75. package/dist/core/merges/grid/steps/prepareImages.js +29 -0
  76. package/dist/core/merges/grid/steps/shuffleImagesAndCaptions.d.ts +7 -0
  77. package/dist/core/merges/grid/steps/shuffleImagesAndCaptions.js +17 -0
  78. package/dist/core/merges/index.d.ts +5 -0
  79. package/dist/core/merges/index.js +4 -0
  80. package/dist/core/merges/masonry/index.d.ts +10 -0
  81. package/dist/core/merges/masonry/index.js +32 -0
  82. package/dist/core/merges/masonry/steps/calculateCanvasDimensions.d.ts +15 -0
  83. package/dist/core/merges/masonry/steps/calculateCanvasDimensions.js +17 -0
  84. package/dist/core/merges/masonry/steps/calculateLaneSize.d.ts +9 -0
  85. package/dist/core/merges/masonry/steps/calculateLaneSize.js +27 -0
  86. package/dist/core/merges/masonry/steps/createComposites.d.ts +25 -0
  87. package/dist/core/merges/masonry/steps/createComposites.js +108 -0
  88. package/dist/core/merges/masonry/steps/resizeImages.d.ts +7 -0
  89. package/dist/core/merges/masonry/steps/resizeImages.js +14 -0
  90. package/dist/core/merges/masonry/steps/splitIntoLanes.d.ts +17 -0
  91. package/dist/core/merges/masonry/steps/splitIntoLanes.js +78 -0
  92. package/dist/core/merges/shared-steps/applyComposites.d.ts +2 -0
  93. package/dist/core/merges/shared-steps/applyComposites.js +16 -0
  94. package/dist/core/merges/shared-steps/createCanvas.d.ts +11 -0
  95. package/dist/core/merges/shared-steps/createCanvas.js +17 -0
  96. package/dist/core/merges/shared-steps/exportCanvas.d.ts +7 -0
  97. package/dist/core/merges/shared-steps/exportCanvas.js +25 -0
  98. package/dist/core/merges/shared-steps/finalizeImagePipelines.d.ts +2 -0
  99. package/dist/core/merges/shared-steps/finalizeImagePipelines.js +9 -0
  100. package/dist/core/merges/shared-steps/loadImages.d.ts +2 -0
  101. package/dist/core/merges/shared-steps/loadImages.js +26 -0
  102. package/dist/core/merges/shared-steps/validateCaptions.d.ts +10 -0
  103. package/dist/core/merges/shared-steps/validateCaptions.js +17 -0
  104. package/dist/core/merges/template/index.d.ts +10 -0
  105. package/dist/core/merges/template/index.js +28 -0
  106. package/dist/core/merges/template/steps/calculateSlotDimensions.d.ts +9 -0
  107. package/dist/core/merges/template/steps/calculateSlotDimensions.js +12 -0
  108. package/dist/core/merges/template/steps/createComposites.d.ts +10 -0
  109. package/dist/core/merges/template/steps/createComposites.js +25 -0
  110. package/dist/core/merges/template/steps/getBlocks.d.ts +13 -0
  111. package/dist/core/merges/template/steps/getBlocks.js +28 -0
  112. package/dist/core/merges/template/types.d.ts +21 -0
  113. package/dist/core/merges/template/types.js +1 -0
  114. package/dist/core/merges/types.d.ts +123 -0
  115. package/dist/core/merges/types.js +1 -0
  116. package/dist/core/modules/messages.d.ts +32 -0
  117. package/dist/core/modules/messages.js +54 -0
  118. package/dist/core/modules/typedEventEmitter.d.ts +7 -0
  119. package/dist/core/modules/typedEventEmitter.js +9 -0
  120. package/dist/core/pipeline/guards.d.ts +4 -0
  121. package/dist/core/pipeline/guards.js +23 -0
  122. package/dist/core/pipeline/mergePipeline.d.ts +60 -0
  123. package/dist/core/pipeline/mergePipeline.js +122 -0
  124. package/dist/core/schemas/collage.d.ts +26 -0
  125. package/dist/core/schemas/collage.js +17 -0
  126. package/dist/core/schemas/grid.d.ts +32 -0
  127. package/dist/core/schemas/grid.js +29 -0
  128. package/dist/core/schemas/masonry.d.ts +56 -0
  129. package/dist/core/schemas/masonry.js +43 -0
  130. package/dist/core/schemas/mergeJob.d.ts +11 -0
  131. package/dist/core/schemas/mergeJob.js +6 -0
  132. package/dist/core/schemas/template.d.ts +34 -0
  133. package/dist/core/schemas/template.js +88 -0
  134. package/dist/core/utils/colors/hexToRgba.d.ts +2 -0
  135. package/dist/core/utils/colors/hexToRgba.js +25 -0
  136. package/dist/core/utils/colors/rgbaToHex.d.ts +2 -0
  137. package/dist/core/utils/colors/rgbaToHex.js +15 -0
  138. package/dist/core/utils/colors/types.d.ts +7 -0
  139. package/dist/core/utils/colors/types.js +1 -0
  140. package/dist/core/utils/fonts/getFontSize.d.ts +9 -0
  141. package/dist/core/utils/fonts/getFontSize.js +40 -0
  142. package/dist/core/utils/images/addImageBorder.d.ts +13 -0
  143. package/dist/core/utils/images/addImageBorder.js +33 -0
  144. package/dist/core/utils/images/getImageHeights.d.ts +2 -0
  145. package/dist/core/utils/images/getImageHeights.js +9 -0
  146. package/dist/core/utils/images/getImageWidths.d.ts +2 -0
  147. package/dist/core/utils/images/getImageWidths.js +9 -0
  148. package/dist/core/utils/images/getSmallestImageDimensions.d.ts +5 -0
  149. package/dist/core/utils/images/getSmallestImageDimensions.js +9 -0
  150. package/dist/core/utils/images/handleImageEdges.d.ts +13 -0
  151. package/dist/core/utils/images/handleImageEdges.js +39 -0
  152. package/dist/core/utils/images/isActualImage.d.ts +5 -0
  153. package/dist/core/utils/images/isActualImage.js +26 -0
  154. package/dist/core/utils/images/parseAspectRatio.d.ts +1 -0
  155. package/dist/core/utils/images/parseAspectRatio.js +22 -0
  156. package/dist/core/utils/images/roundImage.d.ts +9 -0
  157. package/dist/core/utils/images/roundImage.js +27 -0
  158. package/dist/core/utils/images/roundImages.d.ts +8 -0
  159. package/dist/core/utils/images/roundImages.js +19 -0
  160. package/dist/core/utils/images/scaleImage.d.ts +8 -0
  161. package/dist/core/utils/images/scaleImage.js +36 -0
  162. package/dist/core/utils/images/scaleImages.d.ts +8 -0
  163. package/dist/core/utils/images/scaleImages.js +38 -0
  164. package/dist/core/utils/math/median.d.ts +1 -0
  165. package/dist/core/utils/math/median.js +12 -0
  166. package/dist/core/utils/math/randint.d.ts +6 -0
  167. package/dist/core/utils/math/randint.js +11 -0
  168. package/dist/core/utils/math/trimmedMedian.d.ts +1 -0
  169. package/dist/core/utils/math/trimmedMedian.js +12 -0
  170. package/dist/core/utils/svg/createSvgTextBuffer.d.ts +9 -0
  171. package/dist/core/utils/svg/createSvgTextBuffer.js +21 -0
  172. package/dist/validators/aspectRatio.d.ts +2 -0
  173. package/dist/validators/aspectRatio.js +15 -0
  174. package/dist/validators/coercion.d.ts +2 -0
  175. package/dist/validators/coercion.js +12 -0
  176. package/dist/validators/format.d.ts +2 -0
  177. package/dist/validators/format.js +15 -0
  178. package/dist/validators/hexColor.d.ts +7 -0
  179. package/dist/validators/hexColor.js +27 -0
  180. package/dist/validators/index.d.ts +95 -0
  181. package/dist/validators/index.js +64 -0
  182. package/dist/validators/outputFile.d.ts +2 -0
  183. package/dist/validators/outputFile.js +7 -0
  184. package/dist/validators/path.d.ts +3 -0
  185. package/dist/validators/path.js +18 -0
  186. package/dist/validators/sharpImageInput.d.ts +3 -0
  187. package/dist/validators/sharpImageInput.js +6 -0
  188. package/dist/validators/template.d.ts +15 -0
  189. package/dist/validators/template.js +41 -0
  190. package/package.json +26 -9
  191. package/bin/pixeli.js +0 -26
  192. package/commands/merge/collage.js +0 -83
  193. package/commands/merge/grid.js +0 -71
  194. package/commands/merge/helpers/utils.js +0 -11
  195. package/commands/merge/helpers/validations.js +0 -269
  196. package/commands/merge/index.js +0 -12
  197. package/commands/merge/masonry.js +0 -72
  198. package/lib/helpers/loadImages.js +0 -94
  199. package/lib/helpers/progressBar.js +0 -20
  200. package/lib/helpers/templateValidator.js +0 -139
  201. package/lib/helpers/utils.js +0 -208
  202. package/lib/merges/collage-merge/index.js +0 -110
  203. package/lib/merges/collage-merge/presets/artGallery.js +0 -17
  204. package/lib/merges/collage-merge/presets/dashboardShot.js +0 -18
  205. package/lib/merges/collage-merge/presets/horizontalBookSpread.js +0 -15
  206. package/lib/merges/collage-merge/presets/instagramGrid.js +0 -18
  207. package/lib/merges/collage-merge/presets/verticalBookSpread.js +0 -15
  208. package/lib/merges/grid-merge/index.js +0 -152
  209. package/lib/merges/masonry-merge/horizontal.js +0 -157
  210. package/lib/merges/masonry-merge/index.js +0 -57
  211. package/lib/merges/masonry-merge/vertical.js +0 -152
  212. package/lib/merges/merge-utils.js +0 -176
@@ -0,0 +1,9 @@
1
+ import sharp from 'sharp';
2
+ export const getImageWidths = async (images) => {
3
+ const widths = [];
4
+ for (const image of images) {
5
+ const meta = await image.metadata();
6
+ widths.push(meta.width);
7
+ }
8
+ return widths;
9
+ };
@@ -0,0 +1,5 @@
1
+ import sharp from 'sharp';
2
+ export declare const getSmallestImageDimensions: (images: sharp.Sharp[]) => Promise<{
3
+ smallestWidth: number;
4
+ smallestHeight: number;
5
+ }>;
@@ -0,0 +1,9 @@
1
+ import sharp from 'sharp';
2
+ // Width and height do not necessarily have to be from the same image
3
+ export const getSmallestImageDimensions = async (images) => {
4
+ const metas = await Promise.all(images.map((img) => img.metadata()));
5
+ return metas.reduce((acc, meta) => ({
6
+ smallestWidth: Math.min(acc.smallestWidth, meta.width),
7
+ smallestHeight: Math.min(acc.smallestHeight, meta.height),
8
+ }), { smallestWidth: Infinity, smallestHeight: Infinity });
9
+ };
@@ -0,0 +1,13 @@
1
+ import sharp from 'sharp';
2
+ import type { RGBA } from '../colors/types.js';
3
+ interface Options {
4
+ borderWidth: number;
5
+ borderHeight: number;
6
+ borderColor: RGBA;
7
+ cornerRadius: number;
8
+ imageWidth: number;
9
+ imageHeight: number;
10
+ finalizePipeline?: boolean;
11
+ }
12
+ export declare const handleImageEdges: (image: sharp.Sharp, { borderWidth, borderHeight, borderColor, imageWidth, imageHeight, cornerRadius, finalizePipeline }: Options) => Promise<sharp.Sharp>;
13
+ export {};
@@ -0,0 +1,39 @@
1
+ import sharp from 'sharp';
2
+ import { rgbaToHex } from '../colors/rgbaToHex.js';
3
+ export const handleImageEdges = async (image, { borderWidth, borderHeight, borderColor, imageWidth, imageHeight, cornerRadius = 0, finalizePipeline = false }) => {
4
+ // Only change the image's edges if needed
5
+ if (borderWidth <= 0 && borderHeight <= 0 && cornerRadius <= 0)
6
+ return image;
7
+ const maxBorderX = imageWidth / 2;
8
+ const maxBorderY = imageHeight / 2;
9
+ const effectiveBorderWidth = Math.min(borderWidth, maxBorderX);
10
+ const effectiveBorderHeight = Math.min(borderHeight, maxBorderY);
11
+ // Step 1 — Round corners mask
12
+ const mask = `
13
+ <svg width="${imageWidth}" height="${imageHeight}">
14
+ <rect x="0" y="0" width="${imageWidth}" height="${imageHeight}" rx="${cornerRadius}" ry="${cornerRadius}" fill="white"/>
15
+ </svg>
16
+ `;
17
+ // Step 2 — Optional border layer (draw inside the image)
18
+ const border = `
19
+ <svg width="${imageWidth}" height="${imageHeight}">
20
+ <rect x="${effectiveBorderWidth / 2}" y="${effectiveBorderHeight / 2}" width="${imageWidth - effectiveBorderWidth}" height="${imageHeight - effectiveBorderHeight}" rx="${cornerRadius}" ry="${cornerRadius}"
21
+ fill="none" stroke="${rgbaToHex(borderColor)}" stroke-width="${Math.max(effectiveBorderWidth, effectiveBorderHeight)}"/>
22
+ </svg>
23
+ `;
24
+ // Only pick needed composites
25
+ const composites = [];
26
+ if (cornerRadius > 0) {
27
+ composites.push({ input: Buffer.from(mask), blend: 'dest-in', top: 0, left: 0 });
28
+ }
29
+ if (effectiveBorderWidth > 0 || effectiveBorderHeight > 0) {
30
+ composites.push({ input: Buffer.from(border), blend: 'over', top: 0, left: 0 });
31
+ }
32
+ // Step 3 — Composite mask and border
33
+ const processed = composites.length > 0 ? image.toFormat('png').composite(composites) : image;
34
+ // Finalize image if needed
35
+ if (finalizePipeline) {
36
+ return sharp(await processed.toBuffer());
37
+ }
38
+ return processed;
39
+ };
@@ -0,0 +1,5 @@
1
+ import sharp from 'sharp';
2
+ export declare const isActualImage: (input: sharp.SharpInput) => Promise<{
3
+ isImage: boolean;
4
+ reason: string;
5
+ }>;
@@ -0,0 +1,26 @@
1
+ import sharp from 'sharp';
2
+ export const isActualImage = async (input) => {
3
+ try {
4
+ // Try to get metadata
5
+ const metadata = await sharp(input).metadata();
6
+ // Try to get width and height
7
+ /* v8 ignore start */
8
+ if (!metadata.width || !metadata.height) {
9
+ return {
10
+ isImage: false,
11
+ reason: 'Image metadata missing width or height',
12
+ };
13
+ }
14
+ /* v8 ignore stop */
15
+ }
16
+ catch (err) {
17
+ return {
18
+ isImage: false,
19
+ reason: err.message,
20
+ };
21
+ }
22
+ return {
23
+ isImage: true,
24
+ reason: '',
25
+ };
26
+ };
@@ -0,0 +1 @@
1
+ export declare const parseAspectRatio: (aspectRatio: string) => number | false;
@@ -0,0 +1,22 @@
1
+ export const parseAspectRatio = (aspectRatio) => {
2
+ // return ratio straight away if its just a number
3
+ const ratio = Number(aspectRatio);
4
+ if (ratio) {
5
+ return ratio;
6
+ }
7
+ const ratioRegex = /^\s*(\d+)\s*(\/|:|x)\s*(\d+)\s*$/i;
8
+ const match = aspectRatio.match(ratioRegex);
9
+ // Ensure match exists
10
+ if (!match)
11
+ return false;
12
+ // Ensures absolute type safety
13
+ const [, wStr, , hStr] = match;
14
+ if (!wStr || !hStr)
15
+ return false;
16
+ // Return aspect ratio
17
+ const width = parseInt(wStr, 10);
18
+ const height = parseInt(hStr, 10);
19
+ if (width === 0 || height === 0)
20
+ return false;
21
+ return width / height;
22
+ };
@@ -0,0 +1,9 @@
1
+ import sharp from 'sharp';
2
+ interface RoundImageOptions {
3
+ width: number;
4
+ height: number;
5
+ cornerRadius: number;
6
+ finalizePipeline?: boolean;
7
+ }
8
+ export declare const roundImage: (image: sharp.Sharp, { width, height, cornerRadius, finalizePipeline }: RoundImageOptions) => Promise<sharp.Sharp>;
9
+ export {};
@@ -0,0 +1,27 @@
1
+ import sharp from 'sharp';
2
+ export const roundImage = async (image, { width, height, cornerRadius, finalizePipeline = false }) => {
3
+ // Skip if the cornerRadius = zero
4
+ if (!cornerRadius)
5
+ return image;
6
+ // Create rounded svg mask
7
+ const mask = Buffer.from(`
8
+ <svg width="${width}" height="${height}">
9
+ <rect x="0" y="0" width="${width}" height="${height}" rx="${cornerRadius}" ry="${cornerRadius}" />
10
+ </svg>
11
+ `);
12
+ // Apply mask
13
+ const roundedImage = image
14
+ .composite([
15
+ {
16
+ input: mask,
17
+ blend: 'dest-in',
18
+ },
19
+ ])
20
+ .toFormat('png');
21
+ // Finalize pipeline if needed
22
+ if (finalizePipeline) {
23
+ const buffer = await roundedImage.toBuffer();
24
+ return sharp(buffer);
25
+ }
26
+ return roundedImage;
27
+ };
@@ -0,0 +1,8 @@
1
+ import sharp from 'sharp';
2
+ interface RoundImagesOptions {
3
+ width: number;
4
+ height: number;
5
+ cornerRadius: number;
6
+ }
7
+ export declare const roundImages: (images: sharp.Sharp[], { width, height, cornerRadius }: RoundImagesOptions) => Promise<sharp.Sharp[]>;
8
+ export {};
@@ -0,0 +1,19 @@
1
+ import sharp from 'sharp';
2
+ export const roundImages = async (images, { width, height, cornerRadius }) => {
3
+ // Skip if the cornerRadius = zero
4
+ if (!cornerRadius)
5
+ return images;
6
+ // Round images respectively
7
+ return await Promise.all(images.map(async (image) => {
8
+ const mask = Buffer.from(`
9
+ <svg width="${width}" height="${height}">
10
+ <rect x="0" y="0" width="${width}" height="${height}" rx="${cornerRadius}" ry="${cornerRadius}" />
11
+ </svg>
12
+ `);
13
+ const buff = await image
14
+ .composite([{ input: mask, blend: 'dest-in' }])
15
+ .png()
16
+ .toBuffer();
17
+ return sharp(buff);
18
+ }));
19
+ };
@@ -0,0 +1,8 @@
1
+ import sharp from 'sharp';
2
+ interface ScaleImageOptions {
3
+ width?: number;
4
+ height?: number;
5
+ finalizePipeline?: boolean;
6
+ }
7
+ export declare const scaleImage: (image: sharp.Sharp, { width, height, finalizePipeline }: ScaleImageOptions) => Promise<sharp.Sharp>;
8
+ export {};
@@ -0,0 +1,36 @@
1
+ import sharp from 'sharp';
2
+ export const scaleImage = async (image, { width, height, finalizePipeline = false }) => {
3
+ // Ensure either width or height is provided
4
+ if (width == undefined && height === undefined) {
5
+ throw new Error('You must provide either width or height.');
6
+ }
7
+ let targetWidth, targetHeight;
8
+ const meta = await image.metadata();
9
+ // Set target width and height using size factor
10
+ if (width !== undefined && height !== undefined) {
11
+ targetWidth = width;
12
+ targetHeight = height;
13
+ }
14
+ else if (width !== undefined) {
15
+ const f = width / meta.width;
16
+ targetWidth = width;
17
+ targetHeight = Math.floor(meta.height * f);
18
+ }
19
+ else {
20
+ const f = height / meta.height;
21
+ targetHeight = height;
22
+ targetWidth = Math.floor(meta.width * f);
23
+ }
24
+ // Resize the image
25
+ const resizedImage = image.resize(targetWidth, targetHeight);
26
+ // Only finalize changes in the image pipeline if needed
27
+ if (finalizePipeline) {
28
+ // Use jpg format if possible for less memory usage
29
+ const formatPipe = meta.channels === 4 ? resizedImage.toFormat('png') : resizedImage.toFormat('jpg');
30
+ const buffer = await formatPipe.toBuffer();
31
+ return sharp(buffer);
32
+ }
33
+ else {
34
+ return resizedImage;
35
+ }
36
+ };
@@ -0,0 +1,8 @@
1
+ import sharp from 'sharp';
2
+ interface ScaleImagesOptions {
3
+ width?: number;
4
+ height?: number;
5
+ finalizePipeline?: boolean;
6
+ }
7
+ export declare const scaleImages: (images: sharp.Sharp[], { width, height, finalizePipeline }?: ScaleImagesOptions) => Promise<sharp.Sharp[]>;
8
+ export {};
@@ -0,0 +1,38 @@
1
+ import sharp from 'sharp';
2
+ export const scaleImages = async (images, { width, height, finalizePipeline = false } = {}) => {
3
+ // Ensure either width or height is provided
4
+ if (width == undefined && height === undefined) {
5
+ throw new Error('You must provide either width or height.');
6
+ }
7
+ // Return scaled images
8
+ const scaledImages = await Promise.all(images.map(async (image) => {
9
+ const meta = await image.metadata();
10
+ let targetWidth, targetHeight;
11
+ if (width !== undefined && height !== undefined) {
12
+ targetWidth = width;
13
+ targetHeight = height;
14
+ }
15
+ else if (width !== undefined) {
16
+ const f = width / meta.width;
17
+ targetWidth = width;
18
+ targetHeight = Math.floor(meta.height * f);
19
+ }
20
+ else {
21
+ const f = height / meta.height;
22
+ targetHeight = height;
23
+ targetWidth = Math.floor(meta.width * f);
24
+ }
25
+ const newImage = image.resize(targetWidth, targetHeight);
26
+ // Only finalize changes in the image pipeline if needed
27
+ if (finalizePipeline) {
28
+ // Use jpg format if possible for less memory usage
29
+ const formatPipe = meta.channels === 4 ? newImage.toFormat('png') : newImage.toFormat('jpg');
30
+ const buffer = await formatPipe.toBuffer();
31
+ return sharp(buffer);
32
+ }
33
+ else {
34
+ return newImage;
35
+ }
36
+ }));
37
+ return scaledImages;
38
+ };
@@ -0,0 +1 @@
1
+ export declare function median(values: readonly number[]): number | null;
@@ -0,0 +1,12 @@
1
+ export function median(values) {
2
+ if (values.length === 0)
3
+ return null;
4
+ const sorted = [...values].sort((a, b) => a - b);
5
+ const mid = Math.floor(sorted.length / 2);
6
+ if (sorted.length % 2 === 1) {
7
+ return sorted[mid] ?? null;
8
+ }
9
+ const a = sorted[mid - 1];
10
+ const b = sorted[mid];
11
+ return a !== undefined && b !== undefined ? (a + b) / 2 : null;
12
+ }
@@ -0,0 +1,6 @@
1
+ interface Randint {
2
+ (low: number, high: number): number;
3
+ (value: number): number;
4
+ }
5
+ export declare const randint: Randint;
6
+ export {};
@@ -0,0 +1,11 @@
1
+ export const randint = (lowOrValue, high) => {
2
+ if (high === undefined) {
3
+ return Math.round(Math.random() * lowOrValue);
4
+ }
5
+ let low = lowOrValue;
6
+ if (high > low) {
7
+ [low, high] = [high, low];
8
+ }
9
+ const range = high - low;
10
+ return Math.round(low + Math.random() * range);
11
+ };
@@ -0,0 +1 @@
1
+ export declare const trimmedMedian: (values: number[], trimRatio?: number) => number | null;
@@ -0,0 +1,12 @@
1
+ import { median } from './median.js';
2
+ export const trimmedMedian = (values, trimRatio = 0.1) => {
3
+ if (values.length === 0)
4
+ return null;
5
+ const sorted = [...values].sort((a, b) => a - b);
6
+ const trim = Math.floor(sorted.length * trimRatio);
7
+ // Prevent trimming everything
8
+ if (trim * 2 >= sorted.length) {
9
+ return median(sorted);
10
+ }
11
+ return median(sorted.slice(trim, sorted.length - trim));
12
+ };
@@ -0,0 +1,9 @@
1
+ interface CreateSvgTextBufferOptions {
2
+ text: string;
3
+ maxWidth: number;
4
+ maxHeight: number;
5
+ fontSize: number;
6
+ fill?: string;
7
+ }
8
+ export declare const createSvgTextBuffer: ({ text, maxWidth, maxHeight, fontSize, fill }: CreateSvgTextBufferOptions) => Buffer<ArrayBuffer>;
9
+ export {};
@@ -0,0 +1,21 @@
1
+ import { escapeXML } from '../../helpers.js';
2
+ export const createSvgTextBuffer = ({ text, maxWidth, maxHeight, fontSize, fill = '#000000' }) => {
3
+ // Width and viewport are assigned to this svg
4
+ const svg = `
5
+ <svg xmlns="http://www.w3.org/2000/svg"
6
+ width="${maxWidth}" height="${maxHeight}"
7
+ viewBox="0 0 ${maxWidth} ${maxHeight}">
8
+ <text
9
+ x="${maxWidth / 2}"
10
+ y="${maxHeight / 2}"
11
+ font-size="${fontSize}"
12
+ font-family="sans-serif"
13
+ fill="${fill}"
14
+ text-anchor="middle"
15
+ dominant-baseline="middle">
16
+ ${escapeXML(text)}
17
+ </text>
18
+ </svg>
19
+ `;
20
+ return Buffer.from(svg);
21
+ };
@@ -0,0 +1,2 @@
1
+ import z from 'zod';
2
+ export declare const aspectRatioValidator: z.ZodPipe<z.z.ZodCoercedString<unknown>, z.ZodTransform<number, string>>;
@@ -0,0 +1,15 @@
1
+ import z from 'zod';
2
+ import { parseAspectRatio } from '../core/utils/images/parseAspectRatio.js';
3
+ export const aspectRatioValidator = z.coerce.string().transform((ratio, ctx) => {
4
+ const result = parseAspectRatio(ratio);
5
+ // Validate aspect ratio
6
+ if (!result) {
7
+ ctx.addIssue({
8
+ code: 'custom',
9
+ message: 'Invalid aspect ratio: Examples of valid ratios include 16/9, 2:3, 1x2, 1.77.',
10
+ input: ratio,
11
+ });
12
+ return z.NEVER;
13
+ }
14
+ return result;
15
+ });
@@ -0,0 +1,2 @@
1
+ import z from 'zod';
2
+ export declare const useNumberCoercion: (schema: z.ZodNumber) => z.ZodPipe<z.ZodTransform<unknown, unknown>, z.ZodNumber>;
@@ -0,0 +1,12 @@
1
+ import z from 'zod';
2
+ export const useNumberCoercion = (schema) => {
3
+ return z.preprocess((val) => {
4
+ if (typeof val === 'number')
5
+ return val;
6
+ if (typeof val === 'string' && val.trim() !== '') {
7
+ const n = Number(val);
8
+ return Number.isNaN(n) ? val : n;
9
+ }
10
+ return val;
11
+ }, schema);
12
+ };
@@ -0,0 +1,2 @@
1
+ import z from 'zod';
2
+ export declare const formatValidator: z.ZodPipe<z.ZodString, z.ZodTransform<"webp" | "gif" | "jpeg" | "jpg" | "png" | "tiff" | "avif", string>>;
@@ -0,0 +1,15 @@
1
+ import z from 'zod';
2
+ import { isSupportedOutputImage, SUPPORTED_OUTPUT_FORMATS } from '../core/helpers.js';
3
+ export const formatValidator = z.string().transform((extname, ctx) => {
4
+ if (isSupportedOutputImage(extname)) {
5
+ return extname;
6
+ }
7
+ else {
8
+ ctx.addIssue({
9
+ code: 'custom',
10
+ path: ['format'],
11
+ message: `Invalid format type. Valid output formats include: ${SUPPORTED_OUTPUT_FORMATS.join(', ')}`,
12
+ });
13
+ return z.NEVER;
14
+ }
15
+ });
@@ -0,0 +1,7 @@
1
+ import z from 'zod';
2
+ export declare const hexColorValidator: z.ZodUnion<readonly [z.ZodPipe<z.ZodString, z.ZodTransform<import("../core/utils/colors/types.js").RGBA, string>>, z.ZodObject<{
3
+ r: z.ZodNumber;
4
+ g: z.ZodNumber;
5
+ b: z.ZodNumber;
6
+ alpha: z.ZodNumber;
7
+ }, z.z.core.$strip>]>;
@@ -0,0 +1,27 @@
1
+ import z from 'zod';
2
+ import { isValidHexColor } from '../core/helpers.js';
3
+ import { hexToRgba } from '../core/utils/colors/hexToRgba.js';
4
+ export const hexColorValidator = z.union([
5
+ z.string().transform((color, ctx) => {
6
+ // Handle transparency
7
+ if (color === 'transparent') {
8
+ return hexToRgba('#00000000');
9
+ }
10
+ // Handle hex values
11
+ if (!isValidHexColor(color)) {
12
+ ctx.addIssue({
13
+ code: 'custom',
14
+ message: "Invalid color: must be 'transparent', #rgb, #rrggbb, #rrggbbaa, or object of {r, g, b, a}.",
15
+ input: color,
16
+ });
17
+ return z.NEVER;
18
+ }
19
+ return hexToRgba(color);
20
+ }),
21
+ z.object({
22
+ r: z.number().int().min(0).max(255),
23
+ g: z.number().int().min(0).max(255),
24
+ b: z.number().int().min(0).max(255),
25
+ alpha: z.number().min(0).max(1),
26
+ }, "Invalid color: must be 'transparent', #rgb, #rrggbb, #rrggbbaa, or object of {r, g, b, a}."),
27
+ ], "Invalid color: must be 'transparent', #rgb, #rrggbb, #rrggbbaa, or object of {r, g, b, a}.");
@@ -0,0 +1,95 @@
1
+ import z from 'zod';
2
+ export declare const VALIDATORS: {
3
+ files: z.ZodArray<z.ZodString>;
4
+ dir: z.ZodString;
5
+ output: z.ZodString;
6
+ format: z.ZodPipe<z.ZodString, z.ZodTransform<"webp" | "gif" | "jpeg" | "jpg" | "png" | "tiff" | "avif", string>>;
7
+ cliTemplate: z.ZodString;
8
+ imageInputs: z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodCustom<Buffer<ArrayBufferLike>, Buffer<ArrayBufferLike>>, z.ZodCustom<Uint8Array<ArrayBuffer>, Uint8Array<ArrayBuffer>>]>>;
9
+ captions: z.ZodArray<z.ZodString>;
10
+ caption: z.ZodBoolean;
11
+ recursive: z.ZodBoolean;
12
+ shuffle: z.ZodBoolean;
13
+ canvasColor: z.ZodUnion<readonly [z.ZodPipe<z.ZodString, z.ZodTransform<import("../core/utils/colors/types.js").RGBA, string>>, z.ZodObject<{
14
+ r: z.ZodNumber;
15
+ g: z.ZodNumber;
16
+ b: z.ZodNumber;
17
+ alpha: z.ZodNumber;
18
+ }, z.z.core.$strip>]>;
19
+ captionColor: z.ZodUnion<readonly [z.ZodPipe<z.ZodString, z.ZodTransform<import("../core/utils/colors/types.js").RGBA, string>>, z.ZodObject<{
20
+ r: z.ZodNumber;
21
+ g: z.ZodNumber;
22
+ b: z.ZodNumber;
23
+ alpha: z.ZodNumber;
24
+ }, z.z.core.$strip>]>;
25
+ borderColor: z.ZodUnion<readonly [z.ZodPipe<z.ZodString, z.ZodTransform<import("../core/utils/colors/types.js").RGBA, string>>, z.ZodObject<{
26
+ r: z.ZodNumber;
27
+ g: z.ZodNumber;
28
+ b: z.ZodNumber;
29
+ alpha: z.ZodNumber;
30
+ }, z.z.core.$strip>]>;
31
+ cornerRadius: z.ZodNumber;
32
+ gap: z.ZodNumber;
33
+ imageWidth: z.ZodNumber;
34
+ columns: z.ZodNumber;
35
+ maxCaptionSize: z.ZodNumber;
36
+ rowHeight: z.ZodNumber;
37
+ columnWidth: z.ZodNumber;
38
+ canvasWidth: z.ZodNumber;
39
+ canvasHeight: z.ZodNumber;
40
+ overlapPercentage: z.ZodNumber;
41
+ rotationRange: z.ZodNumber;
42
+ imageWidthVariance: z.ZodNumber;
43
+ borderWidth: z.ZodNumber;
44
+ cliCornerRadius: z.ZodPipe<z.ZodTransform<unknown, unknown>, z.ZodNumber>;
45
+ cliGap: z.ZodPipe<z.ZodTransform<unknown, unknown>, z.ZodNumber>;
46
+ cliImageWidth: z.ZodPipe<z.ZodTransform<unknown, unknown>, z.ZodNumber>;
47
+ cliColumns: z.ZodPipe<z.ZodTransform<unknown, unknown>, z.ZodNumber>;
48
+ cliMaxCaptionSize: z.ZodPipe<z.ZodTransform<unknown, unknown>, z.ZodNumber>;
49
+ cliRowHeight: z.ZodPipe<z.ZodTransform<unknown, unknown>, z.ZodNumber>;
50
+ cliColumnWidth: z.ZodPipe<z.ZodTransform<unknown, unknown>, z.ZodNumber>;
51
+ cliCanvasWidth: z.ZodPipe<z.ZodTransform<unknown, unknown>, z.ZodNumber>;
52
+ cliCanvasHeight: z.ZodPipe<z.ZodTransform<unknown, unknown>, z.ZodNumber>;
53
+ cliOverlapPercentage: z.ZodPipe<z.ZodTransform<unknown, unknown>, z.ZodNumber>;
54
+ cliRotationRange: z.ZodPipe<z.ZodTransform<unknown, unknown>, z.ZodNumber>;
55
+ cliImageWidthVariance: z.ZodPipe<z.ZodTransform<unknown, unknown>, z.ZodNumber>;
56
+ cliBorderWidth: z.ZodPipe<z.ZodTransform<unknown, unknown>, z.ZodNumber>;
57
+ flow: z.ZodEnum<{
58
+ horizontal: "horizontal";
59
+ vertical: "vertical";
60
+ }>;
61
+ hAlign: z.ZodEnum<{
62
+ left: "left";
63
+ center: "center";
64
+ right: "right";
65
+ justified: "justified";
66
+ }>;
67
+ vAlign: z.ZodEnum<{
68
+ justified: "justified";
69
+ top: "top";
70
+ middle: "middle";
71
+ bottom: "bottom";
72
+ }>;
73
+ preset: z.ZodEnum<{
74
+ "instagram-grid": "instagram-grid";
75
+ "dashboard-shot": "dashboard-shot";
76
+ "horizontal-book-spread": "horizontal-book-spread";
77
+ "vertical-book-spread": "vertical-book-spread";
78
+ "art-gallery": "art-gallery";
79
+ }>;
80
+ template: z.ZodObject<{
81
+ canvas: z.ZodObject<{
82
+ width: z.ZodNumber;
83
+ height: z.ZodNumber;
84
+ columns: z.ZodNumber;
85
+ rows: z.ZodNumber;
86
+ }, z.z.core.$strip>;
87
+ slots: z.ZodArray<z.ZodObject<{
88
+ col: z.ZodNumber;
89
+ row: z.ZodNumber;
90
+ colSpan: z.ZodNumber;
91
+ rowSpan: z.ZodNumber;
92
+ }, z.z.core.$strip>>;
93
+ }, z.z.core.$strip>;
94
+ aspectRatio: z.ZodPipe<z.z.ZodCoercedString<unknown>, z.ZodTransform<number, string>>;
95
+ };
@@ -0,0 +1,64 @@
1
+ import z from 'zod';
2
+ import { hexColorValidator } from './hexColor.js';
3
+ import { aspectRatioValidator } from './aspectRatio.js';
4
+ import { sharpImageValidation } from './sharpImageInput.js';
5
+ import { dirPathValidator, filePathValidator } from './path.js';
6
+ import { outputFileValidator } from './outputFile.js';
7
+ import { useNumberCoercion } from './coercion.js';
8
+ import { formatValidator } from './format.js';
9
+ import { templateValidator } from './template.js';
10
+ export const VALIDATORS = {
11
+ // Inputs and outputs
12
+ files: z.array(filePathValidator),
13
+ dir: dirPathValidator,
14
+ output: outputFileValidator,
15
+ format: formatValidator,
16
+ cliTemplate: filePathValidator,
17
+ imageInputs: z.array(sharpImageValidation),
18
+ // Strings
19
+ captions: z.array(z.string()),
20
+ // Flags
21
+ caption: z.boolean(),
22
+ recursive: z.boolean(),
23
+ shuffle: z.boolean(),
24
+ // Colors
25
+ canvasColor: hexColorValidator,
26
+ captionColor: hexColorValidator,
27
+ borderColor: hexColorValidator,
28
+ // Numbers
29
+ cornerRadius: z.number().int().gte(0),
30
+ gap: z.number().gte(0).int(),
31
+ imageWidth: z.number().gt(0).int(),
32
+ columns: z.number().gt(0).int(),
33
+ maxCaptionSize: z.number().gt(0).int(),
34
+ rowHeight: z.number().gt(0).int(),
35
+ columnWidth: z.number().gt(0).int(),
36
+ canvasWidth: z.number().gt(0).int(),
37
+ canvasHeight: z.number().gt(0).int(),
38
+ overlapPercentage: z.number().gte(0).lte(100).int(),
39
+ rotationRange: z.number().gte(0).lte(360).int(),
40
+ imageWidthVariance: z.number().gte(0).int(),
41
+ borderWidth: z.number().gte(0).int(),
42
+ // Coerced Numbers
43
+ cliCornerRadius: useNumberCoercion(z.number().int().gte(0)),
44
+ cliGap: useNumberCoercion(z.number().gte(0).int()),
45
+ cliImageWidth: useNumberCoercion(z.number().gt(0).int()),
46
+ cliColumns: useNumberCoercion(z.number().gt(0).int()),
47
+ cliMaxCaptionSize: useNumberCoercion(z.number().gt(0).int()),
48
+ cliRowHeight: useNumberCoercion(z.number().gt(0).int()),
49
+ cliColumnWidth: useNumberCoercion(z.number().gt(0).int()),
50
+ cliCanvasWidth: useNumberCoercion(z.number().gt(0).int()),
51
+ cliCanvasHeight: useNumberCoercion(z.number().gt(0).int()),
52
+ cliOverlapPercentage: useNumberCoercion(z.number().gte(0).lte(100).int()),
53
+ cliRotationRange: useNumberCoercion(z.number().gte(0).lte(360).int()),
54
+ cliImageWidthVariance: useNumberCoercion(z.number().gte(0).int()),
55
+ cliBorderWidth: useNumberCoercion(z.number().gte(0).int()),
56
+ // Enumerations
57
+ flow: z.enum(['horizontal', 'vertical']),
58
+ hAlign: z.enum(['left', 'center', 'right', 'justified']),
59
+ vAlign: z.enum(['top', 'middle', 'bottom', 'justified']),
60
+ preset: z.enum(['instagram-grid', 'dashboard-shot', 'horizontal-book-spread', 'vertical-book-spread', 'art-gallery']),
61
+ // Misc
62
+ template: templateValidator,
63
+ aspectRatio: aspectRatioValidator,
64
+ };