Last active
September 30, 2024 02:55
-
-
Save bddicken/e7c2a4c8b76cae0998f4b79cf4d2829b to your computer and use it in GitHub Desktop.
Contour Images
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<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