diff --git a/src/collages.ts b/src/collages.ts index 4d2095d..073fda7 100644 --- a/src/collages.ts +++ b/src/collages.ts @@ -1,5 +1,5 @@ -import {CollageMode} from "@/types"; -import {choice, randint, shuffle} from "@/utils"; +import {CollageConfig, CollageMode, Segment} from "@/types"; +import {choice, randint, range, shuffle} from "@/utils"; const collageModeType = [ "clean_grid", "chaos_grid", @@ -22,27 +22,40 @@ function cleanDraw(ctx: CanvasRenderingContext2D, image: ImageBitmap, ); } -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]; +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 } = { @@ -52,14 +65,8 @@ const modes: { [key in CollageModeType]: CollageMode } = { 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]); - }); - } + getSegments: getGridSegments, + place: cleanPlace }, "chaos_grid": { name: "Irregular Grid", @@ -67,125 +74,149 @@ const modes: { [key in CollageModeType]: CollageMode } = { 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); + 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, - x - (image.width * scaleRatio / 2), y - (image.height * scaleRatio / 2), + segment.x - (image.width * scaleRatio / 2), segment.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]); + 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, - 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]); + 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, - 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]); + 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, - 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); + 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, - 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; - + 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 / selectedImages.length); + factor = 1 - (1 / numImages); } - selectedImages.forEach((image, idx) => { + return range(numImages).map((idx) => { const ratio = Math.pow(factor, idx); - cleanDraw(ctx, image, x, y, ctx.canvas.width * ratio, ctx.canvas.height * ratio); + 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, - 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), - ); + 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, - 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 - ); + 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); } } }; diff --git a/src/components/Collage.vue b/src/components/Collage.vue index ad356d1..6106681 100644 --- a/src/components/Collage.vue +++ b/src/components/Collage.vue @@ -22,13 +22,19 @@ {{mode.name}} +
+ - +