basic functionality - terrible performance
Grok didn't do a terribly greate job on this one. I'll clean it up with actually good code later.
This commit is contained in:
175
web/index.html
Normal file
175
web/index.html
Normal file
@ -0,0 +1,175 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Stereo Image Viewer</title>
|
||||
<style>
|
||||
body { font-family: Arial, sans-serif; text-align: center; padding: 20px; }
|
||||
#viewer { display: flex; justify-content: center; align-items: center; margin-top: 20px; }
|
||||
#viewer img, #viewer canvas { max-width: 100%; height: auto; }
|
||||
#methodControls { margin: 10px 0; }
|
||||
select, button, input { margin: 5px; padding: 5px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Stereo Image Viewer</h1>
|
||||
<label for="pairSelect">Select Image Pair:</label>
|
||||
<select id="pairSelect"></select><br>
|
||||
<label for="methodSelect">Viewing Method:</label>
|
||||
<select id="methodSelect">
|
||||
<option value="interlaced">Horizontally Interlaced</option>
|
||||
<option value="switching">Actively Switching</option>
|
||||
<option value="anaglyph">Red/Cyan Anaglyph</option>
|
||||
<option value="sidebyside">Side by Side</option>
|
||||
</select><br>
|
||||
<button id="swapButton">Swap Left/Right</button>
|
||||
<div id="methodControls"></div>
|
||||
<div id="viewer"></div>
|
||||
|
||||
<script>
|
||||
let currentLeftUrl, currentRightUrl, pairs = [], intervalId;
|
||||
|
||||
function loadImage(url) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const img = new Image();
|
||||
img.onload = () => resolve(img);
|
||||
img.onerror = reject;
|
||||
img.src = url;
|
||||
});
|
||||
}
|
||||
|
||||
function displayInterlaced(leftImg, rightImg) {
|
||||
const startWithLeft = document.getElementById('swapInterlaced')?.checked ?? true;
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = leftImg.width;
|
||||
canvas.height = leftImg.height;
|
||||
const ctx = canvas.getContext('2d');
|
||||
for (let y = 0; y < canvas.height; y++) {
|
||||
const sourceImg = (y % 2 === 0) ? (startWithLeft ? leftImg : rightImg) : (startWithLeft ? rightImg : leftImg);
|
||||
ctx.drawImage(sourceImg, 0, y, canvas.width, 1, 0, y, canvas.width, 1);
|
||||
}
|
||||
document.getElementById('viewer').innerHTML = '';
|
||||
document.getElementById('viewer').appendChild(canvas);
|
||||
}
|
||||
|
||||
function displaySwitching(leftUrl, rightUrl) {
|
||||
if (intervalId) clearInterval(intervalId);
|
||||
const viewer = document.getElementById('viewer');
|
||||
viewer.innerHTML = `<img id="switchImg" src="${leftUrl}">`;
|
||||
const switchImg = document.getElementById('switchImg');
|
||||
let showLeft = true;
|
||||
intervalId = setInterval(() => {
|
||||
switchImg.src = showLeft ? leftUrl : rightUrl;
|
||||
showLeft = !showLeft;
|
||||
}, 100);
|
||||
}
|
||||
|
||||
function displayAnaglyph(leftImg, rightImg) {
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = leftImg.width;
|
||||
canvas.height = leftImg.height;
|
||||
const ctx = canvas.getContext('2d');
|
||||
ctx.drawImage(leftImg, 0, 0);
|
||||
const leftData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
||||
ctx.drawImage(rightImg, 0, 0);
|
||||
const rightData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
||||
const anaglyphData = ctx.createImageData(canvas.width, canvas.height);
|
||||
for (let i = 0; i < anaglyphData.data.length; i += 4) {
|
||||
anaglyphData.data[i] = leftData.data[i]; // Red from left
|
||||
anaglyphData.data[i+1] = rightData.data[i+1]; // Green from right
|
||||
anaglyphData.data[i+2] = rightData.data[i+2]; // Blue from right
|
||||
anaglyphData.data[i+3] = 255; // Alpha
|
||||
}
|
||||
ctx.putImageData(anaglyphData, 0, 0);
|
||||
document.getElementById('viewer').innerHTML = '';
|
||||
document.getElementById('viewer').appendChild(canvas);
|
||||
}
|
||||
|
||||
function displaySideBySide(leftUrl, rightUrl) {
|
||||
const isCrossEye = document.getElementById('crossEye')?.checked ?? true;
|
||||
const width = document.getElementById('widthSlider')?.value ?? 50;
|
||||
const viewer = document.getElementById('viewer');
|
||||
viewer.innerHTML = '';
|
||||
const leftImg = document.createElement('img');
|
||||
leftImg.src = leftUrl;
|
||||
leftImg.style.width = width + '%';
|
||||
const rightImg = document.createElement('img');
|
||||
rightImg.src = rightUrl;
|
||||
rightImg.style.width = width + '%';
|
||||
if (isCrossEye) {
|
||||
viewer.appendChild(rightImg);
|
||||
viewer.appendChild(leftImg);
|
||||
} else {
|
||||
viewer.appendChild(leftImg);
|
||||
viewer.appendChild(rightImg);
|
||||
}
|
||||
}
|
||||
|
||||
function updateControls(method) {
|
||||
const controls = document.getElementById('methodControls');
|
||||
controls.innerHTML = '';
|
||||
if (method === 'interlaced') {
|
||||
controls.innerHTML = `
|
||||
<label><input type="checkbox" id="swapInterlaced"> Start with Left</label>
|
||||
`;
|
||||
document.getElementById('swapInterlaced').addEventListener('change', () => loadAndDisplay(currentRightUrl, currentLeftUrl));
|
||||
} else if (method === 'sidebyside') {
|
||||
controls.innerHTML = `
|
||||
<label><input type="checkbox" id="crossEye" checked> Cross-Eye (unchecked for Parallel)</label><br>
|
||||
<label>Width: <input type="range" id="widthSlider" min="10" max="100" value="50"></label>
|
||||
`;
|
||||
document.getElementById('crossEye').addEventListener('change', () => loadAndDisplay(currentRightUrl, currentLeftUrl));
|
||||
document.getElementById('widthSlider').addEventListener('input', () => loadAndDisplay(currentLeftUrl, currentRightUrl));
|
||||
}
|
||||
}
|
||||
|
||||
function loadAndDisplay(leftUrl, rightUrl) {
|
||||
currentLeftUrl = leftUrl;
|
||||
currentRightUrl = rightUrl;
|
||||
const method = document.getElementById('methodSelect').value;
|
||||
updateControls(method);
|
||||
if (method === 'interlaced' || method === 'anaglyph') {
|
||||
Promise.all([loadImage(leftUrl), loadImage(rightUrl)]).then(([leftImg, rightImg]) => {
|
||||
if (method === 'interlaced') displayInterlaced(leftImg, rightImg);
|
||||
else displayAnaglyph(leftImg, rightImg);
|
||||
}).catch(err => console.error('Image load failed:', err));
|
||||
} else if (method === 'switching') {
|
||||
displaySwitching(leftUrl, rightUrl);
|
||||
} else if (method === 'sidebyside') {
|
||||
displaySideBySide(leftUrl, rightUrl);
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById('methodSelect').addEventListener('change', () => {
|
||||
if (currentLeftUrl && currentRightUrl) loadAndDisplay(currentLeftUrl, currentRightUrl);
|
||||
});
|
||||
|
||||
document.getElementById('swapButton').addEventListener('click', () => {
|
||||
if (currentLeftUrl && currentRightUrl) {
|
||||
[currentLeftUrl, currentRightUrl] = [currentRightUrl, currentLeftUrl];
|
||||
loadAndDisplay(currentLeftUrl, currentRightUrl);
|
||||
}
|
||||
});
|
||||
|
||||
fetch('/api/pairs')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
pairs = data;
|
||||
const select = document.getElementById('pairSelect');
|
||||
data.forEach(pair => {
|
||||
const option = document.createElement('option');
|
||||
option.value = pair.name;
|
||||
option.text = pair.name;
|
||||
select.appendChild(option);
|
||||
});
|
||||
select.onchange = () => {
|
||||
const selectedPair = pairs.find(p => p.name === select.value);
|
||||
if (selectedPair) loadAndDisplay(selectedPair.left, selectedPair.right);
|
||||
};
|
||||
if (data.length > 0) loadAndDisplay(data[0].left, data[0].right);
|
||||
})
|
||||
.catch(err => console.error('Failed to fetch pairs:', err));
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
Reference in New Issue
Block a user