Tutorial de Canvas – Parte 3 – Usando imagens

Este é o terceiro artigo de uma série que irá apresentar os recursos disponibilizados pela nova tag <canvas> do HTML5. Nesse artigo, veremos como usar manipular imagens com o Canvas. Você pode acessar uma lista de todos os artigos da série nesse link.

Índice do conteúdo

  1. Obtendo imagens para serem desenhadas
    1. Usando imagens da mesma página
    2. Usando imagens de outros domínios
    3. Usando outros elementos canvas
    4. Criando uma imagem do zero
    5. Embutindo uma imagem através de data:URL
    6. Usando quadros de um vídeo
  2. Desenhando imagens
    1. Exemplo: Um gráfico de linha simples
  3. Escalonamento
    1. Exemplo: Imagem lado a lado
  4. Fatiamento
    1. Exemplo: Enquadrando uma imagem
  5. Exemplo de uma galeria de imagens
  6. Controlando o comportamento do escalonamento da imagem

Um dos recursos mais excitantes do <canvas> é a habilidade de usar imagens. Estas podem ser usadas para composição dinâmica de uma foto ou como fundos de gráficos, objetos de jogos, e assim em diante. Imagens externas podem ser usadas em qualquer formato suportado pelo navegador, como PNG, GIF, ou JPEG. Você pode até mesmo usar a imagem produzida por outros elementos canvas da mesma página como fonte!

A importação de imagens para o canvas é basicamente um processo em dois passos:

  1. Obter uma referência para um objeto HTMLImageElement ou para algum outro elemento canvas que será usado com fonte. Não é possível usar imagens apenas pelo fornecimento de uma URL ou caminho para ela.
  2. Desenhar a imagem no canvas através da função drawImage().

Vamos dar uma olhada em como isso é feito.

Obtendo imagens para serem desenhadas

A API do canvas é capaz de usar qualquer um dos seguintes tipos de dados como fonte de uma imagem:

HTMLImageElement
São imagens criadas usando o construtor Image() , de forma idêntica a qualquer elementos <img>.
HTMLVideoElement
Ao usar o elemento <video> do HTML com fonte de sua imagem, será capturado o quadro atual de um vídeo, que será usado como imagem.
HTMLCanvasElement
Você pode usar outro elemento <canvas> como fonte da imagem.
ImageBitmap
Este é um bitmap de alta performance que pode ser renderizado com baixa latência; ele pode ser criado tanto de qualquer uma das fontes acima quanto de muitas outras.

Essas fontes são referenciadas coletivamente pelo tipo CanvasImageSource.

Existem diversas maneiras para obter imagens para uso em um canvas.

Usando imagens da mesma página

Podemos obter uma referência para imagens na mesma página onde está o canvas através de um das opções a seguir:

Usando imagens de outro domínios

Usando o atributo crossOrigin de um HTMLImageElement, você pode requisitar permissão para carregar uma imagem de um outro domínio para uso em sua chamada à drawImage(). Se o domínio hospedeiro permitir acesso entre domínio à imagem, ela poderá ser usada em seu canvas sem contamina-lo; caso contrário, usar a imagem irá contaminar o canvas.

O que é um canvas “contaminado”?

Apesar de você poder usar imagens sem aprovação de acesso em seu canvas, fazendo isso você contaminacanvas. Uma vez que o canvas seja contaminado, você não será capaz de puxar dados dele. Por exemplo, não poderá usar os métodos toBlob()toDataURL(), ougetImageData(); fazer isso irá disparar um erro de segurança.

Isso protege os usuários de ter dados privativos expostos pelo uso de imagens para puxar informações de sites remotos sem permissão.

Usando outros elementos do canvas

De forma análoga ao uso de imagens normais, podemos acessar outros elementos canvas apenas pelo uso dos métodos document.getElementsByTagName() ou document.getElementById(). Certifique-se de ter desenhado algo no canvas antes de usa-lo no destino.

Um dos usos mais práticos desse método seria usar um segundo elemento canvas como miniatura de outros canvas maiores.

Criando uma imagem do zero

Uma outra opção é criar um novo objeto HTMLImageElement em nosso script. Para fazer isso, você pode usar o conveniente construtor Image():

var img = new Image();   // Create new img element
img.src = 'myImage.png'; // Set source path

Quando o script é executado, a imagem começa a ser carregada.

Se você tentar chamar drawImage() antes da imagem ter terminado seu carregamento, nada irá acontecer (ou, em navegadores antigos, um erro pode ser disparado). Dessa forma, certifique-se de usar o evento load de forma que você não tente usar esse método antes do término do carregamento da imagem:

var img = new Image();   // Create new img element
img.addEventListener("load", function() {
  // execute drawImage statements here
}, false);
img.src = 'myImage.png'; // Set source path

Se você estiver usando apenas uma imagem externa, essa é uma boa abordagem, mas uma vez que tenha que rastrear mais de uma, precisaremos recorrer a um recurso mais esperto. Isso vai além do escopo desse artigo, mas você pode dar uma olhada em JavaScript Image Preloader para uma solução mais completa.

Embutindo uma imagem através de data:URL

Uma outra forma possível para incluir imagens é através de data: url. Data URLs permitem que você defina complemente uma imagem como uma String Base64 diretamente em seu código.

var img_src = 'data:image/gif;base64,R0lGODlhCwALAIAAAAAA3pn/ZiH5BAEAAAEALAAAAAALAAsAAAIUhA+hkcuO4lmNVindo7qyrIXiGBYAOw==';

Uma vantagem de Data URLs é que a imagem resultante estará disponível imediatamente sem necessidade de um novo acesso ao servidor. Uma outra vantagem em potencial é que é possível encapsular todos os seus códigos CSS, JavaScript, HTML, e imagens, tornando a página mais portátil.

Algumas desvantagens desse método são que sua imagem não será salva em cache, e para imagens grandes a URL codificada será bem longa.

Usando quadros de um vídeo

Você pode usar quadros de um vídeo que está sendo apresentado por um elemento <video> (mesmo que o vídeo não esteja visível). Por exemplo, se você possuir um elemento <video> com o ID “myvideo“, pode fazer isso:

function getMyVideo() {
  var canvas = document.getElementById('canvas');
  if (canvas.getContext) {
    var ctx = canvas.getContext('2d');

    return document.getElementById('myvideo');
  }
}

Isso irá retornar o objeto HTMLVideoElement para o vídeo, que, como mencionado anteriormente, pode ser usado como um CanvasImageSource.

Desenhando imagens

Uma vez que você tenha uma referência para sua fonte de imagem, podemos usar o  método drawImage() para renderiza-la no canvas. Como veremos mais tarde o método drawImage() é sobrecarregado e tem diversas variações. Em sua forma mais básica, ele se parece com o seguinte:

drawImage(image, x, y)
Desenha o CanvasImageSource especificado pelo parâmetro image nas coordenadas (x, y).

Exemplo: um gráfico de linha simples

No exemplo a seguir, usaremos uma imagem externa como backdrop para um pequeno gráfico de linha. O uso de backdrops pode tornar o seu script consideravelmente menor porque evita a necessidade de código para gerar o fundo. Nesse exemplo, usamos apenas uma imagem, assim foi usado o manipulador de imagem load do objeto image para executar as sentenças de desenho. O método drawImage() posiciona os backdrops na coordenada (0, 0), que é o canto superior esquerdo do canvas.

function draw() {
  var ctx = document.getElementById('canvas').getContext('2d');
  var img = new Image();
  img.onload = function(){
    ctx.drawImage(img,0,0);
    ctx.beginPath();
    ctx.moveTo(30,96);
    ctx.lineTo(70,66);
    ctx.lineTo(103,76);
    ctx.lineTo(170,15);
    ctx.stroke();
  };
  img.src = 'https://mdn.mozillademos.org/files/5395/backdrop.png';
}

O gráfico resultante deve se parecer com isso:

Screenshot Live sample

Escalonamento

A segunda variação do método drawImage() adiciona dois novos parâmetros e permite que posicionemos imagens escalonadas no canvas.

drawImage(image, x, y, width, height)
Essa variação acrescenta os parâmetros width e height, que indicam o tamanho que a imagem terá ao ser desenhada no canvas.

Exemplo: Imagem lado a lado

Nesse exemplo, usaremos uma imagem como papel de parede e repetiremos ela diversas vezes no canvas. Isso é feito simplesmente pelo posicionamento em loop da imagem em diversas posições. No código abaixo, o primeiro loop for itera as linhas. O segundo for itera as colunas. A imagem é escalonada para 1/3 de seu tamanho original, que é 50×38 pixels.

Nota: Imagens podem ficar borradas quando escalonada para um tamanho maior do que o original ou granuladas se escalonadas para tamanhos menores. Escalonamento provavelmente não é a melhor solução se você tiver algum texto que precisa ficar legível.

function draw() {
  var ctx = document.getElementById('canvas').getContext('2d');
  var img = new Image();
  img.onload = function(){
    for (var i=0;i<4;i++){
      for (var j=0;j<3;j++){
        ctx.drawImage(img,j*50,i*38,50,38);
      }
    }
  };
  img.src = 'https://mdn.mozillademos.org/files/5397/rhino.jpg';
}

O canvas resultante ficará assim:

Screenshot Live sample

Fatiamento

A terceira e última variação do método drawImage() possui oito parâmetros. Essa variação recorta uma seção da imagem, escalona essa seção e a desenha no canvas.

drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight)
Dada uma image, essa função obtém a área da imagem especifica pelo retângulo cujo canto superior esquerdo é (sx, sy) e cuja largura e altura são sWidth e sHeight e desenha essa imagem no canvas, posicionando ela em (dx, dy) e escalonando-a para o tamanho especificado por dWidth e dHeight.

Para realmente entender o que esse método faz, pode ajudar dar uma olhada na imagem à direita. Os primeiros quatro parâmetros definem a localização e o tamanho da fatia da imagem fonte. Os últimos quatro definem o retângulo no qual será desenhado a imagem no canvas destino.

O fatiamento pode ser útil quando se quer fazer composições. Você pode ter todos os elementos em um único arquivo de imagem e usar esse método para compor um desenho completo. Por exemplo, se quiser criar um mapa pode ter uma imagem PNG que contenha todo o texto necessário em um único arquivo e dependendo de seus dados poderia alterar a escala de seu mapa de forma bem fácil. Uma outra vantagem é que você não precisa carregar cada imagem individualmente, o que pode melhorar a performance do carregamento.

Exemplo: Enquadrando uma imagem

Nesse exemplo, usaremos a mesma foto usada no exemplo anterior, mas recortaremos sua cabeça e colocaremos ela em um quadro. A imagem do quadro é um PNG de 24 bits que inclui uma sombra. Como um PNG de 24 bits inclui um canal alfa de 8 bits, ele pode ser posicionado em qualquer fundo sem ter que se preocupar com um cor fosca.

<html>
 <body onload="draw();">
   <canvas id="canvas" width="150" height="150"></canvas>
   <div style="display:none;">
     <img id="source" src="http://www.klebermota.eti.br/wp-content/rhino.jpg" width="300" height="227">
     <img id="frame" src="http://www.klebermota.eti.br/wp-content/Canvas_picture_frame.png" width="132" height="150">
   </div>
 </body>
</html>
function draw() {
  var canvas = document.getElementById('canvas');
  var ctx = canvas.getContext('2d');

  // Draw slice
  ctx.drawImage(document.getElementById('source'),
                33, 71, 104, 124, 21, 20, 87, 104);

  // Draw frame
  ctx.drawImage(document.getElementById('frame'),0,0);
}

Usamos uma abordagem diferente para carregar a imagem dessa vez. Ao invés de carrega-las pela criação de um novo objeto HTMLImageElement, incluímos ela com uma tag <img> diretamente em nosso HTML e recuperamos as imagens a partir dela. As imagens ficam ocultas na saída pela configuração da propriedade  display do CSS para none para essas imagens.

Screenshot Live sample

O script em si é bastante simples. Para cada <img> é associado um atributo ID, que torna fácil seleciona-las usando document.getElementById(). Em seguida, simplesmente usamos drawImage() para fatiar a imagem do rinoceronte e escalona-la no canvas, e em seguida desenhar a moldura sobre ela usando uma segunda chamada àdrawImage().

No exemplo final desse artigo, construiremos uma pequena galeria de imagens. Quando a página é carregada, um elemento  <canvas> é inserido para cada imagem e uma moldura é desenhada em torno delas.

Nesse caso, cada imagem possui uma largura e altura fixas, assim como a moldura desenhadas em torno delas. Você poderia aperfeiçoar o script de forma que ele usasse a largura e altura da imagem para fazer com que ela coubesse perfeitamente na moldura.

O código abaixo deve ser auto-explicativo. Percorremos em loop um conêiner document.images e adicionamos novos elementos canvas de acordo. Provavelmente a única coisa digna de nota, para aqueles que não estejam familiarizados com o DOM, é o uso do métodoNode.insertBefore. insertBefore() é um método do nó pai (uma célula da tabela) do elemento (a imagem) antes que nós inserimos nosso novo nó (o elemento canvas).

<html>
 <body onload="draw();">
     <table>
      <tr>
        <td><img src="https://mdn.mozillademos.org/files/5399/gallery_1.jpg"></td>
        <td><img src="https://mdn.mozillademos.org/files/5401/gallery_2.jpg"></td>
        <td><img src="https://mdn.mozillademos.org/files/5403/gallery_3.jpg"></td>
        <td><img src="https://mdn.mozillademos.org/files/5405/gallery_4.jpg"></td>
      </tr>
      <tr>
        <td><img src="https://mdn.mozillademos.org/files/5407/gallery_5.jpg"></td>
        <td><img src="https://mdn.mozillademos.org/files/5409/gallery_6.jpg"></td>
        <td><img src="https://mdn.mozillademos.org/files/5411/gallery_7.jpg"></td>
        <td><img src="https://mdn.mozillademos.org/files/5413/gallery_8.jpg"></td>
      </tr>
     </table>
     <img id="frame" src="https://mdn.mozillademos.org/files/242/Canvas_picture_frame.png" width="132" height="150">
 </body>
</html>

E a seguir algum código CSS para tornar as coisas melhores:

body {
  background: 0 -100px repeat-x url(https://mdn.mozillademos.org/files/5415/bg_gallery.png) #4F191A;
  margin: 10px;
}

img {
  display: none;
}

table {
  margin: 0 auto;
}

td {
  padding: 15px;
}

Agora segue o código JavaScript que põe tudo junto para desenhar as imagens emolduradas:

function draw() {

  // Loop through all images
  for (var i=0;i<document.images.length;i++){

    // Don't add a canvas for the frame image
    if (document.images[i].getAttribute('id')!='frame'){

      // Create canvas element
      canvas = document.createElement('canvas');
      canvas.setAttribute('width',132);
      canvas.setAttribute('height',150);

      // Insert before the image
      document.images[i].parentNode.insertBefore(canvas,document.images[i]);

      ctx = canvas.getContext('2d');

      // Draw image to canvas
      ctx.drawImage(document.images[i],15,20);

      // Add frame
      ctx.drawImage(document.getElementById('frame'),0,0);
    }
  }
}
Screenshot Live sample

Controlando o comportamento do escalonamento da imagem

Como mencionado anteriormente, escalonar imagens pode resultar em artefatos difusos ou quadriculados, devido ao processo de escalonamento. Você pode usar a propriedade imageSmoothingEnabled do contexto de desenho para controlar o uso dos algoritmos de suavização da imagem quando escalonar imagens em seu contexto. Por padrão, isso é true, o que significa que as imagens serão suavizadas quando escalonadas. Você pode desativar esse recurso dessa forma:

ctx.mozImageSmoothingEnabled = false;
  • Guest

    Olá kleber, excelente post, tenho uma dúvida: consigo gerar o data:URL, copio e através de um gerenciador de mysql gravo em um campo blob e consigo recuperar a imagem, no entanto se pegar mesmo dado com. jquery ex. var foto = $(‘#base64’).text(); obs o id base64 é um input textarea, e através do POST do ajax envio para o php gravar o mesmo campo blob, não. recupero a imagem aparece apenas o fundo sem imagem, seria o caso de imagem contaminada.

  • Iran Nunes

    Olá kleber, excelente post, tenho uma dúvida: consigo gerar o data:URL, copio e através de um gerenciador de mysql gravo em um campo blob e consigo recuperar a imagem, no entanto se pegar mesmo dado com. jquery ex. var foto = $(‘#base64’).text(); obs o id base64 é um input textarea, e através do POST do ajax envio para o php gravar o mesmo campo blob, não. recupero a imagem aparece apenas o fundo sem imagem, seria o caso de imagem contaminada.