From c89080ff009285f1ae229992410ce8b02da82eee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Ml=C3=A1dek?= Date: Wed, 19 Jan 2022 21:26:06 +0100 Subject: [PATCH] autoformat --- cli/main.ts | 29 +- src/common/collages.ts | 638 ++++++++++++++++++++++++----------------- 2 files changed, 390 insertions(+), 277 deletions(-) diff --git a/cli/main.ts b/cli/main.ts index f7e3326..e801e34 100644 --- a/cli/main.ts +++ b/cli/main.ts @@ -25,7 +25,7 @@ const args = parse(Deno.args, { include: "*.png, *.jpg", output: "collage.png", rl: 2, - rr: false + rr: false, }, }); @@ -36,7 +36,7 @@ if (args["mode"] === true) { const files: Set = new Set(); const includeExtensions = Array.from( - String(args["include"]).matchAll(/\*\.([\w]+)/g), + String(args["include"]).matchAll(/\*\.([\w]+)/g) ).map(([_, group]) => group); args["_"].forEach((arg) => { @@ -48,7 +48,7 @@ args["_"].forEach((arg) => { includeDirs: false, includeFiles: true, exts: includeExtensions.length ? includeExtensions : undefined, - }), + }) ).forEach((entry) => files.add(entry.path)); } else { files.add(arg); @@ -64,7 +64,9 @@ const shuffledFiles = shuffle(Array.from(files)); const images: ProxyImage[] = shuffledFiles.map((file) => new ProxyImage(file)); -const allModeKeys = args["mode"] ? parseDisplayCollageModes(args["mode"]) : displayCollageModeType; +const allModeKeys = args["mode"] + ? parseDisplayCollageModes(args["mode"]) + : displayCollageModeType; const canvas = createCanvas(args["width"], args["height"]); const context = canvas.getContext("2d"); @@ -76,7 +78,7 @@ const modeKey: DisplayCollageModeType = choice(allModeKeys); if (modeKey === "recursive") { console.log( - `Creating a recursive collage, choosing from ${shuffledFiles.length} files...`, + `Creating a recursive collage, choosing from ${shuffledFiles.length} files...` ); await denoCollageModes.recursiveDraw(context, images, { modes: args["rm"] ? parseCollageModes(args["rm"]) : collageModeType, @@ -86,17 +88,18 @@ if (modeKey === "recursive") { } else { const mode = denoCollageModes.modes[modeKey]; console.log( - `Creating a "${mode.name}" collage, choosing from ${shuffledFiles.length} files...`, - ); - const segments = mode.getSegments( - context, - collageConfig, - images, + `Creating a "${mode.name}" collage, choosing from ${shuffledFiles.length} files...` ); + const segments = mode.getSegments(context, collageConfig, images); mode.place(context, images, segments); - console.log(`Used: ${images.slice(0, segments.length).map((img) => img.path).join(", ")}`) + console.log( + `Used: ${images + .slice(0, segments.length) + .map((img) => img.path) + .join(", ")}` + ); } const output = args["output"]; console.log(`Saving to "${output}"...`); -await Deno.writeFile(output, canvas.toBuffer()); \ No newline at end of file +await Deno.writeFile(output, canvas.toBuffer()); diff --git a/src/common/collages.ts b/src/common/collages.ts index f9522bc..88cc9fd 100644 --- a/src/common/collages.ts +++ b/src/common/collages.ts @@ -1,287 +1,397 @@ -import { CollageCanvas, CollageConfig, CollageContext, CollageImage, CollageMode, Segment } from "@/common/types"; +import { + CollageCanvas, + CollageConfig, + CollageContext, + CollageImage, + CollageMode, + Segment, +} from "@/common/types"; import { choice, randint, range, shuffle } from "@/common/utils"; export const collageModeType = [ - "clean_grid", "chaos_grid", - "row", "irow", - "col", "icol", - "concentric_factor", "concentric_spaced", - "blend" + "clean_grid", + "chaos_grid", + "row", + "irow", + "col", + "icol", + "concentric_factor", + "concentric_spaced", + "blend", ] as const; export type CollageModeType = typeof collageModeType[number]; export function isCollageModeType(value: string): value is CollageModeType { - return (collageModeType as readonly string[]).includes(value); + return (collageModeType as readonly string[]).includes(value); } -export const displayCollageModeType = [...collageModeType, "recursive"] as const; +export const displayCollageModeType = [ + ...collageModeType, + "recursive", +] as const; export type DisplayCollageModeType = typeof displayCollageModeType[number]; -export function isDisplayCollageModeType(value: string): value is DisplayCollageModeType { - return (displayCollageModeType as readonly string[]).includes(value); +export function isDisplayCollageModeType( + value: string +): value is DisplayCollageModeType { + return (displayCollageModeType as readonly string[]).includes(value); } export interface RecursiveCollageConfig { - level: number; - repeat: boolean; - modes: readonly CollageModeType[] + level: number; + repeat: boolean; + modes: readonly CollageModeType[]; } -export abstract class CollageModes { - readonly modes: { [key in CollageModeType]: CollageMode } = { - "clean_grid": { - name: "Clean Grid", - minImages: 4, - forceConfig: { - numImages: 4 - }, - getSegments: this.getGridSegments, - place: this.cleanPlace.bind(this) - }, - "chaos_grid": { - name: "Irregular Grid", - minImages: 4, - forceConfig: { - numImages: 4 - }, - getSegments: this.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); - this.drawImage(ctx, image, 0, 0, image.width, image.height, - 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 || Infinity, 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: this.cleanPlace.bind(this) - }, - "irow": { - name: "Irregular Row", - minImages: 2, - getSegments: (ctx, config, images) => { - const numImages = Math.min(images?.length || Infinity, 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: this.cleanPlace.bind(this) - }, - "col": { - name: "Regular Column", - minImages: 2, - getSegments: (ctx, config, images) => { - const numImages = Math.min(images?.length || Infinity, 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: this.cleanPlace.bind(this) - }, - "icol": { - name: "Irregular Column", - minImages: 2, - getSegments: (ctx, config, images) => { - const numImages = Math.min(images?.length || Infinity, 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: this.cleanPlace.bind(this) - }, - "concentric_factor": { - name: "Constant factor concentric", - minImages: 2, - getSegments: (ctx, config, images) => { - const numImages = Math.min(images?.length || Infinity, 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: this.cleanPlace.bind(this) - }, - "concentric_spaced": { - name: "Equally spaced concentric", - minImages: 2, - getSegments: (ctx, config, images) => { - const numImages = Math.min(images?.length || Infinity, 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: this.cleanPlace.bind(this) - }, - "blend": { - name: "Blending", - minImages: 2, - getSegments: (ctx, config, images) => { - const numImages = Math.min(images?.length || Infinity, 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"]); - this.cleanPlace(ctx, images, segments); - } +export abstract class CollageModes< + C extends CollageContext, + I extends CollageImage, + CS extends CollageCanvas +> { + readonly modes: { [key in CollageModeType]: CollageMode } = { + clean_grid: { + name: "Clean Grid", + minImages: 4, + forceConfig: { + numImages: 4, + }, + getSegments: this.getGridSegments, + place: this.cleanPlace.bind(this), + }, + chaos_grid: { + name: "Irregular Grid", + minImages: 4, + forceConfig: { + numImages: 4, + }, + getSegments: this.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 + ); + this.drawImage( + ctx, + image, + 0, + 0, + image.width, + image.height, + 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 || Infinity, + 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: this.cleanPlace.bind(this), + }, + irow: { + name: "Irregular Row", + minImages: 2, + getSegments: (ctx, config, images) => { + const numImages = Math.min( + images?.length || Infinity, + 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] * 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: this.cleanPlace.bind(this), + }, + col: { + name: "Regular Column", + minImages: 2, + getSegments: (ctx, config, images) => { + const numImages = Math.min( + images?.length || Infinity, + 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: this.cleanPlace.bind(this), + }, + icol: { + name: "Irregular Column", + minImages: 2, + getSegments: (ctx, config, images) => { + const numImages = Math.min( + images?.length || Infinity, + 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] * 0.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: this.cleanPlace.bind(this), + }, + concentric_factor: { + name: "Constant factor concentric", + minImages: 2, + getSegments: (ctx, config, images) => { + const numImages = Math.min( + images?.length || Infinity, + config?.numImages || randint(4) + 2 + ); + let factor: number; + if (Math.random() > 0.5) { + factor = choice([1 / Math.sqrt(2), 0.5, 0.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: this.cleanPlace.bind(this), + }, + concentric_spaced: { + name: "Equally spaced concentric", + minImages: 2, + getSegments: (ctx, config, images) => { + const numImages = Math.min( + images?.length || Infinity, + 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: this.cleanPlace.bind(this), + }, + blend: { + name: "Blending", + minImages: 2, + getSegments: (ctx, config, images) => { + const numImages = Math.min( + images?.length || Infinity, + 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", + ]); + this.cleanPlace(ctx, images, segments); + }, + }, + }; + + public async recursiveDraw( + context: C, + images: I[], + recursiveConfig: RecursiveCollageConfig + ) { + const localImages = images.concat(); + const rootSegment: Segment = { + x: 0, + y: 0, + w: context.canvas.width, + h: context.canvas.height, + }; + const processSegment = async ( + segment: Segment, + level: number + ): Promise => { + // console.debug(segment, level); + if (segment === rootSegment || level <= recursiveConfig.level - 1) { + const canvas = this.createCanvas(segment.w, segment.h); + const modeKey = choice(recursiveConfig.modes); + // console.debug(modeKey); + const mode = this.modes[modeKey]; + const ctx = canvas.getContext("2d") as C; + const segments = mode.getSegments(ctx); + // console.debug(segments); + const bitmaps = await Promise.all( + segments.map((segment: Segment) => processSegment(segment, level + 1)) + ); + mode.place(ctx, bitmaps, segments); + return await this.canvasToImage(canvas); + } else { + if (recursiveConfig.repeat) { + return choice(localImages); + } else { + if (localImages.length > 0) { + return localImages.pop() as I; + } else { + throw "RAN OUT OF IMAGES"; + } + } + } }; - public async recursiveDraw(context: C, images: I[], recursiveConfig: RecursiveCollageConfig) { - const localImages = images.concat(); - const rootSegment: Segment = { - x: 0, - y: 0, - w: context.canvas.width, - h: context.canvas.height, - }; - const processSegment = async ( - segment: Segment, - level: number - ): Promise => { - // console.debug(segment, level); - if ( - segment === rootSegment || - level <= recursiveConfig.level - 1 - ) { - const canvas = this.createCanvas(segment.w, segment.h); - const modeKey = choice(recursiveConfig.modes); - // console.debug(modeKey); - const mode = this.modes[modeKey]; - const ctx = canvas.getContext("2d") as C; - const segments = mode.getSegments(ctx); - // console.debug(segments); - const bitmaps = await Promise.all( - segments.map((segment: Segment) => processSegment(segment, level + 1)) - ); - mode.place(ctx, bitmaps, segments); - return await this.canvasToImage(canvas); - } else { - if (recursiveConfig.repeat) { - return choice(localImages); - } else { - if (localImages.length > 0) { - return localImages.pop() as I; - } else { - throw "RAN OUT OF IMAGES"; - } - } - } - }; + const result = await processSegment(rootSegment, 0); + this.drawImage( + context, + result, + 0, + 0, + result.width, + result.height, + 0, + 0, + context.canvas.width, + context.canvas.height + ); + } - const result = await processSegment(rootSegment, 0); - this.drawImage(context, result, 0, 0, result.width, result.height, 0, 0, context.canvas.width, context.canvas.height); - } + private cleanDraw( + ctx: C, + image: I, + x: number, + y: number, + w: number, + h: number + ) { + const scaleRatio = Math.max(w / image.width, h / image.height); + this.drawImage( + ctx, + 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 + ); + } - private cleanDraw(ctx: C, image: I, - x: number, y: number, w: number, h: number) { - const scaleRatio = Math.max(w / image.width, h / image.height); - this.drawImage(ctx, 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); - } + private cleanPlace(ctx: C, images: I[], segments: Segment[]) { + segments.forEach((segment, idx) => { + this.cleanDraw( + ctx, + images[idx], + segment.x, + segment.y, + segment.w, + segment.h + ); + }); + } - private cleanPlace(ctx: C, images: I[], segments: Segment[]) { - segments.forEach((segment, idx) => { - this.cleanDraw(ctx, images[idx], segment.x, segment.y, segment.w, segment.h); - }); - } + private getGridSegments(ctx: C, config?: CollageConfig) { + return [0, 1, 2, 3].map((idx) => { + let x!: number, y!: number; + switch (idx) { + case 0: + x = ctx.canvas.width * 0.25; + y = ctx.canvas.height * 0.25; + break; + case 1: + x = ctx.canvas.width * 0.75; + y = ctx.canvas.height * 0.25; + break; + case 2: + x = ctx.canvas.width * 0.25; + y = ctx.canvas.height * 0.75; + break; + case 3: + x = ctx.canvas.width * 0.75; + y = ctx.canvas.height * 0.75; + break; + } + return { + x, + y, + w: ctx.canvas.width / 2, + h: ctx.canvas.height / 2, + }; + }); + } - private getGridSegments(ctx: C, 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 - }; - }); - } + abstract createCanvas(w: number, h: number): CS; - abstract createCanvas(w: number, h: number): CS + abstract canvasToImage(canvas: CS): PromiseLike; - abstract canvasToImage(canvas: CS): PromiseLike - - abstract drawImage(ctx: C, image: I, sx: number, sy: number, sw: number, sh: number, dx: number, dy: number, dw: number, dh: number): void; -} \ No newline at end of file + abstract drawImage( + ctx: C, + image: I, + sx: number, + sy: number, + sw: number, + sh: number, + dx: number, + dy: number, + dw: number, + dh: number + ): void; +}