the tainted origin manipulation imagenes has data cross been javascript html5 canvas

javascript - tainted - Lienzo getImageData que devuelve datos incorrectos en ciertos dispositivos móviles



putimagedata (2)

Estoy trabajando en un reproductor de video canvas con algunas características especiales basadas en fotogramas del video. Para superar el tiempo poco confiable en la etiqueta HTML5 de video, los videos que estamos utilizando tienen un código de barras incrustado en cada fotograma que indica el número de fotograma actual. Utilizando el método canvas getImageData, puedo tomar los píxeles y leer el código de barras para obtener el número de fotograma. Esto funciona muy bien y tengo un JSFiddle que demuestra que funciona (no pude JSFiddle CORS en este violín para mostrar el video en el lienzo, para que funcione, tendrás que descargar el video de ejemplo localmente y luego subirlo a través del botón. No es ideal, pero funciona).

En ciertos dispositivos móviles (solo Android hasta el momento) esta lógica se rompe. GetImageData devuelve valores incorrectos.

Funciona correctamente en mi Samsung Galaxy S5 v6.0.1 pero falla en un Google Pixel que ejecuta Android v7.1.2. Trataré de recopilar más datos sobre qué dispositivos / versiones de SO falla.

Por ejemplo, al jugar en el escritorio, la primera iteración de getImageData regresa:

Uint8ClampedArray(64) [3, 2, 3, 255, 255, 255, 255, 255, 246, 245, 247, 255, 243, 242, 243, 255, 241, 239, 241, 255, 242, 240, 242, 255, 242, 240, 242, 255, 242, 240, 242, 255, 242, 240, 242, 255, 242, 240, 242, 255, 242, 240, 242, 255, 242, 240, 242, 255, 242, 240, 242, 255, 242, 240, 242, 255, 242, 240, 242, 255, 242, 240, 242, 255]

que correctamente se calcula como el número 1.

Sin embargo, en la galaxia, la primera iteración regresa:

Uint8ClampedArray(64) [255, 242, 217, 255, 255, 234, 209, 255, 41, 1, 1, 255, 254, 235, 210, 255, 255, 234, 209, 255, 50, 4, 0, 255, 254, 240, 215, 255, 255, 248, 224, 255, 255, 249, 225, 255, 255, 251, 232, 255, 255, 252, 233, 255, 255, 252, 233, 255, 255, 253, 234, 255, 255, 255, 237, 255, 255, 255, 237, 255, 28, 1, 1, 255]

Leí que ciertos dispositivos pueden estar haciendo un alisado adicional, así que he estado jugando con la desactivación en el contexto a través de:

this.ctx.mozImageSmoothingEnabled = false; this.ctx.webkitImageSmoothingEnabled = false; this.ctx.msImageSmoothingEnabled = false; this.ctx.imageSmoothingEnabled = false;

Pero no ayudó.

Aquí está el código que se usa en JSFiddle.

var frameNumberDiv = document.getElementById(''frameNumber''); function load() { var canvas = document.getElementById(''canvas''); var ctx = canvas.getContext(''2d''); var video = document.getElementById(''video''); canvas.width = 568; canvas.height = 640; video.addEventListener(''play'', function() { var that = this; //cache (function loop() { if (!that.paused && !that.ended) { ctx.drawImage(that, 0, 0); var pixels = ctx.getImageData(0, 320 - 1, 16, 1).data; getFrameNumber(pixels); setTimeout(loop, 1000 / 30); // drawing at 30fps } })(); }, 0); } function getFrameNumber(pixels) { let j = 0; let thisFrameNumber = 0; let str = "Pixels: "; for (let i = 0; i < 16; i++) { str += pixels[j] + " "; thisFrameNumber += getBinary(pixels[j], i); j += 4; } document.getElementById(''frameNumber'').innerHTML = "FrameNumber: " + thisFrameNumber; } function getBinary(pixel, binaryPlace) { const binary = [1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768 ]; if (pixel > 128) return 0; if (pixel < 128 && binary[binaryPlace]) { return binary[binaryPlace] } else { return 0; } } (function localFileVideoPlayer() { ''use strict''; var URL = window.URL || window.webkitURL; var displayMessage = function(message, isError) { var element = document.querySelector(''#message''); element.innerHTML = message; element.className = isError ? ''error'' : ''info''; } var playSelectedFile = function(event) { console.log("Playing"); var file = this.files[0]; var type = file.type; var videoNode = document.querySelector(''video''); var canPlay = videoNode.canPlayType(type); if (canPlay === '''') canPlay = ''no''; var message = ''Can play type "'' + type + ''": '' + canPlay; var isError = canPlay === ''no''; displayMessage(message, isError); if (isError) { return; } var fileURL = URL.createObjectURL(file); videoNode.src = fileURL; load(); } var inputNode = document.querySelector(''input'') inputNode.addEventListener(''change'', playSelectedFile, false) })();

EDITAR

  • Funciona en un Nexus 6P con Android v6.0
  • Funciona en un Samsung 6 (Samsung SM G920A) con Android v 5.0.2
  • NO FUNCIONA en un Samsung Galaxy S7 (SAMSUNG-SM-G935A) con Android v7.0

¿Podría ser este un problema de Android 7 ?

Editar 2

En respuesta a una pregunta en los comentarios:

videoNode.videoHeight y videoWidth son 0 en el píxel de Google durante toda su existencia, pero esto es lo mismo que en el escritorio. En ambos dispositivos que no funcionan que he encontrado, la imagen de cada fotograma está completamente pintada. Adjuntaré una captura de pantalla del píxel de Google. Cuando está en pausa, siempre lee el mismo número. En otras palabras, no está saltando, por lo que lo que está leyendo está realmente en el marco del video.

EDIT 3: Descubrimiento

Creo que hice un descubrimiento / realización relevante que debería haber visto antes.

Al mirar la salida de getImageData en el dispositivo roto, estaba avanzando línea por línea. Lo que no había notado (y debería haberlo notado) era que el elemento de video continuaba ligeramente después de que tocara mis puntos de ruptura / declaraciones de depuración. Para cuando se ejecutó el método getImageData, el video pasó del siguiente cuadro. Por lo tanto, el código de barras escaneado fue en realidad para un marco mucho más tarde de lo esperado.

Agregué algunas instrucciones de registro de consola y lo dejé funcionar naturalmente. Al mirar la salida, puedo ver un patrón mucho más reconocible.

Aquí están las primeras lecturas en el pixel de Google:

Uint8ClampedArray(64) [255, 255, 255, 255, 246, 246, 246, 255, 243, 243, 243, 255, 240, 240, 241, 255, 241, 241, 242, 255, 241, 241, 242, 255, 241, 241, 242, 255, 241, 241, 242, 255, 241, 241, 242, 255, 241, 241, 242, 255, 241, 241, 242, 255, 241, 241, 242, 255, 241, 241, 242, 255, 241, 241, 242, 255, 241, 241, 242, 255, 2, 2, 2, 255] Uint8ClampedArray(64) [5, 5, 5, 255, 255, 255, 255, 255, 251, 251, 251, 255, 247, 247, 248, 255, 245, 245, 245, 255, 245, 245, 245, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 6, 3, 2, 255] Uint8ClampedArray(64) [235, 231, 230, 255, 17, 12, 12, 255, 252, 247, 247, 255, 255, 255, 255, 255, 255, 254, 254, 255, 255, 253, 253, 255, 255, 252, 251, 255, 255, 253, 253, 255, 255, 253, 253, 255, 255, 253, 253, 255, 255, 253, 253, 255, 255, 253, 253, 255, 255, 253, 253, 255, 255, 253, 253, 255, 255, 253, 253, 255, 7, 3, 1, 255] Uint8ClampedArray(64) [26, 15, 14, 255, 4, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 10, 0, 0, 255]

Como puede observar, los resultados parecen ser correctos, sin embargo, se desplazan un píxel a la izquierda.

JSFiddle el JSFiddle ligeramente para cambiar la lectura de getImageData por un píxel y da la misma respuesta que en el Pixel.

var pixels = ctx.getImageData(1, 320 - 1, 16, 1).data;

Hacer -1 parece no tener ningún efecto.

Entonces, por algún motivo, estos dispositivos están cambiando toda la textura por un píxel o hay algo mal con el método getImageData.

EDIT 4

Como experimento, reconfigure mi código para usar una textura webGL. Mismo comportamiento en computadoras de escritorio / dispositivos móviles. Esto me permitió usar -1 como el objetivo x usando gl.readPixels. Esperaba que salteando el lienzo la imagen completa se almacenara en la memoria y pudiera acceder a los datos de píxeles que necesitaba ... No funcionó, pero aquí están los datos que produjo, lo que muestra que también se desplazó utilizando puramente webGL .

Uint8Array(64) [0, 0, 0, 0, 255, 248, 248, 255, 25, 18, 18, 255, 254, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255] Uint8Array(64) [0, 0, 0, 0, 255, 254, 247, 255, 255, 244, 236, 255, 48, 18, 10, 255, 254, 246, 238, 255, 255, 247, 239, 255, 255, 247, 239, 255, 255, 248, 241, 255, 255, 251, 243, 255, 255, 251, 243, 255, 255, 251, 243, 255, 255, 251, 243, 255, 255, 251, 243, 255, 255, 251, 243, 255, 255, 250, 240, 255, 255, 250, 240, 255] Uint8Array(64) [0, 0, 0, 0, 31, 0, 0, 255, 254, 243, 230, 255, 43, 6, 1, 255, 254, 243, 230, 255, 255, 244, 231, 255, 255, 244, 231, 255, 255, 244, 231, 255, 255, 244, 231, 255, 255, 244, 231, 255, 255, 244, 231, 255, 255, 244, 231, 255, 255, 244, 231, 255, 255, 244, 231, 255, 255, 244, 229, 255, 255, 244, 229, 255]

Utilizando:

gl.readPixels(-1, height, 16, 1, gl.RGBA, gl.UNSIGNED_BYTE, pixels);

Editar 5

Ok, prometí que obtendría más detalles sobre qué dispositivos estaban fallando / no. JSFiddle algunas pruebas de control de calidad en línea utilizando un JSFiddle ligeramente modificado. Lo modifiqué un poco para ayudar a que sea un poco más idiota para el público en general.

Las respuestas fueron lamentablemente bastante mixtas. Esperaba que estuviera aislado de Android 7, pero ese no parece ser el caso.

Tengo un CSV en mi unidad de Google con los resultados de esta prueba. No es que estas pruebas sean 100% confiables, pero parece que son solo algunos dispositivos aleatorios ......


He estado pasando por el mismo problema y simplemente no pude conseguirlo. Finalmente tengo la respuesta en mi caso que suena 99% como tu problema también.

¡Es la densidad de píxeles!

Las densidades de los píxeles son diferentes en todos los dispositivos que mencionas y los que probablemente tienen 1 dpr (proporción de píxeles del dispositivo) funcionan correctamente, mientras que los otros no.

Entonces en mi caso, usando p5js configuré la densidad de píxeles para que fuera 1 y funcionó como un amuleto;

pixelDensity(1);

¡Así que configúralo como 1 dpr y probablemente estés listo para empezar!

Espero que esto ayude a algunas personas porque pasé bastante tiempo con este problema.


Su código está bien, así que el problema aquí reside en cómo algunos androides representan su código de barras. La implementación de su código de barras es demasiado pequeña (16x1 píxeles) y deja sangría.

Si se deja indentada cualquier alias anti que el dispositivo fabrique en los píxeles externos, se dañará el código de barras y se obtendrán resultados incorrectos, por lo que, cuando se trabaja con procesamiento de video, definitivamente no se desea trabajar en un área segura de un píxel.

Mi sugerencia sería volver a hacer los códigos de barras a un tamaño más grande, digamos 18 píxeles de altura, solo usar blanco y negro (sin grises), centrarlo en el video y el resto de la línea pintarlo en verde: (en este ejemplo, es código de barras para "1")

Luego haga un GetImageData de 320x16 completo y elimine todo lo que tenga un RGB de 0x255x0 (y aproximado) y tendrá su código de barras que podrá usar para obtener su FrameNumber.

Sé que probablemente desee una respuesta en la que no sea necesario volver a hacer el video, pero en este caso, la fuente es el problema.