WebcamBrightnessMeter / index.html
Doobdeedoo's picture
Add 3 files
c150709 verified
Raw
History Blame Contribute Delete
19.2 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Webcam Brightness Meter</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
.brightness-meter {
background: linear-gradient(90deg, #000000 0%, #333333 20%, #666666 40%, #999999 60%, #cccccc 80%, #ffffff 100%);
}
.brightness-indicator {
transition: left 0.3s ease;
}
.pulse {
animation: pulse 2s infinite;
}
@keyframes pulse {
0% {
box-shadow: 0 0 0 0 rgba(255, 255, 255, 0.7);
}
70% {
box-shadow: 0 0 0 10px rgba(255, 255, 255, 0);
}
100% {
box-shadow: 0 0 0 0 rgba(255, 255, 255, 0);
}
}
#chart-container {
height: 200px;
width: 100%;
position: relative;
}
#historyChart {
width: 100% !important;
height: 200px !important;
}
</style>
</head>
<body class="bg-gray-900 text-white min-h-screen">
<div class="container mx-auto px-4 py-8">
<header class="text-center mb-8">
<h1 class="text-4xl font-bold mb-2">Webcam Brightness Meter</h1>
<p class="text-gray-400">Measure ambient light levels using your webcam</p>
</header>
<main class="max-w-4xl mx-auto">
<div class="bg-gray-800 rounded-xl p-6 shadow-lg mb-8">
<div class="flex flex-col md:flex-row gap-6">
<div class="w-full md:w-1/2">
<div class="relative aspect-video bg-black rounded-lg overflow-hidden mb-4">
<video id="webcam" autoplay playsinline class="w-full h-full object-cover"></video>
<div id="overlay" class="absolute inset-0 bg-black bg-opacity-50 flex items-center justify-center">
<div class="text-center p-4">
<i class="fas fa-camera text-4xl mb-2"></i>
<p>Webcam access required</p>
</div>
</div>
</div>
<div class="flex justify-center gap-4">
<button id="startBtn" class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg flex items-center gap-2">
<i class="fas fa-play"></i> Start
</button>
<button id="stopBtn" class="bg-red-600 hover:bg-red-700 text-white px-4 py-2 rounded-lg flex items-center gap-2" disabled>
<i class="fas fa-stop"></i> Stop
</button>
</div>
</div>
<div class="w-full md:w-1/2">
<div class="bg-gray-700 p-4 rounded-lg h-full">
<h2 class="text-xl font-semibold mb-4">Brightness Analysis</h2>
<div class="mb-6">
<div class="flex justify-between mb-2">
<span>0%</span>
<span id="brightnessValue" class="text-xl font-bold">--</span>
<span>100%</span>
</div>
<div class="brightness-meter h-4 rounded-full relative">
<div id="brightnessIndicator" class="brightness-indicator absolute top-0 -ml-2 -mt-2 w-4 h-8 bg-white rounded-full shadow-lg"></div>
</div>
</div>
<div class="grid grid-cols-2 gap-4">
<div class="bg-gray-600 p-3 rounded-lg">
<div class="text-sm text-gray-300 mb-1">Light Level</div>
<div id="lightLevel" class="text-2xl font-bold">--</div>
</div>
<div class="bg-gray-600 p-3 rounded-lg">
<div class="text-sm text-gray-300 mb-1">Status</div>
<div id="lightStatus" class="text-2xl font-bold">Inactive</div>
</div>
</div>
<div class="mt-6 bg-gray-600 p-3 rounded-lg">
<div class="text-sm text-gray-300 mb-1">Tips</div>
<ul class="text-sm list-disc pl-5 space-y-1">
<li>Point your webcam at the area you want to measure</li>
<li>For best results, avoid direct light sources in frame</li>
<li>Values update every second</li>
</ul>
</div>
</div>
</div>
</div>
</div>
<div class="bg-gray-800 rounded-xl p-6 shadow-lg">
<h2 class="text-xl font-semibold mb-4">Brightness History</h2>
<div id="chart-container">
<canvas id="historyChart"></canvas>
</div>
</div>
</main>
</div>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
// DOM Elements
const video = document.getElementById('webcam');
const overlay = document.getElementById('overlay');
const startBtn = document.getElementById('startBtn');
const stopBtn = document.getElementById('stopBtn');
const brightnessValue = document.getElementById('brightnessValue');
const brightnessIndicator = document.getElementById('brightnessIndicator');
const lightLevel = document.getElementById('lightLevel');
const lightStatus = document.getElementById('lightStatus');
// Variables
let stream = null;
let interval = null;
let brightnessHistory = [];
const maxHistory = 30; // Keep last 30 readings
let chart = null;
let isVideoReady = false;
// Initialize Chart
function initChart() {
const ctx = document.getElementById('historyChart').getContext('2d');
chart = new Chart(ctx, {
type: 'line',
data: {
labels: Array(maxHistory).fill(''),
datasets: [{
label: 'Brightness',
data: brightnessHistory,
borderColor: 'rgb(59, 130, 246)',
backgroundColor: 'rgba(59, 130, 246, 0.1)',
tension: 0.3,
fill: true,
pointRadius: 0 // Remove points for cleaner look
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
animation: {
duration: 0 // Disable animation to prevent chart resizing
},
scales: {
y: {
min: 0,
max: 100,
ticks: {
callback: function(value) {
return value + '%';
}
}
},
x: {
display: false // Hide x-axis labels
}
},
plugins: {
legend: {
display: false
},
tooltip: {
callbacks: {
label: function(context) {
return 'Brightness: ' + context.parsed.y.toFixed(1) + '%';
}
}
}
}
}
});
}
// Update Chart
function updateChart() {
if (chart) {
chart.data.datasets[0].data = brightnessHistory;
chart.update('none'); // Update without animation
}
}
// Calculate brightness from video frame
function calculateBrightness() {
if (!isVideoReady || video.videoWidth === 0 || video.videoHeight === 0) {
throw new Error('Video not ready for measurement');
}
const canvas = document.createElement('canvas');
canvas.width = video.videoWidth;
canvas.height = video.videoHeight;
const ctx = canvas.getContext('2d');
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const data = imageData.data;
let brightnessSum = 0;
const pixelCount = data.length / 4;
for (let i = 0; i < data.length; i += 4) {
// Calculate perceived brightness (weighted average of RGB)
const r = data[i];
const g = data[i + 1];
const b = data[i + 2];
const brightness = 0.299 * r + 0.587 * g + 0.114 * b;
brightnessSum += brightness;
}
const avgBrightness = (brightnessSum / pixelCount) / 2.55; // Convert 0-255 to 0-100
return Math.round(avgBrightness * 10) / 10; // Round to 1 decimal place
}
// Update UI with brightness value
function updateBrightnessUI(brightness) {
brightnessValue.textContent = `${brightness}%`;
brightnessIndicator.style.left = `${brightness}%`;
// Update light level description
lightLevel.textContent = `${brightness}%`;
// Update status based on brightness
let status = '';
let statusColor = '';
if (brightness < 20) {
status = 'Very Dark';
statusColor = 'text-purple-400';
} else if (brightness < 40) {
status = 'Dark';
statusColor = 'text-blue-400';
} else if (brightness < 60) {
status = 'Moderate';
statusColor = 'text-green-400';
} else if (brightness < 80) {
status = 'Bright';
statusColor = 'text-yellow-400';
} else {
status = 'Very Bright';
statusColor = 'text-red-400';
}
lightStatus.textContent = status;
lightStatus.className = `text-2xl font-bold ${statusColor}`;
// Add to history
brightnessHistory.push(brightness);
if (brightnessHistory.length > maxHistory) {
brightnessHistory.shift();
}
updateChart();
}
// Start measuring brightness
async function startMeasuring() {
try {
// First stop any existing measurements
if (interval) {
clearInterval(interval);
interval = null;
}
// Initialize webcam if not already active
if (!stream) {
await initWebcam();
}
// Wait for video to be ready
if (!isVideoReady) {
await new Promise((resolve) => {
const checkReady = () => {
if (video.videoWidth > 0 && video.videoHeight > 0) {
isVideoReady = true;
resolve();
} else {
setTimeout(checkReady, 100);
}
};
checkReady();
});
}
overlay.classList.add('hidden');
startBtn.disabled = true;
stopBtn.disabled = false;
// Initial reading
const brightness = calculateBrightness();
updateBrightnessUI(brightness);
// Set up interval for continuous readings
interval = setInterval(() => {
try {
const brightness = calculateBrightness();
updateBrightnessUI(brightness);
} catch (err) {
console.error('Error during measurement:', err);
// Try to recover by stopping and showing error
stopMeasuring();
overlay.classList.remove('hidden');
overlay.innerHTML = `
<div class="text-center p-4">
<i class="fas fa-exclamation-triangle text-4xl mb-2 text-red-500"></i>
<p>Measurement error</p>
<p class="text-sm text-gray-300 mt-2">${err.message}</p>
<button id="retryBtn" class="mt-4 bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg">
<i class="fas fa-sync-alt mr-2"></i> Try Again
</button>
</div>
`;
document.getElementById('retryBtn').addEventListener('click', startMeasuring);
}
}, 1000);
} catch (err) {
console.error('Error starting measurements:', err);
overlay.classList.remove('hidden');
overlay.innerHTML = `
<div class="text-center p-4">
<i class="fas fa-exclamation-triangle text-4xl mb-2 text-red-500"></i>
<p>Error starting measurements</p>
<p class="text-sm text-gray-300 mt-2">${err.message}</p>
<button id="retryBtn" class="mt-4 bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg">
<i class="fas fa-sync-alt mr-2"></i> Try Again
</button>
</div>
`;
document.getElementById('retryBtn').addEventListener('click', startMeasuring);
}
}
// Stop measuring brightness and release webcam
function stopMeasuring() {
clearInterval(interval);
interval = null;
startBtn.disabled = false;
stopBtn.disabled = true;
overlay.classList.remove('hidden');
// Release webcam resources
if (stream) {
stream.getTracks().forEach(track => track.stop());
video.srcObject = null;
stream = null;
isVideoReady = false;
}
// Reset UI values
brightnessValue.textContent = '--';
brightnessIndicator.style.left = '0%';
lightLevel.textContent = '--';
lightStatus.textContent = 'Inactive';
lightStatus.className = 'text-2xl font-bold';
}
// Initialize webcam
async function initWebcam() {
try {
// Stop any existing stream first
if (stream) {
stream.getTracks().forEach(track => track.stop());
}
stream = await navigator.mediaDevices.getUserMedia({ video: true });
video.srcObject = stream;
isVideoReady = false;
// Wait for video metadata to load
await new Promise((resolve) => {
video.onloadedmetadata = () => {
isVideoReady = true;
resolve();
};
});
// Initialize chart if not already done
if (!chart) {
initChart();
}
return true;
} catch (err) {
console.error('Error accessing webcam:', err);
overlay.classList.remove('hidden');
overlay.innerHTML = `
<div class="text-center p-4">
<i class="fas fa-exclamation-triangle text-4xl mb-2 text-red-500"></i>
<p>Could not access webcam</p>
<p class="text-sm text-gray-300 mt-2">${err.message}</p>
<button id="retryBtn" class="mt-4 bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg">
<i class="fas fa-sync-alt mr-2"></i> Try Again
</button>
</div>
`;
document.getElementById('retryBtn').addEventListener('click', initWebcam);
return false;
}
}
// Event Listeners
startBtn.addEventListener('click', startMeasuring);
stopBtn.addEventListener('click', stopMeasuring);
// Clean up on page unload
window.addEventListener('beforeunload', () => {
if (stream) {
stream.getTracks().forEach(track => track.stop());
}
if (interval) {
clearInterval(interval);
}
});
// Initialize chart on load
document.addEventListener('DOMContentLoaded', initChart);
</script>
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=Doobdeedoo/doobidoo" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
</html>