¿Qué es el Canvas?, uso del API en HTML con JavaScript (Guía y Tutoriales)

- Andrés Cruz - EN In english

¿Qué es el Canvas?, uso del API en HTML con JavaScript (Guía y Tutoriales)

El Canvas es una de las tecnologías más potentes que podemos emplear en el desarrollo web. En esencia, no es más que un lienzo digital que permite crear gráficos de todo tipo a partir de las primitivas de siempre: líneas, círculos, cuadrados y rectángulos, como veremos más adelante en esta misma entrada. Además de profundizar en el uso de la API Canvas, encontrarás tutoriales con distintos casos de uso reales —desde recorte de imágenes hasta procesamiento de píxeles— para que puedas explorar todo su potencial.

Una vez aclarado qué es el Canvas, lo siguiente que debemos conocer es su compatibilidad. Hoy en día, la gran mayoría de los navegadores tiene soporte nativo para trabajar con la API Canvas; los principales, como Google Chrome, Firefox e incluso los navegadores de Microsoft, soportan esta tecnología que nos permite dibujar gráficas tan sencillas o complejas como necesitemos, realizar composiciones de imágenes o fotografías, e incluso renderizar vídeo en tiempo real.

En definitiva, el Canvas es básicamente eso: un lienzo en donde podemos realizar cualquier tipo de dibujo controlado mediante código.

Todo comienza a través de la etiqueta <canvas>, que habilita un área (el lienzo) en donde es posible dibujar, escribir o renderizar imágenes mediante scripts realizados con JavaScript, el lenguaje de programación elegido para trabajar con esta tecnología:

<canvas id="myCanvas" width="500" height="500">
<p>Su navegador no soporta canvas :(</p>
</canvas>

Con el canvas es posible crear todo tipo de gráficos, simples o complejos, a partir de primitivas básicas como círculos, óvalos, rectángulos, líneas y polígonos. Además, son fácilmente animables gracias al método requestAnimationFrame. El canvas también es extensible hasta incluir eventos de teclado y ratón, entre muchas otras características que, de otra forma (con solo CSS), serían prácticamente imposibles o muy difíciles de replicar.

Soporte del Canvas en los navegadores

Los navegadores modernos cuentan desde hace tiempo con soporte nativo para la etiqueta <canvas>; sin embargo, es recomendable verificar dicho soporte. Como ocurre con muchas otras etiquetas de HTML5, cualquier cosa que se encuentre entre las etiquetas de apertura y cierre de <canvas> será interpretada únicamente por los navegadores que no soportan la etiqueta:

  <canvas>
    Su navegador no soporta Canvas
  </canvas>

A nivel de JavaScript, podemos realizar la siguiente validación para verificar si la API Canvas está soportada en el navegador del usuario:

function isCanvasSupported(){
  var elem = document.createElement('canvas');
  return !!(elem.getContext && elem.getContext('2d'));
}

Lo anterior nos lleva a que el elemento canvas admite tres atributos esenciales (aunque uno de ellos también puede definirse desde CSS): el id, que es la forma en la que lo referenciamos desde JavaScript, y el ancho y el alto, definidos respectivamente por width y height.

Algunas ventajas de usar el Canvas en tus aplicaciones

  • Animable. Cada objeto que dibujamos en el canvas puede ser animado con total control sobre cada fotograma.
  • Interactivo. El Canvas es 100% interactivo y responde a todas las acciones del teclado y el ratón mediante el sistema de eventos de JavaScript.
  • Flexible. Podemos dibujar cualquier cosa en el Canvas: imágenes, líneas, figuras geométricas, polígonos, etc., y animar cada uno de esos objetos de forma independiente.
  • Soporte de los navegadores. La mayoría de los navegadores modernos soportan el canvas desde hace años, por lo que no necesitas librerías adicionales para empezar a usarlo.
  • El Canvas es un estándar. A diferencia de otras tecnologías como Flash o Silverlight —hoy prácticamente extintas—, el Canvas forma parte de la especificación HTML5 y cualquier navegador moderno que se precie debe darle soporte.
Cualquier cosa que escribamos dentro de las etiquetas <canvas> solamente será mostrada en navegadores que no soportan la etiqueta. Se trata del mecanismo de fallback nativo de HTML5.

Ejemplos y Tutoriales sobre el Canvas: ¿Cómo funciona la etiqueta canvas?

Vayamos a la parte interesante y empecemos con nuestro primer ejemplo. Con tres sencillos pasos podemos comenzar a trabajar con esta etiqueta:

1. Referenciando el Canvas del DOM con JavaScript

Lo primero es obtener una referencia al canvas con el que deseamos trabajar. En este caso, la etiqueta <canvas> tiene el identificador myCanvas:

var myCanvas = document.getElementById("myCanvas")

2. Obteniendo el contexto del Canvas vía JavaScript

Para realizar cualquier operación con el canvas —ya sea dibujar gráficos o imágenes— debemos obtener el contexto del elemento canvas y, de esa forma, acceder a toda la API:

El contexto del canvas es el objeto que expone todos los métodos y propiedades disponibles para dibujar en él. Sin obtenerlo, no es posible dibujar nada. El valor "2d" indica que trabajaremos con gráficos en dos dimensiones; también existe el contexto "webgl" para gráficos 3D acelerados por hardware.

var ctx = c.getContext("2d");

3. Dibujando primitivas

Ahora sí, podemos empezar a dibujar sobre nuestro canvas. Veamos una serie de ejemplos que ilustran los usos más comunes de la API.

3.1 Dibujando una línea con Canvas

El experimento más sencillo que podemos hacer consiste en dibujar una simple línea: el "Hola Mundo" del mundo del Canvas. Para ello necesitamos los siguientes métodos:

  • moveTo(x, y) — punto de comienzo de la línea.
  • lineTo(x, y) — punto final de la línea.
  • stroke() — dibuja efectivamente la línea sobre el canvas.

 

 

3.2 Dibujando un círculo con Canvas

Para dibujar un círculo en el canvas debemos usar los siguientes métodos:

  • Un path es un conjunto de trazados que realizamos dentro de un canvas. Para iniciarlo usamos beginPath() y lo cerramos con closePath(). Nada dentro del path será visible hasta que indiquemos al contexto que lo dibuje con stroke().
  • arc(x, y, r, inicio, final) — dibuja un arco o círculo en el canvas:
    • x e y: coordenadas del centro del círculo.
    • r: radio del círculo.
    • inicio y final: ángulo de inicio y fin, expresados en radianes. Para un círculo completo, los valores típicos son 0 y Math.PI * 2.
  • lineTo(x, y) — punto final de la línea (útil para cerrar el trazado).
  • stroke() — renderiza el contorno del trazado.

3.3 Dibujando texto con Canvas

Para dibujar texto dentro del canvas existen dos métodos principales, cada uno con un resultado visual diferente:

  • font — propiedad que define la fuente, el tamaño y el estilo del texto (por ejemplo, "16px Arial").
  • fillText(texto, x, y) — dibuja el texto con relleno sólido en la posición indicada.
  • strokeText(texto, x, y) — dibuja el texto sin relleno (solo el contorno) en la posición especificada por x e y.
  • stroke() — renderiza el trazado activo del path.

 

 

3.4 Dibujando una imagen con Canvas

Puede que te preguntes cuál es el objetivo de dibujar una imagen en un canvas. La respuesta es que, además de poder mezclarla con otras primitivas e imágenes, podemos alterar cada uno de los píxeles que la componen para aplicar técnicas de Procesamiento Digital de Imágenes: recortes, escala de grises, ajuste de contraste, extracción de canales RGB, entre otros experimentos que exploraremos en las secciones siguientes.

Dibujar una línea en Loop (ciclo) con HTML5 Canvas

Dibujar una Línea en Loop (ciclo) con HTML5 Canvas

Ver demo Descargar Fuente

En este artículo veremos cómo dibujar una línea dentro de un bucle o ciclo en HTML5, concretamente con la API de Canvas. Aunque la API ya ofrece la primitiva lineTo() para trazar líneas, en este ejercicio construiremos una línea a partir de cuadrados de tamaños variables que actuarán como los "píxeles" que la conforman. Para dibujar cada cuadrado emplearemos la siguiente función:

ctx.fillRect(X, Y, Width, Height);
  • X: Coordenada X de la esquina superior izquierda del rectángulo.
  • Y: Coordenada Y de la esquina superior izquierda del rectángulo.
  • Width: Ancho del rectángulo en píxeles.
  • Height: Alto del rectángulo en píxeles.

Dibujando la línea con la API de Canvas

Primero definimos el tamaño del Canvas en píxeles; en este caso será un cuadrado:

var tamCanvas = 360;

A continuación, definimos cuántos "píxeles" tendrá la línea y cuál será el tamaño de cada uno:

var numCuadrados = 10;
var tam = 100;

Ahora calculamos el espaciado entre los "píxeles"; este valor disminuirá a medida que aumentemos el número de rectángulos, de forma que la línea siempre ocupe el lienzo completo:

var espaciado = (tamCanvas - tam) / (numCuadrados - 1);

Finalmente, realizamos el ciclo en donde dibujamos los cuadrados que conformarán la línea:

for (var i = 0; i < numCuadrados; i++) {
    ctx.fillStyle = "rgba(255, 255, 255, 0.8)";
    ctx.fillRect(i * espaciado, i * espaciado, tam, tam);
}

Ver demo Descargar Fuente

Ejemplo interactivo

Si te quedan dudas sobre el efecto de cada parámetro, en este ejemplo interactivo puedes variar sus valores en tiempo real:

 

Recortes de imágenes (crop) con HTML5, Canvas y jQuery

Ver demo Descargar Fuente

Recortes de imágenes (crop) con HTML5 Canvas

Ver demo Descargar Fuente

Poder recortar imágenes —operación también conocida como crop— es sumamente útil en determinados escenarios: subdividir imágenes en trozos es una operación muy común en el desarrollo web actual y hay múltiples situaciones en las que querrás que una aplicación web aplique un recorte sobre una imagen y luego permita descargarla.

En este artículo veremos cómo recortar imágenes y guardarlas en el dispositivo del usuario con HTML5, utilizando las siguientes tecnologías:

  • Canvas y su API en JavaScript para cortar, cargar y guardar la imagen resultante.
  • JavaScript nativo para el manejo de eventos y algunas funciones auxiliares de soporte.

Funcionamiento del experimento de recorte de imágenes

El funcionamiento es sencillo y podemos resumirlo así:

  • El usuario realiza un primer clic sobre el canvas; capturamos las coordenadas de ese clic.
  • El usuario arrastra el ratón (sin mantener el clic presionado) hasta posicionarse en el punto de interés; a medida que se desplaza, se mostrará un hover que representa el área de recorte calculada desde el primer clic.
  • El usuario realiza un segundo clic sobre el canvas; capturamos esas coordenadas.
  • Se genera la imagen recortada a partir de la selección realizada.

Es el mismo flujo de trabajo que el de editores de imágenes como GIMP o Photoshop: primero seleccionas, luego recortas.

El método drawImage()

El método drawImage() permite dibujar una imagen, un canvas o un vídeo dentro de un canvas. Sus parámetros obligatorios son:

  • Elemento: imagen, vídeo o canvas a usar como fuente.
  • x: coordenada X en la que se empezará a pintar en el canvas de destino.
  • y: coordenada Y en la que se empezará a pintar en el canvas de destino.

El comportamiento varía según la cantidad de parámetros que le pasemos:

  • drawImage(img, x, y): pinta la imagen completa en el canvas desde las coordenadas x e y.
  • drawImage(img, x, y, width, height): copia la imagen a partir de los puntos x e y, escalándola a las dimensiones definidas por width y height.
  • drawImage(img, sx, sy, swidth, sheight, x, y, width, height): recorta la imagen desde los puntos sx y sy con las dimensiones swidth y sheight, y la dibuja en el canvas en las coordenadas x, y con el tamaño indicado por width y height.

1. Variables globales e inicialización

En esta parte definiremos las variables del experimento. Las funciones de cada una se explican en los comentarios del código:

//*** variables globales
// canvas y su contexto
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
// imagen en donde se colocará la imagen cortada
var canvasImg = document.getElementById('canvasImg');
// ruta a la imagen fuente
var imgSource = "image.jpg";
// punto X y Y del primer clic sobre el canvas
var iniX;
var iniY;
// punto X y Y del segundo clic sobre el canvas
var endX;
var endY;
// anchura y altura calculadas a partir de los dos clics
var imgW;
var imgH;
// hover que se superpondrá sobre el canvas según la selección del usuario
var hover = document.getElementById('hoverCut');

// inicializo los componentes para el recorte de la imagen
function init() {
    iniX = -1;
    iniY = -1;
    endX = -1;
    endY = -1;
    imgW = -1;
    imgH = -1;
    hoverBol = false;
    hover.style.width = "0";
    hover.style.height = "0";
}

El método init() inicializa las variables y deshabilita el hover (estableciendo su anchura y altura en cero). Se invocará automáticamente cada vez que el usuario complete un recorte sobre el Canvas.

2. Dibujando una imagen en el Canvas

Definimos un método que cargue la imagen fuente en el Canvas. Es importante esperar a que la imagen esté completamente cargada antes de dibujarla, de ahí el uso del evento onload:

function loadImageToCanvas() {
    newImg = new Image();
    newImg.src = imgSource;
    newImg.onload = function() {
        // reescalo el canvas al tamaño de la imagen
        canvas.width = newImg.width;
        canvas.height = newImg.height;
        ctx.drawImage(newImg, 0, 0);
    };
}

3. Retornando el contenido del Canvas hacia una etiqueta <img>

Antes pasamos el contenido de una imagen hacia el Canvas; ahora haremos lo contrario. El siguiente método exporta el Canvas hacia una etiqueta <img> y, además, copia ese contenido en un enlace <a> para que el usuario pueda descargar la imagen recortada. Esto es posible gracias al método toDataURL(), que convierte el contenido del canvas en una cadena base64:

function saveCanvasToImage() {
    var dataURL = canvas.toDataURL();
    canvasImg.src = dataURL;
    document.getElementById("downloadImage").href = dataURL;
}

4. Evento clic en el Canvas

Capturamos dos clics del usuario sobre el Canvas para calcular el área rectangular a recortar:

canvas.addEventListener('click', function(e) {
    const rect = canvas.getBoundingClientRect();

    // Usamos pageX/Y para obtener la posición absoluta en el documento (incluye scroll)
    const auxX = e.pageX - (rect.left + window.scrollX);
    const auxY = e.pageY - (rect.top + window.scrollY);

    if (iniX < 0) {
        iniX = auxX;
        iniY = auxY;
        hover.style.left = e.pageX + "px";
        hover.style.top = e.pageY + "px";
    } else {
        endX = auxX;
        endY = auxY;
        imgW = endX - iniX;
        imgH = endY - iniY;
        const newImg = new Image();
        newImg.src = imgSource;
        newImg.onload = function() {
            canvas.width = imgW;
            canvas.height = imgH;
            ctx.clearRect(0, 0, imgW, imgH);
            ctx.drawImage(newImg, iniX, iniY, imgW, imgH, 0, 0, imgW, imgH);
            saveCanvasToImage();
            loadImageToCanvas();
            init();
        };
    }
});

Analizando la función anterior...

El cuerpo del manejador del evento click está dividido en dos bloques. Usamos la variable iniX como bandera:

  • Si iniX es menor a cero, se trata del primer clic del usuario sobre el Canvas.
  • Si iniX es distinta de -1, se trata del segundo clic y se ejecuta el recorte.

La parte clave es la captura de coordenadas relativas al canvas:

const rect = canvas.getBoundingClientRect();
const auxX = e.clientX - rect.left + window.scrollX;
const auxY = e.clientY - rect.top + window.scrollY;
  • e.clientX y e.clientY capturan las coordenadas del clic relativas a la ventana.
  • getBoundingClientRect() devuelve el rectángulo del canvas relativo a la ventana del navegador, lo que nos permite convertir esas coordenadas al sistema de referencia del canvas.
  • window.scrollX y window.scrollY compensan el desplazamiento de la página cuando el usuario ha hecho scroll antes de hacer clic.

5. El hover sobre el Canvas

Colocamos un div semitransparente que se extienda desde el primer clic hasta la posición actual del ratón. Actualizamos repetidamente su anchura y altura conforme el usuario mueve el ratón:

canvas.addEventListener('mousemove', function(e) {
    if (iniX >= 0) {
        const rect = canvas.getBoundingClientRect();
        const currentX = e.pageX - (rect.left + window.scrollX);
        const currentY = e.pageY - (rect.top + window.scrollY);

        imgW = Math.abs(currentX - iniX);
        imgH = Math.abs(currentY - iniY);

        if (currentX < iniX) {
            hover.style.left = e.pageX + "px";
        }
        if (currentY < iniY) {
            hover.style.top = e.pageY + "px";
        }
        hover.style.width = (imgW - 3) + "px";
        hover.style.height = (imgH - 3) + "px";
    }
});

Resultado Final:

 

Ver demo Descargar Fuente

Existe otra forma de aplicar recortes sin Canvas: los márgenes negativos en CSS. Sin embargo, con esa técnica solo puedes recortar desde los bordes, no desde el interior de la imagen. Otra alternativa es la propiedad clip-path, que puedes explorar en detalle en esta entrada:

La propiedad clip-path en CSS para seleccionar regiones a mostrar en elementos

Detectando los eventos de teclado con Canvas

Detectando los eventos de teclado con Canvas

Ver ejemplo

A continuación veremos un pequeño experimento en donde interactuamos con el Canvas mediante los eventos de teclado; específicamente, usaremos las teclas de dirección para mover un objeto por el lienzo.

Crear animaciones con JavaScript y Canvas que dependan de agentes externos como el teclado o el ratón es mucho más sencillo de lo que parece.

Al introducir el concepto de animación entra en juego la función requestAnimationFrame(), que permite realizar animaciones en el Canvas de manera eficiente y sincronizada con la tasa de refresco del monitor. Si quieres profundizar en cómo funciona, puedes hacerlo en: El secreto de las animaciones en JavaScript.

Empezando con el experimento: Canvas, animaciones y eventos de teclado

El experimento consiste en mover un pequeño círculo por todo el Canvas, dejando una leve estela de color a su paso, controlado mediante las teclas de dirección del teclado.

Inicializando el evento de teclado

Asociaremos el evento al document, aunque también podría asociarse directamente al Canvas:

document.addEventListener('keydown', function(e) { }, false);

En el escuchador 'keydown' asociaremos los códigos de las teclas de dirección con las variables de posición del círculo. Estas variables se actualizan aquí y son leídas en cada llamada a requestAnimationFrame().

Puedes buscar los códigos de cada tecla en la documentación del navegador, o simplemente añadir un console.log(e.keyCode) y consultar el valor al pulsar la tecla que te interese.

Una vez que tenemos los códigos, construimos los condicionales para incrementar o decrementar las variables de posición:

document.addEventListener('keydown', function (e) {
    lastDownTarget = event.target;
    if (e.keyCode === 37) { x -= v; }  // flecha izquierda
    if (e.keyCode === 38) { y -= v; }  // flecha arriba
    if (e.keyCode === 39) { x += v; }  // flecha derecha
    if (e.keyCode === 40) { y += v; }  // flecha abajo
}, false);
  • x e y son variables globales que representan la posición del círculo en un momento dado; por defecto se inicializan en el centro del canvas.
  • v es una constante global que controla la velocidad de desplazamiento del círculo.

Pintando en el Canvas

Ahora veremos cómo usar las variables de posición para dibujar el círculo en cada fotograma:

function draw() {
    $.fillStyle = 'hsla(' + (x * y) / 100 + ',100%, 50%, 1)';
    $.beginPath();
    $.arc(x, y, 8, 0, dosPi);
    $.fill();
}

La función principal del bucle de animación se encarga de limpiar el canvas, limitar el movimiento dentro de los bordes del lienzo y solicitar el siguiente fotograma mediante requestAnimationFrame():

function go() {
    $.fillStyle = 'hsla(0,0%,0%,.08)';
    $.fillRect(0, 0, w, h);
    if (x <= 0) x = 0;
    if (y <= 0) y = 0;
    if (x >= w) x = w;
    if (y >= h) y = h;
    draw();
    window.requestAnimationFrame(go);
}

El código completo del experimento con Canvas y eventos de teclado:

var c = document.getElementById('canv');
var w = c.width = window.innerWidth;
var h = c.height = window.innerHeight;
var $ = c.getContext('2d');
var x = w / 2;
var y = h / 2;
var dosPi = Math.PI * 2;
var v = 5;

window.addEventListener('resize', function () {
    c.width = window.innerWidth;
    c.height = window.innerHeight;
}, false);

function draw() {
    $.fillStyle = 'hsla(' + (x * y) / 100 + ',100%, 50%, 1)';
    $.beginPath();
    $.arc(x, y, 8, 0, dosPi);
    $.fill();
}

function go() {
    $.fillStyle = 'hsla(0,0%,0%,.08)';
    $.fillRect(0, 0, w, h);
    if (x <= 0) x = 0;
    if (y <= 0) y = 0;
    if (x >= w) x = w;
    if (y >= h) y = h;
    draw();
    window.requestAnimationFrame(go);
}

go();

document.addEventListener('keydown', function (e) {
    lastDownTarget = event.target;
    if (e.keyCode === 37) { x -= v; }
    if (e.keyCode === 38) { y -= v; }
    if (e.keyCode === 39) { x += v; }
    if (e.keyCode === 40) { y += v; }
}, false);

Y para no extender demasiado esta entrada, lo dejaremos hasta aquí; en futuras entregas profundizaremos en más formas de interactuar con el Canvas mediante eventos de teclado.

Puedes probar el experimento en el siguiente enlace:

Ver ejemplo

Escalado y recorte de imágenes con Canvas

Siguiendo con los tutoriales de Canvas, veremos cómo aplicar recortes a secciones específicas de una imagen —operación conocida como crop— y cómo escalarla. Con el canvas es posible realizar ambas operaciones con una sola función: drawImage(). Como ya vimos, el comportamiento de esta función varía según la cantidad de parámetros. Repasemos los tres modos:

Parámetros de la función drawImage()

ParámetroDescripción
ElementoImagen, vídeo o canvas a usar como fuente.
sxOpcional. Coordenada X de inicio del recorte en el elemento fuente.
syOpcional. Coordenada Y de inicio del recorte en el elemento fuente.
swidthOpcional. Ancho del área de recorte en el elemento fuente.
sheightOpcional. Alto del área de recorte en el elemento fuente.
xCoordenada X en donde se empezará a pintar en el canvas de destino.
yCoordenada Y en donde se empezará a pintar en el canvas de destino.
widthOpcional. Ancho deseado en el canvas; permite escalar la imagen horizontalmente.
heightOpcional. Alto deseado en el canvas; permite escalar la imagen verticalmente.

Dibujando una imagen en el Canvas

Especificamos la imagen fuente en el HTML y la referenciamos desde JavaScript:

<img src="/public/images/example/paisaje/paisaje.jpg" alt="paisaje" id="paisaje">
paisaje

Procedemos a pintarla en el Canvas:

var img = document.getElementById('paisaje');
drawImage(img, x, y);

La imagen es copiada al Canvas a partir de los puntos definidos por x e y. Si quieres copiarla completa desde la esquina superior izquierda, ambas coordenadas deben ser 0.

Escalando una imagen con Canvas

drawImage(image, x, y, width, height);

La imagen es copiada al Canvas a partir de los puntos x e y, escalándola a las dimensiones definidas por width y height. Si estos valores difieren de los originales, el Canvas escalará la imagen automáticamente.

Recortando y escalando una imagen con JavaScript

Este es el modo más completo del método drawImage(): empleamos todos sus parámetros para indicar tanto el área de recorte en la imagen fuente como las dimensiones finales en el canvas de destino:

drawImage(image, sx, sy, swidth, sheight, x, y, width, height);

La imagen es recortada a partir de los puntos sx y sy con las dimensiones swidth y sheight. Conceptualmente, sería como hacer la selección con la herramienta de recorte en GIMP.

Luego, los últimos cuatro parámetros controlan el resultado: x e y posicionan la imagen en el canvas de destino, mientras que width y height definen las dimensiones finales. Si no quieres escalar la imagen, basta con que width = swidth y height = sheight.

¿Cómo obtener por separado el canal RGB de una imagen con HTML5 y la API Canvas?

El procesamiento digital de imágenes es una tarea frecuente en el desarrollo web: desde cambiar el tamaño de una imagen para optimizarla, hasta aplicar recortes o alterar el color, el brillo, la saturación, e incluso extraer por separado cada uno de los canales RGB de una imagen usando el Canvas, como veremos en esta sección.

Veremos un tutorial de cómo extraer los tres canales RGB de una imagen y operarlos de forma individual, obteniendo los canales R, G y B por separado tal como se aprecia en la imagen de cabecera. Al igual que en los artículos anteriores, usaremos HTML5 y JavaScript nativo.

Definiendo el HTML

El HTML es realmente simple: un elemento <canvas> para dibujar la imagen base con sus tres canales intactos:

<canvas id="canvas">
    <p>Tu navegador no soporta Canvas.</p>
</canvas>

Y tres etiquetas <img> vacías en donde colocaremos cada canal RGB mediante JavaScript:

<img id="r"/>
<img id="g"/>
<img id="b"/>

Definiendo el JavaScript para obtener los canales RGB de la imagen

Canales RGB separados con HTML5 Canvas

Lo primero es declarar las variables globales para obtener el canvas, su contexto y las referencias a las tres imágenes vacías:

var canvas = document.getElementById('canvas'); // canvas
var ctx    = canvas.getContext('2d');            // contexto
var imgR   = document.getElementById('r');      // imagen del canal R
var imgG   = document.getElementById('g');      // imagen del canal G
var imgB   = document.getElementById('b');      // imagen del canal B
var srcImg = "image.png";                       // imagen fuente

Creamos un objeto de tipo Image y le asignamos la imagen fuente:

img = new Image();
img.src = srcImg;

Una vez cargada, redimensionamos el Canvas a las dimensiones de la imagen, la dibujamos en él e invocamos a getRGB():

img.onload = function() {
    canvas.width  = img.width;
    canvas.height = img.height;
    ctx.drawImage(this, 0, 0);
    getRGB();
};

La función getRGB() obtiene el ImageData de la imagen —un array plano donde cada grupo de cuatro valores consecutivos representa los canales R, G, B y A de un píxel— y crea tres copias independientes. En cada copia anula los dos canales que no corresponden, dejando solo el canal de interés activo. Luego, vuelca cada copia de vuelta al canvas y usa toDataURL() para exportarla como fuente de imagen:

function getRGB() {
    var imgd  = ctx.getImageData(0, 0, canvas.width, canvas.height);
    var imgdR = ctx.getImageData(0, 0, canvas.width, canvas.height);
    var imgdG = ctx.getImageData(0, 0, canvas.width, canvas.height);
    var imgdB = ctx.getImageData(0, 0, canvas.width, canvas.height);

    var pixR = imgdR.data;
    var pixG = imgdG.data;
    var pixB = imgdB.data;
    var pix  = imgd.data;

    // iteramos de 4 en 4: [R, G, B, A] por cada píxel
    for (var i = 0, n = pixR.length; i < n; i += 4) {
        pixR[i + 1] = 0; // anulamos G en la copia R
        pixR[i + 2] = 0; // anulamos B en la copia R
        pixG[i]     = 0; // anulamos R en la copia G
        pixG[i + 2] = 0; // anulamos B en la copia G
        pixB[i]     = 0; // anulamos R en la copia B
        pixB[i + 1] = 0; // anulamos G en la copia B
    }

    ctx.putImageData(imgdR, 0, 0);
    imgR.src = canvas.toDataURL();

    ctx.putImageData(imgdG, 0, 0);
    imgG.src = canvas.toDataURL();

    ctx.putImageData(imgdB, 0, 0);
    imgB.src = canvas.toDataURL();

    // restauramos la imagen original en el Canvas
    ctx.putImageData(imgd, 0, 0);
}

Analizando la función getRGB() en detalle

Obtenemos cuatro copias del ImageData: una completa (para restaurar el original al finalizar) y tres para trabajar canal por canal:

var imgd  = ctx.getImageData(0, 0, canvas.width, canvas.height);
var imgdR = ctx.getImageData(0, 0, canvas.width, canvas.height);
var imgdG = ctx.getImageData(0, 0, canvas.width, canvas.height);
var imgdB = ctx.getImageData(0, 0, canvas.width, canvas.height);

Accedemos a los arrays de píxeles de cada copia:

var pixR = imgdR.data;
var pixG = imgdG.data;
var pixB = imgdB.data;
var pix  = imgd.data;

El bucle es el corazón del ejercicio: en cada grupo de cuatro posiciones mantenemos solo el canal de interés y ponemos los demás a cero:

for (var i = 0, n = pixR.length; i < n; i += 4) {
    pixR[i + 1] = 0; // G → 0 en la copia R
    pixR[i + 2] = 0; // B → 0 en la copia R
    pixG[i]     = 0; // R → 0 en la copia G
    pixG[i + 2] = 0; // B → 0 en la copia G
    pixB[i]     = 0; // R → 0 en la copia B
    pixB[i + 1] = 0; // G → 0 en la copia B
}

Por último, guardamos cada canal en su imagen correspondiente usando putImageData() y toDataURL():

ctx.putImageData(imgdR, 0, 0);
imgR.src = canvas.toDataURL();

ctx.putImageData(imgdG, 0, 0);
imgG.src = canvas.toDataURL();

ctx.putImageData(imgdB, 0, 0);
imgB.src = canvas.toDataURL();

ctx.putImageData(imgd, 0, 0); // restauramos el original

Resultado Final

Ver demo Descargar Fuente

Como podemos apreciar, con HTML5 y la API Canvas podemos hacer prácticamente cualquier cosa: juegos, editores de texto, procesadores de imágenes... la imaginación es el límite.

¿Cómo obtener imágenes en blanco y negro o en escala de grises con HTML5?

¿Cómo obtener imágenes en blanco y negro o en escala de grises con solo HTML5?

HTML5 nos abre la puerta a innumerables posibilidades para el procesamiento digital de imágenes. En esta entrega veremos cómo convertir una imagen a escala de grises (o blanco y negro) empleando la API de Canvas.

Definiendo el HTML para el procesamiento en escala de grises

La etiqueta <canvas> nos permite acceder a toda la API del Canvas y, en concreto para este tutorial, nos da acceso a la imagen a nivel de píxeles para su procesamiento:

<canvas id="canvas">
    <p>Tu navegador no soporta Canvas.</p>
</canvas>

También necesitamos una etiqueta <img> donde mostraremos la imagen en escala de grises una vez procesada:

<img id="resul"/>

El JavaScript para referenciar el Canvas

Definimos las variables globales necesarias:

var canvas   = document.getElementById('canvas');
var ctx      = canvas.getContext('2d');
var imgResul = document.getElementById('resul');
var srcImg   = "image.png";

Creamos un objeto Image, le asignamos la imagen fuente y lo pintamos en el Canvas una vez cargado:

img = new Image();
img.src = srcImg;
img.onload = function() {
    canvas.width  = img.width;
    canvas.height = img.height;
    ctx.drawImage(this, 0, 0);
    getGrayScale();
};

El corazón del ejercicio: obtenemos el ImageData y calculamos el promedio de los tres canales RGB de cada píxel para obtener el tono de gris correspondiente:

var imgData = ctx.getImageData(0, 0, canvas.width, canvas.height).data;

En el bucle, calculamos el promedio de los canales R, G y B:

gris = parseInt((imgData[i] + imgData[i + 1] + imgData[i + 2]) / 3);

Y sustituimos los tres canales por ese promedio:

imgData[i]     = gris; // R
imgData[i + 1] = gris; // G
imgData[i + 2] = gris; // B

Obtendríamos un resultado similar igualando los canales entre sí; por ejemplo:

imgData[i + 1] = imgData[i];
imgData[i + 2] = imgData[i];

Sin embargo, el método del promedio produce un resultado más equilibrado y natural.

Resultado final para la escala de grises

Imágenes en blanco y negro o en escala de grises solo con CSS

También es posible convertir imágenes a escala de grises usando un simple filtro CSS, como vimos en nuestra entrada sobre los filtros en CSS3:

img {
   filter: grayscale(100%);
}

La ventaja de esta solución es su simplicidad; la desventaja es que es solo visual: no genera un nuevo archivo de imagen ni permite operar a nivel de píxel como sí lo hace la API Canvas.

¿Cómo cambiar el contraste de una imagen con HTML5?

¿Cómo cambiar el contraste de una imagen con HTML5?

En este artículo veremos cómo aumentar o reducir el contraste de una imagen con HTML5 y la API Canvas; también hablaremos brevemente de cómo ajustar el brillo, que sigue una lógica muy similar.

Tecnologías y conceptos que utilizaremos:

  • Canvas: como lienzo para dibujar y manipular la imagen.
  • getImageData(): permite obtener los datos de la imagen dibujada en el Canvas (ImageData):
    • ImageData.width: anchura en píxeles del ImageData.
    • ImageData.height: altura en píxeles del ImageData.
    • ImageData.resolution: densidad de píxeles de la imagen.
    • ImageData.data: array unidimensional con los valores de los píxeles en formato RGBA (enteros entre 0 y 255). Cada grupo de cuatro posiciones consecutivas representa un píxel completo.
    • img.onload: evento que se dispara cuando la imagen ha sido cargada completamente.

La fórmula del contraste

El contraste puede definirse como la variación de intensidades entre las zonas oscuras y claras de una imagen, lo que permite mejorar el enfoque y la claridad visual. La fórmula aplicada a nivel de píxel es la siguiente:

valorNuevo = (valorAnterior - 128) * tan(ángulo) + 128

En donde:

  • valorAnterior: valor original del píxel (entre 0 y 255).
  • ángulo: calculado como Math.tan(val * Math.PI / 180.0), donde:
    • val: valor del campo range controlado por el usuario (entre -90 y 90 grados).

Definiendo el HTML

Necesitamos un <canvas> como lienzo:

<canvas id="canvas">
    <p>Tu navegador no soporta Canvas.</p>
</canvas>

Y un campo de tipo range para que el usuario controle el contraste de forma interactiva:

<input type="range" id="contrast" min="-90" max="90" step="5" value="0">

Definiendo el JavaScript

Variables globales:

var canvas   = document.getElementById('canvas');      // canvas
var ctx      = canvas.getContext('2d');                 // contexto
var contrast = document.getElementById("contrast");    // input range
var srcImg   = "image.png";                            // imagen fuente

Creamos el objeto Image, lo asignamos y lo pintamos en el Canvas al cargarse:

img = new Image();
img.src = srcImg;
img.onload = function() {
    canvas.width  = img.width;
    canvas.height = img.height;
    ctx.drawImage(this, 0, 0);
};

La función que aplica el contraste:

function AddContrast(val) {
    var contrast = Math.tan(val * Math.PI / 180.0);
    canvas.width  = img.width;
    canvas.height = img.height;
    ctx.drawImage(img, 0, 0);

    var imgd = ctx.getImageData(0, 0, canvas.width, canvas.height);
    var pix  = imgd.data;

    for (var i = 0, n = pix.length; i < n; i += 4) {
        pix[i]     = rangeColor(128 + (pix[i]     - 128) * contrast); // R
        pix[i + 1] = rangeColor(128 + (pix[i + 1] - 128) * contrast); // G
        pix[i + 2] = rangeColor(128 + (pix[i + 2] - 128) * contrast); // B
    }

    ctx.putImageData(imgd, 0, 0);
}

Para garantizar que el valor resultante de cada canal no se salga del rango válido (0–255), empleamos la función auxiliar rangeColor():

function rangeColor(pix) {
    if (pix < 0)   pix = 0;
    if (pix > 255) pix = 255;
    return pix;
}

Ajustando el brillo de una imagen

El ajuste de brillo es aún más sencillo: consiste en sumar una constante K (en el rango 0–255) a cada canal. Basta con reemplazar el bucle de contraste por este:

for (var i = 0, n = pix.length; i < n; i += 4) {
    pix[i]     = rangeColor(pix[i]     + K); // R
    pix[i + 1] = rangeColor(pix[i + 1] + K); // G
    pix[i + 2] = rangeColor(pix[i + 2] + K); // B
}

La constante K puede obtenerse fácilmente desde un campo de tipo range:

<input type="range" id="brightness" min="0" max="255" step="1" value="0">

Resultado Final

Ver demo Descargar Fuente

Experimentos y más ejemplos con Canvas

Te dejamos un listado de experimentos y ejemplos para seguir explorando todo lo que puedes hacer con la API Canvas una vez que termines esta guía:

Aprende qué es el Canvas en HTML5 y cómo usar su API con JavaScript. Dibuja primitivas, recorta imágenes, manipula canales RGB y crea animaciones interactivas.


Únete a la comunidad de desarrolladores que han decidido dejar de picar código y empezar a construir productos reales. Recibe mis mejores trucos de arquitectura cada semana:

Acepto recibir anuncios de interes sobre este Blog.