Search Unity

HOW TO: screenshot in a new tab of the browser (without pop-up blocking).

Discussion in 'Web' started by danielesuppo, Nov 7, 2016.

  1. danielesuppo

    danielesuppo

    Joined:
    Oct 20, 2015
    Posts:
    331
    Hello all,
    for all the newbies (like me) that struggled a lot to understand how to let the users to take a screenshot of their webgl game (or application), and directly open it in a new tab of their browser without blocking, here a brief tutorial (without explanations).
    I've resarched a bit to understand how to use data:URI, that does not write anything on disk (very important since we don't know the target platform) but simply use the buffer, and I've use the preciuos tutorial from Valentin Simonov to open the new tab without blocking.
    http://va.lent.in/
    Please support him! He's great!

    I'm not a coder so probably it will not be the best, and it will have some mistakes (so forgive me), but it work.


    1) First you need a button that users will push to take the screenshot and open it in a new tab of their browser, so create a button.

    2) Create this script and named it "TakeScreenshot" (and put it maybe under your "script" folder)
    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using System.Runtime.InteropServices;
    4.  
    5.  
    6. public class TakeScreenshot : MonoBehaviour {
    7.  
    8.     // Use this for initialization
    9.     public void Screenshot () {
    10.         StartCoroutine(UploadPNG());
    11.         //Debug.log (encodedText);
    12.     }
    13.  
    14.     IEnumerator UploadPNG() {
    15.         // We should only read the screen after all rendering is complete
    16.         yield return new WaitForEndOfFrame();
    17.  
    18.         // Create a texture the size of the screen, RGB24 format
    19.         int width = Screen.width;
    20.         int height = Screen.height;
    21.         var tex = new Texture2D( width, height, TextureFormat.RGB24, false );
    22.  
    23.         // Read screen contents into the texture
    24.         tex.ReadPixels( new Rect(0, 0, width, height), 0, 0 );
    25.         tex.Apply();
    26.  
    27.         // Encode texture into PNG
    28.         byte[] bytes = tex.EncodeToPNG();
    29.         Destroy( tex );
    30.  
    31.         //string ToBase64String byte[]
    32.         string encodedText = System.Convert.ToBase64String (bytes);
    33.    
    34.         var image_url = "data:image/png;base64," + encodedText;
    35.  
    36.         Debug.Log (image_url);
    37.  
    38.         #if !UNITY_EDITOR
    39.         openWindow(image_url);
    40.         #endif
    41.     }
    42.  
    43.     [DllImport("__Internal")]
    44.     private static extern void openWindow(string url);
    45.  
    46. }      
    47.  


    3) Create this other script
    (many thanks to Valentin Simonov! You can watch his thread at http://va.lent.in/opening-links-in-a-unity-webgl-project/)
    and named it "PressHandler" (and put it maybe under your "script" folder)

    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEngine.EventSystems;
    3. using System;
    4. using UnityEngine.Events;
    5.  
    6. public class PressHandler : MonoBehaviour, IPointerDownHandler
    7. {
    8.     [Serializable]
    9.     public class ButtonPressEvent : UnityEvent { }
    10.  
    11.     public ButtonPressEvent OnPress = new ButtonPressEvent();
    12.  
    13.     public void OnPointerDown(PointerEventData eventData)
    14.     {
    15.         OnPress.Invoke();
    16.     }
    17. }

    4) Create this last Java script, give it the name OpenWindow.jslib (not OpenwWindow.js, be careful) and put it under \Plugins\Webgl folder.
    (Again, many thanks to Valentin Simonov! You can watch his thread at http://va.lent.in/opening-links-in-a-unity-webgl-project/)

    Code (JavaScript):
    1. var OpenWindowPlugin = {
    2.     openWindow: function(link)
    3.     {
    4.         var url = Pointer_stringify(link);
    5.         document.onmouseup = function()
    6.         {
    7.             window.open(url);
    8.             document.onmouseup = null;
    9.         }
    10.     }
    11. };
    12.  
    13. mergeInto(LibraryManager.library, OpenWindowPlugin);


    5) Create a new empty GameObject, call it "Script" and attach to it "TakeScreenshot" script.

    6) Select your (previously created) button in Inspector, and drag "PressHandler" script on it.

    7) Under "Runtime Only" put the (previously created) GameObject "Script", and from the functions on the right choose TakeScreenshot/Screenshot()


    THAT'S ALL!
    You must create a WebGL build to see it working.
    When you will push your button the function "Screenshot", from the script "TakeScreenshot", attached to the GameObject "Script", will be called, and a new tab (without pop-up blocking) should open with your screenshot.

    UNFORTUNATELY
    we are not still able to use it on mobile device: on mobiles, as on laptops (in which you use just the Touchpad), the "touch" seem to have a very different behavior from the "click" of the mouse . It seem to be a JavaScript issue and we are fighting on it, but with no luck for now

    Enjoy.
    Daniele
     
    Last edited: Nov 7, 2016
  2. alexsuvorov

    alexsuvorov

    Unity Technologies

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

    I would assume that the purpose of opening the screenshot in a new tab is to let the user save it to his local drive from the browser later. Just in the case if you consider saving the screenshot to the local drive immediately, you may use the following code, which will popup a save dialog with the target filename you specify. Just replace the window.open(url) with:
    Code (JavaScript):
    1. function getFormattedTimestamp() {
    2.   return new Date(Date.now() - 60000 * new Date().getTimezoneOffset()).toISOString().replace(/\..*/g, '').replace(/T/g, ' ').replace(/:/g, '-');
    3. }
    4.  
    5. var button = document.createElement("a");
    6. button.setAttribute("href", url);
    7. button.setAttribute("download", "Screenshot " + getFormattedTimestamp() + ".png");
    8. button.style.display = "none";
    9. document.body.appendChild(button);
    10. button.click();
    11. document.body.removeChild(button);
    About touch screens, did you try to register touchend event handler instead of mouseup?
     
    Marc-Saubion likes this.
  3. danielesuppo

    danielesuppo

    Joined:
    Oct 20, 2015
    Posts:
    331
    Hi alexsuvorov,
    thank-you for your always precious advices.
    Opening the screenshot in a new tab would be quite interesting expecially on mobile, because users could easily and quickly share their picture on socials (fb, instagram, etc) without to save, open, etc the new picture.

    Yes, I've tried with touchend but curiously it does not work (no new tab at all).
    The only way to make the JavaScript work on mobile in some way seem to use touchstart, like that:

    Code (JavaScript):
    1. var OpenWindowPlugin = {
    2.     openWindow: function(link)
    3.     {
    4.         var url = Pointer_stringify(link);
    5.         document.ontouchstart = function()
    6.         {
    7.             window.open(url);
    8.             document.ontouchstart = null;
    9.         }
    10.     }
    11. };
    12. mergeInto(LibraryManager.library, OpenWindowPlugin);

    but it behave quite strange, because when you click on the Unity button nothing seem to happen, but next (after that you have touched the button) you can touch wherever on the screen to open the new tab...
     
  4. danielesuppo

    danielesuppo

    Joined:
    Oct 20, 2015
    Posts:
    331
    Hello alexsuvorov,
    I've tried your javascript to save the picture, exactly as you suggested me but there are some issues:

    1) On Chrome does not open any popup save dialog, it just save the picture with the timestamp in download folder.
    2) On Safari does not open any popup save dialog and does not save any picture, but open the data:URI on the same tab (loosing the webgl page).
    3) On Internet Explorer (Windows 10) the pointer change to the "Working in background" pointer icon, but it does nothing and the pointer stay like that forever.

    Any idea?
    Many thanks!
    Daniele
     
  5. alexsuvorov

    alexsuvorov

    Unity Technologies

    Joined:
    Nov 15, 2015
    Posts:
    327
    Yes, you are right. This does not seem to be cross-browser at all. While in Chrome and Firefox you can control the download behavior via user settings (ask for download location every time or silently save to the default location), Safari does not support download attribute for links at all (IE seems to have some navigator.msSaveBlob which I haven't tried yet).
    I am currently not sure if there is any universal solution to this. Except maybe uploading the screenshot to the server and serving it from there, in which case download to the local drive can be enforced. Does not seem to be a very good solution either, but still can be considered as a fallback for browsers which do not support some functionality.

    P.S. Interestingly, but if you specify a weird MIME type, for example this:
    data:image/octet-stream;base64,...
    then Safari actually does download the image to the drive (just tried on 9.1.1), though you can not specify the download filename (will probably be "unknown").

    And about touchscreen, could you try to register document.onclick instead of document.ontouchstart, and check if this makes any difference for you?
     
    Last edited: Nov 17, 2016
  6. danielesuppo

    danielesuppo

    Joined:
    Oct 20, 2015
    Posts:
    331
    About touchscreen, I'm actually using touchend, with this new JavaScript seem to work fine. I'll try with onclick too, to check for any difference.

    But, right now, the very main issue with this new solution seem to be about memory (but only on mobile):
    when I click on my "take screenshot" button for the 2nd or the 3rd time I get an out of memory error (I'm using 64mb memory size in my build - both for PC and for mobile release - for compatibility on most devices).

    Is there a way to clean the buffer of something like that so that the user can take as many screenshots as he want (obviously without increasing the build memory size)?

    P.S. I thought that Destroy( tex ); could do the job, but it seem not be like that..

    Thanks a lot!
    Daniele
     
  7. XactiumDeveloper

    XactiumDeveloper

    Joined:
    Jun 14, 2017
    Posts:
    4
    Thanks for the script but I have a problem with it. When fullscreen, the screenshot produced only shows the bottom left corner and displays the rest black as shown in the png. What could be going wrong? Thanks

    download.png

    edit: Some additional info, If I hardcode the screen width and height to 1920*1080, it works fine but produces black borders on a non 1080p size. Note I'm using a retina display mac so this could well be the issue

    edit2: hard-coding doesn't actually seem to fix it, just randomly works seemingly, also tested on windows with the same cut off

    Final edit: Found a solution that works:
    http://answers.unity3d.com/questions/22954/how-to-save-a-picture-take-screenshot-from-a-camer.html
    I just edited it to use the js provided here and edited my UI to be on camera space
     
    Last edited: Jun 15, 2017
  8. MKeff

    MKeff

    Joined:
    Jan 26, 2018
    Posts:
    2
    Hello,

    these are really good scripts, they are working quite well for me. I've been looking for a while for code that can do just this, espeically saving to the loca drive directly.

    I wanted to see if any could give some advice on how to combine it with this script. It's made for recording a GIF replay of the last 3 seconds of gameplay. I'm trying to implement it in a WebGL to save in the same way you're doing here with a single frame.

    https://github.com/Chman/Moments

    I'm new to coding so any advice would be really helpful.
     
  9. ronjen20

    ronjen20

    Joined:
    Aug 29, 2018
    Posts:
    3
    Hello guys,
    Sorry to bother. I've tried the code above and it works fine. Thanks for that. :) but I have a question. Can I use a ingame camera as a screenshot capturer? Hope you guys will help :)
     
  10. Lokesh_Sivaprakasam

    Lokesh_Sivaprakasam

    Joined:
    Aug 2, 2017
    Posts:
    3
    Hi I am working on a Concept of Jewellary app
    Here is my WIP Demo



    and I need to make the same app for Desktop and WebGL Platforms.

    Kindly help me a way to save and load screenshot in runtime as I do my demo.
    I am newbie to scripting. where I went through this tutorial


    to achieve my demo
     
  11. ykswobel

    ykswobel

    Joined:
    Apr 5, 2018
    Posts:
    57
    Is it possible to have the script without a pressed button but with a shortcut key to press and have the screenshot?
     
  12. cmann

    cmann

    Joined:
    Aug 1, 2015
    Posts:
    31
    I've used the code above, combined with the following to successfully save screenshots in the browser:

    https://stackoverflow.com/questions...load-a-base64-encoded-image/57385966#57385966

    Code (CSharp):
    1. [DllImport("__Internal")]
    2. private static extern void SaveScreenshotWebGL(string filename, string data);
    Code (JavaScript):
    1. mergeInto(LibraryManager.library,
    2. {
    3.  
    4.     SaveScreenshotWebGL: function(filename, data)
    5.     {
    6.         const pageImage = new Image();
    7.         filename = Pointer_stringify(filename);
    8.        
    9.         if(!filename.endsWith('.png'))
    10.         {
    11.             filename += '.png';
    12.         }
    13.        
    14.         pageImage.src = Pointer_stringify(data);
    15.        
    16.         pageImage.onload = function()
    17.         {
    18.             const canvas = document.createElement('canvas');
    19.             canvas.width = pageImage.naturalWidth;
    20.             canvas.height = pageImage.naturalHeight;
    21.  
    22.             const ctx = canvas.getContext('2d');
    23.             ctx.imageSmoothingEnabled = false;
    24.             ctx.drawImage(pageImage, 0, 0);
    25.             saveScreenshot(canvas);
    26.         }
    27.  
    28.         function saveScreenshot(canvas)
    29.         {
    30.             const link = document.createElement('a');
    31.             link.download = filename;
    32.            
    33.             canvas.toBlob(function(blob)
    34.             {
    35.                 link.href = URL.createObjectURL(blob);
    36.                 link.click();
    37.             });
    38.         };
    39.     }
    40.  
    41. });
     
    wxxhrt likes this.
  13. DrSharky

    DrSharky

    Joined:
    Dec 7, 2016
    Posts:
    17
    For anyone who is looking to make this code work, it is almost correct.
    On line 14, the src should be set like this:
    pageImage.src = 'data:image/png;base64,' + base64string;


    Second of all if you're not sure how to use the plugin properly: In C#, make sure you're encoding your Texture2D object properly by using .EncodeToPNG(). Then convert the byte array to a base64 string using Convert.ToBase64String(byteArray), and input that output to the SaveScreenshotWebGL call, like so: SaveScreenshotWebGL("imageName.png", base64String);
     
  14. DrSharky

    DrSharky

    Joined:
    Dec 7, 2016
    Posts:
    17
    Ok, Unity forums are refusing to let me edit my post, but that line 14 correction is this:
    pageImage.src =  'data:image/png;base64,' + Pointer_stringify(data);
     
  15. Marks4

    Marks4

    Joined:
    Feb 25, 2018
    Posts:
    547
    You can use my asset to take screen shots and much more on webgl.
     
  16. Monsterwald

    Monsterwald

    Joined:
    Jan 19, 2020
    Posts:
    68
    doesn't seem to work, I get a tab of pure nothingness :(