Skip to content

Instantly share code, notes, and snippets.

@bddicken
Last active April 11, 2024 13:56
Show Gist options
  • Star 8 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save bddicken/e7c2a4c8b76cae0998f4b79cf4d2829b to your computer and use it in GitHub Desktop.
Save bddicken/e7c2a4c8b76cae0998f4b79cf4d2829b to your computer and use it in GitHub Desktop.
Contour Images
<html>
<head>
<script is:inline defer src="https://cdn.jsdelivr.net/npm/img-comparison-slider@8/dist/index.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/img-comparison-slider@8/dist/styles.css" />
<style>
.slider {
width: 70%;
fill: #fff;
stroke: #fff;
}
.sliderContainer {
padding: 5px;
}
.newFile {
padding:5px;
}
.noHighlight {
outline: none;
}
.doubleStyle {
--divider-width: 4px;
--divider-color: #808080;
--default-handle-opacity: 1;
--default-handle-width: 100px;
}
.comparisonContainer {
max-width: 1000px;
min-width: 100px;
width: 100%;
}
</style>
<script>
"use strict";
class Contour {
constructor(imageName, containerID, width, height) {
this.container = document.getElementById(containerID);
this.imageName = imageName;
this.updateWH(width, height);
this.sliders = [];
this.canvas = this.initCanvas("canvas");
this.baseCanvas = this.initCanvas("canvasBase");
this.initImage(this.canvas);
this.initImage(this.baseCanvas);
this.addSliders();
}
updateWH(width, height) {
this.width = 1400;
this.height = Math.floor((height / width) * 1400);
}
updateImage(e) {
this.fillCanvas(this.canvas, 0, 0, this.width, this.height, [255, 255, 255]);
this.contourify();
}
addSlider(c) {
const slider = this.container.getElementsByClassName(c)[0];
slider.addEventListener("input", event => this.updateImage());
this.sliders.push(slider);
if (c == "xCoordinate") {
slider.min = 10;
slider.max = this.width - 10;
}
if (c == "yCoordinate") {
slider.min = 10;
slider.max = this.height - 10;
}
}
addSliders() {
this.addSlider("lineSpacing");
this.addSlider("xCoordinate");
this.addSlider("yCoordinate");
this.addSlider("lineWidth");
this.addSlider("angles");
this.addSlider("linePull");
}
initCanvas(id) {
const canvas = this.container.getElementsByClassName(id)[0];
canvas.width = this.width;
canvas.height = this.height;
canvas.style.width = "100%";
return canvas;
}
initImage(canvas) {
var image = new Image();
image.src = this.imageName;
image.addEventListener("load", e => {
const ctx = canvas.getContext("2d");
ctx.imageSmoothingEnabled = false;
ctx.drawImage(image, 0, 0, this.width, this.height);
this.updateImage(null);
});
}
fillCanvas(canvas, x, y, w, h, rgb) {
const ctx = this.canvas.getContext("2d");
const pData = ctx.getImageData(0, 0, this.width, this.height);
const pixels = pData.data;
this.fillRect(pixels, x, y, w, h, rgb);
ctx.putImageData(pData, 0, 0);
}
fillRect(pixels, x, y, w, h, rgb) {
for (let x2 = x; x2 < w; x2++) {
for (let y2 = y; y2 < h; y2++) {
let pi = this.pidx(pixels, x2, y2);
pixels[pi] = rgb[0];
pixels[pi + 1] = rgb[1];
pixels[pi + 2] = rgb[2];
}
}
}
pidx(pixels, x, y) {
return Math.floor(pixels.length / this.height) * y + x * 4;
}
contourify() {
// The context / pixels for the base image canvas
const ctx = this.baseCanvas.getContext("2d");
const pData = ctx.getImageData(0, 0, this.width, this.height);
const pixels = pData.data;
// The context / pixels for the canvas to modify with topo
const ctx2 = this.canvas.getContext("2d");
const pData2 = ctx2.getImageData(0, 0, this.width, this.height);
const pixels2 = pData2.data;
const MaxSpac = Number(this.sliders[0].value);
const xC = Number(this.sliders[1].value);
const yC = Number(this.sliders[2].value);
const lW = Number(this.sliders[3].value);
const angles = Number(this.sliders[4].value);
const linePull = Number(this.sliders[5].value);
let coordinates = Array(angles + 2).fill(MaxSpac / 2);
for (let i = MaxSpac; i < MaxSpac * 2000; i += MaxSpac) {
coordinates = this.drawContour(pixels, ctx2, i, [xC, yC], coordinates, MaxSpac, lW, angles, linePull);
}
}
drawContour(pixels, ctx2, radius,origin, prevRadii, maxSpacing, lineWidth, angles, linePull) {
const radii = [];
const FIRST = -1;
let prev_x = FIRST;
let prev_y = FIRST;
for (let a = 0; a < angles + 2; a++) {
const angle = a / (angles / 360);
const aCos = Math.cos(this.degreesToRads(angle));
const aSin = Math.sin(this.degreesToRads(angle));
const prevRadius = prevRadii[a];
const tempRadius = prevRadius + maxSpacing + (radius - prevRadius) / linePull;
const ab = this.averageBrightnessBetween(pixels, origin, aCos, aSin, prevRadius, tempRadius);
const newRadius = prevRadius + (tempRadius - prevRadius) * (ab / 255);
const nx = Math.floor(origin[0] + newRadius * aCos);
const ny = Math.floor(origin[1] + newRadius * aSin);
radii.push(newRadius);
if (nx <= 0 || ny <= 0 || nx >= this.width || ny >= this.height) continue;
if (prev_x == FIRST) {
prev_x = nx;
prev_y = ny;
}
this.drawLine(ctx2, nx, ny, prev_x, prev_y, lineWidth);
prev_x = nx;
prev_y = ny;
}
return radii;
}
degreesToRads = deg => (deg * Math.PI) / 180.0;
drawLine(context, fromX, fromY, toX, toY, width) {
context.beginPath();
context.moveTo(fromX, fromY);
context.lineTo(toX, toY);
context.strokeStyle = "black";
context.lineWidth = width;
context.stroke();
}
averageBrightnessBetween(pixels, origin, aCos, aSin, r1, r2) {
let s = 0;
let t = 0;
let rr = r1;
while (rr < r2) {
const x = Math.floor(origin[0] + rr * aCos);
const y = Math.floor(origin[1] + rr * aSin);
const pi = this.pidx(pixels, x, y);
s += pixels[pi] + pixels[pi + 1] + pixels[pi + 2];
t += 3;
rr += (r2 - r1) / 5;
}
return s / t;
}
}
function initializeFileLoad(im, containerID) {
console.log(im.container);
const loader = im.container.getElementsByClassName("imgFile")[0];
console.log(loader);
console.log(im.container.getElementsByClassName("imgFile"));
loader.addEventListener("change", event => {
loadImage(im, containerID);
});
}
function loadImage(im, containerID) {
var input, file, fr, img;
if (typeof window.FileReader !== "function") {
alert("The file API isn't supported on this browser yet.");
return;
}
const container = document.getElementById(containerID);
input = container.getElementsByClassName("imgFile")[0];
if (!input || !input.files) {
alert(
"There was a problem. Does your browser support the files API? Maybe try a different browser."
);
} else if (!input.files[0]) {
alert("Please select a file before clicking 'Load'");
} else {
file = input.files[0];
fr = new FileReader();
fr.onload = () => {
img = new Image();
img.onload = () => {
imageLoaded(im, containerID);
};
img.src = fr.result;
};
fr.readAsDataURL(file);
}
function configureImage(im, containerID, c, img) {
const w = 1400;
const h = Math.floor((img.height / img.width) * 1400);
console.log(img.height);
console.log(img.width);
console.log(h);
const container = document.getElementById(containerID);
var canvas = container.getElementsByClassName(c)[0];
canvas.width = w;
canvas.height = h;
im.updateWH(img.width, img.height);
var ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0, w, h);
}
function imageLoaded(im, containerID) {
configureImage(im, containerID, "canvas", img);
configureImage(im, containerID, "canvasBase", img);
im.updateImage();
}
}
</script>
</head>
<body>
<div id="example" class="comparisonContainer">
<img-comparison-slider class="noHighlight doubleStyle">
<canvas slot="first" class="canvas"></canvas>
<canvas slot="second" class="canvasBase"></canvas>
</img-comparison-slider>
<div class="sliderContainer">
<input name="lineSpacing" class="lineSpacing slider" type="range" min="2" max="50" step="1" value="10" />
<label for="lineSpacing"><p style="display:inline;">Line Spacing</p></label>
</div>
<div class="sliderContainer">
<input name="xCoordinate" class="xCoordinate slider" type="range" min="25" max="1500" step="25" value="850" />
<label for="xCoordinate"><p style="display:inline;">X Coordinate</p></label>
</div>
<div class="sliderContainer">
<input name="yCoordinate" class="yCoordinate slider" type="range" min="25" max="1000" step="25" value="575" />
<label for="yCoordinate"><p style="display:inline;">Y Coordinate</p></label>
</div>
<div class="sliderContainer">
<input name="lineWidth" class="lineWidth slider" type="range" min="1" max="5" step="1" value="2" />
<label for="lineWidth"><p style="display:inline;">Line Width</p></label>
</div>
<div class="sliderContainer">
<input name="angles" class="angles slider" type="range" min="90" max="1080" step="30" value="720" />
<label for="angles"><p style="display:inline;">Angles</p></label>
</div>
<div class="sliderContainer">
<input name="linePull" class="linePull slider" type="range" min="1" max="15" step="1" value="1" />
<label for="linePull"><p style="display:inline;">Line Pull</p></label>
</div>
<form action='#' onsubmit="return false;">
<input class="newFile imgFile" type="file" />
</form>
</div>
<script>
im1 = new Contour('camera4.jpg', "example", 1500, 1000);
initializeFileLoad(im1, "example");
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment