ar-trace-app / script.js
shoaibrza9999's picture
Upload folder using huggingface_hub
83b5d8a verified
Raw
History Blame Contribute Delete
9.08 kB
document.addEventListener("DOMContentLoaded", () => {
// DOM Elements
const video = document.getElementById("camera-stream");
const canvas = document.getElementById("overlay-canvas");
const ctx = canvas.getContext("2d", { willReadFrequently: true });
const sourceImage = document.getElementById("source-image");
// Controls
const opacitySlider = document.getElementById("opacity-slider");
const opacityVal = document.getElementById("opacity-val");
const scaleSlider = document.getElementById("scale-slider");
const scaleVal = document.getElementById("scale-val");
const colorPicker = document.getElementById("color-picker");
const toleranceSlider = document.getElementById("tolerance-slider");
const toleranceVal = document.getElementById("tolerance-val");
const imageUpload = document.getElementById("image-upload");
const lockBtn = document.getElementById("lock-btn");
const unlockBtn = document.getElementById("unlock-btn");
const resetBtn = document.getElementById("reset-btn");
// State
let isLocked = false;
let imgX = 0;
let imgY = 0;
let scale = 1.0;
// Touch & Drag State
let isDragging = false;
let startDragX = 0;
let startDragY = 0;
let initialPinchDistance = null;
let initialScale = 1.0;
let initialPinchCenter = { x: 0, y: 0 };
// Original image data for processing
let originalImageData = null;
// --- Camera Setup ---
async function setupCamera() {
try {
const stream = await navigator.mediaDevices.getUserMedia({
video: { facingMode: "environment" }, // Prefer rear camera
});
video.srcObject = stream;
} catch (err) {
console.error("Error accessing camera:", err);
alert(
"Could not access the camera. Please ensure you have granted permission.",
);
}
}
// --- Image Processing ---
function hexToRgb(hex) {
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return result
? {
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16),
}
: { r: 255, g: 255, b: 255 };
}
function processImageColor() {
if (!originalImageData) return;
const targetColor = hexToRgb(colorPicker.value);
const tolerance = parseInt(toleranceSlider.value, 10);
// Create a copy of the original data to modify
const processedData = new ImageData(
new Uint8ClampedArray(originalImageData.data),
originalImageData.width,
originalImageData.height,
);
const data = processedData.data;
for (let i = 0; i < data.length; i += 4) {
const r = data[i];
const g = data[i + 1];
const b = data[i + 2];
// Calculate color distance (simple Manhattan distance for speed)
const rDiff = Math.abs(r - targetColor.r);
const gDiff = Math.abs(g - targetColor.g);
const bDiff = Math.abs(b - targetColor.b);
// If the pixel is within tolerance of the target color, make it transparent
if (rDiff <= tolerance && gDiff <= tolerance && bDiff <= tolerance) {
data[i + 3] = 0; // Set alpha to 0
}
}
ctx.putImageData(processedData, 0, 0);
}
function initImage() {
// Set canvas size to match image
canvas.width = sourceImage.naturalWidth;
canvas.height = sourceImage.naturalHeight;
// Draw initial image
ctx.drawImage(sourceImage, 0, 0);
// Store original data for color processing
originalImageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
// Center the image initially
resetPosition();
// Apply initial transparency if needed
processImageColor();
}
// --- Interactions & Controls ---
function updateTransform() {
canvas.style.transform = `translate(${imgX}px, ${imgY}px) scale(${scale})`;
canvas.style.opacity = opacitySlider.value;
// Sync slider with scale
scaleSlider.value = scale;
scaleVal.textContent = scale.toFixed(1) + "x";
}
function resetPosition() {
scale = 1.0;
// Center based on window size and canvas size
imgX = (window.innerWidth - canvas.width) / 2;
// Place it slightly higher than center to leave room for controls
imgY = (window.innerHeight - canvas.height) / 3;
updateTransform();
}
// --- Multi-Touch Gesture Logic ---
function getPinchDistance(touches) {
const dx = touches[0].clientX - touches[1].clientX;
const dy = touches[0].clientY - touches[1].clientY;
return Math.sqrt(dx * dx + dy * dy);
}
function getPinchCenter(touches) {
return {
x: (touches[0].clientX + touches[1].clientX) / 2,
y: (touches[0].clientY + touches[1].clientY) / 2,
};
}
function startDrag(e) {
if (isLocked) return;
e.preventDefault(); // Prevent default touch actions like scrolling
if (e.touches) {
if (e.touches.length === 1) {
// Single finger: Pan
isDragging = true;
startDragX = e.touches[0].clientX - imgX;
startDragY = e.touches[0].clientY - imgY;
} else if (e.touches.length === 2) {
// Two fingers: Zoom & Pan
isDragging = true;
initialPinchDistance = getPinchDistance(e.touches);
initialScale = scale;
const center = getPinchCenter(e.touches);
startDragX = center.x - imgX;
startDragY = center.y - imgY;
}
} else {
// Mouse drag
isDragging = true;
startDragX = e.clientX - imgX;
startDragY = e.clientY - imgY;
}
}
function doDrag(e) {
if (!isDragging || isLocked) return;
e.preventDefault();
if (e.touches) {
if (e.touches.length === 1) {
// Single finger: Pan
imgX = e.touches[0].clientX - startDragX;
imgY = e.touches[0].clientY - startDragY;
} else if (e.touches.length === 2) {
// Two fingers: Zoom & Pan
// 1. Handle Pan (using midpoint)
const center = getPinchCenter(e.touches);
imgX = center.x - startDragX;
imgY = center.y - startDragY;
// 2. Handle Zoom
if (initialPinchDistance) {
const currentDistance = getPinchDistance(e.touches);
const distanceRatio = currentDistance / initialPinchDistance;
let newScale = initialScale * distanceRatio;
// Constrain scale to reasonable limits
newScale = Math.max(0.1, Math.min(newScale, 5.0));
scale = newScale;
}
}
} else {
// Mouse drag
imgX = e.clientX - startDragX;
imgY = e.clientY - startDragY;
}
updateTransform();
}
function endDrag(e) {
if (e.touches && e.touches.length > 0) {
// If one finger remains after a pinch, reset the drag starting point so it doesn't jump
isDragging = true;
startDragX = e.touches[0].clientX - imgX;
startDragY = e.touches[0].clientY - imgY;
initialPinchDistance = null;
} else {
isDragging = false;
initialPinchDistance = null;
}
}
// Event Listeners for Dragging & Gestures
canvas.addEventListener("mousedown", startDrag);
window.addEventListener("mousemove", doDrag);
window.addEventListener("mouseup", endDrag);
// Use passive: false so we can call e.preventDefault() to stop page scroll
canvas.addEventListener("touchstart", startDrag, { passive: false });
window.addEventListener("touchmove", doDrag, { passive: false });
window.addEventListener("touchend", endDrag);
window.addEventListener("touchcancel", endDrag);
// Controls Listeners
opacitySlider.addEventListener("input", (e) => {
opacityVal.textContent = Math.round(e.target.value * 100) + "%";
canvas.style.opacity = e.target.value;
});
scaleSlider.addEventListener("input", (e) => {
scale = parseFloat(e.target.value);
updateTransform();
});
colorPicker.addEventListener("input", processImageColor);
toleranceSlider.addEventListener("input", (e) => {
toleranceVal.textContent = e.target.value;
processImageColor();
});
resetBtn.addEventListener("click", resetPosition);
// Lock logic
lockBtn.addEventListener("click", () => {
isLocked = true;
document.body.classList.add("locked");
lockBtn.style.display = "none";
unlockBtn.style.display = "block"; // Explicitly show the unlock button
});
unlockBtn.addEventListener("click", () => {
isLocked = false;
document.body.classList.remove("locked");
lockBtn.style.display = "inline-block";
unlockBtn.style.display = "none"; // Hide unlock button
});
imageUpload.addEventListener("change", (e) => {
const file = e.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = (event) => {
sourceImage.onload = () => {
initImage();
};
sourceImage.src = event.target.result;
};
reader.readAsDataURL(file);
});
// --- Initialization ---
// Ensure image loads (especially useful for local dev or cached images)
if (sourceImage.complete) {
initImage();
} else {
sourceImage.onload = initImage;
}
setupCamera();
});