Skip to content

CSS Pixel Art

Posted on:12 de enero de 2020

El pixel art o arte de píxel es una forma de arte digital, creada a través de una computadora mediante el uso de programas de edición de gráficos rasterizados, donde las imágenes son editadas al nivel del píxel. - Wikipedia

Siempre me ha gustado el pixel art, me fascina que con tan poca información podamos reconocer objetos y personajes. Mucha gente cree que al ser con “cuadraditos” (aka pixels) es una técnica de dibujo muy fácil, nada más lejos de la realidad, creo que la dificultad es proporcional a la apariencia de sencillez.

Motivación

Pixel Art

Hace un tiempo quise jugar con el pixel art, creo que la mejor manera de empezar algo nuevo es copiar inspirarse en referencias que no sean muy complejas. Al mismo tiempo lo quería hacer con CSS, siempre quiero mostrar que, aun no siendo un lenguaje de programación (como siempre nos recuerda nuestro amigo Carlos Villuendas), se pueden hacer auténticas maravillas con él… ya dedicaré un post a hablar de < css-doodle /> 😊

A la sombra de CSS

Una de la propiedades CSS que tenemos disponible desde hace mucho tiempo es box-shadow, muy utilizada para resaltar elementos y dar sensación de profundidad o relieve.

Ejemplo de box-shadow en MDN

La sintaxis de box-shadow tiene varias opciones, os invito a echar un vistazo a la documentación en MDN. Si tenéis alguna duda, recordad que tengo un repositorio AMA (Ask Me Anything) donde podéis hacerme llegar vuestras consultas.

Vamos a centrarnos en las sintaxis que nos permitirá crear arte pixelado.

Si nos fijamos en la definición de la propiedad, nos dice:

La propiedad CSS box-shadow añade efectos de sombra alrededor del marco de un elemento. Se pueden definir múltiples efectos separados por comas. La caja de la sombra se describe por los desplazamientos en X e Y, los radios de desenfoque y dispersión, y el color relativos al elemento.

La parte que más nos interesa es la de Se pueden definir múltiples efectos separados por comas. Esto nos va a permitir definir cada uno de los píxeles de nuestro personaje.

En nuestro archivo HTML solo necesitamos un elemento HTML con una clase para aplicarle la magia del CSS.

<!DOCTYPE html>
<html lang="es">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>CSS Pixel Art</title>
    <link rel="stylesheet" href="styles.css" />
  </head>
  <body>
    <div class="pixel-art"></div>
  </body>
</html>

Ahora vamos a añadir el CSS necesario para pintar algunos píxeles.

.pixel-art {
  width: 1px;
  height: 1px;
  margin: 50px;
  transform: scale(20);
  box-shadow: 1px 0 red, 2px 0 green, 3px 0 blue;
}
Analicemos línea a línea este código
  1. Como vamos a trabajar a nivel de píxel, lo primero que hacemos es definir un tamaño de 1px x 1px nuestro elemento HTML.
  2. Con la propiedad margin, simplemente lo estamos posicionando a 50px de la coordenada 0,0 del body.
  3. Esta propiedad es importante, al pintar a un tamaño de píxeles, escalamos el elemento para poder mostrarlo a un tamaño más adecuado, en este caso estamos escalando el elemento 20 veces.
  4. Por último, definimos la propiedad que nos aporta la magia para conseguir crear nuestra obra en pixel art.
Vamos a verlo en funcionamiento

Lo que podemos ver son tres (tristes) píxeles, uno al lado del otro 😅. Pero con solo este simple ejemplo seguro que ya ves la “simplicidad” de lo que tenemos que hacer para dibujar nuestros dibujos con píxeles.

Crearemos otro ejemplo más ilustrativo.

En este otro ejemplo podéis ver que el tamaño del elemento es de 4px y el multiplicador de escala de 5 con scale(5), más tarde veremos el motivo 😊. Lo interesante es que con unos cuantos valores de sombra en nuestro CSS hemos conseguido una versión Pixel Art del logo del blog.

A nivel de pixel

Si analizamos de cerca (a nivel de pixel) cualquier obra de pixel art, podremos ver que se trata de una matriz de píxeles, donde cada uno de esos píxeles tiene la información del color con el que es representado.

Lemming a nivel de pixel

La imagen anterior se vería de la siguiente manera en una matriz donde estemos definiendo el color hexadecimal de cada uno de los píxeles. Obviamos los píxeles de “padding” que hemos dejado para mejorar la lectura de la imagen, así que tendremos una matriz de 10 x 10.

#000000,#000000,#000000,#000000,#00b0b0,#00b0b0,#000000,#000000,#000000,#000000,
#000000,#000000,#000000,#00b0b0,#00b0b0,#00b0b0,#00b0b0,#000000,#000000,#000000,
#000000,#000000,#000000,#00b0b0,#f0d0d0,#f0d0d0,#00b0b0,#000000,#000000,#000000,
#f0d0d0,#000000,#000000,#000000,#f0d0d0,#f0d0d0,#000000,#000000,#000000,#f0d0d0,
#f0d0d0,#f0d0d0,#f0d0d0,#f0d0d0,#4040e0,#4040e0,#f0d0d0,#f0d0d0,#f0d0d0,#f0d0d0,
#000000,#000000,#000000,#000000,#4040e0,#4040e0,#000000,#000000,#000000,#000000,
#000000,#000000,#000000,#000000,#4040e0,#4040e0,#000000,#000000,#000000,#000000,
#000000,#000000,#000000,#4040e0,#4040e0,#4040e0,#4040e0,#000000,#000000,#000000,
#000000,#000000,#000000,#4040e0,#000000,#000000,#4040e0,#000000,#000000,#000000,
#000000,#000000,#f0d0d0,#f0d0d0,#000000,#000000,#f0d0d0,#f0d0d0,#000000,#000000;

Como si se tratase de punto de cruz, simplemente definimos un color por cada uno de los puntos que queremos pintar.

CSS vitaminado

Como estaréis imaginando, conseguir dibujos más complejos nos puede llevar mucho trabajo, y así es. Es por eso que nos vamos a apoyar en Sass para facilitar la generación de nuestras creaciones en pixel art.

Analicemos paso a paso lo que estamos haciendo

Definimos en variables los diferentes colores que necesitamos para crear nuestro dibujo. También definimos una variable con la dimensión de la cuadrícula.

$hair: #00b0b0;
$skin: #f0d0d0;
$clothes: #4040e0;
$grid: 10;

Después, utilizamos un Maps de Sass para poder utilizar las claves de índice del mapa, eso nos facilitará crear la matriz.

$colors: (
  1: $hair,
  2: $skin,
  3: $clothes,
);

El siguiente paso es definir la matriz con los colores de nuestro dibujo. Lo haremos con cada una de las claves del Maps que acabamos de crear, utilizaremos 1 para hacer referencia a la variable $hair, que a su vez es el valor hexadecimal #00b0b0, el color del pelo.

// Lemming Blocker -------------------
$lemming-blocker: 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0,
  0, 0, 1, 2, 2, 1, 0, 0, 0, 2, 0, 0, 0, 2, 2, 0, 0, 0, 2, 2, 2, 2, 2, 3, 3, 2,
  2, 2, 2, 0, 0, 0, 0, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 0, 0, 0, 0, 0, 0, 0,
  3, 3, 3, 3, 0, 0, 0, 0, 0, 0, 3, 0, 0, 3, 0, 0, 0, 0, 0, 2, 2, 0, 0, 2, 2, 0,
  0;

Es difícil de apreciar, pero ahora mismo tenemos una matriz de 10 x 10 igual que la que hemos visto un poco más arriba, con la cuadrícula roja para ver los píxeles, con un valor numérico para cada uno de los colores. Para Sass esto es una lista, así podremos iterar sobre ella. Técnicamente es una sola línea, aquí la vemos en modo cuadrícula para facilitar la edición.

Habrás visto que para el color negro hemos utilizado el valor 0, el cual no tenemos definido en nuestro Maps, ahora veremos cómo lo vamos a utilizar.

Empecemos con la programación

Sass nos provee, como definen, un lenguaje de extensión para CSS. Se trata de un preprocesador CSS con una sintaxis que nos ofrece funciones, listas, maps o bucles entre otros.

El siguiente código contiene la “magia” necesaria para pintar todos los píxeles necesarios para dibujar el lemming.

@function pixeart($lemming-list, $shadow-count: $grid) {
  $shadows: ();
  $row: 0;
  $col: 1;

  @for $i from 1 through length($lemming-list) {
    @if (nth($lemming-list, $i) > 0) {
      $shadows: append(
        $shadows,
        ($col * 1px) ($row * 1px) 0 map-get($colors, nth($lemming-list, $i)),
        "comma"
      );
    }

    @if ($i % $grid == 0) {
      $row: $row + 1;
      $col: 0;
    }

    $col: $col + 1;
  }

  @return $shadows;
}
Analicemos este código en Sass

La función pixelart acepta dos parámetros, el primero es una lista y el segundo la dimensión de la retícula, donde definimos un valor por defecto en el caso de no recibir ninguno.

Al inicio del cuerpo de la función definimos una lista vacía con $shadow, así como el valor inicial para las filas y columnas en $row y $col.

En el siguiente bloque tenemos un @for para iterar cada uno de los elementos de la lista (con nuestra matriz) que hemos recibido como argumento $leming-list.

En el primer @if, donde comprobamos si seguimos teniendo elementos de la lista, lo que estamos haciendo es añadir un elemento a la lista @shadows con addend. A append le estamos pasando 3 parámetros: la lista a la que queremos añadir el elemento, el contenido del elemento y el separador, en este caso 'coma', una palabra reservada de Sass para representar el carácter ,. Entrando en detalle del valor del elemento que estamos añadiendo vemos ($col * 1px) ($row * 1px) 0 map-get($colors,nth($lemming-list,$i)).

En el segundo @if, simplemente comprobamos si ya hemos llegado al máximo de columnas, 10 en este ejemplo, y si es así pasamos el valor de columna a 0 y sumamos una a las filas $row

Por último, devolvemos el contenido de $shadows con @return $shadows.

Llamada desde CSS

Ahora que tenemos disponible nuestra función, la podemos utilizar en CSS con pixelart($lemming-blocker), como podemos ver en la línea 7.

.lemming--blocker {
  width: 1px;
  height: 1px;
  margin: 100px;
  transform: scale(10);

  box-shadow: pixelart($lemming-blocker);
}

Dando vida a las sombras

Ahora que ya tenemos nuestro lemming en pixel art, vamos a darle vida gracias a la animación CSS.

Como ya sabréis, una animación es la ilusión de movimiento al ver una secuencia de imágenes a cierta velocidad. Este tipo de imágenes en muchos juegos 2D son sprites, así que busqué Lemmings sprite animation y dí con esta imagen. Eso me permitió poder ver la secuencia de imágenes para hacer la animación.

El lemming de la izquierda tiene solo 2 fotogramas, así que con dos matrices ya tenemos la animación resuelta.

.lemming--blocker {
  width: 1px;
  height: 1px;
  margin: 100px;

  transform: scale(10);

  animation: blocker 0.65s step-start infinite;
  box-shadow: lemming($lemming-blocker-1);
}

@keyframes blocker {
  50% {
    box-shadow: lemming($lemming-blocker-2);
  }
}

Para conseguir el efecto de andar del lemming de la derecha, requiere de 8 fotogramas, por lo que tenemos que definir 8 matrices (echad un vistazo al código de Codepen).

La asignación de la animación no tiene mucho misterio, la haremos tal y como lo hacemos en cualquier animación CSS con @keyframes.

.lemming--walker {
  width: 1px;
  height: 1px;
  margin: 100px;

  transform: scale(10);

  animation: walker 0.65s step-start infinite;
  box-shadow: lemming($lemming-walker-1);
}

@keyframes walker {
  12% {
    box-shadow: lemming($lemming-walker-2);
  }
  25% {
    box-shadow: lemming($lemming-walker-3);
  }
  37% {
    box-shadow: lemming($lemming-walker-4);
  }
  50% {
    box-shadow: lemming($lemming-walker-5);
  }
  62% {
    box-shadow: lemming($lemming-walker-6);
  }
  75% {
    box-shadow: lemming($lemming-walker-7);
  }
  87% {
    box-shadow: lemming($lemming-walker-8);
  }
}

Matrix no es para mí

Llegados a este punto estarás pensando: Tener que hacer una matriz de n x n de cada imagen, y multiplicarlo por el número de fotogramas necesario si quiero hacer una animación, en serio Joan, estás flipando.

Este tipo de ejercicios nos sirve para practicar y aprender, pero según lo que quieras hacer está claro que es una forma muy tediosa artesana de hacerlo. Es por eso que te comparto el siguiente recurso.

CSS Sprite Animator

CSS Sprite Animator

Se trata de una aplicación desarrollada por Paul Karlik, podéis encontrarla aquí y el repositorio con el código fuente aquí.

Tiene una interfaz intuitiva y simple. En el momento que añadimos más de un fotograma, en la parte superior derecha, podemos ver una previsualización de la animación.

El logo del blog en pixel art que hemos visto antes, está hecho con este editor, que está utilizando múltiples de 4 en la generación de los píxeles. Me he animado y también he creado la animación del lemming cuando está cayendo.

Lemming Falling

La aplicación también está generando el código CSS necesario para la propiedad box-shadow. Tiene una función de exportación que genera un array de objetos con los valores necesarios.

[
  [
    {"x":0,"y":0,"color":"transparent"},
    {"x":1,"y":0,"color":"transparent"},
    ...
  ],
  [ ... ]
]

Curiocssidades

Las animaciones las hemos definido con un valor de step-start para la propiedad animation-timing-function. Con step-start estamos definiendo que el navegador no genere ninguna interpolación entre un fotograma y el siguiente. El valor por defecto de esta propiedad es ease que deja al navegador la responsabilidad de calcular una interpolación entre fotogramas, y en este caso no conseguimos el efecto deseado, pero eso sí, la animación queda curiosa 😊.

Lemming ease

Otros ejemplos

En mi cuenta de Codepen encontraréis este y otros ejemplos, algunos de ellos están hechos con p5.js (ya hablaremos de esta librería en algún momento), pero la base es la misma, utilizar una matriz para generar el dibujo pixel art.