Growing circles

by PhilQ

HTML

<div id="status"></div>
<canvas id="canvas1" class="transparent"></canvas>
<label class="uploadfield">
	<input id="input1" type="file" accept="image/png, image/jpg, image/jpeg">
</label>
<button id="download">Download image</button>

SCSS

*, ::before, ::after { margin: 0; padding: 0; box-sizing: border-box; }

body {
	background: #1f2227;
}

#status {
	display: block;
	position: absolute;
	top: 45px;
	right: 45px;
	width: 60px;
	height: 60px;
	background: rgba(255, 255, 255, 0.1);
	border-radius: 4px;
	
	&::before {
		content: '';
		position: absolute;
		top: 50%;
		left: 50%;
		display: inline-block;
		width: 50%;
		height: 50%;
		border-radius: 50%;
		background-color: #f8b068;
		transform: translate(-50%, -50%);
		transition: all 0.35s ease;
	}
	&.ok::before {
		//background-color: #249d7f;
		background-color: #6ce890;
	}
}

#canvas1 {
	display: block;
	margin: 150px auto 0;
	background: #1a1d21;
	
	&.transparent {
		background: transparent;
	}
}

.uploadfield {
	display: block;
	margin: 50px auto;
	width: 300px;
	height: 80px;
	background: rgba(255, 255, 255, 0.1);
	border-radius: 4px;
	cursor: pointer;

	input {
		opacity: 0;
	}
}

#download {
	position: absolute;
	top: 45px;
	left: 45px;
	width: 300px;
	height: 60px;
	color: #fff;
	background: rgba(255, 255, 255, 0.1);
	border-radius: 4px;
	cursor: pointer;
	border: 0px;
}

JavaScript

var upload,
	canvas,
	ctx,
	imgData = null,
	image_is_loaded = false,
	width = 0,
	height = 0,
	max_width = 720,
	max_height= 520,
	spawn_interval = 10, // in ms
	grow_speed = 50, // in ms
	grow_amount = 0.5, // in px
	min_r = 3, // in px
	max_r = 8, // in px
	spacing = 1,
	averageColorMode = 1,
	with_effect = false,
	this_time,
	last_time,
	frame_time,
	duration = 0,
	first_run = true,
	is_running = false,
	finished = false,
	circles = [],
	growingCircles = [],
	availablePoints = []
;

class circle {
	constructor(_x, _y, _r, _id) {
		this.x = _x;
		this.y = _y;
		this.r = _r;
		this.colorArray = [255,255,255];
		this.growing = true;
		this.id = _id;
	}

	get color() {
		// if (!this.growing) {
		// 	return 'rgb(255,79,104)';
		// }
		return 'rgb(' + this.colorArray.join(',') + ')';
	}

	get top() {
		return this.y - this.r;
	}

	get bottom() {
		return this.y + this.r;
	}

	get left() {
		return this.x - this.r;
	}

	get right() {
		return this.x + this.r;
	}

	grow(frame_time) {
		this.r += (frame_time/grow_speed) * grow_amount;
	}
}

function distance(x1, y1, x2, y2) {
	let a = x1 - x2;
	let b = y1 - y2;
	// let c = Math.sqrt( a*a + b*b ); // Old-school
	let c = Math.hypot(a, b); // Modern
	return c;
}

function outOfBounds(top, right, bottom, left) {
	return top < 0 || right > width || bottom > height || left < 0;
}

function getAverageColor(cx, cy, cr) {
	if (imgData) {
		let left = Math.max(0, Math.round(cx - cr));
		let right = Math.min(width-1, Math.round(cx + cr));
		let top = Math.max(0, Math.round(cy - cr));
		let bottom = Math.min(height-1, Math.round(cy + cr));
		let totals = [0,0,0];
		switch (averageColorMode) {
			case 0:
				totals = imgData.data.slice((cx+cy*width)*4, (cx+cy*width)*4 + 4); // Just RGB from RGBA
				break;
			case 1:
				// Take average of rectangular area
				let partSquare = 1 / ((right-left+1) * (bottom-top+1));
				for (let y = top*width; y < (bottom+1)*width; y += width) {
					let colors = imgData.data.slice((left+y)*4,...