diff --git a/Javascript/Real-Time Filter Camera/README.md b/Javascript/Real-Time Filter Camera/README.md new file mode 100644 index 0000000..0087230 --- /dev/null +++ b/Javascript/Real-Time Filter Camera/README.md @@ -0,0 +1,12 @@ +# 🎥 Live Webcam Filters + +A real-time webcam filter application built with HTML5 Canvas and vanilla JavaScript. Apply fun visual effects to your webcam feed directly in the browser! + +## ✨ Features + - 🎨 Normal (no filter) + - ⚫ Grayscale (black & white) + - 🟤 Sepia (vintage photo) + - 🔄 Invert (negative colors) + - 🌫️ Blur (soft focus) + - ☀️ Brightness (enhanced lighting) + - 🎭 Contrast (dramatic tones) diff --git a/Javascript/Real-Time Filter Camera/index.html b/Javascript/Real-Time Filter Camera/index.html new file mode 100644 index 0000000..db4995b --- /dev/null +++ b/Javascript/Real-Time Filter Camera/index.html @@ -0,0 +1,38 @@ + + + + + + Webcam Filters + + + +
+

🎥 Live Webcam Filters

+ +
+ + +
+ +
+ + +
+ + + + + + + +
+ +
Click "Start Webcam" to begin
+
Current Filter: Normal
+
+
+ + + + diff --git a/Javascript/Real-Time Filter Camera/script.js b/Javascript/Real-Time Filter Camera/script.js new file mode 100644 index 0000000..6eb9747 --- /dev/null +++ b/Javascript/Real-Time Filter Camera/script.js @@ -0,0 +1,137 @@ +const video = document.getElementById('video'); +const canvas = document.getElementById('canvas'); +const ctx = canvas.getContext('2d', { willReadFrequently: true }); +const startBtn = document.getElementById('startBtn'); +const filterBtns = document.querySelectorAll('.filter-btn'); +const status = document.getElementById('status'); +const filterLabel = document.getElementById('filterLabel'); + +let currentFilter = 'none'; +let stream = null; +let animationId = null; +let isWebcamActive = false; + +startBtn.addEventListener('click', async () => { + if (isWebcamActive) { + stopWebcam(); + return; + } + + try { + stream = await navigator.mediaDevices.getUserMedia({ + video: { width: 640, height: 480 } + }); + video.srcObject = stream; + + video.addEventListener('loadedmetadata', () => { + canvas.width = video.videoWidth; + canvas.height = video.videoHeight; + startBtn.textContent = 'Stop Webcam'; + startBtn.style.background = 'linear-gradient(135deg, #f093fb 0%, #f5576c 100%)'; + status.textContent = 'Webcam active! Select a filter below'; + isWebcamActive = true; + applyFilter(); + }); + } catch (err) { + status.textContent = 'Error: Could not access webcam'; + console.error('Webcam error:', err); + } +}); + +function stopWebcam() { + if (stream) { + stream.getTracks().forEach(track => track.stop()); + stream = null; + } + if (animationId) { + cancelAnimationFrame(animationId); + animationId = null; + } + video.srcObject = null; + ctx.clearRect(0, 0, canvas.width, canvas.height); + startBtn.textContent = 'Start Webcam'; + startBtn.style.background = 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)'; + status.textContent = 'Webcam stopped. Click to restart'; + isWebcamActive = false; +} + +filterBtns.forEach(btn => { + btn.addEventListener('click', () => { + filterBtns.forEach(b => b.classList.remove('active')); + btn.classList.add('active'); + currentFilter = btn.dataset.filter; + const filterName = btn.textContent; + filterLabel.textContent = `Current Filter: ${filterName}`; + }); +}); + +function applyFilter() { + if (!video.paused && !video.ended) { + ctx.drawImage(video, 0, 0, canvas.width, canvas.height); + + if (currentFilter !== 'none') { + const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); + const data = imageData.data; + + switch(currentFilter) { + case 'grayscale': + for (let i = 0; i < data.length; i += 4) { + const avg = (data[i] + data[i + 1] + data[i + 2]) / 3; + data[i] = avg; + data[i + 1] = avg; + data[i + 2] = avg; + } + break; + + case 'sepia': + for (let i = 0; i < data.length; i += 4) { + const r = data[i]; + const g = data[i + 1]; + const b = data[i + 2]; + data[i] = Math.min(255, (r * 0.393) + (g * 0.769) + (b * 0.189)); + data[i + 1] = Math.min(255, (r * 0.349) + (g * 0.686) + (b * 0.168)); + data[i + 2] = Math.min(255, (r * 0.272) + (g * 0.534) + (b * 0.131)); + } + break; + + case 'invert': + for (let i = 0; i < data.length; i += 4) { + data[i] = 255 - data[i]; + data[i + 1] = 255 - data[i + 1]; + data[i + 2] = 255 - data[i + 2]; + } + break; + + case 'blur': + ctx.filter = 'blur(4px)'; + ctx.drawImage(canvas, 0, 0); + ctx.filter = 'none'; + break; + + case 'brightness': + for (let i = 0; i < data.length; i += 4) { + data[i] = Math.min(255, data[i] * 1.5); + data[i + 1] = Math.min(255, data[i + 1] * 1.5); + data[i + 2] = Math.min(255, data[i + 2] * 1.5); + } + break; + + case 'contrast': + const factor = 1.5; + const intercept = 128 * (1 - factor); + for (let i = 0; i < data.length; i += 4) { + data[i] = Math.min(255, Math.max(0, data[i] * factor + intercept)); + data[i + 1] = Math.min(255, Math.max(0, data[i + 1] * factor + intercept)); + data[i + 2] = Math.min(255, Math.max(0, data[i + 2] * factor + intercept)); + } + break; + } + + if (currentFilter !== 'blur') { + ctx.putImageData(imageData, 0, 0); + } + } + + animationId = requestAnimationFrame(applyFilter); + } +} \ No newline at end of file diff --git a/Javascript/Real-Time Filter Camera/styles.css b/Javascript/Real-Time Filter Camera/styles.css new file mode 100644 index 0000000..bf58c53 --- /dev/null +++ b/Javascript/Real-Time Filter Camera/styles.css @@ -0,0 +1,125 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + background: linear-gradient(180deg, #070507 0%, #1a50b3 100%); + min-height: 100vh; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 20px; +} + +.container { + background: linear-gradient(180deg, #f9a425e5 0%, #c71b1b 100%); + border-radius: 20px; + padding: 30px; + box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3); + max-width: 900px; + width: 100%; +} + +h1 { + text-align: center; + color: #333; + margin-bottom: 25px; + font-size: 2em; +} + +.video-container { + position: relative; + width: 100%; + max-width: 640px; + margin: 0 auto 25px; + border-radius: 15px; + overflow: hidden; + box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2); +} + +#video { + display: none; +} + +#canvas { + width: 100%; + height: auto; + display: block; + background: #000; +} + +.controls { + display: flex; + flex-direction: column; + gap: 15px; +} + +.button-group { + display: flex; + flex-wrap: wrap; + gap: 10px; + justify-content: center; +} + +button { + padding: 12px 24px; + font-size: 16px; + font-weight: 600; + border: none; + border-radius: 10px; + cursor: pointer; + transition: all 0.3s ease; + color: white; + text-transform: uppercase; + letter-spacing: 0.5px; +} + +#startBtn { + background: linear-gradient(135deg, #4877f9 0%, #50158f 100%); +} + +#startBtn:hover { + transform: translateY(-2px); + box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4); +} + +.filter-btn { + background: linear-gradient(135deg, #f093fb 0%, #bbe1b8 100%); + min-width: 120px; +} + +.filter-btn:hover { + transform: translateY(-2px); + box-shadow: 0 5px 15px rgba(245, 87, 108, 0.4); +} + +.filter-btn.active { + background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%); + box-shadow: 0 0 20px rgba(79, 172, 254, 0.5); +} + +button:disabled { + opacity: 0.5; + cursor: not-allowed; + transform: none !important; +} + +.status { + text-align: center; + padding: 15px; + border-radius: 10px; + background: linear-gradient(135deg, #f1f1f0 0%, #faf7f6 100%); + color: #333; + font-weight: 500; +} + +.filter-label { + text-align: center; + font-size: 14px; + color: #f8f5f5; + margin-top: 10px; +} \ No newline at end of file