Skip to content

Commit 6ad943b

Browse files
authored
Merge pull request #28 from tajulafreen/Image_Filter
50Projects-HTML-CSS-JavaScript : Image Filter
2 parents 3f4727b + e0a33c9 commit 6ad943b

File tree

4 files changed

+484
-0
lines changed

4 files changed

+484
-0
lines changed

README.md

+11
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,17 @@ In order to run this project you need:
287287
</details>
288288
</li>
289289

290+
<li>
291+
<details>
292+
<summary>Image Filter</summary>
293+
<p>The Image Filter Web Application allows users to upload and edit images by applying various filters (brightness, contrast, saturation, and vibrance) and effects (vintage, lomo, clarity, etc.). Users can preview changes on a canvas, download the edited image, or revert to the original. This application is built using HTML, CSS, and vanilla JavaScript.</p>
294+
<ul>
295+
<li><a href="https://tajulafreen.github.io/50Projects-HTML-CSS-JavaScript/Source-Code/ImageFilter/">Live Demo</a></li>
296+
<li><a href="https://github.com/tajulafreen/50Projects-HTML-CSS-JavaScript/tree/main/Source-Code/ImageFilter">Source</a></li>
297+
</ul>
298+
</details>
299+
</li>
300+
290301
</ol>
291302

292303
<p align="right">(<a href="#readme-top">back to top</a>)</p>

Source-Code/ImageFilter/index.html

+88
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
6+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
7+
<title>Image Filter</title>
8+
<link rel="stylesheet" href="./style.css" />
9+
</head>
10+
<body>
11+
<nav>
12+
<div class="container">
13+
<a href="#" class="navbar-brand">Image Filter</a>
14+
</div>
15+
</nav>
16+
<div class="container">
17+
<div class="row">
18+
<div class="col">
19+
<div class="custom-file">
20+
<input type="file" id="upload-file" />
21+
</div>
22+
<canvas id="canvas"></canvas>
23+
24+
<h4>Filters</h4>
25+
<div class="row" id="row-1">
26+
<div class="col">
27+
<div class="btn-group">
28+
<button class="filter-btn brightness-remove">-</button>
29+
<button class="btn-disabled" disabled>Brightness</button>
30+
<button class="filter-btn brightness-add">+</button>
31+
</div>
32+
</div>
33+
<div class="col">
34+
<div class="btn-group">
35+
<button class="filter-btn contrast-remove">-</button>
36+
<button class="btn-disabled" disabled>Contrast</button>
37+
<button class="filter-btn contrast-add">+</button>
38+
</div>
39+
</div>
40+
<div class="col">
41+
<div class="btn-group">
42+
<button class="filter-btn saturation-remove">-</button>
43+
<button class="btn-disabled" disabled>Saturation</button>
44+
<button class="filter-btn saturation-add">+</button>
45+
</div>
46+
</div>
47+
<div class="col">
48+
<div class="btn-group">
49+
<button class="filter-btn vibrance-remove">-</button>
50+
<button class="btn-disabled" disabled>Vibrance</button>
51+
<button class="filter-btn vibrance-add">+</button>
52+
</div>
53+
</div>
54+
</div>
55+
56+
<h4>Effects</h4>
57+
<div class="row" id="row-2">
58+
<button class="vintage-add btn-dark">Vintage</button>
59+
60+
<button class="lomo-add btn-dark">Lomo</button>
61+
62+
<button class="clarity-add btn-dark">Clarity</button>
63+
64+
<button class="sincity-add btn-dark">Sin City</button>
65+
</div>
66+
67+
<div class="row" id="row-3">
68+
<button class="crossprocess-add btn-dark">Cross Process</button>
69+
70+
<button class="pinhole-add btn-dark">Pinhole</button>
71+
72+
<button class="nostalgia-add btn-dark">Nostalgia</button>
73+
74+
<button class="hermajesty-add btn-dark">Majesty</button>
75+
</div>
76+
<div class="row" id="row-4">
77+
<button id="download-btn" class="btn-primary">
78+
Download Image
79+
</button>
80+
81+
<button id="revert-btn" class="btn-danger">Remove Filters</button>
82+
</div>
83+
</div>
84+
</div>
85+
</div>
86+
<script src="./script.js"></script>
87+
</body>
88+
</html>

Source-Code/ImageFilter/script.js

+239
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,239 @@
1+
const canvas = document.getElementById('canvas');
2+
const ctx = canvas.getContext('2d');
3+
const img = new Image();
4+
const fileName = 'edited-image';
5+
let brightness = 0;
6+
let contrast = 0;
7+
let saturation = 0;
8+
let vibrance = 0;
9+
10+
// Handle file upload
11+
document.getElementById('upload-file').addEventListener('change', (e) => {
12+
const file = e.target.files[0];
13+
const reader = new FileReader();
14+
15+
reader.onload = (event) => {
16+
img.src = event.target.result;
17+
};
18+
19+
if (file) {
20+
reader.readAsDataURL(file);
21+
}
22+
});
23+
24+
// Helper function to clamp values
25+
const clamp = (value) => Math.min(Math.max(value, 0), 255);
26+
27+
// Draw image to canvas
28+
img.onload = () => {
29+
canvas.width = img.width;
30+
canvas.height = img.height;
31+
ctx.drawImage(img, 0, 0);
32+
};
33+
34+
// Redraw image with current adjustments
35+
const drawImage = () => {
36+
ctx.drawImage(img, 0, 0);
37+
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
38+
const { data } = imageData;
39+
40+
// Apply brightness
41+
for (let i = 0; i < data.length; i += 4) {
42+
data[i] = clamp(data[i] + brightness); // Red
43+
data[i + 1] = clamp(data[i + 1] + brightness); // Green
44+
data[i + 2] = clamp(data[i + 2] + brightness); // Blue
45+
}
46+
47+
// Apply contrast
48+
const factor = (259 * (contrast + 255)) / (255 * (259 - contrast));
49+
for (let i = 0; i < data.length; i += 4) {
50+
data[i] = clamp(factor * (data[i] - 128) + 128); // Red
51+
data[i + 1] = clamp(factor * (data[i + 1] - 128) + 128); // Green
52+
data[i + 2] = clamp(factor * (data[i + 2] - 128) + 128); // Blue
53+
}
54+
55+
// Apply saturation
56+
for (let i = 0; i < data.length; i += 4) {
57+
const avg = (data[i] + data[i + 1] + data[i + 2]) / 3;
58+
data[i] = clamp(avg + (data[i] - avg) * (1 + saturation / 100)); // Red
59+
data[i + 1] = clamp(avg + (data[i + 1] - avg) * (1 + saturation / 100)); // Green
60+
data[i + 2] = clamp(avg + (data[i + 2] - avg) * (1 + saturation / 100)); // Blue
61+
}
62+
63+
// Apply vibrance
64+
for (let i = 0; i < data.length; i += 4) {
65+
const avg = (data[i] + data[i + 1] + data[i + 2]) / 3;
66+
const max = Math.max(data[i], data[i + 1], data[i + 2]);
67+
const amount = (((max - avg) * 2) / 255) * vibrance;
68+
data[i] = clamp(data[i] + amount); // Red
69+
data[i + 1] = clamp(data[i + 1] + amount); // Green
70+
data[i + 2] = clamp(data[i + 2] + amount); // Blue
71+
}
72+
ctx.putImageData(imageData, 0, 0);
73+
};
74+
75+
// Apply brightness
76+
const applyBrightness = (value) => {
77+
brightness += value;
78+
drawImage();
79+
};
80+
81+
// Apply contrast
82+
const applyContrast = (value) => {
83+
contrast += value;
84+
drawImage();
85+
};
86+
87+
// Apply saturation
88+
const applySaturation = (value) => {
89+
saturation += value;
90+
drawImage();
91+
};
92+
93+
// Apply vibrance
94+
const applyVibrance = (value) => {
95+
vibrance += value;
96+
drawImage();
97+
};
98+
99+
// Apply effect
100+
const applyEffect = (effect) => {
101+
drawImage();
102+
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
103+
const { data } = imageData;
104+
105+
if (effect === 'vintage') {
106+
// Vintage effect
107+
for (let i = 0; i < data.length; i += 4) {
108+
data[i] = clamp(data[i] * 0.9); // Red
109+
data[i + 1] = clamp(data[i + 1] * 0.7); // Green
110+
data[i + 2] = clamp(data[i + 2] * 0.5); // Blue
111+
}
112+
} else if (effect === 'lomo') {
113+
// Lomo effect
114+
for (let i = 0; i < data.length; i += 4) {
115+
data[i] = clamp(data[i] * 1.2); // Red
116+
data[i + 1] = clamp(data[i + 1] * 1.2); // Green
117+
data[i + 2] = clamp(data[i + 2] * 1.2); // Blue
118+
}
119+
} else if (effect === 'clarity') {
120+
// Clarity effect
121+
// (Increase contrast)
122+
applyContrast(20);
123+
} else if (effect === 'sincity') {
124+
// Sin City effect
125+
for (let i = 0; i < data.length; i += 4) {
126+
const avg = (data[i] + data[i + 1] + data[i + 2]) / 3;
127+
data[i] = avg; // Red
128+
data[i + 1] = avg; // Green
129+
data[i + 2] = avg; // Blue
130+
}
131+
} else if (effect === 'crossprocess') {
132+
// Cross Process effect
133+
for (let i = 0; i < data.length; i += 4) {
134+
data[i] = clamp(data[i] * 1.3); // Red
135+
data[i + 1] = clamp(data[i + 1] * 1.1); // Green
136+
data[i + 2] = clamp(data[i + 2] * 0.9); // Blue
137+
}
138+
} else if (effect === 'pinhole') {
139+
// Pinhole effect
140+
for (let i = 0; i < data.length; i += 4) {
141+
const avg = (data[i] + data[i + 1] + data[i + 2]) / 3;
142+
data[i] = avg * 0.9; // Red
143+
data[i + 1] = avg * 0.9; // Green
144+
data[i + 2] = avg * 0.9; // Blue
145+
}
146+
} else if (effect === 'nostalgia') {
147+
// Nostalgia effect
148+
for (let i = 0; i < data.length; i += 4) {
149+
data[i] = clamp(data[i] * 0.9 + 50); // Red
150+
data[i + 1] = clamp(data[i + 1] * 0.7 + 20); // Green
151+
data[i + 2] = clamp(data[i + 2] * 0.5 + 10); // Blue
152+
}
153+
} else if (effect === 'hermajesty') {
154+
// Her Majesty effect
155+
for (let i = 0; i < data.length; i += 4) {
156+
data[i] = clamp(data[i] * 1.1); // Red
157+
data[i + 1] = clamp(data[i + 1] * 0.95); // Green
158+
data[i + 2] = clamp(data[i + 2] * 1.3); // Blue
159+
}
160+
}
161+
162+
ctx.putImageData(imageData, 0, 0);
163+
};
164+
165+
// Download image
166+
const downloadImage = () => {
167+
const link = document.createElement('a');
168+
link.download = fileName;
169+
link.href = canvas.toDataURL('image/jpeg');
170+
link.click();
171+
};
172+
173+
// Revert filters
174+
const revertFilters = () => {
175+
brightness = 0;
176+
contrast = 0;
177+
saturation = 0;
178+
vibrance = 0;
179+
drawImage();
180+
};
181+
182+
// Event listeners for filter buttons
183+
document
184+
.querySelector('.brightness-add')
185+
.addEventListener('click', () => applyBrightness(10));
186+
document
187+
.querySelector('.brightness-remove')
188+
.addEventListener('click', () => applyBrightness(-10));
189+
document
190+
.querySelector('.contrast-add')
191+
.addEventListener('click', () => applyContrast(10));
192+
document
193+
.querySelector('.contrast-remove')
194+
.addEventListener('click', () => applyContrast(-10));
195+
document
196+
.querySelector('.saturation-add')
197+
.addEventListener('click', () => applySaturation(10));
198+
document
199+
.querySelector('.saturation-remove')
200+
.addEventListener('click', () => applySaturation(-10));
201+
document
202+
.querySelector('.vibrance-add')
203+
.addEventListener('click', () => applyVibrance(10));
204+
document
205+
.querySelector('.vibrance-remove')
206+
.addEventListener('click', () => applyVibrance(-10));
207+
208+
// Event listeners for effect buttons
209+
document
210+
.querySelector('.vintage-add')
211+
.addEventListener('click', () => applyEffect('vintage'));
212+
document
213+
.querySelector('.lomo-add')
214+
.addEventListener('click', () => applyEffect('lomo'));
215+
document
216+
.querySelector('.clarity-add')
217+
.addEventListener('click', () => applyEffect('clarity'));
218+
document
219+
.querySelector('.sincity-add')
220+
.addEventListener('click', () => applyEffect('sincity'));
221+
222+
document
223+
.querySelector('.crossprocess-add')
224+
.addEventListener('click', () => applyEffect('crossprocess'));
225+
document
226+
.querySelector('.pinhole-add')
227+
.addEventListener('click', () => applyEffect('pinhole'));
228+
document
229+
.querySelector('.nostalgia-add')
230+
.addEventListener('click', () => applyEffect('nostalgia'));
231+
document
232+
.querySelector('.hermajesty-add')
233+
.addEventListener('click', () => applyEffect('hermajesty'));
234+
235+
// Event listeners for download and revert buttons
236+
document
237+
.getElementById('download-btn')
238+
.addEventListener('click', downloadImage);
239+
document.getElementById('revert-btn').addEventListener('click', revertFilters);

0 commit comments

Comments
 (0)