Skip to content

50Projects-HTML-CSS-JavaScript : Image Filter #28

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Jul 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,17 @@ In order to run this project you need:
</details>
</li>

<li>
<details>
<summary>Image Filter</summary>
<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>
<ul>
<li><a href="https://tajulafreen.github.io/50Projects-HTML-CSS-JavaScript/Source-Code/ImageFilter/">Live Demo</a></li>
<li><a href="https://github.com/tajulafreen/50Projects-HTML-CSS-JavaScript/tree/main/Source-Code/ImageFilter">Source</a></li>
</ul>
</details>
</li>

</ol>

<p align="right">(<a href="#readme-top">back to top</a>)</p>
Expand Down
88 changes: 88 additions & 0 deletions Source-Code/ImageFilter/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Image Filter</title>
<link rel="stylesheet" href="./style.css" />
</head>
<body>
<nav>
<div class="container">
<a href="#" class="navbar-brand">Image Filter</a>
</div>
</nav>
<div class="container">
<div class="row">
<div class="col">
<div class="custom-file">
<input type="file" id="upload-file" />
</div>
<canvas id="canvas"></canvas>

<h4>Filters</h4>
<div class="row" id="row-1">
<div class="col">
<div class="btn-group">
<button class="filter-btn brightness-remove">-</button>
<button class="btn-disabled" disabled>Brightness</button>
<button class="filter-btn brightness-add">+</button>
</div>
</div>
<div class="col">
<div class="btn-group">
<button class="filter-btn contrast-remove">-</button>
<button class="btn-disabled" disabled>Contrast</button>
<button class="filter-btn contrast-add">+</button>
</div>
</div>
<div class="col">
<div class="btn-group">
<button class="filter-btn saturation-remove">-</button>
<button class="btn-disabled" disabled>Saturation</button>
<button class="filter-btn saturation-add">+</button>
</div>
</div>
<div class="col">
<div class="btn-group">
<button class="filter-btn vibrance-remove">-</button>
<button class="btn-disabled" disabled>Vibrance</button>
<button class="filter-btn vibrance-add">+</button>
</div>
</div>
</div>

<h4>Effects</h4>
<div class="row" id="row-2">
<button class="vintage-add btn-dark">Vintage</button>

<button class="lomo-add btn-dark">Lomo</button>

<button class="clarity-add btn-dark">Clarity</button>

<button class="sincity-add btn-dark">Sin City</button>
</div>

<div class="row" id="row-3">
<button class="crossprocess-add btn-dark">Cross Process</button>

<button class="pinhole-add btn-dark">Pinhole</button>

<button class="nostalgia-add btn-dark">Nostalgia</button>

<button class="hermajesty-add btn-dark">Majesty</button>
</div>
<div class="row" id="row-4">
<button id="download-btn" class="btn-primary">
Download Image
</button>

<button id="revert-btn" class="btn-danger">Remove Filters</button>
</div>
</div>
</div>
</div>
<script src="./script.js"></script>
</body>
</html>
239 changes: 239 additions & 0 deletions Source-Code/ImageFilter/script.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
const img = new Image();
const fileName = 'edited-image';
let brightness = 0;
let contrast = 0;
let saturation = 0;
let vibrance = 0;

// Handle file upload
document.getElementById('upload-file').addEventListener('change', (e) => {
const file = e.target.files[0];
const reader = new FileReader();

reader.onload = (event) => {
img.src = event.target.result;
};

if (file) {
reader.readAsDataURL(file);
}
});

// Helper function to clamp values
const clamp = (value) => Math.min(Math.max(value, 0), 255);

// Draw image to canvas
img.onload = () => {
canvas.width = img.width;
canvas.height = img.height;
ctx.drawImage(img, 0, 0);
};

// Redraw image with current adjustments
const drawImage = () => {
ctx.drawImage(img, 0, 0);
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const { data } = imageData;

// Apply brightness
for (let i = 0; i < data.length; i += 4) {
data[i] = clamp(data[i] + brightness); // Red
data[i + 1] = clamp(data[i + 1] + brightness); // Green
data[i + 2] = clamp(data[i + 2] + brightness); // Blue
}

// Apply contrast
const factor = (259 * (contrast + 255)) / (255 * (259 - contrast));
for (let i = 0; i < data.length; i += 4) {
data[i] = clamp(factor * (data[i] - 128) + 128); // Red
data[i + 1] = clamp(factor * (data[i + 1] - 128) + 128); // Green
data[i + 2] = clamp(factor * (data[i + 2] - 128) + 128); // Blue
}

// Apply saturation
for (let i = 0; i < data.length; i += 4) {
const avg = (data[i] + data[i + 1] + data[i + 2]) / 3;
data[i] = clamp(avg + (data[i] - avg) * (1 + saturation / 100)); // Red
data[i + 1] = clamp(avg + (data[i + 1] - avg) * (1 + saturation / 100)); // Green
data[i + 2] = clamp(avg + (data[i + 2] - avg) * (1 + saturation / 100)); // Blue
}

// Apply vibrance
for (let i = 0; i < data.length; i += 4) {
const avg = (data[i] + data[i + 1] + data[i + 2]) / 3;
const max = Math.max(data[i], data[i + 1], data[i + 2]);
const amount = (((max - avg) * 2) / 255) * vibrance;
data[i] = clamp(data[i] + amount); // Red
data[i + 1] = clamp(data[i + 1] + amount); // Green
data[i + 2] = clamp(data[i + 2] + amount); // Blue
}
ctx.putImageData(imageData, 0, 0);
};

// Apply brightness
const applyBrightness = (value) => {
brightness += value;
drawImage();
};

// Apply contrast
const applyContrast = (value) => {
contrast += value;
drawImage();
};

// Apply saturation
const applySaturation = (value) => {
saturation += value;
drawImage();
};

// Apply vibrance
const applyVibrance = (value) => {
vibrance += value;
drawImage();
};

// Apply effect
const applyEffect = (effect) => {
drawImage();
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const { data } = imageData;

if (effect === 'vintage') {
// Vintage effect
for (let i = 0; i < data.length; i += 4) {
data[i] = clamp(data[i] * 0.9); // Red
data[i + 1] = clamp(data[i + 1] * 0.7); // Green
data[i + 2] = clamp(data[i + 2] * 0.5); // Blue
}
} else if (effect === 'lomo') {
// Lomo effect
for (let i = 0; i < data.length; i += 4) {
data[i] = clamp(data[i] * 1.2); // Red
data[i + 1] = clamp(data[i + 1] * 1.2); // Green
data[i + 2] = clamp(data[i + 2] * 1.2); // Blue
}
} else if (effect === 'clarity') {
// Clarity effect
// (Increase contrast)
applyContrast(20);
} else if (effect === 'sincity') {
// Sin City effect
for (let i = 0; i < data.length; i += 4) {
const avg = (data[i] + data[i + 1] + data[i + 2]) / 3;
data[i] = avg; // Red
data[i + 1] = avg; // Green
data[i + 2] = avg; // Blue
}
} else if (effect === 'crossprocess') {
// Cross Process effect
for (let i = 0; i < data.length; i += 4) {
data[i] = clamp(data[i] * 1.3); // Red
data[i + 1] = clamp(data[i + 1] * 1.1); // Green
data[i + 2] = clamp(data[i + 2] * 0.9); // Blue
}
} else if (effect === 'pinhole') {
// Pinhole effect
for (let i = 0; i < data.length; i += 4) {
const avg = (data[i] + data[i + 1] + data[i + 2]) / 3;
data[i] = avg * 0.9; // Red
data[i + 1] = avg * 0.9; // Green
data[i + 2] = avg * 0.9; // Blue
}
} else if (effect === 'nostalgia') {
// Nostalgia effect
for (let i = 0; i < data.length; i += 4) {
data[i] = clamp(data[i] * 0.9 + 50); // Red
data[i + 1] = clamp(data[i + 1] * 0.7 + 20); // Green
data[i + 2] = clamp(data[i + 2] * 0.5 + 10); // Blue
}
} else if (effect === 'hermajesty') {
// Her Majesty effect
for (let i = 0; i < data.length; i += 4) {
data[i] = clamp(data[i] * 1.1); // Red
data[i + 1] = clamp(data[i + 1] * 0.95); // Green
data[i + 2] = clamp(data[i + 2] * 1.3); // Blue
}
}

ctx.putImageData(imageData, 0, 0);
};

// Download image
const downloadImage = () => {
const link = document.createElement('a');
link.download = fileName;
link.href = canvas.toDataURL('image/jpeg');
link.click();
};

// Revert filters
const revertFilters = () => {
brightness = 0;
contrast = 0;
saturation = 0;
vibrance = 0;
drawImage();
};

// Event listeners for filter buttons
document
.querySelector('.brightness-add')
.addEventListener('click', () => applyBrightness(10));
document
.querySelector('.brightness-remove')
.addEventListener('click', () => applyBrightness(-10));
document
.querySelector('.contrast-add')
.addEventListener('click', () => applyContrast(10));
document
.querySelector('.contrast-remove')
.addEventListener('click', () => applyContrast(-10));
document
.querySelector('.saturation-add')
.addEventListener('click', () => applySaturation(10));
document
.querySelector('.saturation-remove')
.addEventListener('click', () => applySaturation(-10));
document
.querySelector('.vibrance-add')
.addEventListener('click', () => applyVibrance(10));
document
.querySelector('.vibrance-remove')
.addEventListener('click', () => applyVibrance(-10));

// Event listeners for effect buttons
document
.querySelector('.vintage-add')
.addEventListener('click', () => applyEffect('vintage'));
document
.querySelector('.lomo-add')
.addEventListener('click', () => applyEffect('lomo'));
document
.querySelector('.clarity-add')
.addEventListener('click', () => applyEffect('clarity'));
document
.querySelector('.sincity-add')
.addEventListener('click', () => applyEffect('sincity'));

document
.querySelector('.crossprocess-add')
.addEventListener('click', () => applyEffect('crossprocess'));
document
.querySelector('.pinhole-add')
.addEventListener('click', () => applyEffect('pinhole'));
document
.querySelector('.nostalgia-add')
.addEventListener('click', () => applyEffect('nostalgia'));
document
.querySelector('.hermajesty-add')
.addEventListener('click', () => applyEffect('hermajesty'));

// Event listeners for download and revert buttons
document
.getElementById('download-btn')
.addEventListener('click', downloadImage);
document.getElementById('revert-btn').addEventListener('click', revertFilters);
Loading
Loading