224 lines
8.1 KiB
TypeScript
224 lines
8.1 KiB
TypeScript
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;
|