Javascript, Programming

Tirando fotos com JS

Hoje em dia todo computador, notebook ou smartphone tem uma (ou mais) câmera(s).

Antigamente, a única maneira de aproveitar essas câmeras em uma aplicação Web era usando Flash.

Mas, hoje em dia, podemos tirar fotos com JS. Para isso, vamos utilizar o WebRTC.

WebRTC é um padrão Web que define protocolos e APIs em JS para enviar vídeo, áudio e dados entre navegadores de maneira peer-to-peer e em tempo real. Está disponível no Chrome para Desktop e Android, Firefox, Opera e Edge.

Pra começar, um <video> e um <canvas>

Precisamos de uma estrutura mínima no nosso HTML.

A tag <video> com a propriedade autoplay, ficará responsável por mostrar para o usuário as imagens obtidas da câmera.

Para guardar a imagem da foto, vamos usar um <canvas>, que começara invisível.

Finalmente, precisaremos de um <button> para que o usuário informe o momento em que deve ser tirada a foto a partir do vídeo.

Os três elementos mencionados ficarão dentro de uma <div>.

<div class="camera">
  <video id="video" class="foto" autoplay>Vídeo não disponível.</video>
  <canvas id="canvas" class="foto" style="display: none;"></canvas>
  <button id="tira-foto">Tirar foto</button>
</div>

Um pouquinho de css

Para posicionar os elementos e definir uma borda, teremos um pequeno trecho de css:

.camera {
  width: 340px;
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 0.5em;
}
.foto {
  width: 320px;
  height: 240px;
  border: 1px solid black;
  margin: 1em;
}

Mude o CSS à vontade, para deixar mais estiloso!

Mostrando o vídeo

No nosso código JS, obteremos o elemento cujo id é video:

var video = document.querySelector('#video');

Para obter as imagens da câmera, precisamos usar o método getUserMedia do navigator.

Uma coisa chata é que a API ainda não está consolidada. Por isso, o getUserMedia tem prefixos diferentes para cada navegador:

  • navigator.webkitGetUserMedia – para o Chrome e Opera
  • navigator.mozGetUserMedia – para o Firefox
  • navigator.msGetUserMedia – para o Edge

Testaremos o getUserMedia correto, armazendo-o em uma variável e ajustando o this através de um bind com o objeto navigator:

var getUserMedia = (navigator.webkitGetUserMedia ||
                       navigator.mozGetUserMedia ||
                          navigator.msGetUserMedia).bind(navigator);

Ao chamar o getUserMedia, devemos passar 3 parâmetros:

  • um objeto de configuração que diz se deve ser obtido vídeo ou áudio da câmera, entre outros detalhes
  • uma função chamada quando houve sucesso ao obter mídia da câmera. Nessa função, obtemos um MediaStream, que devemos passar para a propriedade srcObject do nosso elemento video. Guardaremos o MediaStream em uma variável, para uso posterior.
  • uma função chamada em caso de erro
var configuracaoMedia = {video: true, audio: false};

var mediaStream;
function iniciaVideo (stream) {
  video.srcObject = stream;
  mediaStream = stream;
}

function trataErroMedia (erro) {
  console.error('Erro: ' + erro);
}

getUserMedia(configuracaoMedia, iniciaVideo, trataErroMedia);

O resultado até aqui pode ser encontrado em:
https://jsfiddle.net/alexandreaquiles/xqhLL2wm/1/

Obtendo uma foto

Devemos obter o canvas e o botão a partir de seus ids:

var canvas = document.querySelector('#canvas');
var botaoTiraFoto = document.querySelector('#tira-foto');

Precisamos registrar um tratador do evento de clique no botão.

botaoTiraFoto.addEventListener('click', function (e) {
  //tratamento aqui...
});

Dentro da função de tratamento do clique, devemos obter o contexto 2D a partir do canvas e utilizá-lo para desenhar uma imagem a partir do vídeo. Nesse momento, a foto é tirada.

Podemos obter os dados através do método toDataURL do canvas.

botaoTiraFoto.addEventListener('click', function (e) {
  canvas.getContext('2d').drawImage(video, 0, 0, 320, 240);
  var dados = canvas.toDataURL('image/png');
  //fazer algo com os dados...
});

O método toDataURL retorna uma Data URI, uma maneira de representar imagens e outros tipos de dados como um texto. Por exemplo, um .png com um pontinho preto pode ser representado como: data:image/png;base64,R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs=

Podemos enviar essa Data URI para o servidor com uma chamada AJAX.

Do lado do servidor, você pode gravar a Data URI no BD para uso posterior como src de um <img> ou ainda transformá-lo em um arquivo.

Em PHP, usaríamos str_replace, base64_decode e file_put_contents para salvar a imagem em um arquivo no servidor.

Você pode testar o código até essa parte em:
https://jsfiddle.net/alexandreaquiles/xqhLL2wm/1/

Mostrando a foto e escondendo o vídeo

Quando o usuário clicar o botão, é interessante mostrarmos o <canvas> que tem a imagem da foto tirada e, em seguida, escondermos o vídeo da câmera.

botaoTiraFoto.addEventListener('click', function (e) {
  //código omitido...
  video.style.display = 'none';
  canvas.style.display = '';
});

Ao exibirmos a imagem, percebemos que há uma distorção. Para resolver, é importante fixar a altura e largura do <canvas>:

canvas.width = 320;
canvas.height = 240;

A câmera continuará filmando, mas a imagem ficará parada, sendo alterada só quando o usuário clicar no botão.

Você pode acompanhar o resultado em:
https://jsfiddle.net/alexandreaquiles/xqhLL2wm/2/

Parando a câmera ao obter foto

Para suspender a filmagem logo depois do clique no botão, podemos usar o seguinte código:

mediaStream.getVideoTracks().forEach(function (media) {
  media.stop();
});

É interessante alternar entre filmagem com a câmera e a exibição da foto tirada. Para isso, alternamos entre exibição do <video> e do <canvas>. Além disso, alternamos entre parar e voltar com o funcionamento da câmera.

botaoTiraFoto.addEventListener('click', function (e) {
  if (mediaStream) {
    //código omitido...
    mediaStream.getVideoTracks().forEach(function (media) {
      media.stop();
    });
    video.style.display = 'none';
    canvas.style.display = '';
    mediaStream = null;
  } else {
    getUserMedia(configuracaoMedia, iniciaVideo, trataErroMedia);
    video.style.display = '';
    canvas.style.display = 'none';
  }

O código até aqui está em:
https://jsfiddle.net/alexandreaquiles/xqhLL2wm/3/

Para funcionar no Chrome do Android

Com o código anterior, ocorre um bug quando acessado pelo Chrome do Android: é mostrada uma imagem preta no vídeo.

Para corrigí-lo, é necessário fixar as propriedades opcionais maxWidth e minWidth na configuração de getUserMedia:

var configuracaoMedia = {
  video: {
    optional: [
      { maxWidth: 320},
      { maxHeight: 240}
    ]
  }, 
  audio: false
};

O código final pode ser encontrado em:
https://jsfiddle.net/alexandreaquiles/xqhLL2wm/4/

Consideração importante: tem que ser HTTPS
Para que o código acima funcione no Chrome e Opera quando publicado em um servidor real, não em sua máquina local, é preciso que seja usado HTTPS.
Para o Firefox não há essa restrição.

Anúncios

3 comentários sobre “Tirando fotos com JS

  1. Bom dia Alexandre, minha câmera está finalizando sozinha e não exibe mensagem, diz no console que está com erro na variável stream, consegue me ajudar?

Deixe um comentário

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair /  Alterar )

Foto do Google

Você está comentando utilizando sua conta Google. Sair /  Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair /  Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair /  Alterar )

Conectando a %s

Este site utiliza o Akismet para reduzir spam. Saiba como seus dados em comentários são processados.