私は使用していますglfx.js画像を編集したいのですが、関数を使用してその画像のデータを取得しようとすると、toDataURL()
空白の画像(幅は元の画像と同じサイズ)が表示されます。
奇妙なことに、Chrome ではスクリプトは完璧に動作します。
ここで言及したいのは、画像がcanvas
onload イベントを使用して読み込まれるということです。
img.onload = function(){
try {
canvas = fx.canvas();
} catch (e) {
alert(e);
return;
}
// convert the image to a texture
texture = canvas.texture(img);
// draw and update canvas
canvas.draw(texture).update();
// replace the image with the canvas
img.parentNode.insertBefore(canvas, img);
img.parentNode.removeChild(img);
}
また、私の画像のパスは同じドメイン上にあります。
問題は(Firefox の場合)、保存ボタンを押したときです。Chrome は期待どおりの結果を返しますが、Firefox はこれを返します。
data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAA7YAAAIWCAYAAABjkRHCAAAHxklEQVR4nO3BMQEAAADCoPVPbQZ/oAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
... [ lots of A s ] ...
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAzwD6aAABkwvPRgAAAABJRU5ErkJggg==
この結果の原因は何でしょうか? また、どうすれば修正できますか?
ベストアンサー1
キャンバスに描画してから を呼び出すまでの間に、何らかの非同期イベントが発生する可能性が高くなります。デフォルトでは、キャンバスは合成のたびにクリアされます。次のようにしてtoDataURL
WebGLコンテキストを作成し、キャンバスがクリアされないようにするか、preserveDrawingBuffer: true
var gl = canvas.getContext("webgl", {preserveDrawingBuffer: true});
または(推奨)レンダリングに使用しているイベントを終了する前にtoDataURLが呼び出されるようにしてください。たとえば、次のようにすると
function render() {
drawScene();
requestAnimationFrame(render);
}
render();
そしてどこか他の場所でこれをやってください
someElement.addEventListener('click', function() {
var data = someCanvas.toDataURL();
});
これら 2 つのイベント、animation frame
および はclick
同期されておらず、それらの呼び出しの間にキャンバスがクリアされる可能性があります。注: キャンバスは二重バッファリングされているためクリアされたようには見えませんが、バッファ toDataURL やそのバッファに影響するその他のコマンドはクリアされます。
解決策としては、レンダリングと同じイベント内で を使用するpreserveDrawingBuffer
か、呼び出しを行うことですtoDataURL
。たとえば、有効にしない場合preserveDrawingBuffer
(推奨):
function render() {
drawScene();
requestAnimationFrame(render);
}
render();
someElement.addEventListener('click', function() {
drawScene(); // draw at the same time (same event, same macrotask)
var data = someCanvas.toDataURL();
});
または
var captureFrame = false;
function render() {
drawScene();
if (captureFrame) {
captureFrame = false;
var data = someCanvas.toDataURL();
...
}
requestAnimationFrame(render);
}
render();
someElement.addEventListener('click', function() {
captureFrame = true;
});
たとえば、以下を有効にした場合preserveDrawingBuffer
:
function render() {
drawScene();
requestAnimationFrame(render);
}
render();
someElement.addEventListener('click', function() {
// drawScene(); // no longer needed
var data = someCanvas.toDataURL();
});
どちらがデフォルトであるのか、その意味は何でしょうかpreserveDrawingBuffer: false
。特にモバイルでは、描画バッファを保持する必要がないため、大幅に高速化できます。別の見方をすると、ブラウザにはキャンバスのコピーが 2 つ必要です。描画先のものと表示先のものです。ブラウザには、この 2 つのバッファを処理する方法が 2 つあります。(A) ダブル バッファ。一方に描画し、もう一方に表示し、描画コマンドを発行したイベントの終了から推測されるレンダリングの完了時にバッファを交換します。(B) 描画中のバッファの内容をコピーして、表示中のバッファを作成します。交換はコピーよりもはるかに高速です。したがって、交換がデフォルトです。実際に何が起こるかはブラウザ次第です。唯一の要件は、合成後に描画バッファがクリアされるpreserveDrawingBuffer
ことfalse
(これもまた別の非同期イベントであるため予測できません) の場合、描画バッファの内容が保持されるようにコピーする必要があることpreserveDrawingBuffer
ですtrue
。
キャンバスにコンテキストが設定されると、常に同じコンテキストが保持されることに注意してください。つまり、WebGLコンテキストを初期化するコードを変更しても、preserveDrawingBuffer: true
少なくとも2つの方法があります。
まずキャンバスを見つけて、その背景を理解する
後でコードが同じコンテキストで終了するからです。
<script>
document.querySelector('#somecanvasid').getContext(
'webgl', {preserveDrawingBuffer: true});
</script>
<script src="script/that/will/use/somecanvasid.js"></script>
すでにそのキャンバスのコンテキストを作成しているため、その後に続くスクリプトは同じコンテキストを取得します。
増強getContext
<script>
HTMLCanvasElement.prototype.getContext = function(origFn) {
return function(type, attributes) {
if (type === 'webgl') {
attributes = Object.assign({}, attributes, {
preserveDrawingBuffer: true,
});
}
return origFn.call(this, type, attributes);
};
}(HTMLCanvasElement.prototype.getContext);
</script>
<script src="script/that/will/use/webgl.js"></script>
この場合、を拡張した後に作成されたすべての WebGL コンテキストはtrue に設定getContext
されます。preserveDrawingBuffer