autoformat
This commit is contained in:
parent
f24047b33b
commit
c89080ff00
2 changed files with 390 additions and 277 deletions
27
cli/main.ts
27
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<string> = 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,15 +88,16 @@ 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"];
|
||||
|
|
|
@ -1,87 +1,129 @@
|
|||
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);
|
||||
}
|
||||
|
||||
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 {
|
||||
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[]
|
||||
modes: readonly CollageModeType[];
|
||||
}
|
||||
|
||||
export abstract class CollageModes<C extends CollageContext, I extends CollageImage, CS extends CollageCanvas> {
|
||||
export abstract class CollageModes<
|
||||
C extends CollageContext,
|
||||
I extends CollageImage,
|
||||
CS extends CollageCanvas
|
||||
> {
|
||||
readonly modes: { [key in CollageModeType]: CollageMode<C, I> } = {
|
||||
"clean_grid": {
|
||||
clean_grid: {
|
||||
name: "Clean Grid",
|
||||
minImages: 4,
|
||||
forceConfig: {
|
||||
numImages: 4
|
||||
numImages: 4,
|
||||
},
|
||||
getSegments: this.getGridSegments,
|
||||
place: this.cleanPlace.bind(this)
|
||||
place: this.cleanPlace.bind(this),
|
||||
},
|
||||
"chaos_grid": {
|
||||
chaos_grid: {
|
||||
name: "Irregular Grid",
|
||||
minImages: 4,
|
||||
forceConfig: {
|
||||
numImages: 4
|
||||
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]) => {
|
||||
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);
|
||||
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": {
|
||||
row: {
|
||||
name: "Regular Row",
|
||||
minImages: 2,
|
||||
getSegments: (ctx, config, images) => {
|
||||
const numImages = Math.min(images?.length || Infinity, config?.numImages || randint(4) + 2);
|
||||
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]
|
||||
h: segmentSize[1],
|
||||
};
|
||||
});
|
||||
},
|
||||
place: this.cleanPlace.bind(this)
|
||||
place: this.cleanPlace.bind(this),
|
||||
},
|
||||
"irow": {
|
||||
irow: {
|
||||
name: "Irregular Row",
|
||||
minImages: 2,
|
||||
getSegments: (ctx, config, images) => {
|
||||
const numImages = Math.min(images?.length || Infinity, config?.numImages || randint(4) + 2);
|
||||
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;
|
||||
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,
|
||||
|
@ -90,35 +132,44 @@ export abstract class CollageModes<C extends CollageContext, I extends CollageIm
|
|||
};
|
||||
});
|
||||
},
|
||||
place: this.cleanPlace.bind(this)
|
||||
place: this.cleanPlace.bind(this),
|
||||
},
|
||||
"col": {
|
||||
col: {
|
||||
name: "Regular Column",
|
||||
minImages: 2,
|
||||
getSegments: (ctx, config, images) => {
|
||||
const numImages = Math.min(images?.length || Infinity, config?.numImages || randint(4) + 2);
|
||||
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]
|
||||
h: segmentSize[1],
|
||||
};
|
||||
});
|
||||
},
|
||||
place: this.cleanPlace.bind(this)
|
||||
place: this.cleanPlace.bind(this),
|
||||
},
|
||||
"icol": {
|
||||
icol: {
|
||||
name: "Irregular Column",
|
||||
minImages: 2,
|
||||
getSegments: (ctx, config, images) => {
|
||||
const numImages = Math.min(images?.length || Infinity, config?.numImages || randint(4) + 2);
|
||||
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;
|
||||
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,
|
||||
|
@ -127,18 +178,21 @@ export abstract class CollageModes<C extends CollageContext, I extends CollageIm
|
|||
};
|
||||
});
|
||||
},
|
||||
place: this.cleanPlace.bind(this)
|
||||
place: this.cleanPlace.bind(this),
|
||||
},
|
||||
"concentric_factor": {
|
||||
concentric_factor: {
|
||||
name: "Constant factor concentric",
|
||||
minImages: 2,
|
||||
getSegments: (ctx, config, images) => {
|
||||
const numImages = Math.min(images?.length || Infinity, config?.numImages || randint(4) + 2);
|
||||
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]);
|
||||
if (Math.random() > 0.5) {
|
||||
factor = choice([1 / Math.sqrt(2), 0.5, 0.88]);
|
||||
} else {
|
||||
factor = 1 - (1 / numImages);
|
||||
factor = 1 - 1 / numImages;
|
||||
}
|
||||
return range(numImages).map((idx) => {
|
||||
const ratio = Math.pow(factor, idx);
|
||||
|
@ -146,50 +200,65 @@ export abstract class CollageModes<C extends CollageContext, I extends CollageIm
|
|||
x: ctx.canvas.width / 2,
|
||||
y: ctx.canvas.height / 2,
|
||||
w: ctx.canvas.width * ratio,
|
||||
h: ctx.canvas.height * ratio
|
||||
h: ctx.canvas.height * ratio,
|
||||
};
|
||||
});
|
||||
},
|
||||
place: this.cleanPlace.bind(this)
|
||||
place: this.cleanPlace.bind(this),
|
||||
},
|
||||
"concentric_spaced": {
|
||||
concentric_spaced: {
|
||||
name: "Equally spaced concentric",
|
||||
minImages: 2,
|
||||
getSegments: (ctx, config, images) => {
|
||||
const numImages = Math.min(images?.length || Infinity, config?.numImages || randint(2) + 2);
|
||||
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),
|
||||
w: ctx.canvas.width - (ctx.canvas.width / numImages) * idx,
|
||||
h: ctx.canvas.height - (ctx.canvas.height / numImages) * idx,
|
||||
};
|
||||
});
|
||||
},
|
||||
place: this.cleanPlace.bind(this)
|
||||
place: this.cleanPlace.bind(this),
|
||||
},
|
||||
"blend": {
|
||||
blend: {
|
||||
name: "Blending",
|
||||
minImages: 2,
|
||||
getSegments: (ctx, config, images) => {
|
||||
const numImages = Math.min(images?.length || Infinity, config?.numImages || randint(2) + 2);
|
||||
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
|
||||
h: ctx.canvas.height,
|
||||
};
|
||||
});
|
||||
},
|
||||
place: (ctx, images, segments) => {
|
||||
ctx.globalCompositeOperation = choice(["difference", "saturation", "soft-light", "overlay"]);
|
||||
ctx.globalCompositeOperation = choice([
|
||||
"difference",
|
||||
"saturation",
|
||||
"soft-light",
|
||||
"overlay",
|
||||
]);
|
||||
this.cleanPlace(ctx, images, segments);
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
public async recursiveDraw(context: C, images: I[], recursiveConfig: RecursiveCollageConfig) {
|
||||
public async recursiveDraw(
|
||||
context: C,
|
||||
images: I[],
|
||||
recursiveConfig: RecursiveCollageConfig
|
||||
) {
|
||||
const localImages = images.concat();
|
||||
const rootSegment: Segment = {
|
||||
x: 0,
|
||||
|
@ -202,10 +271,7 @@ export abstract class CollageModes<C extends CollageContext, I extends CollageIm
|
|||
level: number
|
||||
): Promise<I> => {
|
||||
// console.debug(segment, level);
|
||||
if (
|
||||
segment === rootSegment ||
|
||||
level <= recursiveConfig.level - 1
|
||||
) {
|
||||
if (segment === rootSegment || level <= recursiveConfig.level - 1) {
|
||||
const canvas = this.createCanvas(segment.w, segment.h);
|
||||
const modeKey = choice(recursiveConfig.modes);
|
||||
// console.debug(modeKey);
|
||||
|
@ -232,21 +298,53 @@ export abstract class CollageModes<C extends CollageContext, I extends CollageIm
|
|||
};
|
||||
|
||||
const result = await processSegment(rootSegment, 0);
|
||||
this.drawImage(context, result, 0, 0, result.width, result.height, 0, 0, context.canvas.width, context.canvas.height);
|
||||
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) {
|
||||
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);
|
||||
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);
|
||||
this.cleanDraw(
|
||||
ctx,
|
||||
images[idx],
|
||||
segment.x,
|
||||
segment.y,
|
||||
segment.w,
|
||||
segment.h
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -255,33 +353,45 @@ export abstract class CollageModes<C extends CollageContext, I extends CollageIm
|
|||
let x!: number, y!: number;
|
||||
switch (idx) {
|
||||
case 0:
|
||||
x = ctx.canvas.width * .25;
|
||||
y = ctx.canvas.height * .25;
|
||||
x = ctx.canvas.width * 0.25;
|
||||
y = ctx.canvas.height * 0.25;
|
||||
break;
|
||||
case 1:
|
||||
x = ctx.canvas.width * .75;
|
||||
y = ctx.canvas.height * .25;
|
||||
x = ctx.canvas.width * 0.75;
|
||||
y = ctx.canvas.height * 0.25;
|
||||
break;
|
||||
case 2:
|
||||
x = ctx.canvas.width * .25;
|
||||
y = ctx.canvas.height * .75;
|
||||
x = ctx.canvas.width * 0.25;
|
||||
y = ctx.canvas.height * 0.75;
|
||||
break;
|
||||
case 3:
|
||||
x = ctx.canvas.width * .75;
|
||||
y = ctx.canvas.height * .75;
|
||||
x = ctx.canvas.width * 0.75;
|
||||
y = ctx.canvas.height * 0.75;
|
||||
break;
|
||||
}
|
||||
return {
|
||||
x, y,
|
||||
x,
|
||||
y,
|
||||
w: ctx.canvas.width / 2,
|
||||
h: ctx.canvas.height / 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<I>
|
||||
abstract canvasToImage(canvas: CS): PromiseLike<I>;
|
||||
|
||||
abstract drawImage(ctx: C, image: I, sx: number, sy: number, sw: number, sh: number, dx: number, dy: number, dw: number, dh: number): void;
|
||||
abstract drawImage(
|
||||
ctx: C,
|
||||
image: I,
|
||||
sx: number,
|
||||
sy: number,
|
||||
sw: number,
|
||||
sh: number,
|
||||
dx: number,
|
||||
dy: number,
|
||||
dw: number,
|
||||
dh: number
|
||||
): void;
|
||||
}
|
Loading…
Reference in a new issue