add recursion

This commit is contained in:
Tomáš Mládek 2020-07-17 19:14:42 +02:00
parent 7cf393af71
commit 64fa7098a0
2 changed files with 92 additions and 25 deletions

View file

@ -1,7 +1,7 @@
import {CollageConfig, CollageMode, Segment} from "@/types"; import {CollageConfig, CollageMode, Segment} from "@/types";
import {choice, randint, range, shuffle} from "@/utils"; import {choice, randint, range, shuffle} from "@/utils";
const collageModeType = [ export const collageModeType = [
"clean_grid", "chaos_grid", "clean_grid", "chaos_grid",
"row", "irow", "row", "irow",
"col", "icol", "col", "icol",
@ -91,7 +91,7 @@ const modes: { [key in CollageModeType]: CollageMode } = {
name: "Regular Row", name: "Regular Row",
minImages: 2, minImages: 2,
getSegments: (ctx, config, images) => { getSegments: (ctx, config, images) => {
const numImages = Math.min(images?.length || 0, 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]; const segmentSize = [ctx.canvas.width / numImages, ctx.canvas.height];
return range(numImages).map((idx) => { return range(numImages).map((idx) => {
return { return {
@ -108,7 +108,7 @@ const modes: { [key in CollageModeType]: CollageMode } = {
name: "Irregular Row", name: "Irregular Row",
minImages: 2, minImages: 2,
getSegments: (ctx, config, images) => { getSegments: (ctx, config, images) => {
const numImages = Math.min(images?.length || 0, 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]; const segmentSize = [ctx.canvas.width / numImages, ctx.canvas.height];
return range(numImages).map((idx) => { return range(numImages).map((idx) => {
const irregularWidth = images ? const irregularWidth = images ?
@ -128,7 +128,7 @@ const modes: { [key in CollageModeType]: CollageMode } = {
name: "Regular Column", name: "Regular Column",
minImages: 2, minImages: 2,
getSegments: (ctx, config, images) => { getSegments: (ctx, config, images) => {
const numImages = Math.min(images?.length || 0, 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]; const segmentSize = [ctx.canvas.width, ctx.canvas.height / numImages];
return range(numImages).map((idx) => { return range(numImages).map((idx) => {
return { return {
@ -145,7 +145,7 @@ const modes: { [key in CollageModeType]: CollageMode } = {
name: "Irregular Column", name: "Irregular Column",
minImages: 2, minImages: 2,
getSegments: (ctx, config, images) => { getSegments: (ctx, config, images) => {
const numImages = Math.min(images?.length || 0, 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]; const segmentSize = [ctx.canvas.width, ctx.canvas.height / numImages];
return range(numImages).map((idx) => { return range(numImages).map((idx) => {
const irregularHeight = images ? const irregularHeight = images ?
@ -165,7 +165,7 @@ const modes: { [key in CollageModeType]: CollageMode } = {
name: "Constant factor concentric", name: "Constant factor concentric",
minImages: 2, minImages: 2,
getSegments: (ctx, config, images) => { getSegments: (ctx, config, images) => {
const numImages = Math.min(images?.length || 0, config?.numImages || randint(4) + 2); const numImages = Math.min(images?.length || Infinity, config?.numImages || randint(4) + 2);
let factor: number; let factor: number;
if (Math.random() > .5) { if (Math.random() > .5) {
factor = choice([1 / Math.sqrt(2), .5, .88]); factor = choice([1 / Math.sqrt(2), .5, .88]);
@ -188,7 +188,7 @@ const modes: { [key in CollageModeType]: CollageMode } = {
name: "Equally spaced concentric", name: "Equally spaced concentric",
minImages: 2, minImages: 2,
getSegments: (ctx, config, images) => { getSegments: (ctx, config, images) => {
const numImages = Math.min(images?.length || 0, config?.numImages || randint(2) + 2); const numImages = Math.min(images?.length || Infinity, config?.numImages || randint(2) + 2);
return range(numImages).map((idx) => { return range(numImages).map((idx) => {
return { return {
x: ctx.canvas.width / 2, x: ctx.canvas.width / 2,
@ -204,7 +204,7 @@ const modes: { [key in CollageModeType]: CollageMode } = {
name: "Blending", name: "Blending",
minImages: 2, minImages: 2,
getSegments: (ctx, config, images) => { getSegments: (ctx, config, images) => {
const numImages = Math.min(images?.length || 0, config?.numImages || randint(2) + 2); const numImages = Math.min(images?.length || Infinity, config?.numImages || randint(2) + 2);
return range(numImages).map((_) => { return range(numImages).map((_) => {
return { return {
x: ctx.canvas.width / 2, x: ctx.canvas.width / 2,

View file

@ -18,7 +18,7 @@
<label v-for="(mode, idx) in modes" <label v-for="(mode, idx) in modes"
:class="{disabled: images.length < mode.minImages, :class="{disabled: images.length < mode.minImages,
selected: idx === currentModeType, selected: idx === currentModeType,
lastActive: idx === lastActiveModeType}"> lastActive: lastActiveModeTypes.includes(idx)}">
{{mode.name}} {{mode.name}}
<input type="radio" :value="idx" v-model="currentModeType"> <input type="radio" :value="idx" v-model="currentModeType">
</label> </label>
@ -28,10 +28,16 @@
!SHUFFLE ALL! !SHUFFLE ALL!
<input type="radio" value="shuffle" v-model="currentModeType"> <input type="radio" value="shuffle" v-model="currentModeType">
</label> </label>
<label :class="{disabled: images.length < minImages,
selected: 'recursive' === currentModeType}">
#RECURSIVE#
<input type="radio" value="recursive" v-model="currentModeType">
</label>
</div> </div>
<button :disabled="images.length < minImages" @click="renderCollage">REPAINT</button> <button :disabled="images.length < minImages" @click="renderCollage">REPAINT</button>
<hr> <hr>
<div class="config"> <div class="config">
<template v-if="currentModeType !== 'recursive'">
<label class="config-numimages"> <label class="config-numimages">
#N of images: #N of images:
<input type="number" :min="minImages" :max="images.length" <input type="number" :min="minImages" :max="images.length"
@ -39,6 +45,18 @@
:disabled="Object.keys(forceConfig).includes('numImages')" :disabled="Object.keys(forceConfig).includes('numImages')"
v-model="forceConfig.numImages || collageConfig.numImages"> v-model="forceConfig.numImages || collageConfig.numImages">
</label> </label>
</template>
<template v-else>
<label>
Recursion levels:
<input type="number" :min="1" :max="10" v-model="recursiveConfig.level">
</label>
<label>
<input type="checkbox" v-model="recursiveConfig.repeat">
Repeat images?
</label>
</template>
</div> </div>
</div> </div>
</div> </div>
@ -47,10 +65,10 @@
<script lang="ts"> <script lang="ts">
import {Component, Prop, Vue, Watch} from "vue-property-decorator"; import {Component, Prop, Vue, Watch} from "vue-property-decorator";
import collageModes, {CollageModeType} from "../collages"; import collageModes, {CollageModeType} from "../collages";
import {CollageConfig, CollageMode} from "@/types"; import {CollageConfig, CollageMode, Segment} from "@/types";
import {choice, shuffle} from "@/utils"; import {choice, shuffle} from "@/utils";
type DisplayCollageModeType = CollageModeType | & "shuffle"; type DisplayCollageModeType = CollageModeType | & "shuffle" | & "recursive";
@Component @Component
export default class Collage extends Vue { export default class Collage extends Vue {
@ -63,12 +81,16 @@ export default class Collage extends Vue {
private collageConfig: CollageConfig = { private collageConfig: CollageConfig = {
numImages: undefined numImages: undefined
}; };
private recursiveConfig = {
level: 2,
repeat: true
};
private currentModeType: DisplayCollageModeType = "shuffle"; private currentModeType: DisplayCollageModeType = "shuffle";
private lastActiveModeType: CollageModeType | null = null; private lastActiveModeTypes: CollageModeType[] = [];
private modes = collageModes; private modes = collageModes;
private get minImages() { private get minImages() {
if (this.currentModeType === "shuffle") { if (this.currentModeType === "shuffle" || this.currentModeType === "recursive") {
return Math.min(...Object.values(this.modes).map((mode) => mode.minImages)); return Math.min(...Object.values(this.modes).map((mode) => mode.minImages));
} else { } else {
return this.modes[this.currentModeType].minImages; return this.modes[this.currentModeType].minImages;
@ -76,7 +98,9 @@ export default class Collage extends Vue {
} }
private get lastMode() { private get lastMode() {
return this.lastActiveModeType ? this.modes[this.lastActiveModeType] : undefined; if (this.lastActiveModeTypes.length === 1) {
return this.modes[this.lastActiveModeTypes[0]];
}
} }
private get forceConfig() { private get forceConfig() {
@ -91,24 +115,67 @@ export default class Collage extends Vue {
@Watch("images") @Watch("images")
@Watch("currentModeType") @Watch("currentModeType")
@Watch("collageConfig", {deep: true}) @Watch("collageConfig", {deep: true})
@Watch("recursiveConfig", {deep: true})
private renderCollage() { private renderCollage() {
if (this.images.length >= this.minImages) { if (this.images.length >= this.minImages) {
this.reset(); this.reset();
this.lastActiveModeType = this.currentModeType === "shuffle" ? null : this.currentModeType;
if (this.currentModeType !== "recursive") {
let mode: CollageMode; let mode: CollageMode;
if (this.currentModeType === "shuffle") { if (this.currentModeType === "shuffle") {
const permissibleModeKeys = Object.keys(collageModes) const permissibleModeKeys = Object.keys(collageModes)
.filter(k => collageModes[k as CollageModeType].minImages <= this.images.length) as CollageModeType[]; .filter(k => collageModes[k as CollageModeType].minImages <= this.images.length) as CollageModeType[];
const randomModeType = choice(permissibleModeKeys); const randomModeType = choice(permissibleModeKeys);
this.lastActiveModeType = randomModeType; this.lastActiveModeTypes = [randomModeType];
mode = collageModes[randomModeType]; mode = collageModes[randomModeType];
} else { } else {
this.lastActiveModeTypes = [this.currentModeType];
mode = this.modes[this.currentModeType]; mode = this.modes[this.currentModeType];
} }
const shuffledImages = shuffle(this.images); const shuffledImages = shuffle(this.images);
const segments = mode.getSegments(this.context, this.collageConfig, shuffledImages); const segments = mode.getSegments(this.context, this.collageConfig, shuffledImages);
mode.place(this.context, shuffledImages, segments); mode.place(this.context, shuffledImages, segments);
} else {
const permissibleModeKeys = Object.keys(collageModes)
.filter(k => collageModes[k as CollageModeType].minImages <= this.images.length) as CollageModeType[];
this.lastActiveModeTypes = [];
const shuffledImages = shuffle(this.images);
const rootSegment: Segment = {x: 0, y: 0, w: this.context.canvas.width, h: this.context.canvas.height};
const processSegment = async (segment: Segment, level: number): Promise<ImageBitmap> => {
console.log(segment, level);
if (segment === rootSegment || level <= this.recursiveConfig.level - 1) {
let canvas = document.createElement("canvas");
canvas.width = segment.w;
canvas.height = segment.h;
let modeKey = choice(permissibleModeKeys);
console.log(modeKey);
this.lastActiveModeTypes.push(modeKey);
let mode = this.modes[modeKey];
let ctx = canvas.getContext("2d") as CanvasRenderingContext2D;
let segments = mode.getSegments(ctx);
console.log(segments);
let bitmaps = await Promise.all(segments.map((segment) => processSegment(segment, level + 1)));
mode.place(ctx, bitmaps, segments);
return await createImageBitmap(canvas);
} else {
if (this.recursiveConfig.repeat) {
return choice(shuffledImages);
} else {
if (shuffledImages.length > 0) {
return shuffledImages.pop() as ImageBitmap;
} else {
throw "RAN OUT OF IMAGES";
}
}
}
};
processSegment(rootSegment, 0).then((finalCollage) => {
console.log(finalCollage);
this.context.drawImage(finalCollage, 0, 0);
}).catch((error) => {
alert(error);
});
}
} }
} }