diff --git a/README.md b/README.md index b8b538c..f9d09cf 100644 --- a/README.md +++ b/README.md @@ -287,6 +287,17 @@ In order to run this project you need: +
  • +
    +Image Filter +

    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.

    + +
    +
  • +

    (back to top)

    diff --git a/Source-Code/ImageFilter/index.html b/Source-Code/ImageFilter/index.html new file mode 100644 index 0000000..bb4d815 --- /dev/null +++ b/Source-Code/ImageFilter/index.html @@ -0,0 +1,88 @@ + + + + + + + Image Filter + + + + +
    +
    +
    +
    + +
    + + +

    Filters

    +
    +
    +
    + + + +
    +
    +
    +
    + + + +
    +
    +
    +
    + + + +
    +
    +
    +
    + + + +
    +
    +
    + +

    Effects

    +
    + + + + + + + +
    + +
    + + + + + + + +
    +
    + + + +
    +
    +
    +
    + + + diff --git a/Source-Code/ImageFilter/script.js b/Source-Code/ImageFilter/script.js new file mode 100644 index 0000000..2083c12 --- /dev/null +++ b/Source-Code/ImageFilter/script.js @@ -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); diff --git a/Source-Code/ImageFilter/style.css b/Source-Code/ImageFilter/style.css new file mode 100644 index 0000000..ce958f4 --- /dev/null +++ b/Source-Code/ImageFilter/style.css @@ -0,0 +1,146 @@ +body { + font-family: Arial, sans-serif; + background-color: #f8f9fa; + margin: 0; + padding: 0; +} + +nav { + background-color: #17a2b8; + padding: 1rem; + text-align: center; +} + +.navbar-brand { + color: #fff; + text-decoration: none; + font-size: 1.5rem; +} + +.container { + max-width: 700px; + margin: 0 auto; + padding: 1rem; + display: flex; + flex-direction: column; + flex-wrap: wrap; + align-items: center; +} + +#canvas { + margin: auto; + background: #eae7e7; + width: 100%; +} + +.custom-file { + width: 100%; + height: 2.5rem; + margin-bottom: 1rem; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; +} + +canvas { + display: block; + max-width: 100%; + margin: 1rem 0; + border: 1px solid #ced4da; +} + +h4 { + font-size: 1.5rem; + margin: 1rem 0; + text-align: center; + color: #495057; +} + +.row { + display: flex; + flex-wrap: wrap; + justify-content: space-around; + margin: 1rem auto; + gap: 3px; +} + +.filter-btn { + margin-bottom: 1rem; + width: 40px; + height: 30px; + color: #f8fafc; + background-color: #17a2b8; + border-color: #007bff; + border-radius: 7px; + font-size: medium; + cursor: pointer; +} + +.filter-btn:hover { + background-color: #0069d9; + border-color: #0056b3; +} + +.btn-disabled { + width: 80px; + height: 25px; + border-color: #1d2124; + border-radius: 3px; + background-color: #fcfcfc; + color: #343739; +} + +.btn-dark { + color: #fff; + font-size: medium; + background-color: #343a40; + border-color: #343a40; + border-radius: 7px; + width: 100px; + height: 40px; + cursor: pointer; +} + +.btn-dark:hover { + color: #fff; + background-color: #23272b; + border-color: #1d2124; +} + +.btn-primary { + color: #fff; + font-size: medium; + background-color: #007bff; + border-color: #007bff; + height: 50px; + width: 150px; + border-radius: 9px; + cursor: pointer; +} + +.btn-primary:hover { + color: #fff; + background-color: #0056b3; + border-color: #004085; + height: 56px; + width: 160px; +} + +.btn-danger { + font-size: medium; + color: #fff; + background-color: #dc3545; + border-color: #dc3545; + height: 50px; + width: 150px; + border-radius: 9px; +} + +.btn-danger:hover { + color: #fff; + background-color: #c82333; + border-color: #bd2130; + height: 56px; + width: 160px; +}