Search Unity

WebGL - Print Canvas?

Discussion in 'Web' started by QuantumRevenger, Jan 1, 2016.

  1. QuantumRevenger

    QuantumRevenger

    Joined:
    Mar 22, 2014
    Posts:
    25
    Hey Guys,
    Happy New Year by the way! :)

    I am trying to print the end results of my mini-game from WebGL,
    at the moment I have tried using a .jslib and C# which fires my javascript.

    My javascript so far has been:
    Window.Print() opens dialog and prints blank page.

    and a more elaborate:
    Code (JavaScript):
    1. var dataUrl = document.getElementById('canvas').toDataURL(); //attempt to save base64 string to server using this var
    2.     var windowContent = '<!DOCTYPE html>';
    3.     windowContent += '<html>'
    4.     windowContent += '<head><title>Your Wheel of Life</title></head>';
    5.     windowContent += '<body>'
    6.     windowContent += '<img src="' + dataUrl + '">';
    7.     windowContent += '</body>';
    8.     windowContent += '</html>';
    9.     var printWin = window.open('','','width=1134,height=638');
    10.     printWin.document.open();
    11.     printWin.document.write(windowContent);
    12.     printWin.document.close();
    13.     printWin.focus();
    14.     printWin.print();
    15.     printWin.close();
    That also just opens a new dialog and prints a blank page.

    So does anyone have any suggestions because I am a loss?

    Cheers,

    Jordan.
     
  2. alexsuvorov

    alexsuvorov

    Unity Technologies

    Joined:
    Nov 15, 2015
    Posts:
    327
    Hello QuantumRevenger.

    The reason why you are getting a blank page is because the canvas drawing buffer is empty at the moment of the image capture. Your code would only work reliably if the canvas webgl context was created with preserveDrawingBuffer flag. For example, you could add the following line to your game js
    Code (JavaScript):
    1. ... ,createContext:(function(canvas,webGLContextAttributes){
    2.  
    3. webGLContextAttributes.preserveDrawingBuffer=true;
    4.  
    5. if(typeof webGLContextAttributes.majorVersion==="undefined"&& ...
    and this would guarantee reliable webgl canvas capture with .toDataURL() at any given moment. However, this would also affect the game performance and therefore is not recommended.

    Luckily, Unity provides you with a mechanism to determine an appropriate moment for the frame capture. The following adjustment would make your code work:
    Code (CSharp):
    1.     [DllImport("__Internal")]
    2.     private static extern void PrintFrameJs();
    3.  
    4.     IEnumerator PrintRoutine() {
    5.         yield return new WaitForEndOfFrame ();
    6.         PrintFrameJs ();
    7.     }
    8.  
    9.     void PrintFrame () {
    10.         StartCoroutine (PrintRoutine ());
    11.     }
    Moreover, you can go even further and make your code even more reliable, avoiding js toDataURL call, and providing the plugin function with already prepared image data:
    Code (CSharp):
    1.     [DllImport("__Internal")]
    2.     private static extern void PrintFrameJs(byte[] img, int size);
    3.  
    4.     IEnumerator PrintRoutine() {
    5.         yield return new WaitForEndOfFrame ();
    6.         Texture2D tex = new Texture2D (Screen.width, Screen.height);
    7.         tex.ReadPixels (new Rect(0, 0, Screen.width, Screen.height), 0, 0);
    8.         tex.Apply ();
    9.         byte[] img = tex.EncodeToPNG();
    10.         PrintFrameJs (img, img.Length);
    11.     }
    12.  
    13.     void PrintFrame () {
    14.         StartCoroutine (PrintRoutine ());
    15.     }
    which can be handled with the following plugin:
    Code (JavaScript):
    1. var PrintPlugin = {
    2.   PrintFrameJs: function(img, size) {
    3.     var binary = '';
    4.     for (var i = 0; i < size; i++)
    5.       binary += String.fromCharCode(HEAPU8[img + i]);
    6.     var dataUrl = 'data:image/png;base64,' + btoa(binary);
    7.     // use dataUrl for printing etc.
    8.   },
    9. };
    10. mergeInto(LibraryManager.library, PrintPlugin);
    And Happy New Year by the way!
     
    Last edited: Jan 1, 2016
  3. QuantumRevenger

    QuantumRevenger

    Joined:
    Mar 22, 2014
    Posts:
    25
    Hello Alexsuvorov,

    Thank you very much for the advice above, my client is very happy with the end product.

    I have encountered another snag when it was uploaded onto the site, how do I make the canvas scale with browser size and retain a 16:9 aspect ratio?

    Kind regards,

    Jordan.
     
  4. lloydsummers

    lloydsummers

    Joined:
    May 17, 2013
    Posts:
    359
    You'd have to do it through CSS or Javascript.
     
  5. QuantumRevenger

    QuantumRevenger

    Joined:
    Mar 22, 2014
    Posts:
    25
    Cheers, all sorted through CSS.
     
  6. nsmith1024

    nsmith1024

    Joined:
    Mar 18, 2014
    Posts:
    870
    document.getElementById('canvas') doesnt work anymore in unity 5.6, returns null.

    all my Javascript plugin code is now broken!!
     
  7. alexsuvorov

    alexsuvorov

    Unity Technologies

    Joined:
    Nov 15, 2015
    Posts:
    327
    Hello nsmith1024.

    In Unity 5.6 you can access the game canvas in the following ways:
    a) From a .jslib plugin it should be accessible as Module.canvas
    b) From the embedding page it should be accessible as gameInstance.Module.canvas, where "gameInstance" is the game instance the canvas belongs to.

    P.S. You might also want to check the alternative solution using ReadPixels (described above), which is more reliable.
     
  8. mitchmeyer1

    mitchmeyer1

    Joined:
    Sep 19, 2016
    Posts:
    32
    @alexsuvorov Do you know how to set preserveDrawingBuffer=true for a Unity 2017.2? I cannot do your second option of triggering the right time to print at the end of the build because my application of this is for reporting unity crashes to my API. I have a js program that overrides the js alert to trigger a new HTML screen, API call, and hopefully send a screenshot to my API if I can figure it out. Thanks!
     
  9. Marco-Trivellato

    Marco-Trivellato

    Unity Technologies

    Joined:
    Jul 9, 2013
    Posts:
    1,654
    The default value of webglContextAttributes.preserveDrawingBuffer is now defined in the generated json file and you can override it at instantiation time:
    Code (csharp):
    1. var gameInstance = UnityLoader.instantiate("gameContainer", "%UNITY_WEBGL_BUILD_URL%", {onProgress: UnityProgress,
    2.   Module: {
    3.     webglContextAttributes: {"preserveDrawingBuffer": true},
    4.   }
    5. });
     
    matasoy and FionNoir like this.
  10. mitchmeyer1

    mitchmeyer1

    Joined:
    Sep 19, 2016
    Posts:
    32
    Worked perfect thanks so much
     
  11. bdovaz

    bdovaz

    Joined:
    Dec 10, 2011
    Posts:
    1,052
    Can you at some point document all those module options that we can set or override?

    https://docs.unity3d.com/Manual/webgl-templates.html

    Now we need to reverse engineer or search or ask in the forums to see an answer.
     
  12. mitchmeyer1

    mitchmeyer1

    Joined:
    Sep 19, 2016
    Posts:
    32
    @Marco-Trivellato Also is there any performance implications from setting preserveDrawingBuffer to true? Is there any downsides to setting this to true?
     
  13. Marco-Trivellato

    Marco-Trivellato

    Unity Technologies

    Joined:
    Jul 9, 2013
    Posts:
    1,654
    Here is that the WebGL specs say: "While it is sometimes desirable to preserve the drawing buffer, it can cause significant performance loss on some platforms. Whenever possible this flag should remain false and other techniques used."

    You may want to try with your project on different browsers to see if performance becomes an issue.
     
    mitchmeyer1 likes this.
  14. Erik-Sombroek

    Erik-Sombroek

    Joined:
    Jul 21, 2015
    Posts:
    10
    @alexsuvorov @Marco-Trivellato , I had a question. Would it be possible to snap a screenshot of the application without using the preserveDrawingBuffer? On the Three.js forums they say that if you run the ReadPixels/toDataUrl directly after a render() the image is not yet cleared and you can retrieve it. Would be possible, without changing anything to the source of the game, to tap into this render moment? I tried things like running a command in the Module.postMainLoop without success. I see a lot of things are being exposed on window.unityGame, but I'm not sure where to look for this render moment. Thanks in advance for your answer!