pixeli 0.1.8 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (204) hide show
  1. package/README.md +341 -88
  2. package/dist/cli/commands/collage/index.d.ts +2 -0
  3. package/dist/cli/commands/collage/index.js +125 -0
  4. package/dist/cli/commands/grid/index.d.ts +2 -0
  5. package/dist/cli/commands/grid/index.js +127 -0
  6. package/dist/cli/commands/masonry/index.d.ts +2 -0
  7. package/dist/cli/commands/masonry/index.js +129 -0
  8. package/dist/cli/commands/template/index.d.ts +2 -0
  9. package/dist/cli/commands/template/index.js +123 -0
  10. package/dist/cli/commands/template/presets/artGallery.d.ts +15 -0
  11. package/dist/cli/commands/template/presets/artGallery.js +15 -0
  12. package/dist/cli/commands/template/presets/dashboardShot.d.ts +15 -0
  13. package/dist/cli/commands/template/presets/dashboardShot.js +16 -0
  14. package/dist/cli/commands/template/presets/horizontalBookSpread.d.ts +15 -0
  15. package/dist/cli/commands/template/presets/horizontalBookSpread.js +13 -0
  16. package/dist/cli/commands/template/presets/instagramGrid.d.ts +15 -0
  17. package/dist/cli/commands/template/presets/instagramGrid.js +16 -0
  18. package/dist/cli/commands/template/presets/verticalBookSpread.d.ts +15 -0
  19. package/dist/cli/commands/template/presets/verticalBookSpread.js +13 -0
  20. package/dist/cli/commands/template/presets.d.ts +73 -0
  21. package/{lib/merges/collage-merge → dist/cli/commands/template}/presets.js +6 -8
  22. package/dist/cli/index.d.ts +2 -0
  23. package/dist/cli/index.js +24 -0
  24. package/dist/cli/modules/loadImages.d.ts +15 -0
  25. package/dist/cli/modules/loadImages.js +74 -0
  26. package/dist/cli/modules/progressBar.d.ts +10 -0
  27. package/dist/cli/modules/progressBar.js +34 -0
  28. package/dist/cli/schemas/collage.d.ts +29 -0
  29. package/dist/cli/schemas/collage.js +38 -0
  30. package/dist/cli/schemas/grid.d.ts +34 -0
  31. package/dist/cli/schemas/grid.js +38 -0
  32. package/dist/cli/schemas/masonry.d.ts +62 -0
  33. package/dist/cli/schemas/masonry.js +62 -0
  34. package/dist/cli/schemas/template.d.ts +31 -0
  35. package/dist/cli/schemas/template.js +49 -0
  36. package/dist/cli/utils/buildCommandFromSchema.d.ts +8 -0
  37. package/dist/cli/utils/buildCommandFromSchema.js +55 -0
  38. package/dist/cli/utils/configureCommandErrors.d.ts +2 -0
  39. package/dist/cli/utils/configureCommandErrors.js +22 -0
  40. package/dist/cli/utils/stringFormatter.d.ts +1 -0
  41. package/dist/cli/utils/stringFormatter.js +3 -0
  42. package/dist/cli/utils/toErrorMessage.d.ts +4 -0
  43. package/dist/cli/utils/toErrorMessage.js +22 -0
  44. package/dist/core/helpers.d.ts +10 -0
  45. package/dist/core/helpers.js +42 -0
  46. package/dist/core/index.d.ts +1 -0
  47. package/dist/core/index.js +1 -0
  48. package/dist/core/mergeError.d.ts +9 -0
  49. package/dist/core/mergeError.js +10 -0
  50. package/dist/core/merges/collage/index.d.ts +9 -0
  51. package/dist/core/merges/collage/index.js +32 -0
  52. package/dist/core/merges/collage/steps/calculateImageDimensions.d.ts +12 -0
  53. package/dist/core/merges/collage/steps/calculateImageDimensions.js +18 -0
  54. package/dist/core/merges/collage/steps/createComposites.d.ts +8 -0
  55. package/dist/core/merges/collage/steps/createComposites.js +58 -0
  56. package/dist/core/merges/collage/steps/resizeAndBorderImages.d.ts +12 -0
  57. package/dist/core/merges/collage/steps/resizeAndBorderImages.js +26 -0
  58. package/dist/core/merges/collage/steps/rotateImages.d.ts +7 -0
  59. package/dist/core/merges/collage/steps/rotateImages.js +9 -0
  60. package/dist/core/merges/grid/index.d.ts +12 -0
  61. package/dist/core/merges/grid/index.js +36 -0
  62. package/dist/core/merges/grid/steps/calculateCanvasDimensions.d.ts +8 -0
  63. package/dist/core/merges/grid/steps/calculateCanvasDimensions.js +18 -0
  64. package/dist/core/merges/grid/steps/calculateFontSize.d.ts +7 -0
  65. package/dist/core/merges/grid/steps/calculateFontSize.js +19 -0
  66. package/dist/core/merges/grid/steps/calculateImageDimensions.d.ts +8 -0
  67. package/dist/core/merges/grid/steps/calculateImageDimensions.js +18 -0
  68. package/dist/core/merges/grid/steps/createComposites.d.ts +10 -0
  69. package/dist/core/merges/grid/steps/createComposites.js +63 -0
  70. package/dist/core/merges/grid/steps/prepareImages.d.ts +10 -0
  71. package/dist/core/merges/grid/steps/prepareImages.js +29 -0
  72. package/dist/core/merges/grid/steps/shuffleImagesAndCaptions.d.ts +7 -0
  73. package/dist/core/merges/grid/steps/shuffleImagesAndCaptions.js +17 -0
  74. package/dist/core/merges/index.d.ts +3 -0
  75. package/dist/core/merges/index.js +3 -0
  76. package/dist/core/merges/masonry/index.d.ts +10 -0
  77. package/dist/core/merges/masonry/index.js +32 -0
  78. package/dist/core/merges/masonry/steps/calculateCanvasDimensions.d.ts +15 -0
  79. package/dist/core/merges/masonry/steps/calculateCanvasDimensions.js +17 -0
  80. package/dist/core/merges/masonry/steps/calculateLaneSize.d.ts +9 -0
  81. package/dist/core/merges/masonry/steps/calculateLaneSize.js +27 -0
  82. package/dist/core/merges/masonry/steps/createComposites.d.ts +25 -0
  83. package/dist/core/merges/masonry/steps/createComposites.js +108 -0
  84. package/dist/core/merges/masonry/steps/resizeImages.d.ts +7 -0
  85. package/dist/core/merges/masonry/steps/resizeImages.js +14 -0
  86. package/dist/core/merges/masonry/steps/splitIntoLanes.d.ts +17 -0
  87. package/dist/core/merges/masonry/steps/splitIntoLanes.js +78 -0
  88. package/dist/core/merges/shared-steps/applyComposites.d.ts +2 -0
  89. package/dist/core/merges/shared-steps/applyComposites.js +16 -0
  90. package/dist/core/merges/shared-steps/createCanvas.d.ts +11 -0
  91. package/dist/core/merges/shared-steps/createCanvas.js +17 -0
  92. package/dist/core/merges/shared-steps/exportCanvas.d.ts +7 -0
  93. package/dist/core/merges/shared-steps/exportCanvas.js +25 -0
  94. package/dist/core/merges/shared-steps/finalizeImagePipelines.d.ts +2 -0
  95. package/dist/core/merges/shared-steps/finalizeImagePipelines.js +9 -0
  96. package/dist/core/merges/shared-steps/loadImages.d.ts +2 -0
  97. package/dist/core/merges/shared-steps/loadImages.js +26 -0
  98. package/dist/core/merges/shared-steps/validateCaptions.d.ts +10 -0
  99. package/dist/core/merges/shared-steps/validateCaptions.js +17 -0
  100. package/dist/core/merges/template/index.d.ts +10 -0
  101. package/dist/core/merges/template/index.js +28 -0
  102. package/dist/core/merges/template/steps/calculateSlotDimensions.d.ts +9 -0
  103. package/dist/core/merges/template/steps/calculateSlotDimensions.js +12 -0
  104. package/dist/core/merges/template/steps/createComposites.d.ts +10 -0
  105. package/dist/core/merges/template/steps/createComposites.js +25 -0
  106. package/dist/core/merges/template/steps/getBlocks.d.ts +13 -0
  107. package/dist/core/merges/template/steps/getBlocks.js +28 -0
  108. package/dist/core/merges/template/types.d.ts +21 -0
  109. package/dist/core/merges/template/types.js +1 -0
  110. package/dist/core/merges/types.d.ts +102 -0
  111. package/dist/core/merges/types.js +1 -0
  112. package/dist/core/modules/messages.d.ts +32 -0
  113. package/dist/core/modules/messages.js +54 -0
  114. package/dist/core/pipeline/guards.d.ts +4 -0
  115. package/dist/core/pipeline/guards.js +23 -0
  116. package/dist/core/pipeline/mergePipeline.d.ts +60 -0
  117. package/dist/core/pipeline/mergePipeline.js +122 -0
  118. package/dist/core/schemas/collage.d.ts +26 -0
  119. package/dist/core/schemas/collage.js +17 -0
  120. package/dist/core/schemas/grid.d.ts +32 -0
  121. package/dist/core/schemas/grid.js +29 -0
  122. package/dist/core/schemas/masonry.d.ts +56 -0
  123. package/dist/core/schemas/masonry.js +43 -0
  124. package/dist/core/schemas/template.d.ts +34 -0
  125. package/dist/core/schemas/template.js +88 -0
  126. package/dist/core/utils/colors/hexToRgba.d.ts +2 -0
  127. package/dist/core/utils/colors/hexToRgba.js +25 -0
  128. package/dist/core/utils/colors/rgbaToHex.d.ts +2 -0
  129. package/dist/core/utils/colors/rgbaToHex.js +15 -0
  130. package/dist/core/utils/colors/types.d.ts +7 -0
  131. package/dist/core/utils/colors/types.js +1 -0
  132. package/dist/core/utils/fonts/getFontSize.d.ts +9 -0
  133. package/dist/core/utils/fonts/getFontSize.js +40 -0
  134. package/dist/core/utils/images/addImageBorder.d.ts +13 -0
  135. package/dist/core/utils/images/addImageBorder.js +33 -0
  136. package/dist/core/utils/images/getImageHeights.d.ts +2 -0
  137. package/dist/core/utils/images/getImageHeights.js +9 -0
  138. package/dist/core/utils/images/getImageWidths.d.ts +2 -0
  139. package/dist/core/utils/images/getImageWidths.js +9 -0
  140. package/dist/core/utils/images/getSmallestImageDimensions.d.ts +5 -0
  141. package/dist/core/utils/images/getSmallestImageDimensions.js +9 -0
  142. package/dist/core/utils/images/handleImageEdges.d.ts +13 -0
  143. package/dist/core/utils/images/handleImageEdges.js +39 -0
  144. package/dist/core/utils/images/isActualImage.d.ts +5 -0
  145. package/dist/core/utils/images/isActualImage.js +26 -0
  146. package/dist/core/utils/images/parseAspectRatio.d.ts +1 -0
  147. package/dist/core/utils/images/parseAspectRatio.js +22 -0
  148. package/dist/core/utils/images/roundImage.d.ts +9 -0
  149. package/dist/core/utils/images/roundImage.js +27 -0
  150. package/dist/core/utils/images/roundImages.d.ts +8 -0
  151. package/dist/core/utils/images/roundImages.js +19 -0
  152. package/dist/core/utils/images/scaleImage.d.ts +8 -0
  153. package/dist/core/utils/images/scaleImage.js +36 -0
  154. package/dist/core/utils/images/scaleImages.d.ts +8 -0
  155. package/dist/core/utils/images/scaleImages.js +38 -0
  156. package/dist/core/utils/math/median.d.ts +1 -0
  157. package/dist/core/utils/math/median.js +12 -0
  158. package/dist/core/utils/math/randint.d.ts +6 -0
  159. package/dist/core/utils/math/randint.js +11 -0
  160. package/dist/core/utils/math/trimmedMedian.d.ts +1 -0
  161. package/dist/core/utils/math/trimmedMedian.js +12 -0
  162. package/dist/core/utils/svg/createSvgTextBuffer.d.ts +9 -0
  163. package/dist/core/utils/svg/createSvgTextBuffer.js +21 -0
  164. package/dist/validators/aspectRatio.d.ts +2 -0
  165. package/dist/validators/aspectRatio.js +15 -0
  166. package/dist/validators/coercion.d.ts +2 -0
  167. package/dist/validators/coercion.js +12 -0
  168. package/dist/validators/format.d.ts +2 -0
  169. package/dist/validators/format.js +15 -0
  170. package/dist/validators/hexColor.d.ts +7 -0
  171. package/dist/validators/hexColor.js +27 -0
  172. package/dist/validators/index.d.ts +95 -0
  173. package/dist/validators/index.js +64 -0
  174. package/dist/validators/outputFile.d.ts +2 -0
  175. package/dist/validators/outputFile.js +7 -0
  176. package/dist/validators/path.d.ts +3 -0
  177. package/dist/validators/path.js +18 -0
  178. package/dist/validators/sharpImageInput.d.ts +3 -0
  179. package/dist/validators/sharpImageInput.js +6 -0
  180. package/dist/validators/template.d.ts +15 -0
  181. package/dist/validators/template.js +41 -0
  182. package/package.json +26 -9
  183. package/bin/pixeli.js +0 -26
  184. package/commands/merge/collage.js +0 -83
  185. package/commands/merge/grid.js +0 -71
  186. package/commands/merge/helpers/utils.js +0 -11
  187. package/commands/merge/helpers/validations.js +0 -269
  188. package/commands/merge/index.js +0 -12
  189. package/commands/merge/masonry.js +0 -72
  190. package/lib/helpers/loadImages.js +0 -94
  191. package/lib/helpers/progressBar.js +0 -20
  192. package/lib/helpers/templateValidator.js +0 -139
  193. package/lib/helpers/utils.js +0 -208
  194. package/lib/merges/collage-merge/index.js +0 -110
  195. package/lib/merges/collage-merge/presets/artGallery.js +0 -17
  196. package/lib/merges/collage-merge/presets/dashboardShot.js +0 -18
  197. package/lib/merges/collage-merge/presets/horizontalBookSpread.js +0 -15
  198. package/lib/merges/collage-merge/presets/instagramGrid.js +0 -18
  199. package/lib/merges/collage-merge/presets/verticalBookSpread.js +0 -15
  200. package/lib/merges/grid-merge/index.js +0 -152
  201. package/lib/merges/masonry-merge/horizontal.js +0 -157
  202. package/lib/merges/masonry-merge/index.js +0 -57
  203. package/lib/merges/masonry-merge/vertical.js +0 -152
  204. package/lib/merges/merge-utils.js +0 -176
@@ -1,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
- };
@@ -1,176 +0,0 @@
1
- import sharp from 'sharp';
2
-
3
- export const calculateAvgHeight = async (images) => {
4
- let totalHeight = 0;
5
-
6
- for (const img of images) {
7
- const meta = await img.metadata();
8
- totalHeight += meta.height;
9
- }
10
-
11
- return Math.floor(totalHeight / images.length);
12
- };
13
-
14
- export const calculateAvgWidth = async (images) => {
15
- let totalWidth = 0;
16
-
17
- for (const img of images) {
18
- const meta = await img.metadata();
19
- totalWidth += meta.width;
20
- }
21
-
22
- return Math.floor(totalWidth / images.length);
23
- };
24
-
25
- export const scaleImages = async (images, { width = null, height = null }) => {
26
- if (!width && !height) {
27
- throw new Error('You must provide either width or height.');
28
- }
29
-
30
- const scaledImages = await Promise.all(
31
- images.map(async (image) => {
32
- const meta = await image.metadata();
33
-
34
- let targetWidth, targetHeight;
35
-
36
- if (width && height) {
37
- targetWidth = width;
38
- targetHeight = height;
39
- } else if (width) {
40
- const f = width / meta.width;
41
- targetWidth = width;
42
- targetHeight = Math.floor(meta.height * f);
43
- } else {
44
- const f = height / meta.height;
45
- targetHeight = height;
46
- targetWidth = Math.floor(meta.width * f);
47
- }
48
-
49
- const buffer = await image.resize(targetWidth, targetHeight).toBuffer();
50
-
51
- return sharp(buffer);
52
- })
53
- );
54
-
55
- return scaledImages;
56
- };
57
-
58
- export const getSmallestImageDimensions = async (images) => {
59
- const metas = await Promise.all(images.map((img) => img.metadata()));
60
-
61
- return metas.reduce(
62
- (acc, meta) => ({
63
- smallestWidth: Math.min(acc.smallestWidth, meta.width),
64
- smallestHeight: Math.min(acc.smallestHeight, meta.height),
65
- }),
66
- { smallestWidth: Infinity, smallestHeight: Infinity }
67
- );
68
- };
69
-
70
- export const getFontSize = async ({
71
- text,
72
- maxWidth,
73
- maxHeight,
74
- initialFontSize = 100,
75
- minFontSize = 2,
76
- fontFamily = 'sans-serif',
77
- }) => {
78
- const THRESHOLD = 200;
79
- const SMALL_CHANGE = 2;
80
- const LARGE_CHANGE = 5;
81
-
82
- let fontSize = initialFontSize;
83
-
84
- while (fontSize >= minFontSize) {
85
- // No width or viewport given so that the actual size can be determined after rasterization
86
- const svg = `
87
- <svg xmlns="http://www.w3.org/2000/svg">
88
- <text
89
- x="${maxWidth / 2}"
90
- y="10"
91
- font-size="${fontSize}"
92
- font-family="${fontFamily}"
93
- fill="#000000"
94
- text-anchor="middle"
95
- dominant-baseline="middle">
96
- ${escapeXML(text)}
97
- </text>
98
- </svg>
99
- `;
100
-
101
- // Rasterize SVG: measure actual rendered size
102
- const raster = await sharp(Buffer.from(svg)).png().toBuffer();
103
- const meta = await sharp(raster).metadata();
104
-
105
- if (meta.width <= maxWidth && meta.height <= maxHeight) {
106
- return fontSize;
107
- }
108
-
109
- // If the difference is greater than the threshold, use large change
110
- if (maxWidth - meta.width > THRESHOLD || maxHeight - meta.height) {
111
- fontSize -= LARGE_CHANGE;
112
- } else {
113
- fontSize -= SMALL_CHANGE;
114
- }
115
- }
116
-
117
- return minFontSize;
118
- };
119
-
120
- export const createSvgTextBuffer = ({ text, maxWidth, maxHeight, fontSize, fill = '#000000', fontFamily = 'sans-serif' }) => {
121
- // Width and viewport are assigned to this svg
122
- const svg = `
123
- <svg xmlns="http://www.w3.org/2000/svg"
124
- width="${maxWidth}" height="${maxHeight}"
125
- viewBox="0 0 ${maxWidth} ${maxHeight}">
126
- <text
127
- x="${maxWidth / 2}"
128
- y="${maxHeight / 2}"
129
- font-size="${fontSize}"
130
- font-family="${fontFamily}"
131
- fill="${fill}"
132
- text-anchor="middle"
133
- dominant-baseline="middle">
134
- ${escapeXML(text)}
135
- </text>
136
- </svg>
137
- `;
138
-
139
- return Buffer.from(svg);
140
- };
141
-
142
- const escapeXML = (str) => {
143
- return str.replace(
144
- /[<>&'"]/g,
145
- (c) =>
146
- ({
147
- '<': '&lt;',
148
- '>': '&gt;',
149
- '&': '&amp;',
150
- '"': '&quot;',
151
- "'": '&apos;',
152
- }[c])
153
- );
154
- };
155
-
156
- export const roundImages = async (images, { width, height, cornerRadius }) => {
157
- // Skip if the cornerRadius = zero
158
- if (!cornerRadius) return images;
159
-
160
- // Round images respectively
161
- return await Promise.all(
162
- images.map(async (image) => {
163
- const mask = Buffer.from(`
164
- <svg width="${width}" height="${height}">
165
- <rect x="0" y="0" width="${width}" height="${height}" rx="${cornerRadius}" ry="${cornerRadius}" />
166
- </svg>
167
- `);
168
-
169
- const buff = await image
170
- .composite([{ input: mask, blend: 'dest-in' }])
171
- .png()
172
- .toBuffer();
173
- return sharp(buff);
174
- })
175
- );
176
- };