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+ }
0 commit comments