238 lines
7.9 KiB
HTML
238 lines
7.9 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Knee Function Adjustment</title>
|
|
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
|
<style>
|
|
body {
|
|
font-family: Arial, sans-serif;
|
|
margin: 0;
|
|
padding: 0;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
}
|
|
|
|
.container {
|
|
display: flex;
|
|
flex-direction: row;
|
|
gap: 20px;
|
|
margin: 20px 0;
|
|
width: 100%;
|
|
justify-content: center;
|
|
}
|
|
|
|
canvas {
|
|
max-width: 100%;
|
|
height: auto;
|
|
}
|
|
|
|
.slider-container {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 15px;
|
|
margin: 20px;
|
|
}
|
|
|
|
.slider {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
}
|
|
|
|
.slider input {
|
|
width: 200px;
|
|
}
|
|
|
|
.image-wrapper {
|
|
flex: 1;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
}
|
|
|
|
.image-wrapper canvas {
|
|
width: 100%;
|
|
height: auto;
|
|
}
|
|
|
|
.file-input {
|
|
margin: 20px;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<h1>Knee Function Adjustment</h1>
|
|
|
|
<div class="file-input">
|
|
<label for="fileInput">Choose an image:</label>
|
|
<input type="file" id="fileInput" accept="image/*">
|
|
</div>
|
|
|
|
<div class="container">
|
|
<div class="image-wrapper">
|
|
<h3>Original Image</h3>
|
|
<canvas id="originalCanvas"></canvas>
|
|
<p id="originalGrayLabel">Avg Gray: 0.00</p>
|
|
</div>
|
|
|
|
<div class="image-wrapper">
|
|
<h3>Processed Image</h3>
|
|
<canvas id="processedCanvas"></canvas>
|
|
<p id="processedGrayLabel">Avg Gray: 0.00</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="slider-container">
|
|
<div class="slider">
|
|
<label for="sensorOutput1">Sensor Output Shadow</label>
|
|
<input type="range" id="sensorOutput1" min="0" max="255" value="25">
|
|
</div>
|
|
|
|
<div class="slider">
|
|
<label for="mappedOutput1">Mapped Output Shadow</label>
|
|
<input type="range" id="mappedOutput1" min="0" max="255" value="50">
|
|
</div>
|
|
|
|
<div class="slider">
|
|
<label for="sensorOutput2">Sensor Output Bright</label>
|
|
<input type="range" id="sensorOutput2" min="0" max="255" value="200">
|
|
</div>
|
|
|
|
<div class="slider">
|
|
<label for="mappedOutput2">Mapped Output Bright</label>
|
|
<input type="range" id="mappedOutput2" min="0" max="255" value="200">
|
|
</div>
|
|
</div>
|
|
|
|
<h3>Knee Function Graph</h3>
|
|
<canvas id="kneeGraph"></canvas>
|
|
|
|
<script>
|
|
const originalCanvas = document.getElementById('originalCanvas');
|
|
const processedCanvas = document.getElementById('processedCanvas');
|
|
const kneeGraph = document.getElementById('kneeGraph');
|
|
|
|
const originalGrayLabel = document.getElementById('originalGrayLabel');
|
|
const processedGrayLabel = document.getElementById('processedGrayLabel');
|
|
|
|
const sensorOutput1 = document.getElementById('sensorOutput1');
|
|
const mappedOutput1 = document.getElementById('mappedOutput1');
|
|
const sensorOutput2 = document.getElementById('sensorOutput2');
|
|
const mappedOutput2 = document.getElementById('mappedOutput2');
|
|
|
|
const fileInput = document.getElementById('fileInput');
|
|
|
|
let imageArray = null;
|
|
|
|
function drawImage(canvas, imageArray, imgWidth, imgHeight) {
|
|
const ctx = canvas.getContext('2d');
|
|
|
|
// Set the canvas size according to the image aspect ratio
|
|
const aspectRatio = imgWidth / imgHeight;
|
|
const canvasWidth = 500; // Set the desired width for the canvas
|
|
const canvasHeight = canvasWidth / aspectRatio;
|
|
|
|
canvas.width = canvasWidth;
|
|
canvas.height = canvasHeight;
|
|
|
|
const imageData = ctx.createImageData(canvasWidth, canvasHeight);
|
|
for (let i = 0; i < imageArray.length; i++) {
|
|
imageData.data[i * 4] = imageData.data[i * 4 + 1] = imageData.data[i * 4 + 2] = imageArray[i];
|
|
imageData.data[i * 4 + 3] = 255; // Alpha channel
|
|
}
|
|
ctx.putImageData(imageData, 0, 0);
|
|
}
|
|
|
|
function applyKnee(imageArray, kneeLevel, kneeSlope) {
|
|
return imageArray.map(value =>
|
|
value > kneeLevel ? kneeLevel + (value - kneeLevel) * kneeSlope : value
|
|
);
|
|
}
|
|
|
|
function calculateAverageGray(imageArray) {
|
|
return (imageArray.reduce((sum, value) => sum + value, 0) / imageArray.length).toFixed(2);
|
|
}
|
|
|
|
function update() {
|
|
if (!imageArray) return;
|
|
|
|
const sensor1 = parseInt(sensorOutput1.value);
|
|
const map1 = parseInt(mappedOutput1.value);
|
|
const sensor2 = parseInt(sensorOutput2.value);
|
|
const map2 = parseInt(mappedOutput2.value);
|
|
|
|
const kneeLevel = map1;
|
|
const kneeSlope = sensor2 === sensor1 ? 0 : (map2 - map1) / (sensor2 - sensor1);
|
|
|
|
const processedArray = applyKnee(imageArray, kneeLevel, kneeSlope);
|
|
|
|
drawImage(originalCanvas, imageArray, 256, 256); // Adjust this to the actual image size
|
|
drawImage(processedCanvas, processedArray, 256, 256); // Adjust this to the actual image size
|
|
|
|
originalGrayLabel.textContent = `Avg Gray: ${calculateAverageGray(imageArray)}`;
|
|
processedGrayLabel.textContent = `Avg Gray: ${calculateAverageGray(processedArray)}`;
|
|
|
|
updateKneeGraph(kneeLevel, kneeSlope);
|
|
}
|
|
|
|
function updateKneeGraph(kneeLevel, kneeSlope) {
|
|
const x = Array.from({ length: 256 }, (_, i) => i);
|
|
const y = x.map(value =>
|
|
value > kneeLevel ? kneeLevel + (value - kneeLevel) * kneeSlope : value
|
|
);
|
|
|
|
const ctx = kneeGraph.getContext('2d');
|
|
kneeGraph.width = 512;
|
|
kneeGraph.height = 256;
|
|
ctx.clearRect(0, 0, kneeGraph.width, kneeGraph.height);
|
|
|
|
ctx.beginPath();
|
|
ctx.moveTo(0, 256);
|
|
x.forEach((value, i) => {
|
|
ctx.lineTo(value * 2, 256 - y[i]);
|
|
});
|
|
ctx.strokeStyle = 'blue';
|
|
ctx.lineWidth = 2;
|
|
ctx.stroke();
|
|
}
|
|
|
|
function loadImage(file) {
|
|
const img = new Image();
|
|
img.onload = () => {
|
|
const canvas = document.createElement('canvas');
|
|
const ctx = canvas.getContext('2d');
|
|
canvas.width = img.width;
|
|
canvas.height = img.height;
|
|
ctx.drawImage(img, 0, 0);
|
|
|
|
const imageData = ctx.getImageData(0, 0, img.width, img.height);
|
|
imageArray = new Uint8Array(img.width * img.height);
|
|
for (let i = 0; i < imageArray.length; i++) {
|
|
imageArray[i] = imageData.data[i * 4]; // Grayscale from red channel
|
|
}
|
|
|
|
// Draw original and processed images with aspect ratio
|
|
drawImage(originalCanvas, imageArray, img.width, img.height);
|
|
drawImage(processedCanvas, imageArray, img.width, img.height);
|
|
update();
|
|
};
|
|
img.src = URL.createObjectURL(file);
|
|
}
|
|
|
|
fileInput.addEventListener('change', (event) => {
|
|
const file = event.target.files[0];
|
|
if (file) {
|
|
loadImage(file);
|
|
}
|
|
});
|
|
|
|
[sensorOutput1, mappedOutput1, sensorOutput2, mappedOutput2].forEach(slider => {
|
|
slider.addEventListener('input', update);
|
|
});
|
|
</script>
|
|
</body>
|
|
</html>
|