pixeli 0.1.9 → 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.
- package/README.md +341 -88
- package/dist/cli/commands/collage/index.d.ts +2 -0
- package/dist/cli/commands/collage/index.js +125 -0
- package/dist/cli/commands/grid/index.d.ts +2 -0
- package/dist/cli/commands/grid/index.js +127 -0
- package/dist/cli/commands/masonry/index.d.ts +2 -0
- package/dist/cli/commands/masonry/index.js +129 -0
- package/dist/cli/commands/template/index.d.ts +2 -0
- package/dist/cli/commands/template/index.js +123 -0
- package/dist/cli/commands/template/presets/artGallery.d.ts +15 -0
- package/dist/cli/commands/template/presets/artGallery.js +15 -0
- package/dist/cli/commands/template/presets/dashboardShot.d.ts +15 -0
- package/dist/cli/commands/template/presets/dashboardShot.js +16 -0
- package/dist/cli/commands/template/presets/horizontalBookSpread.d.ts +15 -0
- package/dist/cli/commands/template/presets/horizontalBookSpread.js +13 -0
- package/dist/cli/commands/template/presets/instagramGrid.d.ts +15 -0
- package/dist/cli/commands/template/presets/instagramGrid.js +16 -0
- package/dist/cli/commands/template/presets/verticalBookSpread.d.ts +15 -0
- package/dist/cli/commands/template/presets/verticalBookSpread.js +13 -0
- package/dist/cli/commands/template/presets.d.ts +73 -0
- package/{lib/merges/collage-merge → dist/cli/commands/template}/presets.js +6 -8
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +24 -0
- package/dist/cli/modules/loadImages.d.ts +15 -0
- package/dist/cli/modules/loadImages.js +74 -0
- package/dist/cli/modules/progressBar.d.ts +10 -0
- package/dist/cli/modules/progressBar.js +34 -0
- package/dist/cli/schemas/collage.d.ts +29 -0
- package/dist/cli/schemas/collage.js +38 -0
- package/dist/cli/schemas/grid.d.ts +34 -0
- package/dist/cli/schemas/grid.js +38 -0
- package/dist/cli/schemas/masonry.d.ts +62 -0
- package/dist/cli/schemas/masonry.js +62 -0
- package/dist/cli/schemas/template.d.ts +31 -0
- package/dist/cli/schemas/template.js +49 -0
- package/dist/cli/utils/buildCommandFromSchema.d.ts +8 -0
- package/dist/cli/utils/buildCommandFromSchema.js +55 -0
- package/dist/cli/utils/configureCommandErrors.d.ts +2 -0
- package/dist/cli/utils/configureCommandErrors.js +22 -0
- package/dist/cli/utils/stringFormatter.d.ts +1 -0
- package/dist/cli/utils/stringFormatter.js +3 -0
- package/dist/cli/utils/toErrorMessage.d.ts +4 -0
- package/dist/cli/utils/toErrorMessage.js +22 -0
- package/dist/core/helpers.d.ts +10 -0
- package/dist/core/helpers.js +42 -0
- package/dist/core/index.d.ts +1 -0
- package/dist/core/index.js +1 -0
- package/dist/core/mergeError.d.ts +9 -0
- package/dist/core/mergeError.js +10 -0
- package/dist/core/merges/collage/index.d.ts +9 -0
- package/dist/core/merges/collage/index.js +32 -0
- package/dist/core/merges/collage/steps/calculateImageDimensions.d.ts +12 -0
- package/dist/core/merges/collage/steps/calculateImageDimensions.js +18 -0
- package/dist/core/merges/collage/steps/createComposites.d.ts +8 -0
- package/dist/core/merges/collage/steps/createComposites.js +58 -0
- package/dist/core/merges/collage/steps/resizeAndBorderImages.d.ts +12 -0
- package/dist/core/merges/collage/steps/resizeAndBorderImages.js +26 -0
- package/dist/core/merges/collage/steps/rotateImages.d.ts +7 -0
- package/dist/core/merges/collage/steps/rotateImages.js +9 -0
- package/dist/core/merges/grid/index.d.ts +12 -0
- package/dist/core/merges/grid/index.js +36 -0
- package/dist/core/merges/grid/steps/calculateCanvasDimensions.d.ts +8 -0
- package/dist/core/merges/grid/steps/calculateCanvasDimensions.js +18 -0
- package/dist/core/merges/grid/steps/calculateFontSize.d.ts +7 -0
- package/dist/core/merges/grid/steps/calculateFontSize.js +19 -0
- package/dist/core/merges/grid/steps/calculateImageDimensions.d.ts +8 -0
- package/dist/core/merges/grid/steps/calculateImageDimensions.js +18 -0
- package/dist/core/merges/grid/steps/createComposites.d.ts +10 -0
- package/dist/core/merges/grid/steps/createComposites.js +63 -0
- package/dist/core/merges/grid/steps/prepareImages.d.ts +10 -0
- package/dist/core/merges/grid/steps/prepareImages.js +29 -0
- package/dist/core/merges/grid/steps/shuffleImagesAndCaptions.d.ts +7 -0
- package/dist/core/merges/grid/steps/shuffleImagesAndCaptions.js +17 -0
- package/dist/core/merges/index.d.ts +3 -0
- package/dist/core/merges/index.js +3 -0
- package/dist/core/merges/masonry/index.d.ts +10 -0
- package/dist/core/merges/masonry/index.js +32 -0
- package/dist/core/merges/masonry/steps/calculateCanvasDimensions.d.ts +15 -0
- package/dist/core/merges/masonry/steps/calculateCanvasDimensions.js +17 -0
- package/dist/core/merges/masonry/steps/calculateLaneSize.d.ts +9 -0
- package/dist/core/merges/masonry/steps/calculateLaneSize.js +27 -0
- package/dist/core/merges/masonry/steps/createComposites.d.ts +25 -0
- package/dist/core/merges/masonry/steps/createComposites.js +108 -0
- package/dist/core/merges/masonry/steps/resizeImages.d.ts +7 -0
- package/dist/core/merges/masonry/steps/resizeImages.js +14 -0
- package/dist/core/merges/masonry/steps/splitIntoLanes.d.ts +17 -0
- package/dist/core/merges/masonry/steps/splitIntoLanes.js +78 -0
- package/dist/core/merges/shared-steps/applyComposites.d.ts +2 -0
- package/dist/core/merges/shared-steps/applyComposites.js +16 -0
- package/dist/core/merges/shared-steps/createCanvas.d.ts +11 -0
- package/dist/core/merges/shared-steps/createCanvas.js +17 -0
- package/dist/core/merges/shared-steps/exportCanvas.d.ts +7 -0
- package/dist/core/merges/shared-steps/exportCanvas.js +25 -0
- package/dist/core/merges/shared-steps/finalizeImagePipelines.d.ts +2 -0
- package/dist/core/merges/shared-steps/finalizeImagePipelines.js +9 -0
- package/dist/core/merges/shared-steps/loadImages.d.ts +2 -0
- package/dist/core/merges/shared-steps/loadImages.js +26 -0
- package/dist/core/merges/shared-steps/validateCaptions.d.ts +10 -0
- package/dist/core/merges/shared-steps/validateCaptions.js +17 -0
- package/dist/core/merges/template/index.d.ts +10 -0
- package/dist/core/merges/template/index.js +28 -0
- package/dist/core/merges/template/steps/calculateSlotDimensions.d.ts +9 -0
- package/dist/core/merges/template/steps/calculateSlotDimensions.js +12 -0
- package/dist/core/merges/template/steps/createComposites.d.ts +10 -0
- package/dist/core/merges/template/steps/createComposites.js +25 -0
- package/dist/core/merges/template/steps/getBlocks.d.ts +13 -0
- package/dist/core/merges/template/steps/getBlocks.js +28 -0
- package/dist/core/merges/template/types.d.ts +21 -0
- package/dist/core/merges/template/types.js +1 -0
- package/dist/core/merges/types.d.ts +102 -0
- package/dist/core/merges/types.js +1 -0
- package/dist/core/modules/messages.d.ts +32 -0
- package/dist/core/modules/messages.js +54 -0
- package/dist/core/pipeline/guards.d.ts +4 -0
- package/dist/core/pipeline/guards.js +23 -0
- package/dist/core/pipeline/mergePipeline.d.ts +60 -0
- package/dist/core/pipeline/mergePipeline.js +122 -0
- package/dist/core/schemas/collage.d.ts +26 -0
- package/dist/core/schemas/collage.js +17 -0
- package/dist/core/schemas/grid.d.ts +32 -0
- package/dist/core/schemas/grid.js +29 -0
- package/dist/core/schemas/masonry.d.ts +56 -0
- package/dist/core/schemas/masonry.js +43 -0
- package/dist/core/schemas/template.d.ts +34 -0
- package/dist/core/schemas/template.js +88 -0
- package/dist/core/utils/colors/hexToRgba.d.ts +2 -0
- package/dist/core/utils/colors/hexToRgba.js +25 -0
- package/dist/core/utils/colors/rgbaToHex.d.ts +2 -0
- package/dist/core/utils/colors/rgbaToHex.js +15 -0
- package/dist/core/utils/colors/types.d.ts +7 -0
- package/dist/core/utils/colors/types.js +1 -0
- package/dist/core/utils/fonts/getFontSize.d.ts +9 -0
- package/dist/core/utils/fonts/getFontSize.js +40 -0
- package/dist/core/utils/images/addImageBorder.d.ts +13 -0
- package/dist/core/utils/images/addImageBorder.js +33 -0
- package/dist/core/utils/images/getImageHeights.d.ts +2 -0
- package/dist/core/utils/images/getImageHeights.js +9 -0
- package/dist/core/utils/images/getImageWidths.d.ts +2 -0
- package/dist/core/utils/images/getImageWidths.js +9 -0
- package/dist/core/utils/images/getSmallestImageDimensions.d.ts +5 -0
- package/dist/core/utils/images/getSmallestImageDimensions.js +9 -0
- package/dist/core/utils/images/handleImageEdges.d.ts +13 -0
- package/dist/core/utils/images/handleImageEdges.js +39 -0
- package/dist/core/utils/images/isActualImage.d.ts +5 -0
- package/dist/core/utils/images/isActualImage.js +26 -0
- package/dist/core/utils/images/parseAspectRatio.d.ts +1 -0
- package/dist/core/utils/images/parseAspectRatio.js +22 -0
- package/dist/core/utils/images/roundImage.d.ts +9 -0
- package/dist/core/utils/images/roundImage.js +27 -0
- package/dist/core/utils/images/roundImages.d.ts +8 -0
- package/dist/core/utils/images/roundImages.js +19 -0
- package/dist/core/utils/images/scaleImage.d.ts +8 -0
- package/dist/core/utils/images/scaleImage.js +36 -0
- package/dist/core/utils/images/scaleImages.d.ts +8 -0
- package/dist/core/utils/images/scaleImages.js +38 -0
- package/dist/core/utils/math/median.d.ts +1 -0
- package/dist/core/utils/math/median.js +12 -0
- package/dist/core/utils/math/randint.d.ts +6 -0
- package/dist/core/utils/math/randint.js +11 -0
- package/dist/core/utils/math/trimmedMedian.d.ts +1 -0
- package/dist/core/utils/math/trimmedMedian.js +12 -0
- package/dist/core/utils/svg/createSvgTextBuffer.d.ts +9 -0
- package/dist/core/utils/svg/createSvgTextBuffer.js +21 -0
- package/dist/validators/aspectRatio.d.ts +2 -0
- package/dist/validators/aspectRatio.js +15 -0
- package/dist/validators/coercion.d.ts +2 -0
- package/dist/validators/coercion.js +12 -0
- package/dist/validators/format.d.ts +2 -0
- package/dist/validators/format.js +15 -0
- package/dist/validators/hexColor.d.ts +7 -0
- package/dist/validators/hexColor.js +27 -0
- package/dist/validators/index.d.ts +95 -0
- package/dist/validators/index.js +64 -0
- package/dist/validators/outputFile.d.ts +2 -0
- package/dist/validators/outputFile.js +7 -0
- package/dist/validators/path.d.ts +3 -0
- package/dist/validators/path.js +18 -0
- package/dist/validators/sharpImageInput.d.ts +3 -0
- package/dist/validators/sharpImageInput.js +6 -0
- package/dist/validators/template.d.ts +15 -0
- package/dist/validators/template.js +41 -0
- package/package.json +26 -9
- package/bin/pixeli.js +0 -26
- package/commands/merge/collage.js +0 -83
- package/commands/merge/grid.js +0 -71
- package/commands/merge/helpers/utils.js +0 -11
- package/commands/merge/helpers/validations.js +0 -269
- package/commands/merge/index.js +0 -12
- package/commands/merge/masonry.js +0 -72
- package/lib/helpers/loadImages.js +0 -94
- package/lib/helpers/progressBar.js +0 -20
- package/lib/helpers/templateValidator.js +0 -139
- package/lib/helpers/utils.js +0 -208
- package/lib/merges/collage-merge/index.js +0 -110
- package/lib/merges/collage-merge/presets/artGallery.js +0 -17
- package/lib/merges/collage-merge/presets/dashboardShot.js +0 -18
- package/lib/merges/collage-merge/presets/horizontalBookSpread.js +0 -15
- package/lib/merges/collage-merge/presets/instagramGrid.js +0 -18
- package/lib/merges/collage-merge/presets/verticalBookSpread.js +0 -15
- package/lib/merges/grid-merge/index.js +0 -152
- package/lib/merges/masonry-merge/horizontal.js +0 -157
- package/lib/merges/masonry-merge/index.js +0 -57
- package/lib/merges/masonry-merge/vertical.js +0 -152
- package/lib/merges/merge-utils.js +0 -176
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import z from 'zod';
|
|
2
|
+
import { Argument, Command, Option } from 'commander';
|
|
3
|
+
const getDefault = (schema) => {
|
|
4
|
+
try {
|
|
5
|
+
return schema.parse(undefined);
|
|
6
|
+
}
|
|
7
|
+
catch {
|
|
8
|
+
return undefined;
|
|
9
|
+
}
|
|
10
|
+
};
|
|
11
|
+
export const buildCommandFromSchema = (name, description, schema, args, options) => {
|
|
12
|
+
// Initialize command
|
|
13
|
+
const command = new Command(name).description(description);
|
|
14
|
+
// Initialize the shape
|
|
15
|
+
let shape = {};
|
|
16
|
+
if (schema instanceof z.ZodUnion) {
|
|
17
|
+
for (const option of schema.options) {
|
|
18
|
+
if (option instanceof z.ZodObject) {
|
|
19
|
+
shape = { ...shape, ...option.shape };
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
shape = schema.shape;
|
|
25
|
+
}
|
|
26
|
+
// Go through schema
|
|
27
|
+
for (const [key, fieldSchema] of Object.entries(shape)) {
|
|
28
|
+
const argConfig = args?.[key];
|
|
29
|
+
const optionConfig = options?.[key];
|
|
30
|
+
let optionOrArgument;
|
|
31
|
+
if (optionConfig !== undefined) {
|
|
32
|
+
optionOrArgument = new Option(optionConfig.flags, optionConfig.description);
|
|
33
|
+
}
|
|
34
|
+
else if (argConfig !== undefined) {
|
|
35
|
+
optionOrArgument = new Argument(argConfig.flags, argConfig.description);
|
|
36
|
+
}
|
|
37
|
+
else
|
|
38
|
+
continue;
|
|
39
|
+
// 1. Try to get enum choices as a set of strings
|
|
40
|
+
const choices = fieldSchema?.options;
|
|
41
|
+
if (choices) {
|
|
42
|
+
optionOrArgument.choices(choices);
|
|
43
|
+
}
|
|
44
|
+
// 2. Try to get any default value set
|
|
45
|
+
const def = getDefault(fieldSchema);
|
|
46
|
+
if (def !== undefined) {
|
|
47
|
+
optionOrArgument.default(def);
|
|
48
|
+
}
|
|
49
|
+
// 3. Add option/argument to command
|
|
50
|
+
optionConfig !== undefined
|
|
51
|
+
? command.addOption(optionOrArgument)
|
|
52
|
+
: command.addArgument(optionOrArgument);
|
|
53
|
+
}
|
|
54
|
+
return command;
|
|
55
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { MessageRenderer } from '../../core/modules/messages.js';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
export const configureCommandErrors = (cmd) => {
|
|
4
|
+
// Configure error message for the current command
|
|
5
|
+
cmd.configureOutput({
|
|
6
|
+
writeErr: (str) => {
|
|
7
|
+
if (str.includes('error:')) {
|
|
8
|
+
const errorMessage = {
|
|
9
|
+
message: str.trim().replace('error', 'Error'),
|
|
10
|
+
chalk: chalk.red,
|
|
11
|
+
};
|
|
12
|
+
const error = new MessageRenderer(errorMessage);
|
|
13
|
+
return error.render();
|
|
14
|
+
}
|
|
15
|
+
process.stderr.write(str);
|
|
16
|
+
},
|
|
17
|
+
});
|
|
18
|
+
// Recursively configure error messages for all subcommands
|
|
19
|
+
for (const subCmd of cmd.commands) {
|
|
20
|
+
configureCommandErrors(subCmd);
|
|
21
|
+
}
|
|
22
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const formatString: (str: string, ...args: (string | number)[]) => string;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import z from 'zod';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { MESSAGES } from '../../core/modules/messages.js';
|
|
4
|
+
import { MergeError } from '../../core/mergeError.js';
|
|
5
|
+
export const toErrorMessage = (err) => {
|
|
6
|
+
let errorMessage = MESSAGES.ERROR.INTERNAL;
|
|
7
|
+
// Schema errors
|
|
8
|
+
if (err instanceof z.ZodError) {
|
|
9
|
+
errorMessage = {
|
|
10
|
+
message: z.prettifyError(err),
|
|
11
|
+
chalk: chalk.red,
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
// Merge errors
|
|
15
|
+
if (err instanceof MergeError) {
|
|
16
|
+
errorMessage = {
|
|
17
|
+
message: err.message,
|
|
18
|
+
chalk: chalk.red,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
return errorMessage;
|
|
22
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare const SUPPORTED_INPUT_FORMATS: readonly ["webp", "gif", "jpeg", "jpg", "png", "tiff", "avif", "svg"];
|
|
2
|
+
export type SupportedInputFormat = (typeof SUPPORTED_INPUT_FORMATS)[number];
|
|
3
|
+
export declare const SUPPORTED_OUTPUT_FORMATS: readonly ["webp", "gif", "jpeg", "jpg", "png", "tiff", "avif"];
|
|
4
|
+
export type SupportedOutputFormat = (typeof SUPPORTED_OUTPUT_FORMATS)[number];
|
|
5
|
+
export declare const escapeXML: (str: string) => string;
|
|
6
|
+
export declare const shuffleTogether: <T, U>(a: T[], b: U[]) => [T[], U[]];
|
|
7
|
+
export declare const shuffleArray: <T>(array: T[]) => T[];
|
|
8
|
+
export declare const isSupportedInputImage: (extname: string) => extname is SupportedInputFormat;
|
|
9
|
+
export declare const isSupportedOutputImage: (extname: string) => extname is SupportedOutputFormat;
|
|
10
|
+
export declare const isValidHexColor: (hex: string) => boolean;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export const SUPPORTED_INPUT_FORMATS = ['webp', 'gif', 'jpeg', 'jpg', 'png', 'tiff', 'avif', 'svg'];
|
|
2
|
+
export const SUPPORTED_OUTPUT_FORMATS = ['webp', 'gif', 'jpeg', 'jpg', 'png', 'tiff', 'avif'];
|
|
3
|
+
export const escapeXML = (str) => {
|
|
4
|
+
return str.replace(/[<>&'"]/g, (c) => ({
|
|
5
|
+
'<': '<',
|
|
6
|
+
'>': '>',
|
|
7
|
+
'&': '&',
|
|
8
|
+
'"': '"',
|
|
9
|
+
"'": ''',
|
|
10
|
+
}[c]));
|
|
11
|
+
};
|
|
12
|
+
export const shuffleTogether = (a, b) => {
|
|
13
|
+
if (a.length !== b.length) {
|
|
14
|
+
throw new Error('Arrays must have the same length');
|
|
15
|
+
}
|
|
16
|
+
const indices = [...a.keys()];
|
|
17
|
+
shuffleArray(indices);
|
|
18
|
+
return [indices.map((i) => a[i]), indices.map((i) => b[i])];
|
|
19
|
+
};
|
|
20
|
+
export const shuffleArray = (array) => {
|
|
21
|
+
let currentIndex = array.length;
|
|
22
|
+
let randomIndex;
|
|
23
|
+
// While there remain elements to shuffle.
|
|
24
|
+
while (currentIndex !== 0) {
|
|
25
|
+
// Pick a remaining element.
|
|
26
|
+
randomIndex = Math.floor(Math.random() * currentIndex);
|
|
27
|
+
currentIndex--;
|
|
28
|
+
// And swap it with the current element.
|
|
29
|
+
[array[currentIndex], array[randomIndex]] = [array[randomIndex], array[currentIndex]];
|
|
30
|
+
}
|
|
31
|
+
return array;
|
|
32
|
+
};
|
|
33
|
+
export const isSupportedInputImage = (extname) => {
|
|
34
|
+
return SUPPORTED_INPUT_FORMATS.includes(extname);
|
|
35
|
+
};
|
|
36
|
+
export const isSupportedOutputImage = (extname) => {
|
|
37
|
+
return SUPPORTED_OUTPUT_FORMATS.includes(extname);
|
|
38
|
+
};
|
|
39
|
+
export const isValidHexColor = (hex) => {
|
|
40
|
+
const hexRegex = /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/;
|
|
41
|
+
return hexRegex.test(hex);
|
|
42
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './merges/index.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './merges/index.js';
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { collageSchema } from '../../schemas/collage.js';
|
|
2
|
+
import { MergePipeline } from '../../pipeline/mergePipeline.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 { finalizeImagePipelines } from '../shared-steps/finalizeImagePipelines.js';
|
|
8
|
+
import { calculateImageDimensions } from './steps/calculateImageDimensions.js';
|
|
9
|
+
import { resizeAndBorderImages } from './steps/resizeAndBorderImages.js';
|
|
10
|
+
import { rotateImages } from './steps/rotateImages.js';
|
|
11
|
+
import { createComposites } from './steps/createComposites.js';
|
|
12
|
+
export const collageMerge = async (imageInputs, options, onProgress) => {
|
|
13
|
+
const context = {
|
|
14
|
+
inputs: imageInputs,
|
|
15
|
+
captions: [],
|
|
16
|
+
composites: [],
|
|
17
|
+
images: [],
|
|
18
|
+
state: {},
|
|
19
|
+
};
|
|
20
|
+
const collageMergePipeline = await MergePipeline.createPipeline(collageSchema, options, context, onProgress);
|
|
21
|
+
collageMergePipeline
|
|
22
|
+
.use(loadImages)
|
|
23
|
+
.use(calculateImageDimensions)
|
|
24
|
+
.use(resizeAndBorderImages)
|
|
25
|
+
.use(rotateImages)
|
|
26
|
+
.use(finalizeImagePipelines)
|
|
27
|
+
.use(createComposites)
|
|
28
|
+
.use(createCanvas)
|
|
29
|
+
.use(applyComposites)
|
|
30
|
+
.use(exportCanvas);
|
|
31
|
+
return await collageMergePipeline.run();
|
|
32
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { MergeStep } from '../../../pipeline/mergePipeline.js';
|
|
2
|
+
import type { CollageState } from '../index.js';
|
|
3
|
+
interface Options {
|
|
4
|
+
imageWidth?: number | undefined;
|
|
5
|
+
aspectRatio: number;
|
|
6
|
+
imageWidthVariance: number;
|
|
7
|
+
gap: number;
|
|
8
|
+
columns: number;
|
|
9
|
+
overlapPercentage: number;
|
|
10
|
+
}
|
|
11
|
+
export declare const calculateImageDimensions: MergeStep<Options, CollageState>;
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { requireNonEmptyArray } from '../../../pipeline/guards.js';
|
|
2
|
+
import { MESSAGES } from '../../../modules/messages.js';
|
|
3
|
+
import { MergeError } from '../../../mergeError.js';
|
|
4
|
+
import { getImageWidths } from '../../../utils/images/getImageWidths.js';
|
|
5
|
+
import { trimmedMedian } from '../../../utils/math/trimmedMedian.js';
|
|
6
|
+
export const calculateImageDimensions = async (context, options, _onProgress) => {
|
|
7
|
+
requireNonEmptyArray(context.images, 'images');
|
|
8
|
+
// Calculate image width
|
|
9
|
+
const width = options.imageWidth || trimmedMedian(await getImageWidths(context.images));
|
|
10
|
+
if (width === null) {
|
|
11
|
+
throw new MergeError(MESSAGES.ERROR.INTERNAL.message, { type: 'internal', cause: 'trimmedMedian failed' });
|
|
12
|
+
}
|
|
13
|
+
const height = Math.floor(width / options.aspectRatio);
|
|
14
|
+
const rows = Math.ceil(context.images.length / options.columns);
|
|
15
|
+
context.state.imageWidth = width;
|
|
16
|
+
context.state.imageHeight = height;
|
|
17
|
+
context.state.rows = rows;
|
|
18
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { MergeStep } from '../../../pipeline/mergePipeline.js';
|
|
2
|
+
import type { CollageState } from '../index.js';
|
|
3
|
+
interface Options {
|
|
4
|
+
overlapPercentage: number;
|
|
5
|
+
columns: number;
|
|
6
|
+
}
|
|
7
|
+
export declare const createComposites: MergeStep<Options, CollageState>;
|
|
8
|
+
export {};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { requireNonEmptyArray, requireState } from '../../../pipeline/guards.js';
|
|
2
|
+
import { shuffleArray } from '../../../helpers.js';
|
|
3
|
+
export const createComposites = async (context, options, onProgress) => {
|
|
4
|
+
requireState(context, 'rows');
|
|
5
|
+
requireNonEmptyArray(context.images, 'images');
|
|
6
|
+
// Used to track canvas bounds
|
|
7
|
+
let minX = Infinity;
|
|
8
|
+
let minY = Infinity;
|
|
9
|
+
let maxX = -Infinity;
|
|
10
|
+
let maxY = -Infinity;
|
|
11
|
+
for (let i = 0; i < context.state.rows; i++) {
|
|
12
|
+
for (let j = 0; j < options.columns; j++) {
|
|
13
|
+
const index = i * options.columns + j;
|
|
14
|
+
if (index >= context.images.length) {
|
|
15
|
+
break;
|
|
16
|
+
}
|
|
17
|
+
const image = context.images[index];
|
|
18
|
+
const meta = await image.metadata();
|
|
19
|
+
// Calculate overlap
|
|
20
|
+
const widthOverlap = context.state.imageWidth * (options.overlapPercentage / 100);
|
|
21
|
+
const heightOverlap = context.state.imageHeight * (options.overlapPercentage / 100);
|
|
22
|
+
// Calculate image center
|
|
23
|
+
const cx = context.state.imageWidth * i - widthOverlap * i + context.state.imageWidth / 2;
|
|
24
|
+
const cy = context.state.imageHeight * j - heightOverlap * j + context.state.imageHeight / 2;
|
|
25
|
+
// Re-center rotated bitmap on original image center
|
|
26
|
+
const top = Math.round(cx - meta.width / 2);
|
|
27
|
+
const left = Math.round(cy - meta.height / 2);
|
|
28
|
+
// Add to composites
|
|
29
|
+
context.composites.push({
|
|
30
|
+
input: await image.toBuffer(),
|
|
31
|
+
left,
|
|
32
|
+
top,
|
|
33
|
+
});
|
|
34
|
+
// Update bounds
|
|
35
|
+
minX = Math.min(minX, left);
|
|
36
|
+
minY = Math.min(minY, top);
|
|
37
|
+
maxX = Math.max(maxX, left + meta.width);
|
|
38
|
+
maxY = Math.max(maxY, top + meta.height);
|
|
39
|
+
if (onProgress) {
|
|
40
|
+
context.progressInfo.completed += 1;
|
|
41
|
+
context.progressInfo.phase = 'Merging images';
|
|
42
|
+
onProgress({ ...context.progressInfo });
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
// Offset all coords to avoid negatives
|
|
47
|
+
const offsetX = -minX;
|
|
48
|
+
const offsetY = -minY;
|
|
49
|
+
for (const c of context.composites) {
|
|
50
|
+
c.left += offsetX;
|
|
51
|
+
c.top += offsetY;
|
|
52
|
+
}
|
|
53
|
+
// Update composites
|
|
54
|
+
context.composites = shuffleArray(context.composites);
|
|
55
|
+
// Update canvas width and height
|
|
56
|
+
context.state.canvasWidth = Math.ceil(maxX - minX);
|
|
57
|
+
context.state.canvasHeight = Math.ceil(maxY - minY);
|
|
58
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { MergeStep } from '../../../pipeline/mergePipeline.js';
|
|
2
|
+
import type { CollageState } from '../index.js';
|
|
3
|
+
import type { RGBA } from '../../../utils/colors/types.js';
|
|
4
|
+
interface Options {
|
|
5
|
+
imageWidthVariance: number;
|
|
6
|
+
aspectRatio: number;
|
|
7
|
+
borderWidth: number;
|
|
8
|
+
cornerRadius: number;
|
|
9
|
+
borderColor: RGBA;
|
|
10
|
+
}
|
|
11
|
+
export declare const resizeAndBorderImages: MergeStep<Options, CollageState>;
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { requireNonEmptyArray, requireState } from '../../../pipeline/guards.js';
|
|
2
|
+
import { randint } from '../../../utils/math/randint.js';
|
|
3
|
+
import { scaleImage } from '../../../utils/images/scaleImage.js';
|
|
4
|
+
import { handleImageEdges } from '../../../utils/images/handleImageEdges.js';
|
|
5
|
+
export const resizeAndBorderImages = async (context, options, _onProgress) => {
|
|
6
|
+
requireState(context, 'imageWidth');
|
|
7
|
+
requireState(context, 'imageHeight');
|
|
8
|
+
requireNonEmptyArray(context.images, 'images');
|
|
9
|
+
for (let i = 0; i < context.images.length; i++) {
|
|
10
|
+
const varianceWidth = randint(-options.imageWidthVariance, options.imageWidthVariance);
|
|
11
|
+
const width = context.state.imageWidth + varianceWidth;
|
|
12
|
+
const varianceHeight = Math.floor(varianceWidth / options.aspectRatio);
|
|
13
|
+
const height = context.state.imageHeight + varianceHeight;
|
|
14
|
+
const resizedImage = await scaleImage(context.images[i], { width, height });
|
|
15
|
+
const borderedImage = await handleImageEdges(resizedImage, {
|
|
16
|
+
imageWidth: width,
|
|
17
|
+
imageHeight: height,
|
|
18
|
+
borderWidth: options.borderWidth,
|
|
19
|
+
borderHeight: options.borderWidth,
|
|
20
|
+
borderColor: options.borderColor,
|
|
21
|
+
cornerRadius: options.cornerRadius,
|
|
22
|
+
finalizePipeline: true,
|
|
23
|
+
});
|
|
24
|
+
context.images[i] = borderedImage;
|
|
25
|
+
}
|
|
26
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { requireNonEmptyArray } from '../../../pipeline/guards.js';
|
|
2
|
+
import { randint } from '../../../utils/math/randint.js';
|
|
3
|
+
export const rotateImages = async (context, options, _onProgress) => {
|
|
4
|
+
requireNonEmptyArray(context.images, 'images');
|
|
5
|
+
for (let i = 0; i < context.images.length; i++) {
|
|
6
|
+
const angle = randint(-options.rotationRange, options.rotationRange);
|
|
7
|
+
context.images[i] = context.images[i].toFormat('png').rotate(angle, { background: { r: 0, g: 0, b: 0, alpha: 0 } });
|
|
8
|
+
}
|
|
9
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { GridMerge } from '../types.js';
|
|
2
|
+
export interface GridState {
|
|
3
|
+
areCaptionsProvided: boolean;
|
|
4
|
+
imageWidth: number;
|
|
5
|
+
imageHeight: number;
|
|
6
|
+
canvasWidth: number;
|
|
7
|
+
canvasHeight: number;
|
|
8
|
+
captionHeight: number;
|
|
9
|
+
fontSize: number;
|
|
10
|
+
rows: number;
|
|
11
|
+
}
|
|
12
|
+
export declare const gridMerge: GridMerge;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { gridSchema } from '../../schemas/grid.js';
|
|
2
|
+
import { MergePipeline } from '../../pipeline/mergePipeline.js';
|
|
3
|
+
import { loadImages } from '../shared-steps/loadImages.js';
|
|
4
|
+
import { validateCaptions } from '../shared-steps/validateCaptions.js';
|
|
5
|
+
import { applyComposites } from '../shared-steps/applyComposites.js';
|
|
6
|
+
import { createCanvas } from '../shared-steps/createCanvas.js';
|
|
7
|
+
import { exportCanvas } from '../shared-steps/exportCanvas.js';
|
|
8
|
+
import { shuffleImagesAndCaptions } from './steps/shuffleImagesAndCaptions.js';
|
|
9
|
+
import { calculateImageDimensions } from './steps/calculateImageDimensions.js';
|
|
10
|
+
import { prepareImages } from './steps/prepareImages.js';
|
|
11
|
+
import { calculateCanvasDimensions } from './steps/calculateCanvasDimensions.js';
|
|
12
|
+
import { calculateFontSize } from './steps/calculateFontSize.js';
|
|
13
|
+
import { createComposites } from './steps/createComposites.js';
|
|
14
|
+
export const gridMerge = async (imageInputs, options, onProgress) => {
|
|
15
|
+
const context = {
|
|
16
|
+
inputs: imageInputs,
|
|
17
|
+
captions: [],
|
|
18
|
+
composites: [],
|
|
19
|
+
images: [],
|
|
20
|
+
state: {},
|
|
21
|
+
};
|
|
22
|
+
const gridMergePipeline = await MergePipeline.createPipeline(gridSchema, options, context, onProgress);
|
|
23
|
+
gridMergePipeline
|
|
24
|
+
.use(loadImages)
|
|
25
|
+
.use(validateCaptions)
|
|
26
|
+
.use(shuffleImagesAndCaptions)
|
|
27
|
+
.use(calculateImageDimensions)
|
|
28
|
+
.use(prepareImages)
|
|
29
|
+
.use(calculateCanvasDimensions)
|
|
30
|
+
.use(calculateFontSize)
|
|
31
|
+
.use(createCanvas)
|
|
32
|
+
.use(createComposites)
|
|
33
|
+
.use(applyComposites)
|
|
34
|
+
.use(exportCanvas);
|
|
35
|
+
return await gridMergePipeline.run();
|
|
36
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { MergeStep } from '../../../pipeline/mergePipeline.js';
|
|
2
|
+
import type { GridState } from '../index.js';
|
|
3
|
+
interface Options {
|
|
4
|
+
columns: number;
|
|
5
|
+
gap: number;
|
|
6
|
+
}
|
|
7
|
+
export declare const calculateCanvasDimensions: MergeStep<Options, GridState>;
|
|
8
|
+
export {};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { requireNonEmptyArray, requireState } from '../../../pipeline/guards.js';
|
|
2
|
+
export const calculateCanvasDimensions = async (context, options, _onProgress) => {
|
|
3
|
+
requireState(context, 'imageWidth');
|
|
4
|
+
requireState(context, 'imageHeight');
|
|
5
|
+
requireState(context, 'areCaptionsProvided');
|
|
6
|
+
requireNonEmptyArray(context.images, 'images');
|
|
7
|
+
const CAPTION_HEIGHT_TO_CANVAS_WIDTH_RATIO = 0.04;
|
|
8
|
+
const rows = Math.ceil(context.images.length / options.columns);
|
|
9
|
+
const canvasWidth = context.state.imageWidth * options.columns + (options.columns + 1) * options.gap;
|
|
10
|
+
const captionHeight = Math.floor(canvasWidth * CAPTION_HEIGHT_TO_CANVAS_WIDTH_RATIO);
|
|
11
|
+
const minimumCanvasHeight = context.state.imageHeight * rows + (rows + 1) * options.gap;
|
|
12
|
+
const canvasHeight = context.state.areCaptionsProvided ? minimumCanvasHeight + rows * captionHeight : minimumCanvasHeight;
|
|
13
|
+
// Assign values to state
|
|
14
|
+
context.state.rows = rows;
|
|
15
|
+
context.state.canvasWidth = canvasWidth;
|
|
16
|
+
context.state.canvasHeight = canvasHeight;
|
|
17
|
+
context.state.captionHeight = captionHeight;
|
|
18
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { requireNonEmptyArray, requireState } from '../../../pipeline/guards.js';
|
|
2
|
+
import { getFontSize } from '../../../utils/fonts/getFontSize.js';
|
|
3
|
+
export const calculateFontSize = async (context, options, _onProgress) => {
|
|
4
|
+
requireState(context, 'imageWidth');
|
|
5
|
+
requireState(context, 'captionHeight');
|
|
6
|
+
if (!context.state.areCaptionsProvided) {
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
requireNonEmptyArray(context.captions, 'captions');
|
|
10
|
+
const longestCaption = context.captions.reduce((longest, current) => {
|
|
11
|
+
return current.length > longest.length ? current : longest;
|
|
12
|
+
});
|
|
13
|
+
context.state.fontSize = await getFontSize({
|
|
14
|
+
text: longestCaption,
|
|
15
|
+
maxWidth: context.state.imageWidth,
|
|
16
|
+
maxHeight: context.state.captionHeight,
|
|
17
|
+
initialFontSize: options.maxCaptionSize,
|
|
18
|
+
});
|
|
19
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { MergeStep } from '../../../pipeline/mergePipeline.js';
|
|
2
|
+
import type { GridState } from '../index.js';
|
|
3
|
+
interface Options {
|
|
4
|
+
imageWidth?: number | undefined;
|
|
5
|
+
aspectRatio: number;
|
|
6
|
+
}
|
|
7
|
+
export declare const calculateImageDimensions: MergeStep<Options, GridState>;
|
|
8
|
+
export {};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { MESSAGES } from '../../../modules/messages.js';
|
|
2
|
+
import { MergeError } from '../../../mergeError.js';
|
|
3
|
+
import { getImageWidths } from '../../../utils/images/getImageWidths.js';
|
|
4
|
+
import { trimmedMedian } from '../../../utils/math/trimmedMedian.js';
|
|
5
|
+
import { requireNonEmptyArray } from '../../../pipeline/guards.js';
|
|
6
|
+
export const calculateImageDimensions = async (context, options, _onProgress) => {
|
|
7
|
+
requireNonEmptyArray(context.images, 'images');
|
|
8
|
+
// Calculate image width
|
|
9
|
+
const width = options.imageWidth || trimmedMedian(await getImageWidths(context.images));
|
|
10
|
+
if (width === null) {
|
|
11
|
+
throw new MergeError(MESSAGES.ERROR.INTERNAL.message, { type: 'internal', cause: 'trimmedMedian failed' });
|
|
12
|
+
}
|
|
13
|
+
// Calculate image height
|
|
14
|
+
const height = Math.floor(width / options.aspectRatio);
|
|
15
|
+
// Assign to context
|
|
16
|
+
context.state.imageWidth = width;
|
|
17
|
+
context.state.imageHeight = height;
|
|
18
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { MergeStep } from '../../../pipeline/mergePipeline.js';
|
|
2
|
+
import type { GridState } from '../index.js';
|
|
3
|
+
import type { RGBA } from '../../../utils/colors/types.js';
|
|
4
|
+
interface Options {
|
|
5
|
+
columns: number;
|
|
6
|
+
gap: number;
|
|
7
|
+
captionColor: RGBA;
|
|
8
|
+
}
|
|
9
|
+
export declare const createComposites: MergeStep<Options, GridState>;
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { requireNonEmptyArray, requireState } from '../../../pipeline/guards.js';
|
|
2
|
+
import sharp from 'sharp';
|
|
3
|
+
import { createSvgTextBuffer } from '../../../utils/svg/createSvgTextBuffer.js';
|
|
4
|
+
import { rgbaToHex } from '../../../utils/colors/rgbaToHex.js';
|
|
5
|
+
export const createComposites = async (context, options, onProgress) => {
|
|
6
|
+
requireState(context, 'areCaptionsProvided');
|
|
7
|
+
requireState(context, 'captionHeight');
|
|
8
|
+
requireState(context, 'imageWidth');
|
|
9
|
+
requireState(context, 'imageHeight');
|
|
10
|
+
requireState(context, 'rows');
|
|
11
|
+
requireNonEmptyArray(context.images, 'images');
|
|
12
|
+
// Only needed when there are captions
|
|
13
|
+
if (context.state.areCaptionsProvided) {
|
|
14
|
+
requireState(context, 'fontSize');
|
|
15
|
+
}
|
|
16
|
+
const composites = [];
|
|
17
|
+
let x = options.gap;
|
|
18
|
+
let y = options.gap;
|
|
19
|
+
for (let row = 0; row < context.state.rows; row++) {
|
|
20
|
+
for (let col = 0; col < options.columns; col++) {
|
|
21
|
+
const index = row * options.columns + col;
|
|
22
|
+
if (index >= context.images.length)
|
|
23
|
+
break;
|
|
24
|
+
const image = context.images[index];
|
|
25
|
+
composites.push({
|
|
26
|
+
input: await image.toBuffer(),
|
|
27
|
+
left: x,
|
|
28
|
+
top: y,
|
|
29
|
+
});
|
|
30
|
+
// Add caption if required
|
|
31
|
+
if (context.state.areCaptionsProvided) {
|
|
32
|
+
// Create text
|
|
33
|
+
const svgBuffer = createSvgTextBuffer({
|
|
34
|
+
text: context.captions[index],
|
|
35
|
+
maxWidth: context.state.imageWidth,
|
|
36
|
+
maxHeight: context.state.captionHeight,
|
|
37
|
+
fontSize: context.state.fontSize,
|
|
38
|
+
fill: rgbaToHex(options.captionColor),
|
|
39
|
+
});
|
|
40
|
+
// Add text to composites
|
|
41
|
+
composites.push({
|
|
42
|
+
input: svgBuffer,
|
|
43
|
+
left: x,
|
|
44
|
+
top: y + context.state.imageHeight,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
// Update coordinates
|
|
48
|
+
x += context.state.imageWidth + options.gap;
|
|
49
|
+
// Call onProgress
|
|
50
|
+
if (onProgress) {
|
|
51
|
+
context.progressInfo.completed += 1;
|
|
52
|
+
context.progressInfo.phase = 'Merging images';
|
|
53
|
+
onProgress({ ...context.progressInfo });
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
// Update coordinates
|
|
57
|
+
y += context.state.areCaptionsProvided
|
|
58
|
+
? context.state.imageHeight + options.gap + context.state.captionHeight
|
|
59
|
+
: context.state.imageHeight + options.gap;
|
|
60
|
+
x = options.gap;
|
|
61
|
+
}
|
|
62
|
+
context.composites = composites;
|
|
63
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { MergeStep } from '../../../pipeline/mergePipeline.js';
|
|
2
|
+
import type { GridState } from '../index.js';
|
|
3
|
+
import type { RGBA } from '../../../utils/colors/types.js';
|
|
4
|
+
interface Options {
|
|
5
|
+
cornerRadius: number;
|
|
6
|
+
borderWidth: number;
|
|
7
|
+
borderColor: RGBA;
|
|
8
|
+
}
|
|
9
|
+
export declare const prepareImages: MergeStep<Options, GridState>;
|
|
10
|
+
export {};
|