<header class="main-header">
<div class="boxes">
<h2>Filter - Resize - Convert</h2>
</div>
</header>
<main class="main-content">
<section class="boxes">
<div class="box">
<div class="panel">
<input type="file" id="selectImage" accept="image/*">
<img id="image">
<canvas id="canvas"></canvas>
<button id="save">💾 Save</button>
<textarea id="output" rows="10"></textarea>
<button id="downloadImage">💾 Download</button>
</div>
</div>
<div class="box">
<div class="panel">
<label for="contrast">Contrast</label>
<input type="range" min="0" max="200" value="100" class="slider" id="contrast">
<label for="brightness">Brightness</label>
<input type="range" min="0" max="200" value="100" class="slider" id="brightness">
<label for="saturation">Saturation</label>
<input type="range" min="0" max="200" value="100" class="slider" id="saturation">
<label for="blur">Blur</label>
<input type="range" min="0" max="10" step="0.1" value="0" class="slider" id="blur">
<label for="hue">Hue</label>
<input type="range" min="0" max="360" value="0" class="slider" id="hue">
<label for="sepia">Sepia</label>
<input type="range" min="0" max="100" value="0" class="slider" id="sepia">
<label for="grayscale">Grayscale</label>
<input type="range" min="0" max="100" value="0" class="slider" id="grayscale">
<label for="invert">Invert</label>
<input type="range" min="0" max="100" value="0" class="slider" id="invert">
<select name="size" id="imageSize">
<option value="100">100</option>
<option value="200">200</option>
<option value="300">300</option>
<option value="400" selected>400</option>
</select>
<button id="reset">Reset</button>
</div>
</div>
</section>
</main>
*,
*:after,
*:before {
box-sizing: border-box;
margin: 0;
padding: 0;
}
:root {
--primary: #1b1f24;
--secondary: white;
--txt: black;
--font-family: system-ui, -apple-system, Roboto, Arial, sans-serif;
--font-size: 1rem;
--font-weight: 400;
--line-height: 1.6;
--padding: 0.5rem 0.8rem;
--border-radius: 0.2rem;
--shadow: 5px 5px 0px var(--text);
--gutter: 0.5rem;
}
body,
html {
height: 100%;
position: relative;
}
body {
background: var(--primary);
color: var(--txt);
font-family: var(--font-family);
font-size: var(--font-size);
font-weight: var(--font-weight);
height: 100%;
line-height: var(--line-height);
margin: 0 auto;
overflow: auto;
padding: 0;
}
.boxes {
display: flex;
flex-direction: row;
}
.box {
width: calc(70% - 10px);
margin: 5px;
padding: 5px;
}
.box:last-child {
width: calc(30% - 10px);
}
img {
max-width: 100%;
}
button,
textarea,
label,
select,
input {
width: 100%;
display: block;
margin: 10px 0;
}
select,
textarea,
input[type="file"] {
padding: var(--padding);
border-radius: var(--border-radius);
background: color-mix(in srgb, var(--primary), var(--secondary) 15%);
border: 1px solid color-mix(in srgb, var(--primary), var(--secondary) 2%);
color: var(--secondary);
}
button {
display: block;
width: fit-content;
cursor: pointer;
padding: var(--padding);
border-radius: var(--border-radius);
background: color-mix(in srgb, var(--primary), var(--secondary) 15%);
border: 1px solid color-mix(in srgb, var(--primary), var(--secondary) 2%);
color: var(--secondary);
transition: background 500ms ease;
&:is(:hover, :focus) {
background: color-mix(in srgb, var(--primary), var(--secondary) 23%);
transition: background 500ms ease;
}
}
button,
textarea {
display: none;
resize: none;
}
textarea::-webkit-scrollbar {
width: 10px;
height: 10px;
background: color-mix(in srgb, var(--primary), var(--secondary) 18%);
}
textarea::-webkit-scrollbar-thumb {
background: color-mix(in srgb, var(--primary), var(--secondary) 5%);
}
.panel {
padding: var(--padding);
border-radius: var(--border-radius);
background: color-mix(in srgb, var(--primary), var(--secondary) 5%);
border: 1px solid color-mix(in srgb, var(--primary), var(--secondary) 2%);
color: var(--secondary);
}
header.main-header {
background: color-mix(in srgb, var(--primary), var(--secondary) 5%);
color: var(--secondary);
padding: 5px 10px;
height: 3rem;
}
main.main-content {
height: calc(100% - 3rem);
background: color-mix(in srgb, var(--primary), var(--secondary) 10%);
}
const _id = (id) => document.getElementById(id);
// Cachear elementos del DOM
const domElements = {
image: _id("image"),
input: _id("selectImage"),
canvas: _id("canvas"),
sliders: document.querySelectorAll(".slider"),
imageSize: _id("imageSize"),
output: _id("output"),
save: _id("save"),
reset: _id("reset"),
downloadImage: _id("downloadImage")
};
// Asignar eventos
domElements.input.addEventListener("change", handleImage);
domElements.sliders.forEach((slider) =>
slider.addEventListener("input", applyFilters)
);
domElements.imageSize.addEventListener("change", applyFilters);
domElements.save.addEventListener("click", saveImage);
domElements.reset.addEventListener("click", resetFilters);
domElements.downloadImage.addEventListener("click", download);
// Manejar la carga de la imagen
function handleImage(e) {
const [file] = e.target.files;
const reader = new FileReader();
reader.onload = () => {
domElements.image.src = reader.result;
domElements.image.onload = () => {
updateImageSize(domElements.imageSize.valueAsNumber);
applyFilters();
};
};
reader.readAsDataURL(file);
}
// Actualizar tamaño de la imagen
function updateImageSize(newWidth) {
const { naturalWidth, naturalHeight } = domElements.image;
const aspectRatio = naturalWidth / naturalHeight;
const { width, height } = calculateNewDimensions(newWidth, aspectRatio);
domElements.image.style.width = `${width}px`;
domElements.canvas.width = width;
domElements.canvas.height = height;
}
// Calcular nuevas dimensiones
function calculateNewDimensions(newWidth, aspectRatio) {
return { width: newWidth, height: newWidth / aspectRatio };
}
// Aplicar filtros a la imagen
function applyFilters() {
const ctx = domElements.canvas.getContext("2d");
const newWidth =
parseInt(domElements.imageSize.value, 10) || domElements.imageSize.value;
const { width, height } = calculateNewDimensions(
newWidth,
domElements.image.naturalWidth / domElements.image.naturalHeight
);
domElements.canvas.width = width;
domElements.canvas.height = height;
ctx.clearRect(0, 0, width, height);
const filters = [
`contrast(${getValue("contrast")}%)`,
`brightness(${getValue("brightness")}%)`,
`saturate(${getValue("saturation")}%)`,
`blur(${getValue("blur")}px)`,
`hue-rotate(${getValue("hue")}deg)`,
`sepia(${getValue("sepia")}%)`,
`grayscale(${getValue("grayscale")}%)`,
`invert(${getValue("invert")}%)`
].join(" ");
ctx.filter = filters;
ctx.drawImage(domElements.image, 0, 0, width, height);
domElements.image.style.display = "none";
domElements.canvas.style.display = "initial";
domElements.save.style.display = "block";
domElements.reset.style.display = "block";
domElements.downloadImage.style.display = "none";
}
// Obtener valor de los filtros
function getValue(id) {
return _id(id).value;
}
// Guardar la imagen procesada
function saveImage() {
domElements.output.value = domElements.canvas.toDataURL("image/png");
domElements.output.style.display = "initial";
domElements.downloadImage.style.display = "block";
}
// Resetear los filtros
function resetFilters() {
const resetValues = {
contrast: "100",
brightness: "100",
saturation: "100",
blur: "0",
hue: "0",
sepia: "0",
grayscale: "0",
invert: "0"
};
Object.keys(resetValues).forEach((key) => (_id(key).value = resetValues[key]));
domElements.imageSize.value = "400";
applyFilters();
}
// Descargar la imagen
function download() {
saveDataUriToFile(domElements.output.value, "output.png");
}
// Guardar DataURI como archivo
function saveDataUriToFile(dataUri, filename) {
const blob = dataURItoBlob(dataUri);
const url = URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = url;
link.download = filename;
link.style.display = "none";
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);
}
// Convertir DataURI a Blob
function dataURItoBlob(dataURI) {
const byteString = atob(dataURI.split(",")[1]);
const mimeString = dataURI.split(",")[0].split(":")[1].split(";")[0];
const ab = new ArrayBuffer(byteString.length);
const ia = new Uint8Array(ab);
for (let i = 0; i < byteString.length; i++) {
ia[i] = byteString.charCodeAt(i);
}
return new Blob([ab], { type: mimeString });
}
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.