CanvasElement.toDataURL() for WebGL canvases

1.3k views Asked by At

We develop complex WebGL powered application and offer our users an ability to save screenshot of the current scene. While this feature works as expected in Chrome and Firefox, we are facing several issues in safari.

Please take a look at the following code. As you can see, we use toDataURL() method of CanvasElement to make screenshots. While this code works as expected in other browsers, it produces wrong images in safari (at least for WebGL canvases).

You can easily reproduce the issue yourself by openning this file or running the attached code snippet in safari and other browsers.

Are we doing something wrong? Is there workaround for this issue?

Thank you.

UPD: Specially for those who say it is related to preserveDrawingBuffer i changed to attached code snippet to show that even with preserveDrawingBuffer set to true it still behaves wrong in safari.

<script type="text/javascript" src="http://www.html5rocks.com/en/tutorials/webgl/webgl_fundamentals/webgl/resources/webgl-utils.js"></script>
<script id="2d-vertex-shader" type="x-shader/x-vertex">
  attribute vec2 a_position;

  void main() {
    gl_Position = vec4(a_position, 0, 1);
  }
</script>

<script id="2d-fragment-shader" type="x-shader/x-fragment">
  void main() {
    gl_FragColor = vec4(0,1,0,1);  // green
  }
</script>

<div>
  <canvas id="canvas1" width="300" height="300"></canvas>
  <img id="img1"/>
</div>
<div>
  <canvas id="canvas2" width="300" height="300"></canvas>
  <img id="img2"/>
</div>
<div>
  <canvas id="canvas3" width="300" height="300"></canvas>
  <img id="img3"/>
</div>

<div>
  <canvas id="canvas4" width="300" height="300"></canvas>
  <img id="img4"/>
</div>
<div>
  <canvas id="canvas5" width="300" height="300"></canvas>
  <img id="img5"/>
</div>
<div>
  <canvas id="canvas6" width="300" height="300"></canvas>
  <img id="img6"/>
</div>

<script type="text/javascript">
  function test(canvas, img, premultipliedAlpha, alpha, preserveDrawingBuffer) {
    var gl = canvas.getContext("experimental-webgl", {
      premultipliedAlpha: premultipliedAlpha, 
      alpha: alpha,
      preserveDrawingBuffer: preserveDrawingBuffer
    });
    var vertexShader = createShaderFromScriptElement(gl, "2d-vertex-shader");
    var fragmentShader = createShaderFromScriptElement(gl, "2d-fragment-shader");
    var program = createProgram(gl, [vertexShader, fragmentShader]);
    gl.useProgram(program);
    var positionLocation = gl.getAttribLocation(program, "a_position");

    var buffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
    gl.bufferData(
      gl.ARRAY_BUFFER,
      new Float32Array([
        -1.0, -1.0,
        1.0, -1.0,
        -1.0,  1.0,
        1.0, -1.0,
        1.0,  1.0]),
      gl.STATIC_DRAW);
    gl.enableVertexAttribArray(positionLocation);
    gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);
    gl.drawArrays(gl.TRIANGLES, 0, 5);

    img.src = canvas.toDataURL();
  }

  window.onload = function() {
    test(document.getElementById("canvas1"), document.getElementById("img1"), true, true, false);
    test(document.getElementById("canvas2"), document.getElementById("img2"), false, true, false);
    test(document.getElementById("canvas3"), document.getElementById("img3"), false, false, false);

    test(document.getElementById("canvas4"), document.getElementById("img4"), true, true, true);
    test(document.getElementById("canvas5"), document.getElementById("img5"), false, true, true);
    test(document.getElementById("canvas6"), document.getElementById("img6"), false, false, true);
  };
</script>

2

There are 2 answers

0
Palash Bansal On

In Safari on Mac and iPhone, when premultipliedAlpha: false while creating a webgl context, toDataURL returns an image that is vertically flipped.

This bug is still present in Safari in 2021. As a workaround, we can copy to a canvas with 2d context, copy canvas data to that and call toDataURL.


function getDataUrl(origCanvas, mimeType){
        var canvas = document.createElement( 'canvas' );
        canvas.width = origCanvas.width;
        canvas.height = origCanvas.height;
        var destCtx = canvas.getContext('2d');
        if(!destCtx) {
            console.error("Cannot create context")
            return ""
        }
        destCtx?.drawImage(origCanvas, 0, 0);

        return destCtx.canvas.toDataURL(mimeType);
}

0
Dr Bearhands On

Stumbled across this problem myself. I think it's just a bug in safari: when you set premultipliedAlpha: false when getting the gl context, canvas.toDataURL() will return an upside down image. Sent a bug report to apple.