I want to write a function that gets multiple image urls as input and returns them as a masonry image.
The code I came up with so far looks like this:
export interface ImagesToMasonryOptions {
width: number;
height: number;
quality?: number;
imageType?: string;
}
export default async function imagesToMasonry(
urls: string[],
options: ImagesToMasonryOptions
): Promise<string> {
if (urls.length === 1) return urls[0];
const canvas = document.createElement("canvas");
canvas.width = options.width;
canvas.height = options.height;
const ctx = canvas.getContext("2d");
const images: HTMLImageElement[] = [];
await new Promise<void>((resolve) => {
urls.forEach((url) => {
const image = new Image();
image.src = url;
image.crossOrigin = "anonymous";
image.onload = () => {
images.push(image);
if (images.length === urls.length) {
resolve();
}
};
});
});
drawImagesToCanvas();
function drawImagesToCanvas() {
if (!ctx) return;
let offsetX = 0;
let offsetY = 0;
images.forEach((image) => {
ctx.drawImage(image, offsetX, offsetY);
offsetX += image.width;
if (offsetX > options.width) {
offsetY += image.height;
offsetX = 0;
}
});
}
return canvas.toDataURL(
options.imageType || "image/jpeg",
options.quality || 1.0
);
}
My problem is the positioning of the images. I don't know of an algorithm to do this properly.
An example of what I want to archive:

The colored tiles are the images. The images can be scaled, but their aspect ratio has to stay the same. It is ok to "cut" images, but the area A * B has to be completely covered by the images.