import {CollageConfig, CollageMode, Segment} from "@/types"; import {choice, randint, range, 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 getGridSegments(ctx: CanvasRenderingContext2D, config?: CollageConfig) { return [0, 1, 2, 3].map((idx) => { 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, w: ctx.canvas.width / 2, h: ctx.canvas.height / 2 }; }); } function cleanPlace(ctx: CanvasRenderingContext2D, images: ImageBitmap[], segments: Segment[]) { segments.forEach((segment, idx) => { cleanDraw(ctx, images[idx], segment.x, segment.y, segment.w, segment.h); }); } const modes: { [key in CollageModeType]: CollageMode } = { "clean_grid": { name: "Clean Grid", minImages: 4, forceConfig: { numImages: 4 }, getSegments: getGridSegments, place: cleanPlace }, "chaos_grid": { name: "Irregular Grid", minImages: 4, forceConfig: { numImages: 4 }, getSegments: getGridSegments, place: (ctx, images, segments) => { const shuffledImages = shuffle(images); shuffle(segments.map((segment, idx) => [segment, idx] as [Segment, number])) .forEach(([segment, idx]) => { const image = shuffledImages[idx]; const scaleRatio = Math.max(segment.w / image.width, segment.h / image.height); ctx.drawImage(image, segment.x - (image.width * scaleRatio / 2), segment.y - (image.height * scaleRatio / 2), image.width * scaleRatio, image.height * scaleRatio); }); }, }, "row": { name: "Regular Row", minImages: 2, getSegments: (ctx, config, images) => { const numImages = Math.min(images?.length || 0, config?.numImages || randint(4) + 2); const segmentSize = [ctx.canvas.width / numImages, ctx.canvas.height]; return range(numImages).map((idx) => { return { x: idx * segmentSize[0] + segmentSize[0] / 2, y: segmentSize[1] / 2, w: segmentSize[0], h: segmentSize[1] }; }); }, place: cleanPlace }, "irow": { name: "Irregular Row", minImages: 2, getSegments: (ctx, config, images) => { const numImages = Math.min(images?.length || 0, config?.numImages || randint(4) + 2); const segmentSize = [ctx.canvas.width / numImages, ctx.canvas.height]; return range(numImages).map((idx) => { const irregularWidth = images ? segmentSize[0] + Math.random() * ((segmentSize[1] / images[idx].height * images[idx].width) - segmentSize[0]) : segmentSize[0] + Math.random() * segmentSize[0] * .5; return { x: idx * segmentSize[0] + segmentSize[0] / 2, y: segmentSize[1] / 2, w: Math.min(ctx.canvas.width / 2, irregularWidth), h: segmentSize[1], }; }); }, place: cleanPlace }, "col": { name: "Regular Column", minImages: 2, getSegments: (ctx, config, images) => { const numImages = Math.min(images?.length || 0, config?.numImages || randint(4) + 2); const segmentSize = [ctx.canvas.width, ctx.canvas.height / numImages]; return range(numImages).map((idx) => { return { x: segmentSize[0] / 2, y: idx * segmentSize[1] + segmentSize[1] / 2, w: segmentSize[0], h: segmentSize[1] }; }); }, place: cleanPlace }, "icol": { name: "Irregular Column", minImages: 2, getSegments: (ctx, config, images) => { const numImages = Math.min(images?.length || 0, config?.numImages || randint(4) + 2); const segmentSize = [ctx.canvas.width, ctx.canvas.height / numImages]; return range(numImages).map((idx) => { const irregularHeight = images ? segmentSize[1] + Math.random() * ((segmentSize[0] / images[idx].width * images[idx].height) - segmentSize[1]) : segmentSize[1] + Math.random() * segmentSize[1] * .5; return { x: segmentSize[0] / 2, y: idx * segmentSize[1] + segmentSize[1] / 2, w: segmentSize[0], h: Math.min(ctx.canvas.height / 2, irregularHeight), }; }); }, place: cleanPlace }, "concentric_factor": { name: "Constant factor concentric", minImages: 2, getSegments: (ctx, config, images) => { const numImages = Math.min(images?.length || 0, config?.numImages || randint(4) + 2); let factor: number; if (Math.random() > .5) { factor = choice([1 / Math.sqrt(2), .5, .88]); } else { factor = 1 - (1 / numImages); } return range(numImages).map((idx) => { const ratio = Math.pow(factor, idx); return { x: ctx.canvas.width / 2, y: ctx.canvas.height / 2, w: ctx.canvas.width * ratio, h: ctx.canvas.height * ratio }; }); }, place: cleanPlace }, "concentric_spaced": { name: "Equally spaced concentric", minImages: 2, getSegments: (ctx, config, images) => { const numImages = Math.min(images?.length || 0, config?.numImages || randint(2) + 2); return range(numImages).map((idx) => { return { x: ctx.canvas.width / 2, y: ctx.canvas.height / 2, w: ctx.canvas.width - (ctx.canvas.width / numImages * idx), h: ctx.canvas.height - (ctx.canvas.height / numImages * idx), }; }); }, place: cleanPlace }, "blend": { name: "Blending", minImages: 2, getSegments: (ctx, config, images) => { const numImages = Math.min(images?.length || 0, config?.numImages || randint(2) + 2); return range(numImages).map((_) => { return { x: ctx.canvas.width / 2, y: ctx.canvas.height / 2, w: ctx.canvas.width, h: ctx.canvas.height }; }); }, place: (ctx, images, segments) => { ctx.globalCompositeOperation = choice(["difference", "saturation", "soft-light", "overlay"]); cleanPlace(ctx, images, segments); } } }; export default modes;