import {CollageMode} from "@/types"; import {choice, randint, shuffle} from "@/utils"; const collageModeType = [ "clean_grid", "chaos_grid", "row", "irow", "col", "icol", "concentric_factor", "concentric_spaced", "blend" ] as const; export type CollageModeType = typeof collageModeType[number]; function cleanDraw(ctx: CanvasRenderingContext2D, image: ImageBitmap, x: number, y: number, w: number, h: number) { const scaleRatio = Math.max(w / image.width, h / image.height); ctx.drawImage( image, image.width / 2 - w / scaleRatio / 2, image.height / 2 - h / scaleRatio / 2, w / scaleRatio, h / scaleRatio, x - w / 2, y - h / 2, w, h ); } function getGridPoints(ctx: CanvasRenderingContext2D, idx: number) { let x!: number, y!: number; switch (idx) { case 0: x = ctx.canvas.width * .25; y = ctx.canvas.height * .25; break; case 1: x = ctx.canvas.width * .75; y = ctx.canvas.height * .25; break; case 2: x = ctx.canvas.width * .25; y = ctx.canvas.height * .75; break; case 3: x = ctx.canvas.width * .75; y = ctx.canvas.height * .75; break; } return [x, y]; } const modes: { [key in CollageModeType]: CollageMode } = { "clean_grid": { name: "Clean Grid", minImages: 4, forceConfig: { numImages: 4 }, place: (ctx, images, config) => { const quadrantSize = [ctx.canvas.width / 2, ctx.canvas.height / 2]; const selectedImages = shuffle(images).slice(0, 4); selectedImages.forEach((image, idx) => { const [x, y] = getGridPoints(ctx, idx); cleanDraw(ctx, image, x, y, quadrantSize[0], quadrantSize[1]); }); } }, "chaos_grid": { name: "Irregular Grid", minImages: 4, forceConfig: { numImages: 4 }, place: (ctx, images, config) => { const quadrantSize = [ctx.canvas.width / 2, ctx.canvas.height / 2]; const selectedImages = shuffle(images).slice(0, 4); shuffle(selectedImages.map((image, idx) => [image, idx] as [ImageBitmap, number])) .forEach(([image, idx]) => { const [x, y] = getGridPoints(ctx, idx); const scaleRatio = Math.max(quadrantSize[0] / image.width, quadrantSize[1] / image.height); ctx.drawImage(image, x - (image.width * scaleRatio / 2), y - (image.height * scaleRatio / 2), image.width * scaleRatio, image.height * scaleRatio); }); } }, "row": { name: "Regular Row", minImages: 2, place: (ctx, images, config) => { const selectedImages = shuffle(images).slice(0, config.numImages || randint(4) + 2); const quadrantSize = [ctx.canvas.width / selectedImages.length, ctx.canvas.height]; selectedImages.forEach((image, idx) => { const x = idx * quadrantSize[0] + quadrantSize[0] / 2; const y = quadrantSize[1] / 2; cleanDraw(ctx, image, x, y, quadrantSize[0], quadrantSize[1]); }); } }, "irow": { name: "Irregular Row", minImages: 2, place: (ctx, images, config) => { const selectedImages = shuffle(images).slice(0, config.numImages || randint(4) + 2); const quadrantSize = [ctx.canvas.width / selectedImages.length, ctx.canvas.height]; selectedImages.forEach((image, idx) => { const x = idx * quadrantSize[0] + quadrantSize[0] / 2; const y = quadrantSize[1] / 2; const w = Math.min(ctx.canvas.width / 2, quadrantSize[1] + Math.random() * (quadrantSize[1] - (quadrantSize[1] / image.height) * image.width)); cleanDraw(ctx, image, x, y, w, quadrantSize[1]); }); } }, "col": { name: "Regular Column", minImages: 2, place: (ctx, images, config) => { const selectedImages = shuffle(images).slice(0, config.numImages || randint(4) + 2); const quadrantSize = [ctx.canvas.width, ctx.canvas.height / selectedImages.length]; selectedImages.forEach((image, idx) => { const x = quadrantSize[0] / 2; const y = idx * quadrantSize[1] + quadrantSize[1] / 2; cleanDraw(ctx, image, x, y, quadrantSize[0], quadrantSize[1]); }); } }, "icol": { name: "Irregular Column", minImages: 2, place: (ctx, images, config) => { const selectedImages = shuffle(images).slice(0, config.numImages || randint(4) + 2); const quadrantSize = [ctx.canvas.width, ctx.canvas.height / selectedImages.length]; selectedImages.forEach((image, idx) => { const x = quadrantSize[0] / 2; const y = idx * quadrantSize[1] + quadrantSize[1] / 2; const h = Math.min(ctx.canvas.height / 2, quadrantSize[0] + Math.random() * (quadrantSize[0] - (quadrantSize[0] / image.width) * image.height)); cleanDraw(ctx, image, x, y, quadrantSize[0], h); }); }, }, "concentric_factor": { name: "Constant factor concentric", minImages: 2, place: (ctx, images, config) => { const selectedImages = shuffle(images).slice(0, config.numImages || randint(4) + 2); const x = ctx.canvas.width / 2; const y = ctx.canvas.height / 2; let factor: number; if (Math.random() > .5) { factor = choice([1 / Math.sqrt(2), .5, .88]); } else { factor = 1 - (1 / selectedImages.length); } selectedImages.forEach((image, idx) => { const ratio = Math.pow(factor, idx); cleanDraw(ctx, image, x, y, ctx.canvas.width * ratio, ctx.canvas.height * ratio); }); } }, "concentric_spaced": { name: "Equally spaced concentric", minImages: 2, place: (ctx, images, config) => { const selectedImages = shuffle(images).slice(0, config.numImages || randint(4) + 2); selectedImages.forEach((image, idx) => { cleanDraw( ctx, image, ctx.canvas.width / 2, ctx.canvas.height / 2, ctx.canvas.width - (ctx.canvas.width / selectedImages.length * idx), ctx.canvas.height - (ctx.canvas.height / selectedImages.length * idx), ); }); } }, "blend": { name: "Blending", minImages: 2, place: (ctx, images, config) => { const selectedImages = shuffle(images).slice(0, config.numImages || randint(2) + 2); ctx.globalCompositeOperation = choice(["difference", "saturation", "soft-light", "overlay"]); selectedImages.forEach((image) => { cleanDraw( ctx, image, ctx.canvas.width / 2, ctx.canvas.height / 2, ctx.canvas.width, ctx.canvas.height ); }); } } }; export default modes;