Tabla de contenido
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

- Generación de píxeles dinámica: El programa genera una cuadrícula de píxeles que puedes personalizar según tus necesidades.
- 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.
- 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.
- Importación de imágenes: Importa tus propias imágenes para usarlas como referencia o como base para tus creaciones pixeladas.
- Descarga fácil: Descarga tus obras de arte en formato PNG para compartirlas con el mundo o para utilizarlas en tus proyectos personales.
- 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.

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.
