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,25 @@
1
+ import { MESSAGES } from '../../modules/messages.js';
2
+ import { MergeError } from '../../mergeError.js';
3
+ import { requireContextProp } from '../../pipeline/guards.js';
4
+ export const exportCanvas = async (context, options, _onProgress) => {
5
+ // Ensure canvas exists
6
+ requireContextProp(context, 'canvas');
7
+ try {
8
+ return await context.canvas.toFormat(options.format).toBuffer();
9
+ }
10
+ catch (err) {
11
+ // Assuming ALL sharp errors are instances of the Error object
12
+ const sharpError = err;
13
+ // SPECIFIC SHARP ERROR
14
+ // occurs when trying to create a buffer that exceeds the limits of the current image format
15
+ if (sharpError.message.includes('pixel limit') || sharpError.message.includes('Processed image is too large')) {
16
+ const errText = `Error: image to large for "${options.format}" format, try a format that allows larger images`;
17
+ throw new MergeError(errText, { type: 'image' });
18
+ }
19
+ // Other sharp errors
20
+ throw new MergeError(MESSAGES.ERROR.INTERNAL.message, {
21
+ type: 'internal',
22
+ cause: err,
23
+ });
24
+ }
25
+ };
@@ -0,0 +1,2 @@
1
+ import type { MergeStep } from '../../pipeline/mergePipeline.js';
2
+ export declare const finalizeImagePipelines: MergeStep<any, any>;
@@ -0,0 +1,9 @@
1
+ import { requireNonEmptyArray } from '../../pipeline/guards.js';
2
+ import sharp from 'sharp';
3
+ export const finalizeImagePipelines = async (context, _options, _onProgress) => {
4
+ requireNonEmptyArray(context.images);
5
+ for (let i = 0; i < context.images.length; i++) {
6
+ const buffer = await context.images[i].toBuffer();
7
+ context.images[i] = sharp(buffer);
8
+ }
9
+ };
@@ -0,0 +1,2 @@
1
+ import type { MergeStep } from '../../pipeline/mergePipeline.js';
2
+ export declare const loadImages: MergeStep<any, any>;
@@ -0,0 +1,26 @@
1
+ import sharp from 'sharp';
2
+ import { isActualImage } from '../../utils/images/isActualImage.js';
3
+ import { MergeError } from '../../mergeError.js';
4
+ import { requireNonEmptyArray } from '../../pipeline/guards.js';
5
+ export const loadImages = async (context, _options, _onProgress) => {
6
+ // Ensure inputs are provided
7
+ requireNonEmptyArray(context.inputs, 'inputs');
8
+ const images = [];
9
+ for (let i = 0; i < context.inputs.length; i++) {
10
+ // Ensure image is valid
11
+ const input = context.inputs[i];
12
+ const { isImage, reason } = await isActualImage(input);
13
+ if (!isImage) {
14
+ throw new MergeError(`Invalid image input at index ${i}`, {
15
+ type: 'validation',
16
+ cause: reason,
17
+ });
18
+ }
19
+ images.push(sharp(input));
20
+ }
21
+ // Ensure there's at least one image
22
+ if (images.length <= 0) {
23
+ throw new MergeError('No images provided to merge', { type: 'validation' });
24
+ }
25
+ context.images = images;
26
+ };
@@ -0,0 +1,10 @@
1
+ import type { MergeStep } from '../../pipeline/mergePipeline.js';
2
+ interface Options {
3
+ caption: boolean;
4
+ captions?: string[] | undefined;
5
+ }
6
+ interface State {
7
+ areCaptionsProvided: boolean;
8
+ }
9
+ export declare const validateCaptions: MergeStep<Options, State>;
10
+ export {};
@@ -0,0 +1,17 @@
1
+ import { MergeError } from '../../mergeError.js';
2
+ export const validateCaptions = async (context, options, _onProgress) => {
3
+ // Initially set to false
4
+ context.state.areCaptionsProvided = false;
5
+ // Update context
6
+ if (areCaptionsProvided(options.caption, options.captions)) {
7
+ context.state.areCaptionsProvided = true;
8
+ context.captions = options.captions;
9
+ }
10
+ // Ensure caption length is not less than image length
11
+ if (areCaptionsProvided(options.caption, options.captions) && options.captions.length !== context.images.length) {
12
+ throw new MergeError('The same number of captions and images must be provided', { type: 'validation' });
13
+ }
14
+ };
15
+ const areCaptionsProvided = (caption, captions) => {
16
+ return caption;
17
+ };
@@ -0,0 +1,10 @@
1
+ import type { TemplateMerge } from '../types.js';
2
+ import type { Block } from './types.js';
3
+ export interface TemplateState {
4
+ slotWidth: number;
5
+ slotHeight: number;
6
+ canvasWidth: number;
7
+ canvasHeight: number;
8
+ blocks: Block[];
9
+ }
10
+ export declare const templateMerge: TemplateMerge;
@@ -0,0 +1,28 @@
1
+ import { MergePipeline } from '../../pipeline/mergePipeline.js';
2
+ import { templateSchema } from '../../schemas/template.js';
3
+ import { loadImages } from '../shared-steps/loadImages.js';
4
+ import { applyComposites } from '../shared-steps/applyComposites.js';
5
+ import { createCanvas } from '../shared-steps/createCanvas.js';
6
+ import { exportCanvas } from '../shared-steps/exportCanvas.js';
7
+ import { calculateCanvasDimensions } from './steps/calculateSlotDimensions.js';
8
+ import { getBlocks } from './steps/getBlocks.js';
9
+ import { createComposites } from './steps/createComposites.js';
10
+ export const templateMerge = async (imageInputs, options, onProgress) => {
11
+ const context = {
12
+ inputs: imageInputs,
13
+ captions: [],
14
+ composites: [],
15
+ images: [],
16
+ state: {},
17
+ };
18
+ const templateMergePipeline = await MergePipeline.createPipeline(templateSchema, options, context, onProgress);
19
+ templateMergePipeline
20
+ .use(loadImages)
21
+ .use(calculateCanvasDimensions)
22
+ .use(getBlocks)
23
+ .use(createCanvas)
24
+ .use(createComposites)
25
+ .use(applyComposites)
26
+ .use(exportCanvas);
27
+ return await templateMergePipeline.run();
28
+ };
@@ -0,0 +1,9 @@
1
+ import type { MergeStep } from '../../../pipeline/mergePipeline.js';
2
+ import type { Template } from '../types.js';
3
+ import type { TemplateState } from '../index.js';
4
+ interface Options {
5
+ gap: number;
6
+ template: Template;
7
+ }
8
+ export declare const calculateCanvasDimensions: MergeStep<Options, TemplateState>;
9
+ export {};
@@ -0,0 +1,12 @@
1
+ export const calculateCanvasDimensions = async (context, options, _onProgress) => {
2
+ // Calculate each column's width and height
3
+ const workableCanvasWidth = options.template.canvas.width - options.gap * (options.template.canvas.columns + 1);
4
+ const workableCanvasHeight = options.template.canvas.height - options.gap * (options.template.canvas.rows + 1);
5
+ const slotWidth = workableCanvasWidth / options.template.canvas.columns;
6
+ const slotHeight = workableCanvasHeight / options.template.canvas.rows;
7
+ // Assign to state
8
+ context.state.canvasWidth = options.template.canvas.width;
9
+ context.state.canvasHeight = options.template.canvas.height;
10
+ context.state.slotWidth = slotWidth;
11
+ context.state.slotHeight = slotHeight;
12
+ };
@@ -0,0 +1,10 @@
1
+ import type { MergeStep } from '../../../pipeline/mergePipeline.js';
2
+ import type { Template } from '../types.js';
3
+ import type { TemplateState } from '../index.js';
4
+ interface Options {
5
+ template: Template;
6
+ gap: number;
7
+ cornerRadius: number;
8
+ }
9
+ export declare const createComposites: MergeStep<Options, TemplateState>;
10
+ export {};
@@ -0,0 +1,25 @@
1
+ import { requireState } from '../../../pipeline/guards.js';
2
+ export const createComposites = async (context, options, onProgress) => {
3
+ // Require needed states
4
+ requireState(context, 'blocks');
5
+ requireState(context, 'slotWidth');
6
+ requireState(context, 'slotHeight');
7
+ // Collect composites
8
+ const composites = [];
9
+ for (const block of context.state.blocks) {
10
+ const x = (block.col - 1) * context.state.slotWidth + block.col * options.gap;
11
+ const y = (block.row - 1) * context.state.slotHeight + block.row * options.gap;
12
+ composites.push({
13
+ input: block.imageBuffer,
14
+ left: Math.floor(x),
15
+ top: Math.floor(y),
16
+ });
17
+ // Update progress
18
+ if (onProgress) {
19
+ context.progressInfo.phase = 'Merging images';
20
+ context.progressInfo.completed += 1;
21
+ onProgress({ ...context.progressInfo });
22
+ }
23
+ }
24
+ context.composites = composites;
25
+ };
@@ -0,0 +1,13 @@
1
+ import type { MergeStep } from '../../../pipeline/mergePipeline.js';
2
+ import type { Template } from '../types.js';
3
+ import type { TemplateState } from '../index.js';
4
+ import type { RGBA } from '../../../utils/colors/types.js';
5
+ interface Options {
6
+ template: Template;
7
+ gap: number;
8
+ borderWidth: number;
9
+ borderColor: RGBA;
10
+ cornerRadius: number;
11
+ }
12
+ export declare const getBlocks: MergeStep<Options, TemplateState>;
13
+ export {};
@@ -0,0 +1,28 @@
1
+ import { requireNonEmptyArray, requireState } from '../../../pipeline/guards.js';
2
+ import { handleImageEdges } from '../../../utils/images/handleImageEdges.js';
3
+ export const getBlocks = async (context, options, _onProgress) => {
4
+ // Require needed states
5
+ requireState(context, 'slotWidth');
6
+ requireState(context, 'slotHeight');
7
+ requireNonEmptyArray(context.images, 'images');
8
+ const blocks = [];
9
+ for (let i = 0; i < options.template.slots.length && i < context.images.length; i++) {
10
+ const slot = options.template.slots[i];
11
+ const image = context.images[i];
12
+ // Calculate image width and height
13
+ const width = Math.floor(slot.colSpan * context.state.slotWidth + (slot.colSpan - 1) * options.gap);
14
+ const height = Math.floor(slot.rowSpan * context.state.slotHeight + (slot.rowSpan - 1) * options.gap);
15
+ // Resize image
16
+ const resizedImage = image.resize({ width, height });
17
+ const borderedImage = await handleImageEdges(resizedImage, {
18
+ borderWidth: options.borderWidth,
19
+ borderHeight: options.borderWidth,
20
+ borderColor: options.borderColor,
21
+ cornerRadius: options.cornerRadius,
22
+ imageWidth: width,
23
+ imageHeight: height,
24
+ });
25
+ blocks.push({ imageBuffer: await borderedImage.toBuffer(), col: slot.col, row: slot.row });
26
+ }
27
+ context.state.blocks = blocks;
28
+ };
@@ -0,0 +1,21 @@
1
+ export interface Canvas {
2
+ width: number;
3
+ height: number;
4
+ columns: number;
5
+ rows: number;
6
+ }
7
+ export interface Slot {
8
+ col: number;
9
+ row: number;
10
+ colSpan: number;
11
+ rowSpan: number;
12
+ }
13
+ export interface Template {
14
+ canvas: Canvas;
15
+ slots: Slot[];
16
+ }
17
+ export interface Block {
18
+ imageBuffer: Buffer;
19
+ col: number;
20
+ row: number;
21
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,123 @@
1
+ import sharp from 'sharp';
2
+ import type { Color } from '../utils/colors/types.js';
3
+ import type { Template } from './template/types.js';
4
+ export interface ProgressInfo {
5
+ /** How many images have been processed so far */
6
+ completed: number;
7
+ /** The total number of images to be processed */
8
+ total: number;
9
+ /** The current phase of the merge */
10
+ phase: string;
11
+ }
12
+ export type OnProgress = (info: ProgressInfo) => void;
13
+ /**
14
+ * Generic image merge command.
15
+ *
16
+ * @template T - Merge-specific configuration options
17
+ * @param imageInputs - Input images to merge
18
+ * @param options - Merge options
19
+ * @param onProgress - Optional progress callback
20
+ * @returns A Promise resolving to the merged image buffer
21
+ */
22
+ interface MergeCommand<T> {
23
+ (imageInputs: sharp.SharpInput[], options: T, onProgress?: OnProgress): Promise<Buffer>;
24
+ }
25
+ interface BaseMergeOptions {
26
+ /** Whether to randomize image order before merging. */
27
+ shuffle?: boolean;
28
+ /** Rounded corner radius in pixels. */
29
+ cornerRadius?: number;
30
+ /** Gap between images in pixels. */
31
+ gap?: number;
32
+ /** Background canvas color. */
33
+ canvasColor?: Color;
34
+ /** Width of the border around each image. Borders are placed internally in each image. */
35
+ borderWidth?: number;
36
+ /** Color of the border around each image. */
37
+ borderColor?: Color;
38
+ /** Output image format (png, jpeg, webp). */
39
+ format?: string;
40
+ }
41
+ export interface GridMergeOptions extends BaseMergeOptions {
42
+ /** The aspect ratio of each image. Used to calculate image height based on given width.
43
+ *
44
+ * Examples include `16:9`, `3x2` or `1.777`. */
45
+ aspectRatio?: string | number;
46
+ /** Width of each image cell in pixels, uses the median image width if undefined. */
47
+ imageWidth?: number | undefined;
48
+ /** Number of columns in the grid. */
49
+ columns?: number;
50
+ /** Enable captions under images. */
51
+ caption?: boolean;
52
+ /** Captions text (one per image, in order). */
53
+ captions?: string[];
54
+ /** Caption text color. */
55
+ captionColor?: Color;
56
+ /** Maximum caption font size in pixels. */
57
+ maxCaptionSize?: number;
58
+ }
59
+ export interface MasonryMergeOptions extends BaseMergeOptions {
60
+ /** The height of each row, defaults to the trimmed median image height if undefined. Only applied in horizontal flows. */
61
+ rowHeight?: number | undefined;
62
+ /** The width of each column, defaults to the trimmed median image width if undefined. Only applied in vertical flows. */
63
+ columnWidth?: number | undefined;
64
+ /** The width of the entire canvas. Only required in horizontal flows. */
65
+ canvasWidth?: number | undefined;
66
+ /** The width of the entire canvas. Only required in vertical flows. */
67
+ canvasHeight?: number | undefined;
68
+ /** The orientation of the masonry layout. */
69
+ flow: 'horizontal' | 'vertical';
70
+ /** The horizontal alignment of each row. Only applied in horizontal layouts. */
71
+ hAlign?: 'left' | 'center' | 'right' | 'justified';
72
+ /** The vertical alignment of each column. Only applied in vertical layouts. */
73
+ vAlign?: 'top' | 'middle' | 'bottom' | 'justified';
74
+ }
75
+ export interface TemplateMergeOptions extends BaseMergeOptions {
76
+ template: Template;
77
+ }
78
+ export interface CollageMergeOptions extends Omit<BaseMergeOptions, 'gap'> {
79
+ /** Width of each image cell in pixels, uses the median image width if undefined. */
80
+ imageWidth?: number | undefined;
81
+ /** The aspect ratio of each image. Used to calculate image height based on given width.
82
+ *
83
+ * Examples include `16:9`, `3x2` or `1.777`. */
84
+ aspectRatio?: string | number;
85
+ /** Number of columns in the collage grid. */
86
+ columns?: number;
87
+ /** The number of pixels to potentially variate `imageWidth` by. Used to create random-sized images in the collage.
88
+ *
89
+ * For example, a value of `50` results in images being up to `50` pixels wider or narrower than the given `imageWidth`. */
90
+ imageWidthVariance?: number;
91
+ /** The estimated percentage of overlap for every image pair. A higher percentage creates a tighter collage. */
92
+ overlapPercentage?: number;
93
+ /** The maximum and minimum degree to rotate each image.
94
+ *
95
+ * For example, a value of `10` will result in a random degree being picked from `-10` to `+10` degrees. */
96
+ rotationRange?: number;
97
+ }
98
+ export type MergeTypeOptions = {
99
+ /** Use the grid layout engine. */
100
+ type: 'grid';
101
+ /** Options specific to grid merges. */
102
+ options: GridMergeOptions;
103
+ } | {
104
+ /** Use the masonry layout engine. */
105
+ type: 'masonry';
106
+ /** Options specific to masonry merges. */
107
+ options: MasonryMergeOptions;
108
+ } | {
109
+ /** Use the template-based merge engine. */
110
+ type: 'template';
111
+ /** Options specific to template merges. */
112
+ options: TemplateMergeOptions;
113
+ } | {
114
+ /** Use the free-form collage merge engine. */
115
+ type: 'collage';
116
+ /** Options specific to collage merges. */
117
+ options: CollageMergeOptions;
118
+ };
119
+ export type GridMerge = MergeCommand<GridMergeOptions>;
120
+ export type MasonryMerge = MergeCommand<MasonryMergeOptions>;
121
+ export type TemplateMerge = MergeCommand<TemplateMergeOptions>;
122
+ export type CollageMerge = MergeCommand<CollageMergeOptions>;
123
+ export {};
@@ -0,0 +1 @@
1
+ import sharp from 'sharp';
@@ -0,0 +1,32 @@
1
+ interface Message {
2
+ message: string;
3
+ chalk?: (text: string) => string;
4
+ }
5
+ export declare const MESSAGES: {
6
+ WARNINGS: {
7
+ IGNORED_FILES: {
8
+ message: string;
9
+ chalk: import("chalk").ChalkInstance;
10
+ };
11
+ };
12
+ SUCCESS: {
13
+ OUTPUT: {
14
+ message: string;
15
+ chalk: import("chalk").ChalkInstance;
16
+ };
17
+ };
18
+ ERROR: {
19
+ INTERNAL: {
20
+ message: string;
21
+ chalk: import("chalk").ChalkInstance;
22
+ };
23
+ };
24
+ };
25
+ export declare class MessageRenderer {
26
+ #private;
27
+ message: Message;
28
+ constructor(message: Message, ...inputs: (string | number)[]);
29
+ render(): void;
30
+ confirm(defaultAnswer?: boolean): Promise<boolean>;
31
+ }
32
+ export {};
@@ -0,0 +1,54 @@
1
+ import readline from 'node:readline';
2
+ import chalk from 'chalk';
3
+ import { formatString } from '../../cli/utils/stringFormatter.js';
4
+ export const MESSAGES = {
5
+ WARNINGS: {
6
+ IGNORED_FILES: {
7
+ message: '{0}\nThe following files will be ignored. Proceed?',
8
+ chalk: chalk.yellow,
9
+ },
10
+ },
11
+ SUCCESS: {
12
+ OUTPUT: {
13
+ message: 'Image created at {0}.',
14
+ chalk: chalk.blue,
15
+ },
16
+ },
17
+ ERROR: {
18
+ INTERNAL: {
19
+ message: 'An internal error has occurred.',
20
+ chalk: chalk.red,
21
+ },
22
+ },
23
+ };
24
+ export class MessageRenderer {
25
+ message;
26
+ #formattedMessage;
27
+ #message;
28
+ constructor(message, ...inputs) {
29
+ this.message = message;
30
+ this.#message = message;
31
+ const base = formatString(message.message, ...inputs);
32
+ this.#formattedMessage = message.chalk ? message.chalk(base) : base;
33
+ }
34
+ render() {
35
+ console.log(this.#formattedMessage);
36
+ }
37
+ confirm(defaultAnswer = true) {
38
+ const rl = readline.createInterface({
39
+ input: process.stdin,
40
+ output: process.stdout,
41
+ });
42
+ const options = defaultAnswer ? '(Y/n)' : '(y/N)';
43
+ const question = this.#message.chalk
44
+ ? `${this.#formattedMessage} ${this.#message.chalk(options)} `
45
+ : `${this.#formattedMessage} ${options} `;
46
+ return new Promise((resolve) => {
47
+ rl.question(question, (value) => {
48
+ const cleanedValue = value.toLowerCase().trim();
49
+ resolve((!cleanedValue.length && !!defaultAnswer) || cleanedValue === 'y');
50
+ rl.close();
51
+ });
52
+ });
53
+ }
54
+ }
@@ -0,0 +1,7 @@
1
+ import EventEmitter from 'node:events';
2
+ type EventKey<T> = keyof T & string;
3
+ export declare class TypedEventEmitter<TEvents> extends EventEmitter {
4
+ on<K extends EventKey<TEvents>>(event: K, listener: (payload: TEvents[K]) => void): this;
5
+ emit<K extends EventKey<TEvents>>(event: K, payload: TEvents[K]): boolean;
6
+ }
7
+ export {};
@@ -0,0 +1,9 @@
1
+ import EventEmitter from 'node:events';
2
+ export class TypedEventEmitter extends EventEmitter {
3
+ on(event, listener) {
4
+ return super.on(event, listener);
5
+ }
6
+ emit(event, payload) {
7
+ return super.emit(event, payload);
8
+ }
9
+ }
@@ -0,0 +1,4 @@
1
+ import type { MergeContext } from './mergePipeline.js';
2
+ export declare function requireState<TState, K extends keyof TState>(context: MergeContext<TState>, key: K): asserts context is MergeContext<TState & Required<Pick<TState, K>>>;
3
+ export declare function requireContextProp<TState, K extends keyof MergeContext<TState>>(context: MergeContext<TState>, key: K): asserts context is MergeContext<TState> & Required<Pick<MergeContext<TState>, K>>;
4
+ export declare function requireNonEmptyArray<T>(arr: readonly T[], name?: string): asserts arr is readonly [T, ...T[]];
@@ -0,0 +1,23 @@
1
+ import { MergeError } from '../mergeError.js';
2
+ // |-------------------|
3
+ // | Structural Guards |
4
+ // |-------------------|
5
+ export function requireState(context, key) {
6
+ if (context.state[key] === undefined) {
7
+ throw new MergeError(`State "${String(key)}" was not initialized`, { type: 'internal' });
8
+ }
9
+ }
10
+ export function requireContextProp(context, key) {
11
+ const value = context[key];
12
+ if (value === undefined) {
13
+ throw new MergeError(`Context "${String(key)}" was not initialized`, { type: 'internal' });
14
+ }
15
+ }
16
+ // |------------------|
17
+ // | Invariant Guards |
18
+ // |------------------|
19
+ export function requireNonEmptyArray(arr, name = 'Array') {
20
+ if (arr.length === 0) {
21
+ throw new MergeError(`"${name}" must not be empty`, { type: 'internal' });
22
+ }
23
+ }
@@ -0,0 +1,60 @@
1
+ import sharp from 'sharp';
2
+ import type z from 'zod';
3
+ import type { OnProgress, ProgressInfo } from '../merges/types.js';
4
+ export interface MergeContext<TState> {
5
+ inputs: sharp.SharpInput[];
6
+ progressInfo: ProgressInfo;
7
+ images: sharp.Sharp[];
8
+ canvas?: sharp.Sharp;
9
+ composites: sharp.OverlayOptions[];
10
+ captions: string[];
11
+ state: TState;
12
+ }
13
+ export type MergeStep<TOptions, TState> = (context: MergeContext<TState>, options: TOptions, onProgress?: OnProgress) => Promise<Buffer | void>;
14
+ /**
15
+ * Pipeline orchestrator for running a sequence of merge steps that
16
+ * progressively build or modify a final merged image.
17
+ *
18
+ * Generic type parameters:
19
+ * - TOptions: the shape of the validated options object passed to each step.
20
+ * - TState: the shape of the pipeline's runtime state held in MergeContext.
21
+ *
22
+ * The pipeline:
23
+ * - Validates incoming options via a Zod schema (see createPipeline).
24
+ * - Maintains an ordered list of MergeStep functions to execute.
25
+ * - Provides optional progress reporting via an OnProgress callback.
26
+ *
27
+ * Usage:
28
+ * - Create an instance via createPipeline to ensure options are validated and
29
+ * progressInfo is initialized on the supplied MergeContext.
30
+ * - Register steps with use(...).
31
+ * - Execute with run() — in normal usage this returns a Buffer containing the
32
+ * final image. When run in "test" mode (testOptions supplied) the pipeline
33
+ * may perform a dry-run and return void.
34
+ *
35
+ * Errors:
36
+ * - Validation-related errors thrown from createPipeline are surfaced as
37
+ * MergeError instances with type 'validation' or 'internal'.
38
+ * - During execution, any thrown non-MergeError is converted into a runtime
39
+ * Error indicating the offending step name.
40
+ *
41
+ * @template TOptions - Validated options shape used by registered steps.
42
+ * @template TState - Context/state shape managed by the pipeline.
43
+ */
44
+ export declare class MergePipeline<TOptions, TState> {
45
+ private options;
46
+ private context;
47
+ private onProgress?;
48
+ private steps;
49
+ private optionsCounter;
50
+ private stateCounter;
51
+ constructor(options: TOptions, context: MergeContext<TState>, onProgress?: OnProgress | undefined);
52
+ static createPipeline<TZodSchema extends z.ZodType, TOptions, TState>(schema: TZodSchema, options: TOptions, context: Omit<MergeContext<TState>, 'progressInfo'>, onProgress?: OnProgress): Promise<MergePipeline<z.z.core.output<TZodSchema>, TState>>;
53
+ use(step: MergeStep<TOptions, TState>): this;
54
+ run(): Promise<Buffer>;
55
+ run(testOptions: {
56
+ logOptions?: boolean;
57
+ logContext?: boolean;
58
+ }): Promise<Buffer | void>;
59
+ private logForDebugging;
60
+ }