Skip to content

Commit baa5347

Browse files
authored
Merge pull request #78 from yuvam2005/main
Real-Time Filter Camera
2 parents 6680023 + 3cf2c3d commit baa5347

File tree

4 files changed

+312
-0
lines changed

4 files changed

+312
-0
lines changed
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# 🎥 Live Webcam Filters
2+
3+
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!
4+
5+
## ✨ Features
6+
- 🎨 Normal (no filter)
7+
- ⚫ Grayscale (black & white)
8+
- 🟤 Sepia (vintage photo)
9+
- 🔄 Invert (negative colors)
10+
- 🌫️ Blur (soft focus)
11+
- ☀️ Brightness (enhanced lighting)
12+
- 🎭 Contrast (dramatic tones)
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6+
<title>Webcam Filters</title>
7+
<link rel="stylesheet" href="styles.css">
8+
</head>
9+
<body>
10+
<div class="container">
11+
<h1>🎥 Live Webcam Filters</h1>
12+
13+
<div class="video-container">
14+
<video id="video" autoplay playsinline></video>
15+
<canvas id="canvas"></canvas>
16+
</div>
17+
18+
<div class="controls">
19+
<button id="startBtn">Start Webcam</button>
20+
21+
<div class="button-group">
22+
<button class="filter-btn active" data-filter="none">Normal</button>
23+
<button class="filter-btn" data-filter="grayscale">Grayscale</button>
24+
<button class="filter-btn" data-filter="sepia">Sepia</button>
25+
<button class="filter-btn" data-filter="invert">Invert</button>
26+
<button class="filter-btn" data-filter="blur">Blur</button>
27+
<button class="filter-btn" data-filter="brightness">Bright</button>
28+
<button class="filter-btn" data-filter="contrast">Contrast</button>
29+
</div>
30+
31+
<div class="status" id="status">Click "Start Webcam" to begin</div>
32+
<div class="filter-label" id="filterLabel">Current Filter: Normal</div>
33+
</div>
34+
</div>
35+
36+
<script src="script.js"></script>
37+
</body>
38+
</html>
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
const video = document.getElementById('video');
2+
const canvas = document.getElementById('canvas');
3+
const ctx = canvas.getContext('2d', { willReadFrequently: true });
4+
const startBtn = document.getElementById('startBtn');
5+
const filterBtns = document.querySelectorAll('.filter-btn');
6+
const status = document.getElementById('status');
7+
const filterLabel = document.getElementById('filterLabel');
8+
9+
let currentFilter = 'none';
10+
let stream = null;
11+
let animationId = null;
12+
let isWebcamActive = false;
13+
14+
startBtn.addEventListener('click', async () => {
15+
if (isWebcamActive) {
16+
stopWebcam();
17+
return;
18+
}
19+
20+
try {
21+
stream = await navigator.mediaDevices.getUserMedia({
22+
video: { width: 640, height: 480 }
23+
});
24+
video.srcObject = stream;
25+
26+
video.addEventListener('loadedmetadata', () => {
27+
canvas.width = video.videoWidth;
28+
canvas.height = video.videoHeight;
29+
startBtn.textContent = 'Stop Webcam';
30+
startBtn.style.background = 'linear-gradient(135deg, #f093fb 0%, #f5576c 100%)';
31+
status.textContent = 'Webcam active! Select a filter below';
32+
isWebcamActive = true;
33+
applyFilter();
34+
});
35+
} catch (err) {
36+
status.textContent = 'Error: Could not access webcam';
37+
console.error('Webcam error:', err);
38+
}
39+
});
40+
41+
function stopWebcam() {
42+
if (stream) {
43+
stream.getTracks().forEach(track => track.stop());
44+
stream = null;
45+
}
46+
if (animationId) {
47+
cancelAnimationFrame(animationId);
48+
animationId = null;
49+
}
50+
video.srcObject = null;
51+
ctx.clearRect(0, 0, canvas.width, canvas.height);
52+
startBtn.textContent = 'Start Webcam';
53+
startBtn.style.background = 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)';
54+
status.textContent = 'Webcam stopped. Click to restart';
55+
isWebcamActive = false;
56+
}
57+
58+
filterBtns.forEach(btn => {
59+
btn.addEventListener('click', () => {
60+
filterBtns.forEach(b => b.classList.remove('active'));
61+
btn.classList.add('active');
62+
currentFilter = btn.dataset.filter;
63+
const filterName = btn.textContent;
64+
filterLabel.textContent = `Current Filter: ${filterName}`;
65+
});
66+
});
67+
68+
function applyFilter() {
69+
if (!video.paused && !video.ended) {
70+
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
71+
72+
if (currentFilter !== 'none') {
73+
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
74+
const data = imageData.data;
75+
76+
switch(currentFilter) {
77+
case 'grayscale':
78+
for (let i = 0; i < data.length; i += 4) {
79+
const avg = (data[i] + data[i + 1] + data[i + 2]) / 3;
80+
data[i] = avg;
81+
data[i + 1] = avg;
82+
data[i + 2] = avg;
83+
}
84+
break;
85+
86+
case 'sepia':
87+
for (let i = 0; i < data.length; i += 4) {
88+
const r = data[i];
89+
const g = data[i + 1];
90+
const b = data[i + 2];
91+
data[i] = Math.min(255, (r * 0.393) + (g * 0.769) + (b * 0.189));
92+
data[i + 1] = Math.min(255, (r * 0.349) + (g * 0.686) + (b * 0.168));
93+
data[i + 2] = Math.min(255, (r * 0.272) + (g * 0.534) + (b * 0.131));
94+
}
95+
break;
96+
97+
case 'invert':
98+
for (let i = 0; i < data.length; i += 4) {
99+
data[i] = 255 - data[i];
100+
data[i + 1] = 255 - data[i + 1];
101+
data[i + 2] = 255 - data[i + 2];
102+
}
103+
break;
104+
105+
case 'blur':
106+
ctx.filter = 'blur(4px)';
107+
ctx.drawImage(canvas, 0, 0);
108+
ctx.filter = 'none';
109+
break;
110+
111+
case 'brightness':
112+
for (let i = 0; i < data.length; i += 4) {
113+
data[i] = Math.min(255, data[i] * 1.5);
114+
data[i + 1] = Math.min(255, data[i + 1] * 1.5);
115+
data[i + 2] = Math.min(255, data[i + 2] * 1.5);
116+
}
117+
break;
118+
119+
case 'contrast':
120+
const factor = 1.5;
121+
const intercept = 128 * (1 - factor);
122+
for (let i = 0; i < data.length; i += 4) {
123+
data[i] = Math.min(255, Math.max(0, data[i] * factor + intercept));
124+
data[i + 1] = Math.min(255, Math.max(0, data[i + 1] * factor + intercept));
125+
data[i + 2] = Math.min(255, Math.max(0, data[i + 2] * factor + intercept));
126+
}
127+
break;
128+
}
129+
130+
if (currentFilter !== 'blur') {
131+
ctx.putImageData(imageData, 0, 0);
132+
}
133+
}
134+
135+
animationId = requestAnimationFrame(applyFilter);
136+
}
137+
}
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
* {
2+
margin: 0;
3+
padding: 0;
4+
box-sizing: border-box;
5+
}
6+
7+
body {
8+
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
9+
background: linear-gradient(180deg, #070507 0%, #1a50b3 100%);
10+
min-height: 100vh;
11+
display: flex;
12+
flex-direction: column;
13+
align-items: center;
14+
justify-content: center;
15+
padding: 20px;
16+
}
17+
18+
.container {
19+
background: linear-gradient(180deg, #f9a425e5 0%, #c71b1b 100%);
20+
border-radius: 20px;
21+
padding: 30px;
22+
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
23+
max-width: 900px;
24+
width: 100%;
25+
}
26+
27+
h1 {
28+
text-align: center;
29+
color: #333;
30+
margin-bottom: 25px;
31+
font-size: 2em;
32+
}
33+
34+
.video-container {
35+
position: relative;
36+
width: 100%;
37+
max-width: 640px;
38+
margin: 0 auto 25px;
39+
border-radius: 15px;
40+
overflow: hidden;
41+
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
42+
}
43+
44+
#video {
45+
display: none;
46+
}
47+
48+
#canvas {
49+
width: 100%;
50+
height: auto;
51+
display: block;
52+
background: #000;
53+
}
54+
55+
.controls {
56+
display: flex;
57+
flex-direction: column;
58+
gap: 15px;
59+
}
60+
61+
.button-group {
62+
display: flex;
63+
flex-wrap: wrap;
64+
gap: 10px;
65+
justify-content: center;
66+
}
67+
68+
button {
69+
padding: 12px 24px;
70+
font-size: 16px;
71+
font-weight: 600;
72+
border: none;
73+
border-radius: 10px;
74+
cursor: pointer;
75+
transition: all 0.3s ease;
76+
color: white;
77+
text-transform: uppercase;
78+
letter-spacing: 0.5px;
79+
}
80+
81+
#startBtn {
82+
background: linear-gradient(135deg, #4877f9 0%, #50158f 100%);
83+
}
84+
85+
#startBtn:hover {
86+
transform: translateY(-2px);
87+
box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4);
88+
}
89+
90+
.filter-btn {
91+
background: linear-gradient(135deg, #f093fb 0%, #bbe1b8 100%);
92+
min-width: 120px;
93+
}
94+
95+
.filter-btn:hover {
96+
transform: translateY(-2px);
97+
box-shadow: 0 5px 15px rgba(245, 87, 108, 0.4);
98+
}
99+
100+
.filter-btn.active {
101+
background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
102+
box-shadow: 0 0 20px rgba(79, 172, 254, 0.5);
103+
}
104+
105+
button:disabled {
106+
opacity: 0.5;
107+
cursor: not-allowed;
108+
transform: none !important;
109+
}
110+
111+
.status {
112+
text-align: center;
113+
padding: 15px;
114+
border-radius: 10px;
115+
background: linear-gradient(135deg, #f1f1f0 0%, #faf7f6 100%);
116+
color: #333;
117+
font-weight: 500;
118+
}
119+
120+
.filter-label {
121+
text-align: center;
122+
font-size: 14px;
123+
color: #f8f5f5;
124+
margin-top: 10px;
125+
}

0 commit comments

Comments
 (0)