V1
This commit is contained in:
@@ -0,0 +1,237 @@
|
||||
<!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>
|
||||
Reference in New Issue
Block a user