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
@@ -1,18 +0,0 @@
1
- export default {
2
- canvas: {
3
- width: 1200,
4
- height: 1600,
5
- columns: 3,
6
- rows: 6,
7
- gap: 12,
8
- background: '#000',
9
- },
10
- slots: [
11
- { col: 1, row: 1, colSpan: 2, rowSpan: 2 },
12
- { col: 3, row: 1, colSpan: 1, rowSpan: 1 },
13
- { col: 3, row: 2, colSpan: 1, rowSpan: 1 },
14
- { col: 1, row: 3, colSpan: 1, rowSpan: 2 },
15
- { col: 2, row: 3, colSpan: 2, rowSpan: 2 },
16
- { col: 1, row: 5, colSpan: 3, rowSpan: 2 },
17
- ],
18
- };
@@ -1,15 +0,0 @@
1
- export default {
2
- canvas: {
3
- width: 1400,
4
- height: 2100,
5
- columns: 2,
6
- rows: 3,
7
- gap: 20,
8
- background: '#000',
9
- },
10
- slots: [
11
- { col: 1, row: 1, colSpan: 2, rowSpan: 1 },
12
- { col: 1, row: 2, colSpan: 1, rowSpan: 2 },
13
- { col: 2, row: 2, colSpan: 1, rowSpan: 2 },
14
- ],
15
- };
@@ -1,152 +0,0 @@
1
- import path from 'node:path';
2
- import sharp from 'sharp';
3
- import { getSmallestImageDimensions, getFontSize, createSvgTextBuffer, roundImages } from '../merge-utils.js';
4
- import { progressBar, WRITING_TO_FILE_PERCENTAGE } from '../../helpers/progressBar.js';
5
-
6
- export const gridMerge = async (files, images, validatedParams) => {
7
- // Destructure params
8
- const { aspectRatio, imageWidth, columns, gap, cornerRadius, canvasColor, caption, captionColor, maxCaptionSize } = validatedParams;
9
-
10
- // Calculate width if needed, and height from aspect ratio
11
- const width = imageWidth || (await getSmallestImageDimensions(images)).smallestWidth;
12
- const height = Math.floor(width / aspectRatio);
13
-
14
- // resize images to match width and height
15
- const resizedImages = images.map((image) => {
16
- return image.resize({
17
- width,
18
- height,
19
- fit: 'cover',
20
- });
21
- });
22
-
23
- // Round images if needed
24
- const roundedImages = await roundImages(resizedImages, { width, height, cornerRadius });
25
-
26
- // Get filenames if needed
27
- let filenames = null;
28
- if (caption) {
29
- filenames = files.map((file) => path.basename(file));
30
- }
31
-
32
- // Lay images in a grid
33
- const gridParams = {
34
- images: roundedImages,
35
- width,
36
- height,
37
- columns,
38
- gap,
39
- canvasColor,
40
- filenames,
41
- caption,
42
- captionColor,
43
- maxCaptionSize,
44
- };
45
-
46
- return await layImagesInGrid(gridParams);
47
- };
48
-
49
- const layImagesInGrid = async (opts) => {
50
- // Destructure params
51
- const { images, width, height, columns, gap, canvasColor, filenames, caption, captionColor, maxCaptionSize } = opts;
52
-
53
- // Use 5% of images.length for writing to file
54
- const fileWriteAmount = Math.ceil(images.length * WRITING_TO_FILE_PERCENTAGE);
55
- progressBar.start(images.length + fileWriteAmount, 0, {
56
- stage: 'Merging images',
57
- });
58
-
59
- // Set constant
60
- const CAPTION_HEIGHT_TO_CANVAS_WIDTH_RATIO = 0.04;
61
-
62
- // Calculate number of rows
63
- const rows = Math.ceil(images.length / columns);
64
-
65
- // Calculate canvas width and caption height
66
- const canvasWidth = width * columns + (columns + 1) * gap;
67
- const captionHeight = Math.floor(canvasWidth * CAPTION_HEIGHT_TO_CANVAS_WIDTH_RATIO);
68
-
69
- // Calculate canvas height
70
- const minimumCanvasHeight = height * rows + (rows + 1) * gap;
71
- const canvasHeight = caption ? minimumCanvasHeight + rows * captionHeight : minimumCanvasHeight;
72
-
73
- // Calculate font size if needed
74
- let fontSize = null;
75
- if (caption) {
76
- const longestFilename = filenames.reduce((longest, current) => {
77
- return current.length > longest.length ? current : longest;
78
- });
79
-
80
- fontSize = await getFontSize({
81
- text: longestFilename,
82
- maxWidth: width,
83
- maxHeight: captionHeight,
84
- initialFontSize: maxCaptionSize,
85
- });
86
- }
87
-
88
- // Create canvas
89
- const canvas = sharp({
90
- limitInputPixels: false,
91
- create: {
92
- width: canvasWidth,
93
- height: canvasHeight,
94
- channels: 4,
95
- background: canvasColor,
96
- },
97
- });
98
-
99
- // Collect composites
100
- const composites = [];
101
-
102
- let x = gap;
103
- let y = gap;
104
-
105
- for (let row = 0; row < rows; row++) {
106
- for (let col = 0; col < columns; col++) {
107
- const index = row * columns + col;
108
- if (index >= images.length) break;
109
-
110
- const image = images[index];
111
-
112
- composites.push({
113
- input: await image.toBuffer(),
114
- left: x,
115
- top: y,
116
- });
117
-
118
- // Add caption if required
119
- if (caption) {
120
- // Create text
121
- const svgBuffer = createSvgTextBuffer({
122
- text: filenames[index],
123
- maxWidth: width,
124
- maxHeight: captionHeight,
125
- fontSize,
126
- fill: captionColor,
127
- });
128
-
129
- // Add text to composites
130
- composites.push({
131
- input: svgBuffer,
132
- left: x,
133
- top: y + height,
134
- });
135
- }
136
-
137
- // Update coordinates
138
- x += width + gap;
139
-
140
- // Update progress bar
141
- progressBar.increment();
142
- }
143
-
144
- // Update coordinates
145
- y += caption ? height + gap + captionHeight : height + gap;
146
- x = gap;
147
- }
148
-
149
- // Create final grid
150
- canvas.composite(composites);
151
- return canvas;
152
- };
@@ -1,157 +0,0 @@
1
- import sharp from 'sharp';
2
- import { roundImages, scaleImages } from '../merge-utils.js';
3
- import { progressBar, WRITING_TO_FILE_PERCENTAGE } from '../../helpers/progressBar.js';
4
-
5
- export const buildHorizontalMasonry = async (images, params) => {
6
- const { gap, canvasColor, cornerRadius, canvasWidth, rowHeight, hAlign } = params;
7
-
8
- // Use 5% of images.length for writing to file
9
- const fileWriteAmount = Math.ceil(images.length * WRITING_TO_FILE_PERCENTAGE);
10
- progressBar.start(images.length + fileWriteAmount, 0, {
11
- stage: 'Merging images',
12
- });
13
-
14
- // Rescale images to match rowHeight
15
- const scaledImages = await scaleImages(images, { height: rowHeight });
16
-
17
- // Split images into rows, then calculate canvasHeight
18
- const rows = await splitIntoRows(scaledImages, canvasWidth, gap, hAlign);
19
- const canvasHeight = rows.length * rowHeight + (rows.length + 1) * gap;
20
-
21
- // Create and return grid of images
22
- return await createMasonryLayout(rows, rowHeight, canvasWidth, canvasHeight, canvasColor, cornerRadius, gap, hAlign);
23
- };
24
-
25
- const createMasonryLayout = async (rows, rowHeight, canvasWidth, canvasHeight, canvasColor, cornerRadius, gap, hAlign) => {
26
- const canvas = sharp({
27
- limitInputPixels: false,
28
- create: {
29
- width: canvasWidth,
30
- height: canvasHeight,
31
- channels: 4,
32
- background: canvasColor,
33
- },
34
- });
35
-
36
- const composites = [];
37
-
38
- let currentWidth = gap;
39
- let x = gap;
40
- let y = gap;
41
-
42
- for (const row of rows) {
43
- const rowXStart = await computeRowXOffset(row, canvasWidth, gap, hAlign);
44
- x = rowXStart;
45
-
46
- for (const im of row) {
47
- const meta = await im.metadata();
48
- let finalizedImage = im;
49
- let finalizedMeta = meta;
50
- currentWidth += meta.width + gap;
51
-
52
- if (currentWidth >= canvasWidth) {
53
- // Calculate overflow
54
- const overflow = currentWidth - canvasWidth;
55
-
56
- // Resize (crop) image to justify
57
- const resizeOptions = {
58
- width: meta.width - overflow,
59
- height: meta.height,
60
- fit: 'cover',
61
- };
62
-
63
- // Update finalized image and metadata
64
- const buff = await im.resize(resizeOptions).toBuffer();
65
- finalizedImage = sharp(buff);
66
- finalizedMeta = await finalizedImage.metadata();
67
- }
68
-
69
- // Round the finalized image
70
- const roundingOptions = {
71
- width: finalizedMeta.width,
72
- height: finalizedMeta.height,
73
- cornerRadius,
74
- };
75
-
76
- const roundedImage = (await roundImages([finalizedImage], roundingOptions))[0];
77
-
78
- composites.push({
79
- input: await roundedImage.toBuffer(),
80
- left: x,
81
- top: y,
82
- });
83
-
84
- x += finalizedMeta.width + gap;
85
-
86
- // Update progress
87
- progressBar.increment();
88
- }
89
-
90
- x = gap;
91
- currentWidth = gap;
92
- y += rowHeight + gap;
93
- }
94
-
95
- return canvas.composite(composites);
96
- };
97
-
98
- const splitIntoRows = async (images, canvasWidth, gap, hAlign) => {
99
- const rows = [];
100
- let currentRow = [];
101
- let currentWidth = gap; // initial leading gap
102
-
103
- for (const im of images) {
104
- const meta = await im.metadata();
105
- const nextWidth = currentWidth + meta.width + gap;
106
-
107
- if (hAlign === 'justified') {
108
- // Greedy: always push image, fix overflow later
109
- currentRow.push(im);
110
- currentWidth = nextWidth;
111
-
112
- if (currentWidth + gap >= canvasWidth) {
113
- rows.push(currentRow.slice());
114
- currentRow.length = 0;
115
- currentWidth = gap;
116
- }
117
- } else {
118
- // Non-greedy: break BEFORE adding image that doesn't fit
119
- if (nextWidth > canvasWidth && currentRow.length > 0) {
120
- rows.push(currentRow.slice());
121
- currentRow = [];
122
- currentWidth = gap;
123
- }
124
-
125
- // Add the image (may be first in a new row)
126
- currentRow.push(im);
127
- currentWidth += meta.width + gap;
128
- }
129
- }
130
-
131
- if (currentRow.length > 0) {
132
- rows.push(currentRow);
133
- }
134
-
135
- return rows;
136
- };
137
-
138
- const computeRowXOffset = async (row, canvasWidth, gap, hAlign) => {
139
- // Calculate total row width
140
- let totalWidth = gap * (row.length + 1);
141
- for (const im of row) {
142
- const meta = await im.metadata();
143
- totalWidth += meta.width;
144
- }
145
-
146
- // Get x offset
147
- if (hAlign === 'left' || hAlign === 'justified') {
148
- return gap;
149
- }
150
- if (hAlign === 'right') {
151
- return canvasWidth - totalWidth + gap;
152
- }
153
- if (hAlign === 'center') {
154
- const canvasGap = gap * 2;
155
- return Math.floor((canvasWidth + canvasGap - totalWidth) / 2);
156
- }
157
- };
@@ -1,57 +0,0 @@
1
- import { calculateAvgWidth, calculateAvgHeight } from '../merge-utils.js';
2
- import { buildHorizontalMasonry } from './horizontal.js';
3
- import { buildVerticalMasonry } from './vertical.js';
4
-
5
- const FLOW_DEFAULTS = {
6
- horizontal: {
7
- needed: ['canvasWidth', 'rowHeight', 'hAlign'],
8
- defaults: {
9
- rowHeight: calculateAvgHeight,
10
- hAlign: () => 'justified',
11
- },
12
- },
13
- vertical: {
14
- needed: ['canvasHeight', 'columnWidth', 'vAlign'],
15
- defaults: {
16
- columnWidth: calculateAvgWidth,
17
- vAlign: () => 'justified',
18
- },
19
- },
20
- };
21
-
22
- export const masonryMerge = async (images, opts) => {
23
- const { flow } = opts;
24
- const params = await getFlowSpecificParams(images, opts);
25
-
26
- return await generateGrid(flow, images, params);
27
- };
28
-
29
- const getFlowSpecificParams = async (images, currentParams) => {
30
- const { flow, gap, canvasColor, cornerRadius } = currentParams;
31
- const config = FLOW_DEFAULTS[flow];
32
-
33
- const output = { gap, canvasColor, cornerRadius };
34
-
35
- for (const key of config.needed) {
36
- if (currentParams[key] != null) {
37
- output[key] = currentParams[key];
38
- }
39
- }
40
-
41
- // Assign static defaults
42
- for (const [key, getter] of Object.entries(config.defaults)) {
43
- if (output[key] == null) {
44
- output[key] = await getter(images);
45
- }
46
- }
47
-
48
- return output;
49
- };
50
-
51
- const generateGrid = async (flow, images, params) => {
52
- if (flow === 'horizontal') {
53
- return await buildHorizontalMasonry(images, params);
54
- } else {
55
- return buildVerticalMasonry(images, params);
56
- }
57
- };
@@ -1,152 +0,0 @@
1
- import sharp from 'sharp';
2
- import { roundImages, scaleImages } from '../merge-utils.js';
3
- import { progressBar, WRITING_TO_FILE_PERCENTAGE } from '../../helpers/progressBar.js';
4
-
5
- export const buildVerticalMasonry = async (images, params) => {
6
- const { gap, canvasColor, cornerRadius, canvasHeight, columnWidth, vAlign } = params;
7
-
8
- // Use 5% of images.length for writing to file
9
- const fileWriteAmount = Math.ceil(images.length * WRITING_TO_FILE_PERCENTAGE);
10
- progressBar.start(images.length + fileWriteAmount, 0, {
11
- stage: 'Merging images',
12
- });
13
-
14
- // Rescale images to match columnWidth
15
- const scaledImages = await scaleImages(images, { width: columnWidth });
16
-
17
- // Split images into columns, then calculate canvasWidth
18
- const columns = await splitIntoColumns(scaledImages, canvasHeight, gap, vAlign);
19
- const canvasWidth = columns.length * columnWidth + (columns.length + 1) * gap;
20
-
21
- // Create and return grid of images
22
- return await createMasonryLayout(columns, columnWidth, canvasWidth, canvasHeight, canvasColor, cornerRadius, gap, vAlign);
23
- };
24
-
25
- const createMasonryLayout = async (cols, columnWidth, canvasWidth, canvasHeight, canvasColor, cornerRadius, gap, vAlign) => {
26
- const composites = [];
27
-
28
- const canvas = sharp({
29
- limitInputPixels: false,
30
- create: {
31
- width: canvasWidth,
32
- height: canvasHeight,
33
- channels: 4,
34
- background: canvasColor,
35
- },
36
- });
37
-
38
- let x = gap;
39
- let currentHeight = gap;
40
-
41
- for (const col of cols) {
42
- let y = await computeColYOffset(col, canvasHeight, gap, vAlign);
43
-
44
- for (const im of col) {
45
- let finalizedImage = im;
46
- let finalizedMeta = await im.metadata();
47
-
48
- currentHeight += finalizedMeta.height + gap;
49
-
50
- if (currentHeight >= canvasHeight) {
51
- const yOverflow = currentHeight - canvasHeight;
52
-
53
- const resizeOptions = {
54
- width: finalizedMeta.width,
55
- height: finalizedMeta.height - yOverflow,
56
- fit: 'cover',
57
- };
58
-
59
- const buffer = await finalizedImage.resize(resizeOptions).toBuffer();
60
- finalizedImage = sharp(buffer);
61
- finalizedMeta = await finalizedImage.metadata();
62
- }
63
-
64
- // Round the finalized image
65
- const roundingOptions = {
66
- width: finalizedMeta.width,
67
- height: finalizedMeta.height,
68
- cornerRadius,
69
- };
70
-
71
- const roundedImage = (await roundImages([finalizedImage], roundingOptions))[0];
72
-
73
- composites.push({
74
- input: await roundedImage.toBuffer(),
75
- left: x,
76
- top: y,
77
- });
78
-
79
- y += finalizedMeta.height + gap;
80
-
81
- // Update progress bar
82
- progressBar.increment();
83
- }
84
-
85
- y = gap;
86
- currentHeight = gap;
87
- x += columnWidth + gap;
88
- }
89
-
90
- return canvas.composite(composites);
91
- };
92
-
93
- const splitIntoColumns = async (images, canvasHeight, gap, vAlign) => {
94
- const cols = [];
95
- const currentCol = [];
96
- let currentHeight = gap;
97
-
98
- for (const im of images) {
99
- const meta = await im.metadata();
100
- let nextHeight = currentHeight + meta.height + gap;
101
-
102
- if (vAlign === 'justified') {
103
- // Greedy: always push image, fix overflow later
104
- currentCol.push(im);
105
- currentHeight = nextHeight;
106
-
107
- if (currentHeight + gap >= canvasHeight) {
108
- cols.push(currentCol.slice());
109
- currentCol.length = 0;
110
- currentHeight = gap;
111
- }
112
- } else {
113
- // Non-greedy: break BEFORE adding image that doesn't fit
114
- if (nextHeight > canvasHeight && currentCol.length > 0) {
115
- cols.push(currentCol.slice());
116
- currentCol.length = 0;
117
- currentHeight = gap;
118
- }
119
-
120
- // Add the image (may be first in a new column)
121
- currentCol.push(im);
122
- currentHeight += meta.height + gap;
123
- }
124
- }
125
-
126
- if (currentCol.length > 0) {
127
- cols.push(currentCol);
128
- }
129
-
130
- return cols;
131
- };
132
-
133
- const computeColYOffset = async (col, canvasHeight, gap, vAlign) => {
134
- // Calculate total row width
135
- let totalHeight = gap * (col.length + 1);
136
- for (const im of col) {
137
- const meta = await im.metadata();
138
- totalHeight += meta.height;
139
- }
140
-
141
- // Get x offset
142
- if (vAlign === 'top' || vAlign === 'justified') {
143
- return gap;
144
- }
145
- if (vAlign === 'bottom') {
146
- return canvasHeight - totalHeight + gap;
147
- }
148
- if (vAlign === 'middle') {
149
- const canvasGap = gap * 2;
150
- return Math.floor((canvasHeight + canvasGap - totalHeight) / 2);
151
- }
152
- };