Os navegadores modernos suportam a reprodução de vídeo através do elemento <video>
. A maioria dos navegadores também pode acessar a câmera por meio da API MediaDevices.getUserMedia(). Mas mesmo que essas duas coisas sejam combinadas, não podemos acessar e manipular diretamente esses pixels.
Felizmente, os navegadores possuem uma API Canvas que nos permite desenhar gráficos usando JavaScript. Na verdade, podemos desenhar imagens do próprio vídeo em <canvas>
, o que nos permite manipular e exibir esses pixels.
O que você aprender aqui sobre como manipular pixels fornecerá a base para trabalhar com imagens e vídeos de qualquer tipo ou fonte, não apenas com tela.
Adicionar imagem à telaAntes de começarmos o vídeo, vamos ver como adicionar uma imagem à tela.
<img src><div> <canvas id=Canvas class=video></canvas></div>
Criamos um elemento de imagem para representar a imagem a ser desenhada na tela. Alternativamente, podemos usar o objeto Image em JavaScript.
var canvas;var context;function init() { var image = document.getElementById('SourceImage'); canvas = document.getElementById('Canvas'); // Ou // var imagem = new Image(); // imagem.onload = function () { // drawImage(image); 'image.jpg';}function drawImage(image) { // Define a tela com a mesma largura e altura da imagem canvas.width = image.width = image.height; 0);}window.addEventListener('carregar', init);
O código acima desenha a imagem inteira na tela.
Confira a imagem Paint on canvas de Welling Guzman (@wellingguzman) no CodePen.
Agora podemos começar a brincar com esses pixels!
Atualizar dados de imagemOs dados da imagem na tela nos permitem manipular e alterar pixels.
O atributo data é um objeto ImageData que possui três propriedades - largura, altura e dados/todas representando algo baseado na imagem original. Todas essas propriedades são somente leitura. O que nos importa são os dados, um array unidimensional representado por um objeto Uint8ClampedArray contendo dados para cada pixel no formato RGBA.
Mesmo que uma propriedade de dados seja somente leitura, isso não significa que não possamos alterar seu valor. Isso significa que não podemos atribuir outro array a esta propriedade.
// Obtenha a imagem da tela datavar imageData = context.getImageData(0, 0, canvas.width, canvas.height);image.data = new Uint8ClampedArray(); // WRONGimage.data[1] = 0;
Você pode perguntar: qual valor um objeto Uint8ClampedArray representa? Aqui está a descrição do MDN:
A matriz do tipo Uint8ClampedArray representa uma matriz de inteiros não assinados de 8 bits que são limitados a 0-255 se você especificar um valor fora do intervalo [0,255], 0 ou 255 será definido se você especificar um número não inteiro; O número inteiro será definido. O conteúdo é inicializado com 0. Uma vez estabelecido, os elementos do array podem ser referenciados usando os métodos do objeto ou usando a sintaxe de indexação de array padrão (ou seja, usando a notação de colchetes)
Resumindo, este array armazena valores que variam de 0 a 255 em cada local, o que o torna uma solução perfeita para o formato RGBA já que cada parte é representada por um valor de 0 a 255.
Cor RGBAA cor pode ser representada no formato RGBA, que é uma combinação de vermelho, verde e azul. A representa o valor alfa da opacidade da cor.
Cada posição na matriz representa um valor de canal de cor (pixel).
Se você tiver uma imagem 2x2, então temos um array de 16 bits (2x2 pixels x 4 valores cada).
Imagem 2x2 reduzida
A matriz ficará assim:
// VERMELHO VERDE AZUL BRANCO[255, 0, 0, 255, 0, 255, 0, 255, 0, 0, 255, 255, 255, 255, 255, 255]Alterar dados de pixel
Uma das coisas mais rápidas que podemos fazer é definir todos os pixels para branco, alterando todos os valores RGBA para 255.
// Use um botão para acionar o effectvar button = document.getElementById('Button');button.addEventListener('click', onClick);function changeToWhite(data) { for (var i = 0; i < data.length; i++) { dados[i] = 255 }}function onClick() { var imageData = context.getImageData(0, 0, canvas.width, canvas.height); changeToWhite(imageData.data); // Atualizar a tela com os novos dados context.putImageData(imageData, 0, 0);}
Os dados serão passados como referência, o que significa que qualquer modificação que fizermos neles alterará o valor do parâmetro passado.
Inverter coresUm efeito bacana que não exige muitos cálculos é inverter as cores de uma imagem.
Os valores das cores podem ser invertidos usando o operador XOR (^) ou esta fórmula 255 - valor (o valor deve estar entre 0-255).
function invertColors(data) { for (var i = 0; i < data.length; i+= 4) { data[i] = data[i] ^ 255; // Inverter vermelho data[i+1] = data[i] +1] ^ 255; // Inverter verde data[i+2] = data[i+2] ^ 255; // Inverter azul }}function onClick() { var imageData = context.getImageData(0, 0, canvas.width, canvas.height); invertColors(imageData.data); // Atualizar a tela com os novos dados context.putImageData(imageData, 0, 0);}
Estamos incrementando o loop em 4 em vez de 1 como fizemos antes, para que possamos preencher 4 elementos na matriz de pixel a pixel, cada pixel.
O valor alfa não tem efeito na inversão das cores, então o ignoramos.
Brilho e contrasteO brilho de uma imagem pode ser ajustado usando a seguinte fórmula: newValue = currentValue + 255 * (brilho/100).
fator = (259 * (contraste + 255)) / (255 * (259 - contraste))cor = GetPixelColor(x, y)newRed = Truncar(fator * (Vermelho(cor) - 128) + 128)newVerde = Truncar( fator * (Verde(cor) - 128) + 128)novoAzul = Truncar(fator * (Azul(cor) - 128) + 128)
O cálculo principal é obter o fator de contraste que será aplicado a cada valor de cor. Truncamento é uma função que garante que o valor permaneça entre 0 e 255.
Vamos escrever essas funções em JavaScript:
function applyBrightness(dados, brilho) { for (var i = 0; i < data.length; i+= 4) { dados[i] += 255 * (brilho dados[i+1] += 255 * (brilho/100);dados[i+2] += 255 * (brilho/100);função truncateColor(valor) { se (valor < 0) { valor = 0 } else if (valor > 255) { valor = 255; valor de retorno;}function applyContrast(dados, contraste) { var factor = (259,0 * (contraste + 255,0)) / ( 255,0 * (259,0 - contraste)); for (var i = 0; i < data.length; i+= 4) { dados[i] = truncateColor(fator * (dados[i] - 128,0) + 128,0); dados[i+1] = truncateColor(fator * (dados[i+1] - 128,0) + 128,0) fator * (dados[i+2] - 128,0) + 128,0 }}
Neste caso você não precisa da função truncateColor porque Uint8ClampedArray trunca os valores, mas sim para traduzir o algoritmo que adicionamos nela.
Uma coisa a lembrar é que se você aplicar brilho ou contraste, os dados da imagem serão substituídos e não poderão retornar ao estado anterior. Se quisermos voltar ao estado original, os dados da imagem original devem ser armazenados separadamente para referência. Manter a variável image acessível a outras funções será útil porque você pode usar a imagem para redesenhar a tela e a imagem original.
var imagem = document.getElementById('SourceImage'); função redrawImage() { context.drawImage(image, 0, 0);}Usar vídeo
Para que funcione em vídeo, pegaremos nosso script de imagem inicial e código HTML e faremos algumas pequenas modificações.
HTMLAltere o elemento Image do elemento video substituindo a seguinte linha:
<img fonte>
...com isto:
<src de vídeo></vídeo>
JavaScript
Substitua esta linha:
var imagem = document.getElementById('SourceImage');
... adicione esta linha:
var vídeo = document.getElementById('SourceVideo');
Para começar a processar o vídeo, temos que esperar até que o vídeo esteja pronto para ser reproduzido.
video.addEventListener('canplay', function () { // Defina a tela com a mesma largura e altura do vídeo canvas.width = video.videoWidth; canvas.height = video.videoHeight; // Reproduza o vídeo video.play( ); // começa a desenhar os frames drawFrame(video);});
A reprodução do evento é reproduzida por pelo menos alguns quadros quando há dados suficientes para reproduzir a mídia.
Não podemos ver nenhum vídeo exibido na tela porque estamos exibindo apenas o primeiro quadro. Temos que executar drawFrame a cada n milissegundos para acompanhar a taxa de quadros do vídeo.
Dentro do drawFrame, chamamos drawFrame novamente a cada 10ms.
function drawFrame(vídeo) { context.drawImage(vídeo, 0, 0); setTimeout(function () { drawFrame(vídeo); }, 10);}
Após executar drawFrame, criamos um loop que executa drawFrame a cada 10ms – tempo suficiente para que o vídeo fique sincronizado dentro do canvas.
Adicione efeitos ao vídeoPodemos usar a mesma função que criamos anteriormente para inverter as cores:
function invertColors(data) { for (var i = 0; i < data.length; i+= 4) { data[i] = data[i] ^ 255; // Inverter vermelho data[i+1] = data[i] +1] ^ 255; // Inverter dados verdes[i+2] = dados[i+2] ^ 255;
e adicione isto à função drawFrame:
função drawFrame (vídeo) { context.drawImage (vídeo, 0, 0); var imageData = context.getImageData (0, 0, canvas.width, canvas.height); 0, 0); setTimeout(função() { drawFrame(vídeo); }, 10);}
Podemos adicionar um botão e alternar o efeito:
função drawFrame (vídeo) { context.drawImage (vídeo, 0, 0); if (applyEffect) { var imageData = context.getImageData (0, 0, canvas.width, canvas.height); .putImageData(imageData, 0, 0); } setTimeout(function () { drawFrame(vídeo); }, 10);}usar câmera
Manteremos o mesmo código que usamos para o vídeo, a única diferença é que usaremos MediaDevices.getUserMedia para alterar o stream de vídeo de um arquivo para um stream de câmera.
MediaDevices.getUserMedia é uma nova API que descontinua a API anterior MediaDevices.getUserMedia(). Os navegadores ainda suportam versões mais antigas, e alguns navegadores não suportam versões mais recentes, e temos que recorrer a polyfills para garantir que o navegador suporte uma delas.
Primeiro, remova o atributo src do elemento video:
<video><code></pre><pre><code>// Defina a fonte do vídeo para a câmera streamfunction initCamera(stream) { video.src = window.URL.createObjectURL(stream);}if (navigator .mediaDevices.getUserMedia) {navigator.mediaDevices.getUserMedia({vídeo: verdadeiro, áudio: falso}) .then(initCamera) .catch(console.error) );}
Demonstração ao vivo
EfeitoTudo o que cobrimos até agora é a base que precisamos para criar diferentes efeitos para vídeos ou imagens. Podemos usar muitos efeitos diferentes convertendo cada cor de forma independente.
Tons de cinzaA conversão de cores para tons de cinza pode ser feita de diferentes maneiras usando diferentes fórmulas/técnicas, para evitar me aprofundar muito no problema vou mostrar cinco fórmulas baseadas na ferramenta de dessaturação do GIMP e no Luma:
Cinza = 0,21R + 0,72G + 0,07B // LuminosityGray = (R + G + B) ÷ 3 // Brilho médioGray = 0,299R + 0,587G + 0,114B // rec601 standardGray = 0,2126R + 0,7152G + 0,0722B / /ITU-RBT.709 cinza padrão = 0,2627R + 0,6780G + 0,0593B // padrão ITU-R BT.2100
O que queremos encontrar usando essas fórmulas é o nível de brilho de cada cor de pixel. O valor varia de 0 (preto) a 255 (branco). Esses valores criarão um efeito de escala de cinza (preto e branco).
Isso significa que a cor mais clara estará mais próxima de 255 e a cor mais escura estará mais próxima de 0.
Demonstração ao vivo
dois tonsA diferença entre um efeito duotônico e um efeito de escala de cinza é que duas cores são usadas. Na escala de cinza você tem um gradiente do preto para o branco, enquanto no duotone você tem um gradiente de qualquer cor para qualquer outra cor (do azul ao rosa).
Usando o valor da intensidade da escala de cinza, podemos substituí-lo pelo valor do gradiente.
Precisamos criar um gradiente de ColorA para ColorB.
function createGradient(colorA, colorB) { // Valores do gradiente de colorA para colorB var gradiente = [] // o valor máximo da cor é 255 var maxValue = 255; object var from = getRGBColor(colorA var to = getRGBColor(colorB); // Cria 256 cores da Cor A à Cor B for (var i = 0; i); <= maxValue; i++) { // IntensityB irá de 0 a 255 // IntensityA irá de 255 a 0 // IntensityA diminuirá a intensidade enquanto intensidadeB aumentará // O que isso significa é que ColorA começará sólido e lentamente se transformará em ColorB // Se você olhar de outra forma a transparência da cor A aumentará e a transparência da cor B diminuirá var intensidadeB = i var intensidadeA = maxValue - intensidadeB; a fórmula abaixo combina as duas cores com base em sua intensidade // (IntensidadeA * CorA + IntensidadeB * CorB) / maxValue gradiente[i] = { r: (intensidadeA*from.r + intensidadeB*to.r) / maxValue, g: ( intensidadeA*de.g + intensidadeB*para.g) / maxValue, b: (intensidadeA*de.b + intensidadeB*para.b) / maxValue } } return; gradiente;}// Função auxiliar para converter valores hexadecimais de 6 dígitos em um objeto de cor RGBfunction getRGBColor(hex){ var colorValue if (hex[0] === '#') { hex = hex.substr(1); } colorValue = parseInt(hex, 16); return { r: colorValue >> 16, g: (colorValue >> 8) & 255, b: colorValue & 255 }}
Resumindo, criamos um conjunto de valores de cores começando pela cor A, diminuindo a intensidade, indo até a cor B e aumentando a intensidade.
De #0096ff a #ff00f0
var gradientes = [ {r: 32, g: 144, b: 254}, {r: 41, g: 125, b: 253}, {r: 65, g: 112, b: 251}, {r: 91 , g: 96, b: 250}, {r: 118, g: 81, b: 248}, {r: 145, g: 65, b: 246}, {r: 172, g: 49, b: 245}, {r: 197, g: 34, b: 244}, {r: 220, g: 21 , b: 242}, {r: 241, g: 22, b: 242},];
Representação de transições de cores em escala
Acima está um exemplo de gradiente de 10 valores de cores de #0096ff a #ff00f0.
Representação em tons de cinza de transições de cores
Agora que temos uma representação da imagem em tons de cinza, podemos usá-la para mapeá-la para um valor de gradiente duotônico.
O gradiente duotônico possui 256 cores, enquanto a escala de cinza também possui 256 cores, variando de preto (0) a branco (255). Isso significa que um valor de cor em escala de cinza será mapeado para um índice de elemento de gradiente.
var gradienteColors = createGradient('#0096ff', '#ff00f0');var imageData = context.getImageData(0, 0, canvas.width, canvas.height);applyGradient(imageData.data);for (var i = 0; i < data.length; i += 4) { // Obtenha o valor da cor de cada canal var redValue = data[i]; data[i+1]; var blueValue = data[i+2]; // Mapeando os valores de cor para o índice de gradiente // Substituindo o valor da cor da escala de cinza por uma cor para o gradiente duotônico data[i] = gradienteColors[ redValue] .r; dados[i+1] = gradienteColors[greenValue].g; dados[i+2] = gradienteColors[blueValue].b;
Demonstração ao vivo
para concluirEste tópico poderia ser mais aprofundado ou explicar mais implicações. A lição de casa para você é encontrar diferentes algoritmos que possam ser aplicados a esses exemplos básicos.
Compreender a estrutura dos pixels na tela permitirá que você crie um número ilimitado de efeitos, como sépia, mistura de cores, efeitos de tela verde, flashes/falhas de imagem, etc.
Você pode até criar efeitos instantaneamente, sem usar imagens ou vídeos
O texto acima é todo o conteúdo deste artigo. Espero que seja útil para o estudo de todos. Também espero que todos apoiem a Rede VeVb Wulin.