Los navegadores modernos admiten la reproducción de vídeo a través del elemento <video>
. La mayoría de los navegadores también pueden acceder a la cámara a través de la API MediaDevices.getUserMedia(). Pero incluso si estas dos cosas se combinan, no podemos acceder ni manipular directamente estos píxeles.
Afortunadamente, los navegadores tienen una API Canvas que nos permite dibujar gráficos usando JavaScript. De hecho, podemos dibujar imágenes en <canvas>
desde el propio vídeo, lo que nos permite manipular y mostrar esos píxeles.
Lo que aprenderá aquí sobre cómo manipular píxeles le proporcionará la base para trabajar con imágenes y vídeos de cualquier tipo o fuente, no sólo lienzos.
Agregar imagen al lienzoAntes de comenzar el video, veamos cómo agregar una imagen al lienzo.
<img src><div> <canvas id=Canvas class=video></canvas></div>
Creamos un elemento de imagen para representar la imagen que se dibujará en el lienzo. Alternativamente, podemos usar el objeto Imagen en JavaScript.
var lienzo;var contexto;función init() { var imagen = document.getElementById('SourceImage'); lienzo = document.getElementById('Canvas'); // O // var imagen = nueva Imagen(); // imagen.onload = función () { // dibujarImagen(imagen // } // imagen.src =); 'image.jpg';}function drawImage(image) { // Establece el lienzo con el mismo ancho y alto que la imagen canvas.width = image.width; canvas.height = image.height; 0);}window.addEventListener('cargar', init);
El código anterior dibuja la imagen completa en el lienzo.
Consulte la imagen de pintura sobre lienzo de Welling Guzman (@wellingguzman) en CodePen.
¡Ahora podemos empezar a jugar con esos píxeles!
Actualizar datos de imagenLos datos de la imagen en el lienzo nos permiten manipular y cambiar píxeles.
El atributo de datos es un objeto ImageData que tiene tres propiedades: ancho, alto y datos, todas las cuales representan algo basado en la imagen original. Todas estas propiedades son de solo lectura. Lo que nos importa son los datos, una matriz unidimensional representada por un objeto Uint8ClampedArray que contiene datos para cada píxel en formato RGBA.
Aunque una propiedad de datos sea de solo lectura, eso no significa que no podamos cambiar su valor. Esto significa que no podemos asignar otra matriz a esta propiedad.
// Obtener la imagen del lienzo datavar imageData = context.getImageData(0, 0, canvas.width, canvas.height);image.data = new Uint8ClampedArray(); // WRONGimage.data[1] = 0;
Quizás te preguntes, ¿qué valor representa un objeto Uint8ClampedArray? Aquí está la descripción de MDN:
La matriz de tipo Uint8ClampedArray representa una matriz de enteros sin signo de 8 bits que están limitados a 0-255, si especifica un valor fuera del rango [0,255], se establecerá 0 o 255 si especifica un número no entero, el más cercano; Se establecerá un número entero. Los contenidos se inicializan a 0. Una vez establecidos, se puede hacer referencia a los elementos de la matriz utilizando los métodos del objeto o utilizando la sintaxis de indexación de matrices estándar (es decir, usando notación entre corchetes).
En resumen, este arreglo almacena valores que van de 0 a 255 en cada ubicación, lo que lo convierte en una solución perfecta para el formato RGBA ya que cada parte está representada por un valor de 0 a 255.
color RGBAEl color se puede representar en formato RGBA, que es una combinación de rojo, verde y azul. A representa el valor alfa de la opacidad del color.
Cada posición en la matriz representa un valor de canal de color (píxel).
Si tiene una imagen de 2x2, entonces tenemos una matriz de 16 bits (2x2 píxeles x 4 valores cada uno).
Imagen 2x2 reducida
La matriz se verá así:
// ROJO VERDE AZUL BLANCO[255, 0, 0, 255, 0, 255, 0, 255, 0, 0, 255, 255, 255, 255, 255, 255]Cambiar datos de píxeles
Una de las cosas más rápidas que podemos hacer es configurar todos los píxeles en blanco cambiando todos los valores RGBA a 255.
// Usa un botón para activar el efectovar button = document.getElementById('Button');button.addEventListener('click', onClick);function changeToWhite(data) { for (var i = 0; i < data.length; i++) { datos[i] = 255; }}función onClick() { var imageData = context.getImageData(0, 0, canvas.width, canvas.height); changeToWhite(imageData.data); // Actualiza el lienzo con los nuevos datos context.putImageData(imageData, 0, 0);}
Los datos se pasarán como referencia, lo que significa que cualquier modificación que le hagamos cambiará el valor del parámetro pasado.
invertir coloresUn bonito efecto que no requiere demasiados cálculos es invertir los colores de una imagen.
Los valores de color se pueden invertir usando el operador XOR (^) o esta fórmula 255 - valor (el valor debe estar entre 0-255).
función invertColors(datos) { for (var i = 0; i < data.length; i+= 4) { datos[i] = datos[i] ^ 255 // Invertir datos rojos[i+1] = datos[i] +1] ^ 255; // Invertir datos verdes[i+2] = datos[i+2] ^ 255; // Invertir azul }}función onClick() { var imageData = context.getImageData(0, 0, canvas.width, canvas.height); invertColors(imageData.data // Actualiza el lienzo con los nuevos datos context.putImageData(imageData, 0, 0);}
Estamos incrementando el bucle en 4 en lugar de 1 como hicimos antes, para que podamos llenar 4 elementos en la matriz de píxel a píxel, cada píxel.
El valor alfa no tiene ningún efecto en la inversión de los colores, por lo que lo omitimos.
Brillo y contrasteEl brillo de una imagen se puede ajustar usando la siguiente fórmula: nuevoValor = valoractual + 255 * (brillo / 100).
factor = (259 * (contraste + 255)) / (255 * (259 - contraste))color = GetPixelColor(x, y)nuevoRojo = Truncar(factor * (Rojo(color) - 128) + 128)nuevoVerde = Truncar( factor * (Verde(color) - 128) + 128)nuevoAzul = Truncar(factor * (Azul(color) - 128) + 128)
El cálculo principal es obtener el factor de contraste que se aplicará a cada valor de color. El truncamiento es una función que garantiza que el valor permanezca entre 0 y 255.
Escribamos estas funciones en JavaScript:
función applyBrightness(datos, brillo) { for (var i = 0; i < data.length; i+= 4) { datos[i] += 255 * (brillo / 100); (brillo/100); datos[i+2] += 255 * (brillo/100)}función truncateColor(valor) {si); (valor < 0) { valor = 0; } else if (valor > 255) { valor = 255; } valor de retorno;}función applyContrast(datos, contraste) { var factor = (259.0 * (contraste + 255.0)) / ( 255,0 * (259,0 - contraste)); for (var i = 0; i < data.length; i+= 4) { data[i] = truncateColor(factor * (datos[i] - 128.0) + 128.0); datos[i+1] = truncateColor(factor * (datos[i+1] - 128.0) + 128.0); factor * (datos[i+2] - 128,0) + 128,0);
En este caso no necesitas la función truncateColor porque Uint8ClampedArray trunca los valores, sino traducir el algoritmo que agregamos en ella.
Una cosa para recordar es que si aplica brillo o contraste, los datos de la imagen se sobrescriben y no pueden volver a su estado anterior. Si queremos restablecer el estado original, los datos de la imagen original deben almacenarse por separado como referencia. Mantener la variable de imagen accesible para otras funciones será útil porque puede usar la imagen para volver a dibujar el lienzo y la imagen original.
var imagen = document.getElementById('SourceImage'); función redrawImage() { context.drawImage(imagen, 0, 0);}Usar vídeo
Para que funcione para video, tomaremos nuestro script de imagen inicial y código HTML y haremos algunas modificaciones menores.
HTMLCambie el elemento Imagen del elemento de video reemplazando la siguiente línea:
<fuente img>
...con esto:
<fuente de vídeo></vídeo>
javascript
Reemplace esta línea:
var imagen = document.getElementById('SourceImage');
...agrega esta línea:
var vídeo = document.getElementById('SourceVideo');
Para comenzar a procesar el vídeo, tenemos que esperar hasta que el vídeo esté listo para reproducirse.
video.addEventListener('canplay', function () { // Establece el lienzo con el mismo ancho y alto que el video canvas.width = video.videoWidth; canvas.height = video.videoHeight; // Reproduce el video video.play( ); // comenzamos a dibujar los cuadros drawFrame(video);});
La reproducción de eventos se reproduce durante al menos unos pocos fotogramas cuando hay suficientes datos para reproducir los medios.
No podemos ver ninguno de los videos que se muestran en el lienzo porque solo mostramos el primer fotograma. Tenemos que ejecutar drawFrame cada n milisegundos para mantener la velocidad de cuadros del video.
Dentro de drawFrame, volvemos a llamar a drawFrame cada 10 ms.
función drawFrame(video) { contexto.drawImage(video, 0, 0); setTimeout(function () { drawFrame(video); }, 10);}
Después de ejecutar drawFrame, creamos un bucle que ejecuta drawFrame cada 10 ms: tiempo suficiente para que el vídeo permanezca sincronizado dentro del lienzo.
Agregar efectos al videoPodemos usar la misma función que creamos anteriormente para invertir los colores:
función invertColors(datos) { for (var i = 0; i < data.length; i+= 4) { datos[i] = datos[i] ^ 255 // Invertir datos rojos[i+1] = datos[i] +1] ^ 255; // Invertir datos verdes[i+2] = datos[i+2] ^ 255 // Invertir azul }}
y agregue esto a la función drawFrame:
función drawFrame(vídeo) { contexto.drawImage(vídeo, 0, 0); var imageData = contexto.getImageData(0, 0, lienzo.ancho, lienzo.altoColores(imageData.data)putImageData(imageData, 0, 0); setTimeout(función () { drawFrame(vídeo); }, 10);}
Podemos agregar un botón y alternar el efecto:
función drawFrame(video) { contexto.drawImage(video, 0, 0); if (applyEffect) { var imageData = context.getImageData(0, 0, canvas.width, canvas.height); .putImageData(imageData, 0, 0); } setTimeout(función () { drawFrame(video); }, 10);}usar camara
Mantendremos el mismo código que usamos para el video, la única diferencia es que usaremos MediaDevices.getUserMedia para cambiar la transmisión de video de un archivo a una transmisión de cámara.
MediaDevices.getUserMedia es una nueva API que deja en desuso la API anterior MediaDevices.getUserMedia(). Los navegadores aún admiten versiones anteriores y algunos navegadores no admiten versiones más nuevas, y tenemos que recurrir a polyfills para asegurarnos de que el navegador admita una de ellas.
Primero, elimine el atributo src del elemento de vídeo:
<video><code></pre><pre><code>// Establece la fuente del video en la función de transmisión de la cámara initCamera(stream) { video.src = window.URL.createObjectURL(stream);}if (navigator .mediaDevices.getUserMedia) { navigator.mediaDevices.getUserMedia({vídeo: verdadero, audio: falso}) .entonces(initCamera) .catch(consola.error) );}
Demostración en vivo
EfectoTodo lo que hemos cubierto hasta ahora es la base que necesitamos para crear diferentes efectos para videos o imágenes. Podemos utilizar muchos efectos diferentes convirtiendo cada color de forma independiente.
Escala de grisesLa conversión de colores a escala de grises se puede realizar de diferentes maneras usando diferentes fórmulas/técnicas, para evitar profundizar demasiado en el problema les mostraré cinco fórmulas basadas en la herramienta de desaturación de GIMP y Luma:
Gris = 0.21R + 0.72G + 0.07B // LuminosidadGris = (R + G + B) ÷ 3 // Brillo promedio Gris = 0.299R + 0.587G + 0.114B // rec601 gris estándar = 0.2126R + 0.7152G + 0.0722B / /UIT-RBT.709 estándarGris = 0.2627R + 0.6780G + 0.0593B // Estándar ITU-R BT.2100
Lo que queremos encontrar usando estas fórmulas es el nivel de brillo de cada color de píxel. El valor oscila entre 0 (negro) y 255 (blanco). Estos valores crearán un efecto de escala de grises (blanco y negro).
Esto significa que el color más claro será el más cercano a 255 y el color más oscuro será el más cercano a 0.
Demostración en vivo
dos tonosLa diferencia entre un efecto duotono y un efecto en escala de grises es que se utilizan dos colores. En escala de grises tienes un degradado de negro a blanco, mientras que en duotono tienes un degradado de cualquier color a cualquier otro color (de azul a rosa).
Usando el valor de intensidad de la escala de grises, podemos reemplazarlo con el valor de gradiente.
Necesitamos crear un degradado de ColorA a ColorB.
function createGradient(colorA, colorB) { // Valores del degradado de colorA a colorB var gradient = [] // el valor máximo de color es 255 var maxValue = 255 // Convierte los valores de color hexadecimales a RGB object var from = getRGBColor(colorA); var to = getRGBColor(colorB // Crea 256 colores del color A al color B para (var i = 0; i); <= maxValue; i++) { // IntensidadB pasará de 0 a 255 // IntensidadA pasará de 255 a 0 // IntensidadA disminuirá la intensidad mientras que la intensidadB aumentará // Lo que esto significa es que ColorA comenzará sólido y se transformará lentamente en ColorB // Si lo miras de otra manera, la transparencia del color A aumentará y la transparencia del color B disminuirá var intensidadB = i var intensidadA = maxValue - intensidadB; La siguiente fórmula combina los dos colores según su intensidad // (IntensidadA * ColorA + IntensidadB * ColorB) / gradiente maxValue[i] = { r: (intensidadA*from.r + intensidadB*to.r) / maxValue, g: ( intensidadA*de.g + intensidadB*a.g) / maxValue, b: (intensidadA*de.b + intensidadB*a.b) / maxValue } } return gradient;}// Función auxiliar para convertir valores hexadecimales de 6 dígitos en una función de objeto de color RGB getRGBColor(hex){ var colorValue if (hex[0] === '#') { hex = hex.substr(1); } colorValue = parseInt(hex, 16); return { r: colorValue >> 16, g: (colorValue >> 8) & 255, b: colorValor & 255 }}
En resumen, creamos un conjunto de valores de color comenzando por el color A, disminuyendo la intensidad, mientras vamos al color B y aumentamos la intensidad.
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},];
Representación de transiciones de color a escala.
Arriba se muestra un ejemplo de un degradado de 10 valores de color desde #0096ff hasta #ff00f0.
Representación en escala de grises de transiciones de color.
Ahora que tenemos una representación en escala de grises de la imagen, podemos usarla para asignarla a un valor de gradiente de duotono.
El degradado duotono tiene 256 colores, mientras que la escala de grises también tiene 256 colores que van del negro (0) al blanco (255). Eso significa que un valor de color en escala de grises se asignará a un índice de elemento de degradado.
var gradientColors = 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) { // Obtiene el valor de color de cada canal var redValue = data[i]; data[i+1]; var blueValue = data[i+2] // Asignación de los valores de color al índice de gradiente // Reemplazar el valor de color en escala de grises con un color para el gradiente de duotono data[i] = gradientColors[ redValue] .r; datos[i+1] = gradientColors[greenValue].g; datos[i+2] = gradientColors[blueValue].b;
Demostración en vivo
en conclusiónEste tema podría ser más profundo o explicar más implicaciones. La tarea para usted es encontrar diferentes algoritmos que puedan aplicarse a estos ejemplos de esqueleto.
Comprender la estructura de los píxeles en el lienzo le permitirá crear una cantidad ilimitada de efectos como sepia, combinación de colores, efectos de pantalla verde, destellos/fallos en la imagen, etc.
Incluso puedes crear efectos sobre la marcha sin utilizar imágenes o vídeos.
Lo anterior es el contenido completo de este artículo. Espero que sea útil para el estudio de todos. También espero que todos apoyen VeVb Wulin Network.