Canvas getImageData在某些移动设备上返回不正确的数据

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Canvas getImageData在某些移动设备上返回不正确的数据相关的知识,希望对你有一定的参考价值。

我正在制作一个画布视频播放器,它具有一些基于视频帧的特殊功能。为了克服视频html5标签中不可靠的时序,我们使用的视频在每个帧中都嵌入了条形码,指示当前帧编号。使用canvas getImageData方法,我可以抓取像素并读取条形码以获取帧编号。这很好用,我有一个JSFiddle证明它的工作原理(我无法绕过CORS这个小提琴将视频提供给画布,所以看到它工作你必须在本地下载示例视频然后通过按钮。不理想,但它的工作原理)。

在某些移动设备上(到目前为止只有android),这种逻辑会中断。 getImageData返回不正确的值。

它在我的三星Galaxy S5 v6.0.1上正常工作,但在运行android v7.1.2的Google Pixel上失败。我将尝试收集有关它失败的设备/操作系统版本的更多数据。

例如,在桌面上播放时,getImageData的第一次迭代返回:

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]

正确计算为framenumber 1。

然而在星系中,第一次迭代返回:

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] 

我读到某些设备可能正在进行额外的平滑处理,因此我一直在通过以下方式在上下文中禁用它:

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

但它没有帮助。

这是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)
})();

编辑

  • 适用于运行Android v6.0的Nexus 6P
  • 适用于运行Android v 5.0.2的Samsung 6(Samsung SM G920A)
  • 它不适用于运行Android v7.0的三星Galaxy S7(SAMSUNG-SM-G935A)

这可能是Android 7问题吗?

编辑2

回答评论中的问题:

videoNode.videoHeight和videoWidth在谷歌像素上都是0,但它们与桌面上的相同。在我遇到的两个不起作用的设备中,每个帧的图像都是完全绘制的。我将附上谷歌像素的屏幕截图。暂停时,它始终读取相同的数字。换句话说,它不会四处跳跃,所以它正在阅读的内容真正在视频的框架上。 enter image description here

编辑3:发现

我相信我已经做了一个相关的发现/实现,我应该早些看到。

当在破碎的设备上查看getImageData的输出时,我逐行地逐步执行。我没有(也应该)注意到的是,视频元素在达到我的断点/调试器语句之后仍在继续。到执行getImageData方法时,视频已移过下一帧。因此,扫描的条形码实际上是比预期更晚的帧。

我添加了一些控制台日志语句,让它自然运行。看看输出,我可以看到一个更容易识别的模式。

以下是谷歌像素的前几个读数:

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]

您可能会注意到,结果似乎是正确的,但它们向左移动了一个像素。

我稍微修改了JSFiddle,将getImageData读取了一个像素,它给出了与Pixel完全相同的响应。

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

做-1似乎没有效果。

因此,由于某些原因,这些设备要么将整个纹理移动一个像素,要么getImageData方法有问题。

编辑4

作为实验,我重新配置了我的代码以使用webGL纹理。桌面/移动设备上的行为相同。这允许我使用gl.readPixels将-1用作x目标。我希望通过跳过使用画布,整个图像将存储在内存中,我可以访问我需要的像素数据....没有工作,但这里是它生成的数据,表明它也使用纯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]

使用:

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

编辑5

好的,我保证我会得到更多有关哪些设备发生故障/未发生故障的细节。我使用略微修改的JSFiddle完成了一些在线QA测试。我对它进行了一些修改,以帮助公众更好地证明这一点。

不幸的是,答复相当复杂。我希望它能与Android 7隔离,但似乎并非如此。

我在谷歌硬盘上有一个CSV,测试结果。并不是说这些测试是100%可靠的,但它似乎只是一些随机设备......

答案

我一直在经历同样的问题而且无法得到它。我终于在我的案例中得到了答案,听起来也像你的问题一样99%。

这是像素密度!!

您提到的所有设备上的像素密度都不同,并且可能有1 dpr(设备像素比)的设备正常工作,而其他设备没有。

所以在我使用p5js的情况下,我将像素密度设置为1,它就像一个魅力;

pixelDensity(1);

所以把它设置为1 dpr,你最有可能去!

我希望这可以帮助一些人,因为我花了很长时间来解决这个问题。

另一答案

你的代码很好,所以这里的问题在于一些orroids如何呈现你的条形码。条形码的实现太小(16x1像素),并且左侧缩进。

如果设备在外部像素上留下缩进的任何反别名会使条形码变得混乱并给你不正确的结果,因此,在处理视频渲染时,你肯定不想在一个像素安全区域上工作。

我的建议是将条形码重做为更大的尺寸 - 比如高度为18像素 - 仅使用黑色和白色(不灰色),将其置于视频中心,其余部分将其绘制为绿色:(在此示例中,它是一个条形码为“1”)

Barcode example that will give you the value "1"

然后制作一个完整的320x16的GetImageData并摆脱RGB为0x255x0(和近似值)的所有东西,你就可以用你的条形码来获取你的FrameNumber。

我知道你可能想要一个没有必要重做视频的答案,但在这种情况下,源是问题所在。

以上是关于Canvas getImageData在某些移动设备上返回不正确的数据的主要内容,如果未能解决你的问题,请参考以下文章

h5标签canvas关于getImageData跨域的问题

canvas关于getImageData跨域问题解决方法

HTML5 Canvas getImageData 和同源策略

离屏 Canvas 原理与 SpriteBuffer

在画布getImageData周围画一个框

html5 Canvas getImageData 或 toDataURL 的结果 - 哪个占用更多内存?