├── 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 |
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 |
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 |
98 |
99 | {images.map((image, index) => (
100 |
101 |
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 = 'No saved images yet
';
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 |
118 |
119 |
120 |
123 |
143 |
144 |
145 | {capturedImage && (
146 |
147 |
148 |
149 | )}
150 |
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 | });
--------------------------------------------------------------------------------