javascript - 为什么将 blob 转换为数据 URI 会导致与直接数据 URI 方法不同的 URI?
问题描述
我正在做一个实验,我发现将 Canvas 转换为 blob 然后转换为数据 URI 会导致与直接从画布获取数据 URI 不同的 URI。打开时的内容在两个 URI 上几乎相同。
使用 blob 方法时,如何获得与直接数据 URI 方法相同的 URI 结果?
let canvas = document.createElement('canvas');
let ctx = canvas.getContext('2d');
let text = "Bufferoverrun";
ctx.textBaseline = "top";
ctx.font = "16px 'Arial'";
ctx.textBaseline = "alphabetic";
ctx.rotate(.05);
ctx.fillStyle = "#f60";
ctx.fillRect(125, 1, 62, 20);
ctx.fillStyle = "#069";
ctx.fillText(text, 2, 15);
ctx.fillStyle = "rgba(102, 200, 0, 0.7)";
ctx.fillText(text, 4, 17);
ctx.shadowBlur = 10;
ctx.shadowColor = "blue";
ctx.fillRect(-20, 10, 234, 5);
const blobToBase64 = blob => {
const reader = new FileReader();
reader.readAsDataURL(blob);
return new Promise(resolve => {
reader.onloadend = () => {
resolve(reader.result);
};
});
};
canvas.toBlob(async function (result) {
let blobToURL = await blobToBase64(result)
if (blobToURL != canvas.toDataURL()) {
console.log("Data mismatch");
} else {
console.log("Match")
}
})
我查看了 Chrome 的 Blink 内部结构,找不到任何可以解释这种变化的东西。
解决方案
当前的区别在于颜色空间转换的方式toBlob
和处理方式不同。toDataURL
toBlob
在某些情况下,可能会保留当前的颜色空间并将其包含在生成的 Blob 中,toDataURL
永远不会,并且何时toBlob
会使用其他路径进行转换。
这是他们为toDataURL进行转换的地方 ,这里是toBlob。
有趣的是,Chrome 会在再次将它们重新绘制到画布上时从这两个文件生成相同的位图,但是如果您将两个结果保存到磁盘并在像 Firefox 这样检查源颜色空间的浏览器中绘制它们,您将看到它们实际上是不同的。
请注意,它可能不是您发现两种方法之间差异的唯一地方。
例如,我还注意到toBlob
会受到画布加速与否的影响(下面的演示需要chrome://flags/#enable-experimental-web-platform-features
):
const test = (accelerated) => {
const HW = accelerated ? "accelerated" : "not-accelerated"
let canvas = document.createElement('canvas');
let ctx = canvas.getContext('2d', {
willReadFrequently: !accelerated
});
let text = "Bufferoverrun";
ctx.fillStyle = "#f60";
ctx.fillRect(125, 1, 62, 20);
ctx.fillStyle = "#069";
ctx.fillText(text, 2, 15);
ctx.fillStyle = "rgba(102, 200, 0, 0.7)";
ctx.shadowBlur = 10;
ctx.shadowColor = "blue";
ctx.fillRect(-20, 10, 234, 5);
canvas.toBlob(async function(result) {
let blobToURL = await blobToBase64(result)
const data = canvas.toDataURL();
if (blobToURL != data) {
console.log(HW, "Data mismatch");
} else {
console.log(HW, "Match")
}
});
function blobToBase64(blob) {
const reader = new FileReader();
reader.readAsDataURL(blob);
return new Promise(resolve => {
reader.onloadend = () => {
resolve(reader.result);
};
});
}
};
test(true);
test(false);
而且我应该注意,您不应该期望这些方法无论如何都会返回相同的值。规范中没有任何要求,恰恰相反,由于toBlob
它是异步的并且并行执行其编码,因此它的编码器将使用比同步更慢但质量更好的选项toDataURL
。
但是,如果对于您自己的情况,您希望通过两种方法获得相同的结果,您可以通过禁用硬件加速 ( chrome://settings/?search=hardware acceleration
) 来强制软件渲染,它们将产生相同的结果。
从评论中 OP 解释说它们实际上是在一个网络扩展中,并且区域实际上是在处理一个没有toDataURL
方法的 OffscreenCanvas。
因此,首先,在 Firefox中,ChromeContexts(网络扩展)有一个关于 2D 上下文可用的demote
方法,它应该允许强制使用软件渲染器,但 Chrome 没有实现此功能。
现在关于工人案例中的 OffscreenCanvas,一种解决方法是toDataURL()
从 HTMLCanvasElement 上的主线程调用,我们从中获取 OffscreenCanvas:
const worker = new Worker( worker_url );
const workercanvas = document.createElement( "canvas" );
const offscreen = workercanvas.transferControlToOffscreen();
worker.postMessage(offscreen, [offscreen]);
worker.onmessage = (evt) => {
const fromworker = workercanvas.toDataURL();
const frommain = getDataURLFromMain();
console.log( fromworker === frommain ? "Match" : "Data mismatch" );
};
<script>
// boiler plate prepare scripts content for SO's StackSnippet
const drawing_ops = `
const ctx = canvas.getContext("2d");
const text = "Bufferoverrun";
ctx.textBaseline = "top";
ctx.font = "16px 'Arial'";
ctx.textBaseline = "alphabetic";
ctx.rotate(.05);
ctx.fillStyle = "#f60";
ctx.fillRect(125, 1, 62, 20);
ctx.fillStyle = "#069";
ctx.fillText(text, 2, 15);
ctx.fillStyle = "rgba(102, 200, 0, 0.7)";
ctx.fillText(text, 4, 17);
ctx.shadowBlur = 10;
ctx.shadowColor = "blue";
ctx.fillRect(-20, 10, 234, 5);
`;
const getDataURLFromMain = new Function(`
const canvas = document.createElement("canvas");
${ drawing_ops }
return canvas.toDataURL();
`);
const worker_script = new Blob( [ `
onmessage = (evt) => {
const canvas = evt.data;
${ drawing_ops }
if( ctx.commit ) { // might require WebPlatformFeatures flags
ctx.commit();
postMessage("");
}
else {
requestAnimationFrame(() => postMessage(""));
}
};` ] );
const worker_url = URL.createObjectURL( worker_script );
</script>
也可作为小故障使用,因为它可能更清晰。
推荐阅读
- python - keras 在 keras.layers.Conv2D 的过滤器参数中使用了哪些类型的“过滤器”?
- javascript - 当 a、b、c 和 d 不相等时,如何返回 a+b = c+d 的所有正值?
- sqlite - 使用 Flask-Migration 向 sqlite3 表添加 UniqueKey 约束失败并出现 IntrgrityError
- excel - 改进 VBA 代码 - 它在运行后保持 word 实例打开
- r - 合并和转换两组数据
- ssh - 更新时 Vagrant 2.2.3(IPC 代码)中的 Rsync 错误
- angular - 将数据传递给工具提示Angular 2
- delphi - 如何比较一个事件及其相应的程序避免 E2035 和 E2036?
- javascript - 如何使用自定义按钮在 reactjs 中读取和上传文件
- snakemake - Snakemake 分组输出