web-dev-qa-db-ja.com

Canvas toDataURL()は空白の画像を返します

glfx.js を使用して画像を編集していますが、toDataURL()関数を使用してその画像のデータを取得しようとすると、空白の画像(幅はと同じサイズ)が表示されます元の画像)。

奇妙なことに、Chromeではスクリプトが完全に機能します。

私が言及したいのは、画像がonloadイベントを使用してcanvasにロードされるということです。

           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==

この結果の原因は何ですか?また、どうすれば修正できますか?

17
boyd

ほとんどの場合、キャンバスに描画してからtoDataURLを呼び出すまでの間に非同期イベントが発生します。デフォルトでは、キャンバスはすべてのコンポジットの後にクリアされます。次のようにpreserveDrawingBuffer: trueを使用してWebGLコンテキストを作成することにより、キャンバスがクリアされないようにします。

var gl = canvas.getContext("webgl", {preserveDrawingBuffer: true});

または、レンダリングに使用しているイベントを終了する前に、toDataURLが呼び出されていることを確認してください。たとえば、これを行う場合

function render() {
  drawScene(); 
  requestAnimationFrame(render);
}
render();

そしてどこかでこれを行う

someElement.addEventListener('click', function() {
  var data = someCanvas.toDataURL();
}, false);

これらの2つのイベント、animation frameclickは同期しておらず、それらを呼び出す間にキャンバスがクリアされる可能性があります。注:キャンバスはダブルバッファーであるためクリアされたようには見えませんが、バッファーtoDataURLおよびバッファーが参照していることに影響を与えるその他のコマンドはクリアされます。

解決策は、preserveDrawingBufferを使用するか、レンダリングと同じイベント内でtoDataURLを呼び出すことです。例えば

var captureFrame = false;

function render() {
  drawScene(); 

  if (captureFrame) {
    captureFrame = false;
    var data = someCanvas.toDataURL();
    ...
  }

  requestAnimationFrame(render);
}
render();

someElement.addEventListener('click', function() {
  captureFrame = true;
}, false);

デフォルトであるpreserveDrawingBuffer: falseのポイントは何ですか?特にモバイルでは、描画バッファを保持する必要がないため、大幅に高速化できます。別の見方をすると、ブラウザにはキャンバスのコピーが2つ必要です。あなたが描いているものとそれが表示しているもの。これらの2つのバッファを処理する方法は2つあります。 (A)ダブルバッファ。描画コマンドを発行したイベントの終了から推測されるレンダリングが完了したら、一方に描画し、もう一方を表示し、バッファを交換します(B)描画しているバッファの内容をコピーして、表示されているバッファを実行します。交換はコピーよりもはるかに高速です。したがって、スワッピングがデフォルトです。実際に何が起こるかはブラウザ次第です。唯一の要件は、preserveDrawingBufferfalseの場合、preserveDrawingBuffertrue次に、drawingbufferの内容が保持されるようにコピーする必要があります。

キャンバスにコンテキストがあると、常に同じコンテキストになることに注意してください。つまり、WebGLコンテキストを初期化するコードを変更したが、それでもpreserveDrawingBuffer: trueを設定したいとします。

少なくとも2つの方法があります。

最初にキャンバスを見つけて、そのコンテキストを取得します

後でコードが同じコンテキストになるためです。

<script>
document.querySelector('#somecanvasid').getContext(
    'webgl', {preserveDrawingBuffer: true});
</script>
<script src="script/that/will/use/somecanvasid.js"></script>

そのキャンバスのコンテキストはすでに作成されているため、後続のスクリプトはすべて同じコンテキストを取得します。

augment 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>

この場合、getContextを拡張した後に作成されたwebglコンテキストでは、preserveDrawingBufferがtrueに設定されます。

25
gman