1

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>
appr
  • 11
  • 4
  • Its called [`preserveDrawingBuffer`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/getContext#Parameters) – LJᛃ Jun 27 '15 at 00:27
  • 1
    possible duplicate of [Canvas toDataURL() returns blank image only in Firefox](http://stackoverflow.com/questions/26783586/canvas-todataurl-returns-blank-image-only-in-firefox) – LJᛃ Jun 27 '15 at 00:28
  • @LJ_1102 i don't think that this is somehow related to preserveDrawingBuffer: specially for you i've created [another testcase](http://delightex.com/test/test1.html) with preserveDrawingBuffer set to true. it still works incorrectly in safari. – appr Jun 27 '15 at 21:53
  • @LJ_1102 i've also modified the attached code snippet to show that even with preserveDrawingBuffer it still works incorrectly in safari. – appr Jun 27 '15 at 22:02
  • @LJ_1102 please also note that toDataURL() call here is made right after actual render so there is nothing async, compared to the question you have linked. – appr Jun 27 '15 at 22:04

2 Answers2

3

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);
}

Palash Bansal
  • 708
  • 4
  • 9
0

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.

Dr Bearhands
  • 303
  • 2
  • 11