├── index.html ├── login.js ├── server.js ├── index.js ├── package.json ├── App.js ├── login.html ├── FilterHistory.js ├── Gallery.js ├── gallery.js ├── filterHistory.js ├── README.md ├── WebcamSection.js ├── style.css └── script.js /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 14 | FiltroVision 15 | 16 | 17 | 18 |
19 | 20 | -------------------------------------------------------------------------------- /login.js: -------------------------------------------------------------------------------- 1 | document.getElementById('loginForm').addEventListener('submit', function(e) { 2 | e.preventDefault(); 3 | 4 | const username = document.getElementById('username').value; 5 | const password = document.getElementById('password').value; 6 | const errorMessage = document.getElementById('error-message'); 7 | 8 | // For demo purposes, using a simple hardcoded check 9 | // In a real application, this should be handled server-side 10 | if (username === 'admin' && password === 'password') { 11 | // Store login state 12 | localStorage.setItem('isLoggedIn', 'true'); 13 | // Redirect to camera page 14 | window.location.href = 'index.html'; 15 | } else { 16 | errorMessage.textContent = 'Invalid username or password'; 17 | } 18 | }); -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const cors = require('cors'); 3 | const bodyParser = require('body-parser'); 4 | const path = require('path'); 5 | 6 | const app = express(); 7 | const PORT = process.env.PORT || 5000; 8 | 9 | // Middleware 10 | app.use(cors()); 11 | app.use(bodyParser.json()); 12 | app.use(express.static(path.join(__dirname, 'client/build'))); 13 | 14 | // API Routes 15 | app.get('/api/health', (req, res) => { 16 | res.json({ status: 'ok' }); 17 | }); 18 | 19 | // Serve React app in production 20 | if (process.env.NODE_ENV === 'production') { 21 | app.use(express.static('client/build')); 22 | app.get('*', (req, res) => { 23 | res.sendFile(path.join(__dirname, 'client/build', 'index.html')); 24 | }); 25 | } 26 | 27 | app.listen(PORT, () => { 28 | console.log(`Server is running on port ${PORT}`); 29 | }); -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom/client'; 3 | import App from './App'; 4 | import { createGlobalStyle } from 'styled-components'; 5 | 6 | const GlobalStyle = createGlobalStyle` 7 | * { 8 | margin: 0; 9 | padding: 0; 10 | box-sizing: border-box; 11 | } 12 | 13 | body { 14 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 15 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 16 | sans-serif; 17 | -webkit-font-smoothing: antialiased; 18 | -moz-osx-font-smoothing: grayscale; 19 | background-color: #f0f2f5; 20 | } 21 | 22 | code { 23 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 24 | monospace; 25 | } 26 | `; 27 | 28 | const root = ReactDOM.createRoot(document.getElementById('root')); 29 | root.render( 30 | 31 | 32 | 33 | 34 | ); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "filtrovision-client", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@tensorflow/tfjs": "^2.7.0", 7 | "face-api.js": "^0.22.2", 8 | "react": "^18.2.0", 9 | "react-dom": "^18.2.0", 10 | "react-scripts": "5.0.1", 11 | "styled-components": "^6.1.1", 12 | "axios": "^1.6.2" 13 | }, 14 | "scripts": { 15 | "start": "react-scripts start", 16 | "build": "react-scripts build", 17 | "test": "react-scripts test", 18 | "eject": "react-scripts eject" 19 | }, 20 | "eslintConfig": { 21 | "extends": [ 22 | "react-app", 23 | "react-app/jest" 24 | ] 25 | }, 26 | "browserslist": { 27 | "production": [ 28 | ">0.2%", 29 | "not dead", 30 | "not op_mini all" 31 | ], 32 | "development": [ 33 | "last 1 chrome version", 34 | "last 1 firefox version", 35 | "last 1 safari version" 36 | ] 37 | }, 38 | "proxy": "http://localhost:5000" 39 | } -------------------------------------------------------------------------------- /App.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styled from 'styled-components'; 3 | import WebcamSection from './components/WebcamSection'; 4 | import FilterHistory from './components/FilterHistory'; 5 | import Gallery from './components/Gallery'; 6 | 7 | const AppContainer = styled.div` 8 | max-width: 1200px; 9 | margin: 0 auto; 10 | padding: 20px; 11 | `; 12 | 13 | const Title = styled.h1` 14 | text-align: center; 15 | color: #333; 16 | margin-bottom: 30px; 17 | `; 18 | 19 | const MainContent = styled.div` 20 | display: flex; 21 | gap: 20px; 22 | `; 23 | 24 | const LeftSection = styled.div` 25 | flex: 1; 26 | display: flex; 27 | flex-direction: column; 28 | gap: 20px; 29 | `; 30 | 31 | const RightSection = styled.div` 32 | flex: 2; 33 | `; 34 | 35 | function App() { 36 | return ( 37 | 38 | FiltroVision: Smart Webcam Filters & Face Detection 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | ); 50 | } 51 | 52 | export default App; -------------------------------------------------------------------------------- /login.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Login - Webcam Face Filters 7 | 8 | 9 | 10 | 11 |
12 |
13 | 17 |
18 |
19 | 20 | 21 |
22 |
23 | 24 | 25 |
26 | 27 |
28 |

29 | 34 |
35 |
36 | 37 | 38 | -------------------------------------------------------------------------------- /FilterHistory.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styled from 'styled-components'; 3 | 4 | const Container = styled.div` 5 | background: #f8f9fa; 6 | border-radius: 8px; 7 | padding: 15px; 8 | `; 9 | 10 | const Header = styled.h2` 11 | margin: 0 0 15px 0; 12 | color: #333; 13 | font-size: 1.2rem; 14 | `; 15 | 16 | const FilterList = styled.ul` 17 | list-style: none; 18 | padding: 0; 19 | margin: 0; 20 | `; 21 | 22 | const FilterItem = styled.li` 23 | padding: 8px 12px; 24 | background: white; 25 | border-radius: 4px; 26 | margin-bottom: 8px; 27 | box-shadow: 0 1px 3px rgba(0,0,0,0.1); 28 | display: flex; 29 | justify-content: space-between; 30 | align-items: center; 31 | `; 32 | 33 | const FilterName = styled.span` 34 | color: #333; 35 | `; 36 | 37 | const Timestamp = styled.span` 38 | color: #666; 39 | font-size: 0.8rem; 40 | `; 41 | 42 | const FilterHistory = () => { 43 | const [filterHistory, setFilterHistory] = React.useState([]); 44 | 45 | React.useEffect(() => { 46 | // Load filter history from localStorage 47 | const savedHistory = localStorage.getItem('filterHistory'); 48 | if (savedHistory) { 49 | setFilterHistory(JSON.parse(savedHistory)); 50 | } 51 | }, []); 52 | 53 | return ( 54 | 55 |
Filter History
56 | 57 | {filterHistory.map((filter, index) => ( 58 | 59 | {filter.name} 60 | {new Date(filter.timestamp).toLocaleTimeString()} 61 | 62 | ))} 63 | 64 |
65 | ); 66 | }; 67 | 68 | export default FilterHistory; -------------------------------------------------------------------------------- /Gallery.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styled from 'styled-components'; 3 | 4 | const Container = styled.div` 5 | background: #f8f9fa; 6 | border-radius: 8px; 7 | padding: 15px; 8 | margin-top: 20px; 9 | `; 10 | 11 | const Header = styled.h2` 12 | margin: 0 0 15px 0; 13 | color: #333; 14 | font-size: 1.2rem; 15 | `; 16 | 17 | const Grid = styled.div` 18 | display: grid; 19 | grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); 20 | gap: 15px; 21 | `; 22 | 23 | const ImageCard = styled.div` 24 | position: relative; 25 | border-radius: 8px; 26 | overflow: hidden; 27 | box-shadow: 0 2px 4px rgba(0,0,0,0.1); 28 | 29 | &:hover { 30 | .overlay { 31 | opacity: 1; 32 | } 33 | } 34 | `; 35 | 36 | const Image = styled.img` 37 | width: 100%; 38 | height: 150px; 39 | object-fit: cover; 40 | `; 41 | 42 | const Overlay = styled.div` 43 | position: absolute; 44 | top: 0; 45 | left: 0; 46 | right: 0; 47 | bottom: 0; 48 | background: rgba(0,0,0,0.5); 49 | display: flex; 50 | justify-content: center; 51 | align-items: center; 52 | opacity: 0; 53 | transition: opacity 0.3s; 54 | `; 55 | 56 | const Button = styled.button` 57 | background: white; 58 | border: none; 59 | padding: 8px 12px; 60 | border-radius: 4px; 61 | cursor: pointer; 62 | margin: 0 5px; 63 | 64 | &:hover { 65 | background: #f0f0f0; 66 | } 67 | `; 68 | 69 | const Gallery = () => { 70 | const [images, setImages] = React.useState([]); 71 | 72 | React.useEffect(() => { 73 | // Load saved images from localStorage 74 | const savedImages = localStorage.getItem('galleryImages'); 75 | if (savedImages) { 76 | setImages(JSON.parse(savedImages)); 77 | } 78 | }, []); 79 | 80 | const handleDownload = (imageUrl) => { 81 | const link = document.createElement('a'); 82 | link.href = imageUrl; 83 | link.download = `filtrovision-${Date.now()}.png`; 84 | document.body.appendChild(link); 85 | link.click(); 86 | document.body.removeChild(link); 87 | }; 88 | 89 | const handleDelete = (index) => { 90 | const newImages = images.filter((_, i) => i !== index); 91 | setImages(newImages); 92 | localStorage.setItem('galleryImages', JSON.stringify(newImages)); 93 | }; 94 | 95 | return ( 96 | 97 |
Saved Images
98 | 99 | {images.map((image, index) => ( 100 | 101 | {`Saved 102 | 103 | 104 | 105 | 106 | 107 | ))} 108 | 109 |
110 | ); 111 | }; 112 | 113 | export default Gallery; -------------------------------------------------------------------------------- /gallery.js: -------------------------------------------------------------------------------- 1 | class Gallery { 2 | constructor() { 3 | this.galleryGrid = document.getElementById('galleryGrid'); 4 | this.saveBtn = document.getElementById('saveBtn'); 5 | this.capturedImg = document.getElementById('capturedImg'); 6 | this.savedImages = JSON.parse(localStorage.getItem('savedImages')) || []; 7 | 8 | this.initializeEventListeners(); 9 | this.loadGallery(); 10 | } 11 | 12 | initializeEventListeners() { 13 | this.saveBtn.addEventListener('click', () => this.saveImage()); 14 | } 15 | 16 | saveImage() { 17 | if (!this.capturedImg.src) { 18 | this.showNotification('No image to save'); 19 | return; 20 | } 21 | 22 | const imageData = { 23 | id: Date.now(), 24 | src: this.capturedImg.src, 25 | timestamp: new Date().toLocaleString(), 26 | filter: document.getElementById('filterSelect').value 27 | }; 28 | 29 | this.savedImages.unshift(imageData); 30 | this.savedImages = this.savedImages.slice(0, 20); // Keep only last 20 images 31 | localStorage.setItem('savedImages', JSON.stringify(this.savedImages)); 32 | 33 | this.loadGallery(); 34 | this.showNotification('Image saved to gallery!'); 35 | } 36 | 37 | loadGallery() { 38 | if (!this.galleryGrid) { 39 | console.error('Gallery grid element not found'); 40 | return; 41 | } 42 | 43 | this.galleryGrid.innerHTML = ''; 44 | 45 | if (this.savedImages.length === 0) { 46 | this.galleryGrid.innerHTML = ''; 47 | return; 48 | } 49 | 50 | this.savedImages.forEach(image => { 51 | const item = document.createElement('div'); 52 | item.className = 'gallery-item'; 53 | 54 | const img = document.createElement('img'); 55 | img.src = image.src; 56 | img.alt = `Saved image with ${image.filter} filter`; 57 | 58 | const deleteBtn = document.createElement('button'); 59 | deleteBtn.className = 'delete-btn'; 60 | deleteBtn.innerHTML = '×'; 61 | deleteBtn.addEventListener('click', (e) => { 62 | e.stopPropagation(); 63 | this.deleteImage(image.id); 64 | }); 65 | 66 | item.appendChild(img); 67 | item.appendChild(deleteBtn); 68 | this.galleryGrid.appendChild(item); 69 | }); 70 | } 71 | 72 | deleteImage(id) { 73 | this.savedImages = this.savedImages.filter(img => img.id !== id); 74 | localStorage.setItem('savedImages', JSON.stringify(this.savedImages)); 75 | this.loadGallery(); 76 | this.showNotification('Image deleted from gallery'); 77 | } 78 | 79 | showNotification(message) { 80 | const notification = document.createElement('div'); 81 | notification.className = 'notification'; 82 | notification.textContent = message; 83 | 84 | document.body.appendChild(notification); 85 | 86 | setTimeout(() => { 87 | notification.classList.add('show'); 88 | }, 100); 89 | 90 | setTimeout(() => { 91 | notification.classList.remove('show'); 92 | setTimeout(() => { 93 | notification.remove(); 94 | }, 300); 95 | }, 2000); 96 | } 97 | } 98 | 99 | // Initialize gallery when DOM is loaded 100 | document.addEventListener('DOMContentLoaded', () => { 101 | window.gallery = new Gallery(); 102 | }); -------------------------------------------------------------------------------- /filterHistory.js: -------------------------------------------------------------------------------- 1 | class FilterHistory { 2 | constructor() { 3 | console.log('Initializing FilterHistory'); 4 | this.filterList = document.getElementById('filterList'); 5 | console.log('Filter list element:', this.filterList); 6 | this.filters = JSON.parse(localStorage.getItem('filterHistory')) || []; 7 | this.loadFilterHistory(); 8 | } 9 | 10 | saveFilter(filter) { 11 | console.log('Saving filter:', filter); 12 | const timestamp = new Date().toLocaleString(); 13 | this.filters.unshift({ filter, timestamp }); 14 | this.filters = this.filters.slice(0, 5); // Keep only last 5 filters 15 | localStorage.setItem('filterHistory', JSON.stringify(this.filters)); 16 | this.loadFilterHistory(); 17 | } 18 | 19 | loadFilterHistory() { 20 | console.log('Loading filter history'); 21 | if (!this.filterList) { 22 | console.error('Filter list element not found'); 23 | return; 24 | } 25 | 26 | this.filterList.innerHTML = ''; 27 | 28 | if (this.filters.length === 0) { 29 | this.filterList.innerHTML = '
  • No filters used yet
  • '; 30 | return; 31 | } 32 | 33 | this.filters.forEach(item => { 34 | const li = document.createElement('li'); 35 | li.className = 'filter-item'; 36 | 37 | const filterName = document.createElement('span'); 38 | filterName.className = 'filter-name'; 39 | filterName.textContent = item.filter; 40 | 41 | const timestamp = document.createElement('span'); 42 | timestamp.className = 'filter-timestamp'; 43 | timestamp.textContent = item.timestamp; 44 | 45 | li.appendChild(filterName); 46 | li.appendChild(timestamp); 47 | this.filterList.appendChild(li); 48 | }); 49 | 50 | console.log('Filter history loaded:', this.filters); 51 | } 52 | 53 | displayFilterHistory(filters) { 54 | console.log('Displaying filter history'); 55 | if (!this.historyContainer) { 56 | console.error('History container not found'); 57 | return; 58 | } 59 | 60 | // Clear previous content 61 | this.historyContainer.innerHTML = ''; 62 | 63 | // Create header 64 | const header = document.createElement('h3'); 65 | header.textContent = 'Last 5 Used Filters'; 66 | header.className = 'history-header'; 67 | this.historyContainer.appendChild(header); 68 | 69 | if (filters.length === 0) { 70 | const noFilters = document.createElement('p'); 71 | noFilters.textContent = 'No filters used yet'; 72 | noFilters.className = 'no-filters'; 73 | this.historyContainer.appendChild(noFilters); 74 | return; 75 | } 76 | 77 | // Create filter list 78 | const filterList = document.createElement('ul'); 79 | filterList.className = 'filter-list'; 80 | 81 | filters.forEach(filter => { 82 | const listItem = document.createElement('li'); 83 | listItem.className = 'filter-item'; 84 | 85 | // Create filter name span 86 | const filterName = document.createElement('span'); 87 | filterName.className = 'filter-name'; 88 | filterName.textContent = filter.filter; 89 | 90 | // Create timestamp span 91 | const timestamp = document.createElement('span'); 92 | timestamp.className = 'filter-timestamp'; 93 | timestamp.textContent = new Date(filter.timestamp).toLocaleString(); 94 | 95 | // Add both spans to list item 96 | listItem.appendChild(filterName); 97 | listItem.appendChild(timestamp); 98 | 99 | filterList.appendChild(listItem); 100 | }); 101 | 102 | this.historyContainer.appendChild(filterList); 103 | console.log('Filter history displayed'); 104 | } 105 | } 106 | 107 | // Initialize filter history when DOM is loaded 108 | document.addEventListener('DOMContentLoaded', () => { 109 | window.filterHistory = new FilterHistory(); 110 | }); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Webcam Face Detection & Filters Application 2 | 3 | ## Overview 4 | A modern web application that provides real-time face detection and image filtering capabilities. The application allows users to capture images from their webcam, upload images from their device, and apply various artistic filters to them. 5 | 6 | ## Features 7 | 8 | ### 1. Authentication 9 | - Secure login system 10 | - User session management 11 | - Demo credentials for testing 12 | - Responsive login page with modern design 13 | 14 | ### 2. Webcam Functionality 15 | - Real-time webcam access 16 | - Face detection using face-api.js 17 | - Start/Stop camera controls 18 | - Automatic face detection with visual indicators 19 | 20 | ### 3. Image Upload 21 | - Support for uploading local images 22 | - Automatic canvas resizing for uploaded images 23 | - Seamless switching between webcam and uploaded images 24 | - Maintains aspect ratio of uploaded images 25 | 26 | ### 4. Filter System 27 | #### Basic Filters 28 | - Grayscale 29 | - Blur 30 | - Invert 31 | - Sepia 32 | 33 | #### Color Effects 34 | - Red Tint 35 | - Blue Tint 36 | - Green Tint 37 | 38 | #### Artistic Filters 39 | - Pixelate 40 | - Mirror 41 | - Vintage 42 | - Neon Glow 43 | - Solarize 44 | - Posterize 45 | - Emboss 46 | 47 | ### 5. Gallery System 48 | - Save filtered images 49 | - View saved images in a grid layout 50 | - Delete unwanted images 51 | - Persistent storage using localStorage 52 | - Maximum of 20 saved images 53 | - Image metadata (filter used, timestamp) 54 | 55 | ### 6. Filter History 56 | - Tracks last 5 used filters 57 | - Timestamps for each filter use 58 | - Persistent storage 59 | - Real-time updates 60 | 61 | ### 7. Image Management 62 | - Capture images from webcam 63 | - Download filtered images 64 | - Save to gallery 65 | - Delete from gallery 66 | 67 | ## Technical Implementation 68 | 69 | ### Frontend Technologies 70 | - HTML5 71 | - CSS3 72 | - JavaScript (ES6+) 73 | - face-api.js for face detection 74 | - Canvas API for image processing 75 | 76 | ### Key Components 77 | 78 | #### 1. Authentication (`login.html`, `login.js`) 79 | - Form validation 80 | - Session management 81 | - Secure credential handling 82 | 83 | #### 2. Main Application (`index.html`, `script.js`) 84 | - Webcam integration 85 | - Face detection 86 | - Filter processing 87 | - Image capture and manipulation 88 | 89 | #### 3. Filter System (`script.js`) 90 | - Real-time filter application 91 | - Multiple filter algorithms 92 | - Canvas-based image processing 93 | 94 | #### 4. Gallery Management (`gallery.js`) 95 | - Image storage 96 | - Grid display 97 | - Delete functionality 98 | - Local storage integration 99 | 100 | #### 5. Filter History (`filterHistory.js`) 101 | - Usage tracking 102 | - Timestamp management 103 | - History display 104 | 105 | ### Styling (`style.css`) 106 | - Modern, responsive design 107 | - Gradient backgrounds 108 | - Glassmorphic effects 109 | - Smooth animations 110 | - Mobile-friendly layout 111 | 112 | ## Usage Guide 113 | 114 | ### 1. Getting Started 115 | 1. Open `login.html` in a modern web browser 116 | 2. Use demo credentials: 117 | - Username: admin 118 | - Password: password 119 | 3. Allow webcam access when prompted 120 | 121 | ### 2. Using the Webcam 122 | 1. Click "Start Camera" to begin 123 | 2. Position yourself in frame 124 | 3. Select a filter from the dropdown 125 | 4. Click "Capture" to take a photo 126 | 5. Use "Save" to add to gallery or "Download" to save locally 127 | 128 | ### 3. Uploading Images 129 | 1. Click "Upload Image" 130 | 2. Select an image from your device 131 | 3. Apply filters as desired 132 | 4. Save or download the filtered image 133 | 134 | ### 4. Managing Gallery 135 | 1. View saved images in the gallery section 136 | 2. Hover over images to see delete option 137 | 3. Click × to remove images 138 | 4. Maximum of 20 images stored 139 | 140 | ## Browser Compatibility 141 | - Chrome (recommended) 142 | - Firefox 143 | - Edge 144 | - Safari 145 | 146 | ## Requirements 147 | - Modern web browser 148 | - Webcam (for webcam features) 149 | - JavaScript enabled 150 | - Local storage support 151 | 152 | ## Security Features 153 | - Client-side authentication 154 | - Secure session management 155 | - Local storage for data persistence 156 | - No server-side dependencies 157 | 158 | ## Performance Considerations 159 | - Optimized image processing 160 | - Efficient filter algorithms 161 | - Responsive design 162 | - Smooth animations 163 | 164 | ## Future Enhancements 165 | 1. Additional filter effects 166 | 2. Filter customization options 167 | 3. Social media sharing 168 | 4. Cloud storage integration 169 | 5. Multiple face detection 170 | 6. Filter intensity controls 171 | 7. Batch processing 172 | 8. Export/Import gallery 173 | 174 | ## Support 175 | For issues or feature requests, please contact the development team. 176 | 177 | ## License 178 | This project is licensed under the MIT License. -------------------------------------------------------------------------------- /WebcamSection.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useRef, useEffect } from 'react'; 2 | import styled from 'styled-components'; 3 | import * as faceapi from 'face-api.js'; 4 | 5 | const Container = styled.div` 6 | position: relative; 7 | width: 100%; 8 | `; 9 | 10 | const Video = styled.video` 11 | width: 100%; 12 | border-radius: 8px; 13 | `; 14 | 15 | const Canvas = styled.canvas` 16 | position: absolute; 17 | top: 0; 18 | left: 0; 19 | width: 100%; 20 | height: 100%; 21 | `; 22 | 23 | const Controls = styled.div` 24 | margin-top: 20px; 25 | display: flex; 26 | gap: 10px; 27 | flex-wrap: wrap; 28 | `; 29 | 30 | const Button = styled.button` 31 | padding: 8px 16px; 32 | border: none; 33 | border-radius: 4px; 34 | background-color: #007bff; 35 | color: white; 36 | cursor: pointer; 37 | &:hover { 38 | background-color: #0056b3; 39 | } 40 | `; 41 | 42 | const Select = styled.select` 43 | padding: 8px; 44 | border-radius: 4px; 45 | border: 1px solid #ccc; 46 | `; 47 | 48 | const CapturedImage = styled.div` 49 | margin-top: 20px; 50 | img { 51 | max-width: 100%; 52 | border-radius: 8px; 53 | } 54 | `; 55 | 56 | const WebcamSection = () => { 57 | const videoRef = useRef(null); 58 | const canvasRef = useRef(null); 59 | const [isCameraOn, setIsCameraOn] = useState(false); 60 | const [selectedFilter, setSelectedFilter] = useState('none'); 61 | const [capturedImage, setCapturedImage] = useState(null); 62 | 63 | useEffect(() => { 64 | loadFaceDetectionModels(); 65 | }, []); 66 | 67 | const loadFaceDetectionModels = async () => { 68 | try { 69 | await faceapi.nets.tinyFaceDetector.loadFromUri('/models'); 70 | await faceapi.nets.faceLandmark68Net.loadFromUri('/models'); 71 | await faceapi.nets.faceRecognitionNet.loadFromUri('/models'); 72 | } catch (error) { 73 | console.error('Error loading face detection models:', error); 74 | } 75 | }; 76 | 77 | const startCamera = async () => { 78 | try { 79 | const stream = await navigator.mediaDevices.getUserMedia({ video: true }); 80 | if (videoRef.current) { 81 | videoRef.current.srcObject = stream; 82 | setIsCameraOn(true); 83 | } 84 | } catch (error) { 85 | console.error('Error accessing camera:', error); 86 | } 87 | }; 88 | 89 | const stopCamera = () => { 90 | if (videoRef.current && videoRef.current.srcObject) { 91 | const stream = videoRef.current.srcObject; 92 | const tracks = stream.getTracks(); 93 | tracks.forEach(track => track.stop()); 94 | videoRef.current.srcObject = null; 95 | setIsCameraOn(false); 96 | } 97 | }; 98 | 99 | const captureImage = () => { 100 | if (videoRef.current) { 101 | const canvas = document.createElement('canvas'); 102 | canvas.width = videoRef.current.videoWidth; 103 | canvas.height = videoRef.current.videoHeight; 104 | const ctx = canvas.getContext('2d'); 105 | ctx.drawImage(videoRef.current, 0, 0); 106 | setCapturedImage(canvas.toDataURL('image/png')); 107 | } 108 | }; 109 | 110 | const applyFilter = (filter) => { 111 | setSelectedFilter(filter); 112 | // Filter application logic will be implemented here 113 | }; 114 | 115 | return ( 116 | 117 | 151 | ); 152 | }; 153 | 154 | export default WebcamSection; -------------------------------------------------------------------------------- /style.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: 'Poppins', sans-serif; 3 | background: linear-gradient(135deg, rgba(255, 182, 193, 0.1) 0%, rgba(176, 224, 230, 0.1) 50%, rgba(221, 160, 221, 0.1) 100%); 4 | color: #666; 5 | display: flex; 6 | flex-direction: column; 7 | align-items: center; 8 | min-height: 100vh; 9 | margin: 0; 10 | } 11 | 12 | h1 { 13 | color: #FF69B4; 14 | margin-top: 32px; 15 | margin-bottom: 16px; 16 | text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.05); 17 | } 18 | 19 | .container { 20 | position: relative; 21 | width: 640px; 22 | height: 480px; 23 | margin-bottom: 16px; 24 | border-radius: 20px; 25 | overflow: hidden; 26 | box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1); 27 | border: 1px solid rgba(255, 182, 193, 0.3); 28 | } 29 | 30 | video, canvas { 31 | position: absolute; 32 | top: 0; 33 | left: 0; 34 | border-radius: 20px; 35 | box-shadow: 0 4px 24px rgba(0, 0, 0, 0.1); 36 | } 37 | 38 | .controls { 39 | display: flex; 40 | flex-direction: column; 41 | gap: 16px; 42 | margin-bottom: 32px; 43 | align-items: center; 44 | background: rgba(255, 255, 255, 0.95); 45 | padding: 20px; 46 | border-radius: 20px; 47 | box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1); 48 | border: 1px solid rgba(255, 182, 193, 0.3); 49 | } 50 | 51 | .start-btn { 52 | padding: 12px 24px; 53 | font-size: 1.2rem; 54 | background: linear-gradient(135deg, #FFB6C1 0%, #B0E0E6 50%, #DDA0DD 100%); 55 | color: #666; 56 | border: none; 57 | border-radius: 8px; 58 | cursor: pointer; 59 | transition: all 0.3s ease; 60 | } 61 | 62 | .start-btn:hover { 63 | background: linear-gradient(135deg, #B0E0E6 0%, #DDA0DD 50%, #FFB6C1 100%); 64 | transform: translateY(-2px); 65 | box-shadow: 0 4px 12px rgba(255, 182, 193, 0.3); 66 | } 67 | 68 | .start-btn:disabled { 69 | background: #e0e0e0; 70 | cursor: not-allowed; 71 | transform: none; 72 | box-shadow: none; 73 | } 74 | 75 | .filter-controls { 76 | display: flex; 77 | gap: 12px; 78 | align-items: center; 79 | } 80 | 81 | select { 82 | padding: 8px 16px; 83 | border-radius: 8px; 84 | border: 2px solid #FFB6C1; 85 | font-size: 1rem; 86 | background: #fff; 87 | color: #666; 88 | cursor: pointer; 89 | transition: all 0.3s ease; 90 | } 91 | 92 | select:hover { 93 | border-color: #FF69B4; 94 | box-shadow: 0 0 0 3px rgba(255, 105, 180, 0.1); 95 | } 96 | 97 | .button-controls { 98 | display: flex; 99 | gap: 12px; 100 | } 101 | 102 | button { 103 | padding: 8px 16px; 104 | border-radius: 8px; 105 | border: none; 106 | font-size: 1rem; 107 | background: linear-gradient(135deg, #FFB6C1 0%, #B0E0E6 50%, #DDA0DD 100%); 108 | color: #666; 109 | cursor: pointer; 110 | transition: all 0.3s ease; 111 | } 112 | 113 | button:hover { 114 | background: linear-gradient(135deg, #B0E0E6 0%, #DDA0DD 50%, #FFB6C1 100%); 115 | transform: translateY(-2px); 116 | box-shadow: 0 4px 12px rgba(255, 182, 193, 0.3); 117 | } 118 | 119 | #downloadBtn { 120 | background: linear-gradient(135deg, #B0E0E6 0%, #DDA0DD 50%, #FFB6C1 100%); 121 | } 122 | 123 | #downloadBtn:hover { 124 | background: linear-gradient(135deg, #DDA0DD 0%, #FFB6C1 50%, #B0E0E6 100%); 125 | } 126 | 127 | .captured-image { 128 | margin-top: 20px; 129 | max-width: 640px; 130 | display: none; 131 | border-radius: 20px; 132 | overflow: hidden; 133 | box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1); 134 | border: 1px solid rgba(255, 182, 193, 0.3); 135 | } 136 | 137 | .captured-image img { 138 | width: 100%; 139 | border-radius: 20px; 140 | box-shadow: 0 4px 24px rgba(0, 0, 0, 0.1); 141 | } 142 | 143 | /* Reset default margins and padding */ 144 | * { 145 | margin: 0; 146 | padding: 0; 147 | box-sizing: border-box; 148 | } 149 | 150 | /* Login Page Styles */ 151 | .login-page { 152 | width: 100vw; 153 | height: 100vh; 154 | margin: 0; 155 | padding: 0; 156 | overflow: hidden; 157 | background: url('https://images.unsplash.com/photo-1557683316-973673baf926?q=80&w=2029&auto=format&fit=crop') center/cover no-repeat; 158 | font-family: 'Poppins', sans-serif; 159 | position: fixed; 160 | top: 0; 161 | left: 0; 162 | } 163 | 164 | .login-page::before { 165 | content: ''; 166 | position: absolute; 167 | top: 0; 168 | left: 0; 169 | width: 100%; 170 | height: 100%; 171 | background: linear-gradient(135deg, rgba(255, 182, 193, 0.9) 0%, rgba(176, 224, 230, 0.9) 50%, rgba(221, 160, 221, 0.9) 100%); 172 | backdrop-filter: blur(5px); 173 | } 174 | 175 | .login-container { 176 | width: 100%; 177 | height: 100%; 178 | display: flex; 179 | justify-content: center; 180 | align-items: center; 181 | position: absolute; 182 | top: 0; 183 | left: 0; 184 | z-index: 1; 185 | } 186 | 187 | .login-box { 188 | background: rgba(255, 255, 255, 0.95); 189 | padding: 40px; 190 | border-radius: 20px; 191 | box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1); 192 | width: 100%; 193 | max-width: 400px; 194 | border: 1px solid rgba(255, 255, 255, 0.2); 195 | margin: 20px; 196 | position: relative; 197 | overflow: hidden; 198 | } 199 | 200 | .login-box::before { 201 | content: ''; 202 | position: absolute; 203 | top: 0; 204 | left: 0; 205 | width: 100%; 206 | height: 5px; 207 | background: linear-gradient(90deg, #FFB6C1, #B0E0E6, #DDA0DD); 208 | } 209 | 210 | .login-header { 211 | text-align: center; 212 | margin-bottom: 30px; 213 | } 214 | 215 | .login-header h1 { 216 | color: #FF69B4; 217 | font-size: 2.2rem; 218 | margin: 0 0 10px 0; 219 | font-weight: 600; 220 | text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.05); 221 | } 222 | 223 | .login-header p { 224 | color: #666; 225 | margin: 0; 226 | font-size: 1.1rem; 227 | } 228 | 229 | .form-group { 230 | margin-bottom: 25px; 231 | position: relative; 232 | } 233 | 234 | .form-group label { 235 | display: block; 236 | margin-bottom: 8px; 237 | color: #FF69B4; 238 | font-size: 0.9rem; 239 | font-weight: 500; 240 | transition: all 0.3s ease; 241 | } 242 | 243 | .form-group input { 244 | width: 100%; 245 | padding: 12px 15px; 246 | border: 2px solid #FFB6C1; 247 | border-radius: 8px; 248 | background: #fff; 249 | color: #666; 250 | font-size: 1rem; 251 | transition: all 0.3s ease; 252 | } 253 | 254 | .form-group input:focus { 255 | outline: none; 256 | border-color: #FF69B4; 257 | box-shadow: 0 0 0 3px rgba(255, 105, 180, 0.1); 258 | } 259 | 260 | .form-group input:focus + label { 261 | color: #FF69B4; 262 | transform: translateY(-2px); 263 | } 264 | 265 | .form-group input::placeholder { 266 | color: #B0E0E6; 267 | } 268 | 269 | .login-btn { 270 | width: 100%; 271 | padding: 14px; 272 | background: linear-gradient(135deg, #FFB6C1 0%, #B0E0E6 50%, #DDA0DD 100%); 273 | color: #666; 274 | border: none; 275 | border-radius: 8px; 276 | font-size: 1rem; 277 | font-weight: 500; 278 | cursor: pointer; 279 | transition: all 0.3s ease; 280 | margin-top: 10px; 281 | position: relative; 282 | overflow: hidden; 283 | } 284 | 285 | .login-btn::before { 286 | content: ''; 287 | position: absolute; 288 | top: 0; 289 | left: -100%; 290 | width: 100%; 291 | height: 100%; 292 | background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.3), transparent); 293 | transition: 0.5s; 294 | } 295 | 296 | .login-btn:hover::before { 297 | left: 100%; 298 | } 299 | 300 | .login-btn:hover { 301 | background: linear-gradient(135deg, #B0E0E6 0%, #DDA0DD 50%, #FFB6C1 100%); 302 | transform: translateY(-2px); 303 | box-shadow: 0 4px 12px rgba(255, 182, 193, 0.3); 304 | } 305 | 306 | .login-btn:active { 307 | transform: translateY(0); 308 | } 309 | 310 | .error-message { 311 | color: #FF69B4; 312 | text-align: center; 313 | margin-top: 15px; 314 | font-size: 0.9rem; 315 | min-height: 20px; 316 | font-weight: 500; 317 | } 318 | 319 | .login-footer { 320 | margin-top: 30px; 321 | padding-top: 20px; 322 | border-top: 1px solid #FFB6C1; 323 | text-align: center; 324 | color: #666; 325 | font-size: 0.9rem; 326 | } 327 | 328 | .login-footer p { 329 | margin: 5px 0; 330 | } 331 | 332 | /* Add animation for the login box */ 333 | @keyframes fadeIn { 334 | from { 335 | opacity: 0; 336 | transform: translateY(20px); 337 | } 338 | to { 339 | opacity: 1; 340 | transform: translateY(0); 341 | } 342 | } 343 | 344 | .login-box { 345 | animation: fadeIn 0.5s ease-out; 346 | } 347 | 348 | /* Add floating animation for the background */ 349 | @keyframes float { 350 | 0% { 351 | transform: translateY(0px); 352 | } 353 | 50% { 354 | transform: translateY(-20px); 355 | } 356 | 100% { 357 | transform: translateY(0px); 358 | } 359 | } 360 | 361 | .login-page { 362 | animation: float 20s ease-in-out infinite; 363 | } 364 | 365 | .main-content { 366 | display: flex; 367 | gap: 20px; 368 | justify-content: center; 369 | align-items: flex-start; 370 | padding: 20px; 371 | max-width: 1200px; 372 | margin: 0 auto; 373 | background: linear-gradient(135deg, rgba(255, 182, 193, 0.1) 0%, rgba(176, 224, 230, 0.1) 50%, rgba(221, 160, 221, 0.1) 100%); 374 | min-height: 100vh; 375 | } 376 | 377 | .left-section { 378 | display: flex; 379 | flex-direction: column; 380 | gap: 20px; 381 | } 382 | 383 | .filter-history { 384 | margin-top: 0; 385 | padding: 20px; 386 | background: rgba(255, 255, 255, 0.95); 387 | border-radius: 20px; 388 | box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1); 389 | width: 300px; 390 | min-height: 480px; 391 | position: sticky; 392 | top: 20px; 393 | border: 1px solid rgba(255, 182, 193, 0.3); 394 | } 395 | 396 | .history-header { 397 | color: #FF69B4; 398 | margin: 0 0 15px 0; 399 | font-size: 1.2rem; 400 | border-bottom: 2px solid #FFB6C1; 401 | padding-bottom: 8px; 402 | } 403 | 404 | .filter-list { 405 | list-style: none; 406 | padding: 0; 407 | margin: 0; 408 | } 409 | 410 | .filter-item { 411 | display: flex; 412 | justify-content: space-between; 413 | align-items: center; 414 | padding: 12px 0; 415 | border-bottom: 1px solid rgba(255, 182, 193, 0.3); 416 | color: #666; 417 | } 418 | 419 | .filter-item:last-child { 420 | border-bottom: none; 421 | } 422 | 423 | .filter-name { 424 | font-weight: bold; 425 | color: #FF69B4; 426 | text-transform: capitalize; 427 | } 428 | 429 | .filter-timestamp { 430 | color: #B0E0E6; 431 | font-size: 0.9rem; 432 | } 433 | 434 | .no-filters { 435 | color: #B0E0E6; 436 | text-align: center; 437 | font-style: italic; 438 | margin: 10px 0; 439 | } 440 | 441 | .gallery-section { 442 | margin-top: 20px; 443 | padding: 20px; 444 | background: rgba(255, 255, 255, 0.95); 445 | border-radius: 20px; 446 | box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1); 447 | width: 300px; 448 | border: 1px solid rgba(255, 182, 193, 0.3); 449 | } 450 | 451 | .gallery-header { 452 | color: #FF69B4; 453 | margin: 0 0 15px 0; 454 | font-size: 1.2rem; 455 | border-bottom: 2px solid #FFB6C1; 456 | padding-bottom: 8px; 457 | } 458 | 459 | .gallery-grid { 460 | display: grid; 461 | grid-template-columns: repeat(2, 1fr); 462 | gap: 10px; 463 | max-height: 400px; 464 | overflow-y: auto; 465 | padding-right: 5px; 466 | } 467 | 468 | .gallery-grid::-webkit-scrollbar { 469 | width: 6px; 470 | } 471 | 472 | .gallery-grid::-webkit-scrollbar-track { 473 | background: rgba(255, 182, 193, 0.1); 474 | border-radius: 3px; 475 | } 476 | 477 | .gallery-grid::-webkit-scrollbar-thumb { 478 | background: #FFB6C1; 479 | border-radius: 3px; 480 | } 481 | 482 | .gallery-item { 483 | position: relative; 484 | border-radius: 10px; 485 | overflow: hidden; 486 | aspect-ratio: 1; 487 | cursor: pointer; 488 | transition: all 0.3s ease; 489 | } 490 | 491 | .gallery-item:hover { 492 | transform: scale(1.05); 493 | box-shadow: 0 4px 12px rgba(255, 182, 193, 0.3); 494 | } 495 | 496 | .gallery-item img { 497 | width: 100%; 498 | height: 100%; 499 | object-fit: cover; 500 | } 501 | 502 | .gallery-item .delete-btn { 503 | position: absolute; 504 | top: 5px; 505 | right: 5px; 506 | background: rgba(255, 255, 255, 0.9); 507 | border: none; 508 | border-radius: 50%; 509 | width: 24px; 510 | height: 24px; 511 | display: flex; 512 | align-items: center; 513 | justify-content: center; 514 | cursor: pointer; 515 | opacity: 0; 516 | transition: opacity 0.3s ease; 517 | color: #FF69B4; 518 | font-size: 14px; 519 | } 520 | 521 | .gallery-item:hover .delete-btn { 522 | opacity: 1; 523 | } 524 | 525 | .gallery-item .delete-btn:hover { 526 | background: #FF69B4; 527 | color: white; 528 | } 529 | 530 | .gallery-empty { 531 | text-align: center; 532 | color: #B0E0E6; 533 | font-style: italic; 534 | padding: 20px; 535 | grid-column: 1 / -1; 536 | } 537 | 538 | .notification { 539 | position: fixed; 540 | bottom: 20px; 541 | right: 20px; 542 | background: rgba(255, 255, 255, 0.95); 543 | color: #FF69B4; 544 | padding: 12px 24px; 545 | border-radius: 8px; 546 | box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); 547 | transform: translateY(100px); 548 | opacity: 0; 549 | transition: all 0.3s ease; 550 | z-index: 1000; 551 | border: 1px solid rgba(255, 182, 193, 0.3); 552 | } 553 | 554 | .notification.show { 555 | transform: translateY(0); 556 | opacity: 1; 557 | } 558 | 559 | .upload-section { 560 | display: flex; 561 | gap: 10px; 562 | margin-bottom: 10px; 563 | } 564 | 565 | .upload-btn { 566 | padding: 8px 16px; 567 | background: linear-gradient(135deg, #FFB6C1 0%, #B0E0E6 50%, #DDA0DD 100%); 568 | color: #666; 569 | border: none; 570 | border-radius: 8px; 571 | cursor: pointer; 572 | transition: all 0.3s ease; 573 | } 574 | 575 | .upload-btn:hover { 576 | background: linear-gradient(135deg, #B0E0E6 0%, #DDA0DD 50%, #FFB6C1 100%); 577 | transform: translateY(-2px); 578 | box-shadow: 0 4px 12px rgba(255, 182, 193, 0.3); 579 | } -------------------------------------------------------------------------------- /script.js: -------------------------------------------------------------------------------- 1 | // Check if user is logged in 2 | if (!localStorage.getItem('isLoggedIn')) { 3 | window.location.href = 'login.html'; 4 | } 5 | 6 | const video = document.getElementById('video'); 7 | const canvas = document.getElementById('canvas'); 8 | const ctx = canvas.getContext('2d'); 9 | const filterSelect = document.getElementById('filterSelect'); 10 | const startBtn = document.getElementById('startBtn'); 11 | const captureBtn = document.getElementById('captureBtn'); 12 | const saveBtn = document.getElementById('saveBtn'); 13 | const downloadBtn = document.getElementById('downloadBtn'); 14 | const capturedImg = document.getElementById('capturedImg'); 15 | const capturedImage = document.getElementById('capturedImage'); 16 | const imageUpload = document.getElementById('imageUpload'); 17 | const uploadBtn = document.getElementById('uploadBtn'); 18 | 19 | let isStreaming = false; 20 | let filterHistory; 21 | let uploadedImage = null; 22 | 23 | // Initialize filter history when DOM is loaded 24 | document.addEventListener('DOMContentLoaded', () => { 25 | console.log('DOM Content Loaded'); 26 | filterHistory = new FilterHistory(); 27 | 28 | // Add filter change handler 29 | filterSelect.addEventListener('change', function(e) { 30 | const selectedFilter = e.target.value; 31 | if (filterHistory) { 32 | filterHistory.saveFilter(selectedFilter); 33 | } 34 | if (uploadedImage) { 35 | applyFilterToUploadedImage(selectedFilter); 36 | } 37 | }); 38 | }); 39 | 40 | // Handle image upload 41 | uploadBtn.addEventListener('click', () => { 42 | imageUpload.click(); 43 | }); 44 | 45 | imageUpload.addEventListener('change', (e) => { 46 | const file = e.target.files[0]; 47 | if (file) { 48 | const reader = new FileReader(); 49 | reader.onload = (event) => { 50 | uploadedImage = new Image(); 51 | uploadedImage.onload = () => { 52 | // Stop video if it's running 53 | if (isStreaming) { 54 | stopVideo(); 55 | startBtn.textContent = 'Start Camera'; 56 | isStreaming = false; 57 | } 58 | 59 | // Set canvas size to match uploaded image 60 | canvas.width = uploadedImage.width; 61 | canvas.height = uploadedImage.height; 62 | 63 | // Draw the uploaded image 64 | ctx.drawImage(uploadedImage, 0, 0); 65 | 66 | // Apply current filter 67 | const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); 68 | const filteredImageData = applyFilter(imageData, filterSelect.value); 69 | ctx.putImageData(filteredImageData, 0, 0); 70 | 71 | // Show save and download buttons 72 | saveBtn.style.display = 'inline-block'; 73 | downloadBtn.style.display = 'inline-block'; 74 | }; 75 | uploadedImage.src = event.target.result; 76 | }; 77 | reader.readAsDataURL(file); 78 | } 79 | }); 80 | 81 | function applyFilterToUploadedImage(filter) { 82 | if (!uploadedImage) return; 83 | 84 | ctx.drawImage(uploadedImage, 0, 0); 85 | const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); 86 | const filteredImageData = applyFilter(imageData, filter); 87 | ctx.putImageData(filteredImageData, 0, 0); 88 | } 89 | 90 | // Load face-api.js models 91 | Promise.all([ 92 | faceapi.nets.tinyFaceDetector.loadFromUri('https://justadudewhohacks.github.io/face-api.js/models'), 93 | faceapi.nets.faceLandmark68Net.loadFromUri('https://justadudewhohacks.github.io/face-api.js/models'), 94 | faceapi.nets.faceRecognitionNet.loadFromUri('https://justadudewhohacks.github.io/face-api.js/models'), 95 | faceapi.nets.faceExpressionNet.loadFromUri('https://justadudewhohacks.github.io/face-api.js/models') 96 | ]).then(() => { 97 | console.log('Face detection models loaded'); 98 | startBtn.disabled = false; 99 | }) 100 | .catch(err => { 101 | console.error('Error loading face-api models:', err); 102 | alert('Error loading face detection models. Please check your internet connection and try again.'); 103 | }); 104 | 105 | // Start Camera button click handler 106 | startBtn.addEventListener('click', () => { 107 | if (!isStreaming) { 108 | startVideo(); 109 | startBtn.textContent = 'Stop Camera'; 110 | } else { 111 | stopVideo(); 112 | startBtn.textContent = 'Start Camera'; 113 | } 114 | isStreaming = !isStreaming; 115 | }); 116 | 117 | // Capture button click handler 118 | captureBtn.addEventListener('click', () => { 119 | if (!isStreaming) { 120 | alert('Please start the camera first'); 121 | return; 122 | } 123 | 124 | // Create a temporary canvas to capture the current frame 125 | const tempCanvas = document.createElement('canvas'); 126 | tempCanvas.width = canvas.width; 127 | tempCanvas.height = canvas.height; 128 | const tempCtx = tempCanvas.getContext('2d'); 129 | 130 | // Draw the current frame to the temporary canvas 131 | tempCtx.drawImage(canvas, 0, 0); 132 | 133 | // Get the image data and apply the current filter 134 | const imageData = tempCtx.getImageData(0, 0, tempCanvas.width, tempCanvas.height); 135 | const filteredImageData = applyFilter(imageData, filterSelect.value); 136 | tempCtx.putImageData(filteredImageData, 0, 0); 137 | 138 | // Set the captured image source 139 | capturedImg.src = tempCanvas.toDataURL('image/png'); 140 | capturedImage.style.display = 'block'; 141 | saveBtn.style.display = 'inline-block'; 142 | downloadBtn.style.display = 'inline-block'; 143 | }); 144 | 145 | // Save button click handler 146 | saveBtn.addEventListener('click', () => { 147 | if (!window.gallery) { 148 | console.error('Gallery not initialized'); 149 | return; 150 | } 151 | 152 | // Get the current canvas content 153 | const imageData = canvas.toDataURL('image/png'); 154 | capturedImg.src = imageData; 155 | 156 | // Save to gallery 157 | window.gallery.saveImage(); 158 | }); 159 | 160 | // Download button click handler 161 | downloadBtn.addEventListener('click', () => { 162 | const link = document.createElement('a'); 163 | link.download = `filtered-image-${Date.now()}.png`; 164 | link.href = canvas.toDataURL('image/png'); 165 | link.click(); 166 | }); 167 | 168 | function startVideo() { 169 | if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) { 170 | alert('Your browser does not support webcam access. Please try using Chrome, Firefox, or Edge.'); 171 | return; 172 | } 173 | 174 | navigator.mediaDevices.getUserMedia({ 175 | video: { 176 | width: { ideal: 640 }, 177 | height: { ideal: 480 }, 178 | facingMode: 'user' 179 | } 180 | }) 181 | .then(stream => { 182 | console.log('Camera access granted'); 183 | video.srcObject = stream; 184 | video.play(); 185 | }) 186 | .catch(err => { 187 | console.error('Error accessing camera:', err); 188 | if (err.name === 'NotAllowedError') { 189 | alert('Camera access was denied. Please allow camera access and try again.'); 190 | } else if (err.name === 'NotFoundError') { 191 | alert('No camera found. Please connect a camera and try again.'); 192 | } else if (err.name === 'NotReadableError') { 193 | alert('Camera is already in use by another application. Please close other applications using the camera.'); 194 | } else { 195 | alert('Error accessing camera: ' + err.message); 196 | } 197 | isStreaming = false; 198 | startBtn.textContent = 'Start Camera'; 199 | }); 200 | } 201 | 202 | function stopVideo() { 203 | if (video.srcObject) { 204 | const tracks = video.srcObject.getTracks(); 205 | tracks.forEach(track => track.stop()); 206 | video.srcObject = null; 207 | ctx.clearRect(0, 0, canvas.width, canvas.height); 208 | } 209 | } 210 | 211 | // Handle video stream 212 | video.addEventListener('loadedmetadata', () => { 213 | // Set canvas size to match video 214 | canvas.width = video.videoWidth; 215 | canvas.height = video.videoHeight; 216 | }); 217 | 218 | // Process video frames 219 | video.addEventListener('play', () => { 220 | function processFrame() { 221 | if (video.paused || video.ended) return; 222 | 223 | // Clear canvas 224 | ctx.clearRect(0, 0, canvas.width, canvas.height); 225 | 226 | // Draw current video frame 227 | ctx.drawImage(video, 0, 0, canvas.width, canvas.height); 228 | 229 | // Get the entire frame data 230 | const frameData = ctx.getImageData(0, 0, canvas.width, canvas.height); 231 | 232 | // Apply filter to the entire frame 233 | const filteredFrame = applyFilter(frameData, filterSelect.value); 234 | ctx.putImageData(filteredFrame, 0, 0); 235 | 236 | // Process face detection 237 | faceapi.detectAllFaces(video, new faceapi.TinyFaceDetectorOptions()) 238 | .then(detections => { 239 | detections.forEach(det => { 240 | const { x, y, width, height } = det.box; 241 | // Draw face box 242 | ctx.strokeStyle = '#00FF00'; 243 | ctx.lineWidth = 2; 244 | ctx.strokeRect(x, y, width, height); 245 | }); 246 | }) 247 | .catch(error => { 248 | console.error('Face detection error:', error); 249 | }); 250 | 251 | // Request next frame 252 | requestAnimationFrame(processFrame); 253 | } 254 | 255 | // Start processing frames 256 | processFrame(); 257 | }); 258 | 259 | function applyFilter(imageData, filter) { 260 | const data = imageData.data; 261 | 262 | switch (filter) { 263 | case 'grayscale': 264 | for (let i = 0; i < data.length; i += 4) { 265 | const avg = (data[i] + data[i + 1] + data[i + 2]) / 3; 266 | data[i] = data[i + 1] = data[i + 2] = avg; 267 | } 268 | break; 269 | 270 | case 'invert': 271 | for (let i = 0; i < data.length; i += 4) { 272 | data[i] = 255 - data[i]; 273 | data[i + 1] = 255 - data[i + 1]; 274 | data[i + 2] = 255 - data[i + 2]; 275 | } 276 | break; 277 | 278 | case 'sepia': 279 | for (let i = 0; i < data.length; i += 4) { 280 | const r = data[i], g = data[i + 1], b = data[i + 2]; 281 | data[i] = Math.min(0.393 * r + 0.769 * g + 0.189 * b, 255); 282 | data[i + 1] = Math.min(0.349 * r + 0.686 * g + 0.168 * b, 255); 283 | data[i + 2] = Math.min(0.272 * r + 0.534 * g + 0.131 * b, 255); 284 | } 285 | break; 286 | 287 | case 'pixelate': 288 | const pixelSize = 8; 289 | for (let y = 0; y < imageData.height; y += pixelSize) { 290 | for (let x = 0; x < imageData.width; x += pixelSize) { 291 | const r = data[((y * imageData.width + x) * 4)]; 292 | const g = data[((y * imageData.width + x) * 4) + 1]; 293 | const b = data[((y * imageData.width + x) * 4) + 2]; 294 | for (let py = 0; py < pixelSize; py++) { 295 | for (let px = 0; px < pixelSize; px++) { 296 | const idx = ((y + py) * imageData.width + (x + px)) * 4; 297 | data[idx] = r; 298 | data[idx + 1] = g; 299 | data[idx + 2] = b; 300 | } 301 | } 302 | } 303 | } 304 | break; 305 | 306 | case 'mirror': 307 | const temp = new Uint8ClampedArray(data); 308 | for (let y = 0; y < imageData.height; y++) { 309 | for (let x = 0; x < imageData.width; x++) { 310 | const idx = (y * imageData.width + x) * 4; 311 | const mirrorIdx = (y * imageData.width + (imageData.width - x - 1)) * 4; 312 | data[idx] = temp[mirrorIdx]; 313 | data[idx + 1] = temp[mirrorIdx + 1]; 314 | data[idx + 2] = temp[mirrorIdx + 2]; 315 | data[idx + 3] = temp[mirrorIdx + 3]; 316 | } 317 | } 318 | break; 319 | 320 | case 'red': 321 | for (let i = 0; i < data.length; i += 4) { 322 | data[i] = Math.min(data[i] * 1.5, 255); 323 | data[i + 1] *= 0.5; 324 | data[i + 2] *= 0.5; 325 | } 326 | break; 327 | 328 | case 'blue': 329 | for (let i = 0; i < data.length; i += 4) { 330 | data[i] *= 0.5; 331 | data[i + 1] *= 0.5; 332 | data[i + 2] = Math.min(data[i + 2] * 1.5, 255); 333 | } 334 | break; 335 | 336 | case 'green': 337 | for (let i = 0; i < data.length; i += 4) { 338 | data[i] *= 0.5; 339 | data[i + 1] = Math.min(data[i + 1] * 1.5, 255); 340 | data[i + 2] *= 0.5; 341 | } 342 | break; 343 | 344 | case 'vintage': 345 | for (let i = 0; i < data.length; i += 4) { 346 | const r = data[i], g = data[i + 1], b = data[i + 2]; 347 | data[i] = Math.min(0.393 * r + 0.769 * g + 0.189 * b, 255); 348 | data[i + 1] = Math.min(0.349 * r + 0.686 * g + 0.168 * b, 255); 349 | data[i + 2] = Math.min(0.272 * r + 0.534 * g + 0.131 * b, 255); 350 | data[i] = Math.min(data[i] * 1.1, 255); 351 | data[i + 1] = Math.min(data[i + 1] * 1.1, 255); 352 | data[i + 2] *= 0.9; 353 | } 354 | break; 355 | 356 | case 'neon': 357 | for (let i = 0; i < data.length; i += 4) { 358 | const r = data[i], g = data[i + 1], b = data[i + 2]; 359 | const brightness = (r + g + b) / 3; 360 | const neonIntensity = 1.5; 361 | 362 | data[i] = Math.min(r * neonIntensity + brightness * 0.5, 255); 363 | data[i + 1] = Math.min(g * neonIntensity + brightness * 0.5, 255); 364 | data[i + 2] = Math.min(b * neonIntensity + brightness * 0.5, 255); 365 | } 366 | break; 367 | 368 | case 'solarize': 369 | for (let i = 0; i < data.length; i += 4) { 370 | const threshold = 128; 371 | data[i] = data[i] > threshold ? 255 - data[i] : data[i]; 372 | data[i + 1] = data[i + 1] > threshold ? 255 - data[i + 1] : data[i + 1]; 373 | data[i + 2] = data[i + 2] > threshold ? 255 - data[i + 2] : data[i + 2]; 374 | } 375 | break; 376 | 377 | case 'posterize': 378 | const levels = 4; 379 | const step = 255 / (levels - 1); 380 | for (let i = 0; i < data.length; i += 4) { 381 | data[i] = Math.round(data[i] / step) * step; 382 | data[i + 1] = Math.round(data[i + 1] / step) * step; 383 | data[i + 2] = Math.round(data[i + 2] / step) * step; 384 | } 385 | break; 386 | 387 | case 'emboss': 388 | const w = imageData.width; 389 | const h = imageData.height; 390 | const copy = new Uint8ClampedArray(data); 391 | for (let y = 1; y < h - 1; y++) { 392 | for (let x = 1; x < w - 1; x++) { 393 | for (let c = 0; c < 3; c++) { 394 | const idx = (y * w + x) * 4 + c; 395 | const topLeft = copy[((y - 1) * w + (x - 1)) * 4 + c]; 396 | const bottomRight = copy[((y + 1) * w + (x + 1)) * 4 + c]; 397 | const diff = topLeft - bottomRight; 398 | data[idx] = Math.min(Math.max(diff + 128, 0), 255); 399 | } 400 | } 401 | } 402 | break; 403 | 404 | case 'blur': 405 | const blurW = imageData.width; 406 | const blurH = imageData.height; 407 | const blurCopy = new Uint8ClampedArray(data); 408 | for (let y = 1; y < blurH - 1; y++) { 409 | for (let x = 1; x < blurW - 1; x++) { 410 | for (let c = 0; c < 3; c++) { 411 | let sum = 0; 412 | for (let dy = -1; dy <= 1; dy++) { 413 | for (let dx = -1; dx <= 1; dx++) { 414 | const idx = ((y + dy) * blurW + (x + dx)) * 4 + c; 415 | sum += blurCopy[idx]; 416 | } 417 | } 418 | const idx = (y * blurW + x) * 4 + c; 419 | data[idx] = sum / 9; 420 | } 421 | } 422 | } 423 | break; 424 | } 425 | 426 | return imageData; 427 | } 428 | 429 | // Add logout functionality 430 | document.getElementById('logout').addEventListener('click', () => { 431 | // Stop video if it's running 432 | if (isStreaming) { 433 | stopVideo(); 434 | } 435 | // Clear login state 436 | localStorage.removeItem('isLoggedIn'); 437 | // Redirect to login page 438 | window.location.href = 'login.html'; 439 | }); --------------------------------------------------------------------------------