Inicio Apuntes FPApuntes DAW Pixeleand0, crea tus imágenes pixel a pixel

Pixeleand0, crea tus imágenes pixel a pixel

Si quieres empezar en eso del Pixel-art

Publicado por entreunosyceros

Una vez más aquí. Hoy vengo a dejar un pequeño artículo sobre una pequeña aplicación que hace una par de semanas pensé. El caso es que alguien me pidió que le creara una web utilizando Pixel Art. Y la verdad es que no tenía ni idea de que era esto, aun que resultó ser algo conocido por todos aquellos que teníamos videoconsolas o pc’s en los años 90. Pues bueno, pensando un poco, me decidí a crear una pequeña aplicación web a la que he llamado Pixeleand0, que permite a todo el que quiera poder crear pequeñas imágenes pixel a pixel. Y buscando un poco, me encontré una comunidad de bastante más grande de lo que me esperaba. Además encontré el canal de Youtube de un hombre que me parece un fenómeno de este tipo de arte, @Elias_Lozano. Sé que no será el único, pero sus vídeos me han llamado mucho la atención.

Bueno, como decía, el arte pixelado es un estilo visual único que ha perdurado a través de las décadas, desde los primeros videojuegos hasta las modernas obras de diseño. Es por eso que he creado Pixeland0, que viene siendo una pequeña y básica herramienta para que el usuario explore su creatividad y pueda crear imágenes pixel a pixel desde el navegador web, sin necesidad de meterse en programas más complicados. Aun que evidentemente, si quieres conseguir algo realmente bueno, tendrás que utilizar otras herramientas mucho más completas que la que vengo a presentar hoy aquí.

¿Qué es Pixeleand0?

Como decía líneas más arriba, Pixeleand0 es una aplicación web básica e intuitiva que te permite crear tus imágenes pixeladas de forma sencilla y sin conocimientos previos de ningún tipo. Con una interfaz fácil de usar y una pequeñisima gama de herramientas, puedes dar vida a tus ideas con solo unos pocos clics.

Características principales

cigarro
  1. Generación de píxeles dinámica: El programa genera una cuadrícula de píxeles que puedes personalizar según tus necesidades.
  2. Amplia paleta de colores: Escoge entre una variedad de colores predefinidos o selecciona tu propio color usando el selector de color integrado. Podrás ir guardando tu paleta de cinco colores en los selectores disponibles.
  3. Herramientas de dibujo versátiles: Utiliza el pincel para dibujar libremente, el cubo de pintura para rellenar áreas con un solo clic o el cuentagotas para seleccionar colores de la imagen.
  4. Importación de imágenes: Importa tus propias imágenes para usarlas como referencia o como base para tus creaciones pixeladas.
  5. Descarga fácil: Descarga tus obras de arte en formato PNG para compartirlas con el mundo o para utilizarlas en tus proyectos personales.
  6. Botón deshacer: Si te equivocas en uno o varios pixels, no importará. Siempre tendrás la posibilidad de deshacer los cambios con este botón.

El código de Pixeleand0

Todo este código que vamos a ver a continuación, está colgado en el repositorio de GitHub en el que he alojado el proyecto. Ahí será donde publicaré actualizaciones (si tengo tiempo para ello).

Esta aplicación se compone de tan solo tres archivos. Uno .html para crear el entorno, un .css para darle un poco de estilo a la aplicación y otro .js en el que se estará toda la lógica de la aplicación. A continuación veremos el código archivo por archivo.

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Pixeleand0</title>
    <link rel="stylesheet" href="styles.css">
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.css" crossorigin="anonymous" />
</head>
<body>
    <div>
        <button id="paintBucketButton" title="Cubo de pintura"><i class="fas fa-fill-drip"></i></button>
        <button id="brushButton" title="Pincel"><i class="fas fa-paint-brush"></i></button>
        <button id="eyedropperButton" title="Cuentagotas"><i class="fas fa-eye-dropper"></i></button>
        <input type="color" id="colorPicker">
        <input type="number" id="sizeSelector" min="1" max="100" value="16" title="Tamaño de lienzo, en pixels">
        <button id="clearButton" title="Limpiar todos los campos"><i class="fas fa-trash-alt"></i></button>
        <button id="zoomInButton" title="Zoom In"><i class="fas fa-search-plus"></i></button>
        <button id="zoomOutButton" title="Zoom Out"><i class="fas fa-search-minus"></i></button>
        <button id="downloadButton" title="Descargar imagen"><i class="fas fa-download"></i></button>
        <input type="text" id="fileNameInput" placeholder="Nombre imagen a descargar">
        <button id="openButton" title="Abrir imagen"><i class="fas fa-folder-open"></i></button>
        <input type="file" id="imageUpload" accept="image/*" style="display:none;">
        <button id="undoButton" title="Deshacer"><i class="fas fa-undo"></i></button>
    </div>
    <hr id="hr"/>
    <div class="color-palette">
        <button class="color-button" style="background-color: #000000;"></button>
        <button class="color-button" style="background-color: #00FF00;"></button>
        <button class="color-button" style="background-color: #0000FF;"></button>
        <button class="color-button" style="background-color: #FFFF00;"></button>
        <button class="color-button" style="background-color: #FF00FF;"></button>
        <button class="color-button" style="background-color: #00FFFF;"></button>
    </div>
    <div class="container"></div>
    <script src="script.js"></script>
</body>
</html>

script.js

// Esperar a que se cargue el DOM antes de ejecutar el script
document.addEventListener("DOMContentLoaded", function() {
    // Obtener referencias a los elementos del DOM
    const container = document.querySelector(".container"); // Contenedor de píxeles
    const colorPicker = document.querySelector("#colorPicker"); // Selector de color
    const sizeSelector = document.querySelector("#sizeSelector"); // Selector de tamaño
    const clearButton = document.querySelector("#clearButton"); // Botón de limpiar
    const downloadButton = document.querySelector("#downloadButton"); // Botón de descargar
    const fileNameInput = document.querySelector("#fileNameInput"); // Entrada de nombre de archivo
    const paintBucketButton = document.querySelector("#paintBucketButton"); // Botón de cubo de pintura
    const brushButton = document.querySelector("#brushButton"); // Botón de pincel
    const zoomInButton = document.querySelector("#zoomInButton"); // Botón de hacer zoom
    const zoomOutButton = document.querySelector("#zoomOutButton"); // Botón de alejar
    const colorButtons = document.querySelectorAll(".color-button"); // Botones de color
    const openButton = document.querySelector("#openButton"); // Botón de abrir imagen
    const imageUpload = document.querySelector("#imageUpload"); // Entrada de carga de imagen
    const eyedropperButton = document.querySelector("#eyedropperButton"); // Botón de cuentagotas
    const undoButton = document.querySelector("#undoButton"); // Botón de deshacer

    // Variables de estado y configuración inicial
    let isPaintBucketActive = false; // Estado de activación del cubo de pintura
    let isBrushActive = true; // Estado de activación del pincel
    let isEyedropperActive = false; // Estado de activación del cuentagotas
    let isDrawing = false; // Estado de dibujo
    let activeColor = "#000000"; // Color activo inicial
    let pixelSize = 15; // Tamaño de píxel inicial
    let maxSize = parseInt(sizeSelector.value); // Tamaño máximo inicial
    let history = []; // Historial de acciones

    // Generar los píxeles en el contenedor
    function generatePixels(size) {
        container.innerHTML = ''; // Limpiar el contenedor
        container.style.gridTemplateColumns = `repeat(${size}, ${pixelSize}px)`; // Establecer el número de columnas
        for (let i = 0; i < size * size; i++) {
            const pixel = document.createElement("div"); // Crear un píxel
            pixel.classList.add("pixel");
            pixel.style.width = `${pixelSize}px`; // Establecer el tamaño
            pixel.style.height = `${pixelSize}px`;
            pixel.style.backgroundColor = "#fff"; // Color inicial
            container.appendChild(pixel); // Agregar píxel al contenedor
        }
    }

    // Obtener los píxeles adyacentes del mismo color
    function getAdjacentPixels(pixel, color) {
        const pixels = Array.from(container.children); // Obtener todos los píxeles
        const size = Math.sqrt(pixels.length); // Calcular el tamaño de la cuadrícula
        const index = pixels.indexOf(pixel); // Obtener el índice del píxel
        const queue = [index]; // Inicializar la cola de píxeles
        const result = []; // Inicializar el resultado

        while (queue.length > 0) {
            const current = queue.shift(); // Obtener el próximo píxel de la cola
            if (result.includes(current)) continue; // Si ya está en el resultado, continuar

            const x = current % size; // Calcular la coordenada x
            const y = Math.floor(current / size); // Calcular la coordenada y

            if (pixels[current].style.backgroundColor === color) {
                // Si el color del píxel es el mismo que el color dado
                result.push(current); // Agregar el índice al resultado
                // Agregar los píxeles adyacentes a la cola
                if (x > 0) queue.push(current - 1); // Izquierda
                if (x < size - 1) queue.push(current + 1); // Derecha
                if (y > 0) queue.push(current - size); // Arriba
                if (y < size - 1) queue.push(current + size); // Abajo
            }
        }

        return result; // Devolver los píxeles adyacentes del mismo color
    }

    // Pintar los píxeles adyacentes del mismo color
    function paintAdjacentPixels(pixel, color) {
        const pixels = Array.from(container.children); // Obtener todos los píxeles
        const originalColor = pixel.style.backgroundColor; // Obtener el color original del píxel
        const adjacentPixels = getAdjacentPixels(pixel, originalColor); // Obtener los píxeles adyacentes del mismo color

        // Iterar sobre los píxeles adyacentes
        adjacentPixels.forEach(index => {
            const targetPixel = pixels[index]; // Obtener el píxel objetivo
            // Agregar la acción al historial
            history.push({
                pixel: targetPixel,
                previousColor: targetPixel.style.backgroundColor,
                newColor: color
            });
            targetPixel.style.backgroundColor = color; // Pintar el píxel
        });
    }

    // Manejador de eventos: clic en el contenedor de píxeles
    container.addEventListener("mousedown", function(event) {
        if (event.target.classList.contains("pixel")) {
            isDrawing = true; // Iniciar el dibujo
            if (isPaintBucketActive) {
                // Si el cubo de pintura está activo
                paintAdjacentPixels(event.target, activeColor); // Pintar píxeles adyacentes del mismo color
            } else if (isEyedropperActive) {
                // Si el cuentagotas está activo
                activeColor = event.target.style.backgroundColor; // Obtener el color del píxel
                const activeButton = document.querySelector(".color-button.active"); // Obtener el botón de color activo
                activeButton.style.backgroundColor = activeColor; // Establecer el color activo en el botón
                colorPicker.value = rgbToHex(activeColor); // Establecer el valor del selector de color
                isEyedropperActive = false; // Desactivar el cuentagotas
                eyedropperButton.classList.remove("active"); // Eliminar la clase activa del botón de cuentagotas
                container.classList.remove("eyedropper-active"); // Eliminar la clase activa del contenedor
            } else {
                // Si se está utilizando el pincel
                const targetPixel = event.target; // Obtener el píxel objetivo
                // Agregar la acción al historial
                history.push({
                    pixel: targetPixel,
                    previousColor: targetPixel.style.backgroundColor,
                    newColor: activeColor
                });
                targetPixel.style.backgroundColor = activeColor;
            }
        }
    });

    // Manejador de eventos: movimiento del ratón sobre el contenedor de píxeles
    container.addEventListener("mouseover", function(event) {
        if (isDrawing && event.target.classList.contains("pixel")) {
            if (isBrushActive) {
                const targetPixel = event.target; // Obtener el píxel objetivo
                // Agregar la acción al historial
                history.push({
                    pixel: targetPixel,
                    previousColor: targetPixel.style.backgroundColor,
                    newColor: activeColor
                });
                targetPixel.style.backgroundColor = activeColor; // Pintar el píxel
            }
        }
    });

    // Manejador de eventos: liberación del botón del ratón sobre el contenedor de píxeles
    container.addEventListener("mouseup", function() {
        isDrawing = false; // Finalizar el dibujo
    });

    // Manejador de eventos: salida del ratón del contenedor de píxeles
    container.addEventListener("mouseleave", function() {
        isDrawing = false; // Finalizar el dibujo
    });

    // Manejador de eventos: clic en el botón "Paint Bucket"
    paintBucketButton.addEventListener("click", function() {
        isPaintBucketActive = true; // Activar el cubo de pintura
        isBrushActive = false; // Desactivar el pincel
        isEyedropperActive = false; // Desactivar el cuentagotas
        paintBucketButton.classList.add("active"); // Agregar clase activa al botón de cubo de pintura
        brushButton.classList.remove("active"); // Eliminar clase activa del botón de pincel
        eyedropperButton.classList.remove("active"); // Eliminar clase activa del botón de cuentagotas
        container.classList.remove("eyedropper-active"); // Eliminar clase activa del contenedor
    });

    // Manejador de eventos: clic en el botón "Brush"
    brushButton.addEventListener("click", function() {
        isBrushActive = true; // Activar el pincel
        isPaintBucketActive = false; // Desactivar el cubo de pintura
        isEyedropperActive = false; // Desactivar el cuentagotas
        brushButton.classList.add("active"); // Agregar clase activa al botón de pincel
        paintBucketButton.classList.remove("active"); // Eliminar clase activa del botón de cubo de pintura
        eyedropperButton.classList.remove("active"); // Eliminar clase activa del botón de cuentagotas
        container.classList.remove("eyedropper-active"); // Eliminar clase activa del contenedor
    });

    // Manejador de eventos: clic en el botón "Eyedropper"
    eyedropperButton.addEventListener("click", function() {
        isEyedropperActive = true; // Activar el cuentagotas
        isBrushActive = false; // Desactivar el pincel
        isPaintBucketActive = false; // Desactivar el cubo de pintura
        eyedropperButton.classList.add("active"); // Agregar clase activa al botón de cuentagotas
        brushButton.classList.remove("active"); // Eliminar clase activa del botón de pincel
        paintBucketButton.classList.remove("active"); // Eliminar clase activa del botón de cubo de pintura
        container.classList.add("eyedropper-active"); // Agregar clase activa al contenedor
    });

    // Manejador de eventos: clic en el botón "Clear"
    clearButton.addEventListener("click", function() {
        const pixels = document.querySelectorAll(".pixel"); // Obtener todos los píxeles
        pixels.forEach(pixel => {
            pixel.style.backgroundColor = "#fff"; // Restablecer el color a blanco
        });
        fileNameInput.value = ""; // Limpiar el valor de la entrada de nombre de archivo
        history = []; // Limpiar el historial de acciones
    });

    // Manejador de eventos: clic en el botón "Download"
    downloadButton.addEventListener("click", async function() {
        const size = parseInt(sizeSelector.value);
        const canvas = document.createElement("canvas");
        const ctx = canvas.getContext("2d");
        canvas.width = size;
        canvas.height = size;
        ctx.imageSmoothingEnabled = false;

        const pixels = document.querySelectorAll(".pixel"); // Obtener todos los píxeles
        for (let i = 0; i < pixels.length; i++) {
            const x = i % size; // Calcular la coordenida x
            const y = Math.floor(i / size); // Calcular la coordenada y
            const color = pixels[i].style.backgroundColor; // Obtener el color del píxel
            ctx.fillStyle = color; // Establecer el color de relleno
            ctx.fillRect(x, y, 1, 1); // Dibujar un píxel
        }

        const link = document.createElement("a"); // Crear un elemento de enlace
        link.download = fileNameInput.value ? fileNameInput.value + ".png" : "pixeleand0.png"; // Establecer el nombre de archivo para descargar
        link.href = canvas.toDataURL(); // Establecer el enlace de descarga
        link.click(); // Simular clic en el enlace
    });

    // Manejador de eventos: cambio en el selector de tamaño
    sizeSelector.addEventListener("change", function() {
        maxSize = parseInt(sizeSelector.value); // Actualizar el tamaño máximo
        generatePixels(maxSize); // Generar los píxeles
        history = []; // Limpiar el historial de acciones
    });

    // Manejador de eventos: clic en el botón "Zoom In"
    zoomInButton.addEventListener("click", function() {
        pixelSize += 5; // Aumentar el tamaño de píxel
        adjustPixelSize(); // Ajustar el tamaño de los píxeles
    });

    // Manejador de eventos: clic en el botón "Zoom Out"
    zoomOutButton.addEventListener("click", function() {
        if (pixelSize > 5) { // Verificar que el tamaño de píxel no sea menor que 5
            pixelSize -= 5; // Disminuir el tamaño de píxel
            adjustPixelSize(); // Ajustar el tamaño de los píxeles
        }
    });

    // Función para ajustar el tamaño de los píxeles
    function adjustPixelSize() {
        const pixels = document.querySelectorAll(".pixel"); // Obtener todos los píxeles
        pixels.forEach(pixel => {
            pixel.style.width = `${pixelSize}px`; // Establecer el ancho del píxel
            pixel.style.height = `${pixelSize}px`; // Establecer la altura del píxel
        });
        container.style.gridTemplateColumns = `repeat(${maxSize}, ${pixelSize}px)`; // Establecer las columnas de la cuadrícula
        container.style.gridTemplateRows = `repeat(${maxSize}, ${pixelSize}px)`; // Establecer las filas de la cuadrícula
    }

    // Cargar imagen desde el botón "Abrir"
    openButton.addEventListener("click", function() {
        imageUpload.click();
    });

    imageUpload.addEventListener("change", function(event) {
        const file = event.target.files[0];
        if (file) {
            const reader = new FileReader();
            reader.onload = function(e) {
                const img = new Image();
                img.onload = function() {
                    // Ajustar el tamaño del lienzo al tamaño de la imagen
                    maxSize = img.width > img.height ? img.width : img.height;
                    pixelSize = container.clientWidth / maxSize;
                    container.style.gridTemplateColumns = `repeat(${img.width}, ${pixelSize}px)`;
                    container.style.gridTemplateRows = `repeat(${img.height}, ${pixelSize}px)`;

                    // Dibujar la imagen en la cuadrícula de píxeles
                    const ctx = document.createElement("canvas").getContext("2d");
                    ctx.drawImage(img, 0, 0, img.width, img.height);
                    const imageData = ctx.getImageData(0, 0, img.width, img.height).data;
                    const pixels = container.querySelectorAll(".pixel");
                    for (let i = 0; i < pixels.length; i++) {
                        const r = imageData[i * 4];
                        const g = imageData[i * 4 + 1];
                        const b = imageData[i * 4 + 2];
                        const a = imageData[i * 4 + 3] / 255;
                        pixels[i].style.backgroundColor = `rgba(${r}, ${g}, ${b}, ${a})`;
                    }
                };
                img.src = e.target.result;
            };
            reader.readAsDataURL(file);
        }
    });

    // Manejador de eventos: cambio en el selector de color
    colorPicker.addEventListener("input", function() {
        activeColor = colorPicker.value; // Actualizar el color activo
        const activeButton = document.querySelector(".color-button.active"); // Obtener el botón de color activo
        if (activeButton) {
            activeButton.style.backgroundColor = activeColor; // Establecer el color activo en el botón
        }
    });

    // Función para convertir RGB a código hexadecimal
    function rgbToHex(rgb) {
        const rgbArray = rgb.match(/\d+/g).map(Number); // Obtener los componentes RGB como números
        const hex = rgbArray.map(value => {
            const hexValue = value.toString(16); // Convertir a hexadecimal
            return hexValue.length === 1 ? "0" + hexValue : hexValue; // Agregar ceros a la izquierda si es necesario
        });
        return `#${hex.join("")}`; // Devolver el código hexadecimal
    }
    // Verificar si hay botones de color
    if (colorButtons.length > 0) {
        colorButtons[0].classList.add("active");
        activeColor = colorButtons[0].style.backgroundColor;
        colorPicker.value = rgbToHex(activeColor);
    }

     // Asignar manejadores de eventos a los botones de color
     colorButtons.forEach(button => {
        button.addEventListener("click", function() {
            colorButtons.forEach(btn => btn.classList.remove("active")); // Desactivar todos los botones de color
            button.classList.add("active"); // Activar el botón seleccionado
            activeColor = button.style.backgroundColor; // Actualizar el color activo
            colorPicker.value = rgbToHex(activeColor); // Actualizar el selector de color
        });
    });

    // Manejador de eventos: clic en el botón "Undo"
    undoButton.addEventListener("click", function() {
        if (history.length > 0) { // Verificar si hay acciones en el historial
            const lastAction = history.pop(); // Obtener la última acción del historial
            lastAction.pixel.style.backgroundColor = lastAction.previousColor; // Restaurar el color anterior del píxel
        }
    });

    // Generar los píxeles iniciales
    generatePixels(maxSize);
});

styles.css

body {
    background-color: #f0f0f0; /* Color de fondo */
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: flex-start; /* Alinear al inicio para el menú fijo */
    height: 100vh;
    margin: 0;
}

#hr{
    width: 85%;
}

.menu {
    position: fixed;
    top: 0;
    width: 100%;
    display: flex;
    justify-content: center;
    background-color: #444;
    padding: 10px;
    z-index: 1000;
    color: #fff;
    font-weight: bold;
    font-family: Arial, Helvetica, sans-serif;
}

.menu button, .menu input {
    margin-right: 10px;
    margin-left: 10px;
    background-color: #444;
    border: none;
    padding: 10px;
    cursor: pointer;
    color: #fff;
    font-weight: bold;
}

.menu input[type="text"] {
    flex-grow: 1;
    max-width: 200px;
}

.menu button:hover, .menu input:hover {
    background-color: #555;
}

.container {
    display: grid;
    gap: 1px;
    border: 1px solid #ccc;
    margin: 20px auto;
    width: fit-content;
    background-color: #ddd;
}

.pixel {
    box-sizing: border-box;
    border: 1px solid #ccc;
    cursor: pointer;
}

button.active {
    background-color: #666;
    color: #fff;
}



.controls button {
    padding: 8px 16px;
    font-size: 16px;
    background-color: #007bff;
    color: #fff;
    border: none;
    cursor: pointer;
}

.controls input[type="color"] {
    padding: 8px;
    border: none;
    cursor: pointer;
}

.controls {
    margin: 20px;
    display: flex;
    align-items: center;
}

.color-palette {
    display: flex;
    margin-left: 10px;
}

.color-button {
    width: 30px;
    height: 30px;
    border: none;
    cursor: pointer;
    margin-right: 5px;
}

.color-button:focus, .color-button.active {
    outline: 2px solid #000;
}

.eyedropper-active {
    cursor: crosshair;
}

¿Cómo empezar con Pixeleand0?

Para empezar a crear tus propias imágenes pixeladas, simplemente abre Pixeland0 en tu navegador web desde la sección de ejemplos de esta web.

imagen creada con Pixeleand0

Utiliza las herramientas disponibles para dibujar, pintar y experimentar con diferentes estilos y técnicas. Una vez que hayas terminado tu obra maestra y descárgala.

Como decía al principio del artículo, Pixeleand0es una herramienta simple de dibujo que he creado para todos aquellos que empezamos en esto del Pixel Art. Supongo que todos aquellos que saben del tema, utilizarán herramientas más complejas como Libresprite, que permiten realizar muchas más acciones que esta aplicación. También quiero aclarar que esta aplicación ha salido del tirón, por lo que es posible que falle en algunos casos.

También te puede interesar ...

Deja un comentario

* Al utilizar este formulario, aceptas que este sitio web almacene y maneje tus datos.

Adblock Detectado!!

Ayúdanos deshabilitando la extensión AdBlocker de tu navegador para visitar esta web.
Si no sabes hacerlo en Chrome, consulta el siguiente enlace. Si utilizas Firefox, puedes consultar este otro enlace.
Esto mejorará tu experiencia en este sitio web.