Looking to let a user export a screenshot or other image from within a webgl app so I want to give a user the contents of a Texture2D in effect. As much a js/php question as Unity really Now I can save image to a server and then direct them to the URL but seems very wasteful when the data is to hand so can it be done another way?
I use megagrab for this. Aviable in the asset store. Works perfect for HiRes screenshot downloads, or upload them direct to a defined location.
Yes but does not handle download through webgl right? It is not hard to do the grab part with a Render Texture, its offering to download. Guess it may have to be uploaded to a URL and the user pointed to that
my solution for webGL: Megagrab to create the screenshot, and following code to download it: Spoiler Code (CSharp): [DllImport("__Internal")] private static extern void ImageDownloader(string str, string fn); public static byte[] ssData = null; public static string imageFilename = ""; void DownloadScreenshot () { if(ssData != null) { Debug.Log("Downloading..."+imageFilename); ImageDownloader(System.Convert.ToBase64String(imageData), imageFilename); } } .jslib in the Assets/Plugins folder: (ImageDownloader.jslib) Spoiler Code (JavaScript): var ImageDownloaderPlugin = { ImageDownloader: function(str, fn) { var msg = Pointer_stringify(str); var fname = Pointer_stringify(fn); var contentType = 'image/jpeg'; function fixBinary (bin) { var length = bin.length; var buf = new ArrayBuffer(length); var arr = new Uint8Array(buf); for (var i = 0; i < length; i++) { arr[i] = bin.charCodeAt(i); } return buf; } var binary = fixBinary(atob(msg)); var data = new Blob([binary], {type: contentType}); var link = document.createElement('a'); link.download = fname; link.innerHTML = 'DownloadFile'; link.setAttribute('id', 'ImageDownloaderLink'); if(window.webkitURL != null) { link.href = window.webkitURL.createObjectURL(data); } else { link.href = window.URL.createObjectURL(data); link.onclick = function() { var child = document.getElementById('ImageDownloaderLink'); child.parentNode.removeChild(child); }; link.style.display = 'none'; document.body.appendChild(link); } link.click(); } }; mergeInto(LibraryManager.library, ImageDownloaderPlugin);
Thanks for the solution. Does anyone have an idea to convert/expand this to a generic file downloader to cover other content types like doc, pdf...etc.? I'm not a Javascript coder but when I skimmed the code I couldn't find anything image specific except the content type. Any ideas?
This is the one I use personally for different file types. You simply pass it a byte[] array, the length of the byte array and the desired file name. The mouse event is a little bit of a hack to get it to download but otherwise it's fairly simple code. Code (JavaScript): DownloadFile : function(array, size, fileNamePtr) { var fileName = UTF8ToString(fileNamePtr); var bytes = new Uint8Array(size); for (var i = 0; i < size; i++) { bytes[i] = HEAPU8[array + i]; } var blob = new Blob([bytes]); var link = document.createElement('a'); link.href = window.URL.createObjectURL(blob); link.download = fileName; var event = document.createEvent("MouseEvents"); event.initMouseEvent("click"); link.dispatchEvent(event); window.URL.revokeObjectURL(link.href); } C# example in case of a screenshot (CaptureScreenshotAsTexture introduced in 2018.1 function I believe): Code (CSharp): [DllImport("__Internal")] private static extern void DownloadFile(byte[] array, int byteLength, string fileName); var texture = ScreenCapture.CaptureScreenshotAsTexture(); byte[] textureBytes = texture.EncodeToPNG(); DownloadFile(textureBytes, textureBytes.Length, "screenshot.png"); Destroy(texture); I used a PNG here but it works for any file type. No base64 string conversion needed.
I just tested JJJohan's solution, and I give it high marks. I was replacing a hacky no-longer-working solution from 2015, and this simplified my code substantially.
I tried both solutions, even in an empty project using 2018.3.5f1 and my images comes up pitch black (I believe transparent if it's png). sumpfkraut , JJJohan do you have any ideas what might be wrong? I tried a lot of stuff past few hours but couldn't get it to work so had to ask at this point
I just tried again using exact same js you posted and; Code (CSharp): public void DownloadScreenshot() { var texture = ScreenCapture.CaptureScreenshotAsTexture(); byte[] textureBytes = texture.EncodeToJPG(); ImageDownloader(System.Convert.ToBase64String(textureBytes), imageFilename); } byte array looks good in editor, I mean I see some values which shouldn't be the case with pitch black image. I'm using .net 4.x runtime and api compatibility level. managed stripping level is low. tried using both asm and web assembly. edit: this is an empty project so there is no other scripts, post processing or fancy camera stuff.
I see that you are encoding the data as base64 before sending it to the jslib. If you're using the jslib snippet I posted then that won't work, as it expects you to just pass the byte array as such: Code (CSharp): [DllImport("__Internal")] private static extern void DownloadFile(byte[] array, int byteLength, string fileName); private void DownloadScreenshot() { Texture2D texture = ScreenCapture.CaptureScreenshotAsTexture(); byte[] textureBytes = texture.EncodeToJPG(); DownloadFile(textureBytes, textureBytes.Length, "screenshot.jpg"); Destroy(texture); } I've slightly modified my jslib snippet a few posts above so it cleans up better and uses Utf8ToString since it looks like Pointer_Stringify is deprecated in newer Emscripten versions.
I tried your version before and again today but I'm getting same results. At this point I'm pretty sure it's not code related but there's something else (locale? unity/build settings? version?). Thanks a lot to you both for responding guys, I really appreciate it. I'll post again if I can find the issue Edit: actually I just tried it on a totally different machine with EN-US locale (I'm saying this as I had problems with that before) and had the same result. I'm totally confused now.
Edit: It all works now, I think it was because of my wrong wait till end of frame part I never checked. Can't believe how many hours I wasted on that Thanks again for all the help!
and if you want a upscaled screenshot, you can try this: Code (CSharp): IEnumerator RecordUpscaledFrame(int screenshotUpscale) { yield return new WaitForEndOfFrame(); int resWidthN = Camera.main.pixelWidth * screenshotUpscale; int resHeightN = Camera.main.pixelHeight * screenshotUpscale; string dateFormat = "dd-MM-yyyy-HH-mm-ss"; RenderTexture rt = new RenderTexture(resWidthN, resHeightN, 24); Camera.main.targetTexture = rt; TextureFormat tFormat = TextureFormat.RGB24; Texture2D screenShot = new Texture2D(resWidthN, resHeightN, tFormat, false); Camera.main.Render(); RenderTexture.active = rt; screenShot.ReadPixels(new Rect(0, 0, resWidthN, resHeightN), 0, 0); Camera.main.targetTexture = null; RenderTexture.active = null; byte[] bytes = screenShot.EncodeToJPG(); string filename = resWidthN.ToString() + "x" + resHeightN.ToString() + "px_" + System.DateTime.Now.ToString(dateFormat); Object.Destroy(rt); Object.Destroy(screenShot); }
Alternatively, Screenshot.CaptureScreenshotAsTexture has an integer parameter overload to set the resolution scale .
the CaptureScreenshotAsTexture parameter scale the rendered image. the result is a really bad quality.
Interesting, I figured it'd be a bit more useful than simply scaling the existing output. Good to know, thanks.
Hi, thank you very much for these informations! Exactly what I was looking for and very useful! Unfortunately I can't make it work for me. Are these examples still working? But maybe I'm just too stupid to make it work, I've never dealt with jslib files or anything like that So this is how I'm trying to make it work: 1. I'm creating a .jslib file called DownloadFile.jslib which I place under /assets/plugins and which looks like this : Code (JavaScript): var DownloadFile = { DownloadFile : function(array, size, fileNamePtr) { var fileName = UTF8ToString(fileNamePtr); var bytes = new Uint8Array(size); for (var i = 0; i < size; i++) { bytes[i] = HEAPU8[array + i]; } var blob = new Blob([bytes]); var link = document.createElement('a'); link.href = window.URL.createObjectURL(blob); link.download = fileName; var event = document.createEvent("MouseEvents"); event.initMouseEvent("click"); link.dispatchEvent(event); window.URL.revokeObjectURL(link.href); link.parentNode.removeChild(link); } }; 2. I create a C# Script in Unity which looks like this: Code (CSharp): using System.Collections; using System.Collections.Generic; using System.Runtime.InteropServices; using UnityEngine; public class DownloadImage : MonoBehaviour { public Texture2D img; void Start() { img = this.GetComponent<CreateColorShapePC>().img; } [DllImport("__Internal")] private static extern void DownloadFile(byte[] array, int byteLength, string fileName); public void DownloadSaved() { Texture2D texture = img; byte[] textureBytes = texture.EncodeToJPG(); DownloadFile(textureBytes, textureBytes.Length, "image.jpg"); Destroy(texture); } } (I want to let the user download an image (img) which is created in another script (CreateColorShapePC)). 3. I create a button which calls the function DownloadSaved(). Is this how you make it work? Because in my case it always gives me an Error, when I try it online. I'm very thankful for any help!!
We'd have to know what the error is to figure out the problem. If your texture doesn't exist (is null) that'll definitely be a problem. It might also be a problem if you intend to keep using the texture after you hit the button and you might want to remove the Destroy(texture) line in that case.
Thanks for the script, it works wonderfully, with a small change. The last line Code (JavaScript): link.parentNode.removeChild(link); had to be removed, link.parentNode was null. I suppose because the link was never added to the DOM, it was detached, hence had no parent.
Ah, nice find. Interestingly my browser must have silently ignored it - I've edited my original post to prevent confusing anyone else.
According to this: https://docs.unity3d.com/Manual/webgl-interactingwithbrowserscripting.html javascript functions in Unity need to follow a certain syntax, this is what fixed my problems so I thought I would share.
Currently trying to get the sample by JJJohan to work. Been debugging for a while now and my problem is that the picture downloaded is the right size, but all black? The data just before sending it to the jslib is correct. I've tried printing out the image data and displaying it in the browser as Base64 which works (not using Base64 in the code). Any ideas? EDIT: Interestingly, when I run the game in the Editor and print the Base64 of the image in C#, it gets one value. But when I run it in the browser using WebGL the exact same printout is suddenly a completely different Base64 string... The first string can be put in a data string and the image can be viewed, while the other one is black. EDIT2: In the end I couldn't solve it and had to resolve to using a package from the Asset Store so I could progress. Will have to back to this issue at another time. The package did resolve it however (Screenshot Companion).
Hi jra8908, I have the same problem than you, and I 'm going to use the same asset; Screenshot Companion. Did you managed to use it to solve the black screen ? How did you get the picture from the asset API ? Thank's a lot if you can help me !
I created a free-to-use plugin for anyone wanting to save/download a generic file in WebGL. It works seamlessly with all major browsers and operating systems, and is as simple as calling 'WebGLFileSaver.SaveFile()'. Get it here: https://github.com/nateonusapps/WebGLFileSaverForUnity
I just solved this myself and so wanted to respond to you (and also jra8908), I think the issue you were running into was that WebGL was not rendering the image out properly with CaptureScreenshotAsTexture. It's better to put it in a coroutine and do a WaitForEndOfFrame yield. I've fixed JJJohan's code for you below: Code (CSharp): [DllImport("__Internal")] private static extern void DownloadFile(byte[] array, int byteLength, string fileName); public IEnumerator DownloadScreenshot() { yield return new WaitForEndOfFrame(); Texture2D texture = ScreenCapture.CaptureScreenshotAsTexture(1); byte[] textureBytes = texture.EncodeToJPG(); DownloadFile(textureBytes, textureBytes.Length, "screenshot.jpg"); Destroy(texture); } void Update() { // Example of how to call the coroutine if (Input.GetKeyDown(KeyCode.S)) { StartCoroutine(DownloadScreenshot()); } }
This plugin works like a charm! Thank you very much for making it available! I managed to export an .fbx file of my gameobject that is instantiated at runtime in my WebGL build. This works like Code (CSharp): EditorUtility.SaveFilePanel($"Export {objectToExport} as .fbx", "", objectToExport.name + ".fbx", "fbx"); in Editor. Code (CSharp): using UnityEngine; using UnityFBXExporter; public class WebGLBuildFBXExportAtRuntime : MonoBehaviour { //fbx exporter package by https://github.com/KellanHiggins/UnityFBXExporter // webgl file saver plugin by https://github.com/nateonusapps/WebGLFileSaverForUnity //make sure that the objectToExport has a MeshFilter and MeshRenderer Component with some Material assigned to it // it does not have to have its own mesh, the meshes of children will be exported with their respective material colors [SerializeField] private GameObject objectToExport; [SerializeField] private Material someMaterial; public void DownloadFBX() { // to assign a material, skip this if you already have one objectToExport.GetComponent<MeshRenderer>().material = someMaterial; string content = FBXExporter.MeshToString(objectToExport, null, true, true); // octet-stream is the right MIME Type for fbx files WebGLFileSaver.SaveFile(content, "myAmazingObject.fbx", "application/octet-stream"); } } To use it with a UI Download Button, you can attach this class to your button and access the public "DownloadFBX()" function in the OnClick event. It took me a couple of days to figure this out. I hope it benefits someone in the same situation
Would you know by chance how to get this setup to download a Png? I keep getting nothing when I try and use this
byte[] bytes = screenShot.EncodeToJPG(); //JPG byte[] bytes = screenShot.EncodeToPNG(); // PNG c# Code (CSharp): [DllImport("__Internal")] public static extern void DownloadFile(byte[] array, int byteLength, string fileName); IEnumerator RecordUpscaledFrame(int screenshotUpscale) { yield return new WaitForEndOfFrame(); int resWidthN = Camera.main.pixelWidth * screenshotUpscale; int resHeightN = Camera.main.pixelHeight * screenshotUpscale; string dateFormat = "yyyy-MM-dd-HH-mm-ss"; string filename = resWidthN.ToString() + "x" + resHeightN.ToString() + "px_" + System.DateTime.Now.ToString(dateFormat); Texture2D screenShot = ScreenCapture.CaptureScreenshotAsTexture(screenshotUpscale); byte[] texture = screenShot.EncodeToPNG(); DownloadFile(texture, texture.Length, filename + ".png"); Object.Destroy(screenShot); } DownloadFile.jslib Spoiler Code (JavaScript): var DownloadFilePlugin = { DownloadFile : function(array, size, fileNamePtr) { var fileName = UTF8ToString(fileNamePtr); var bytes = new Uint8Array(size); for (var i = 0; i < size; i++) { bytes[i] = HEAPU8[array + i]; } var blob = new Blob([bytes]); var link = document.createElement('a'); link.href = window.URL.createObjectURL(blob); link.download = fileName; var event = document.createEvent("MouseEvents"); event.initMouseEvent("click"); link.dispatchEvent(event); window.URL.revokeObjectURL(link.href); } }; mergeInto(LibraryManager.library, DownloadFilePlugin);
I tried what you posted and am getting nothing https://fracturedroguestudios.com/PrintTest31/index.html I made a simple script that houses the Ienumerator and has a call in it to startthe coroutine [DllImport("__Internal")] public static extern void DownloadFile(byte[] array, int byteLength, string fileName); IEnumerator RecordUpscaledFrame(int screenshotUpscale) { yield return new WaitForEndOfFrame(); int resWidthN = Camera.main.pixelWidth * screenshotUpscale; int resHeightN = Camera.main.pixelHeight * screenshotUpscale; string dateFormat = "yyyy-MM-dd-HH-mm-ss"; string filename = resWidthN.ToString() + "x" + resHeightN.ToString() + "px_" + System.DateTime.Now.ToString(dateFormat); Texture2D screenShot = ScreenCapture.CaptureScreenshotAsTexture(screenshotUpscale); byte[] texture = screenShot.EncodeToPNG(); DownloadFile(texture, texture.Length, filename + ".png"); Object.Destroy(screenShot); } public void RunRecordUpscaledFrame() { StartCoroutine("RecordUpscaledFrame(1)"); } than a button calls the runrecordupscaledframe on click
How would you set this up for a PNG or a JPG I got it downloading but when I open the downloaded file there is nothing there
the console say Coroutine 'RecordUpscaledFrame(1)' couldn't be started StartCoroutine("RecordUpscaledFrame(1)"); i think you should use Code (CSharp): StartCoroutine(RecordUpscaledFrame(1)); or Code (CSharp): StartCoroutine("RecordUpscaledFrame", 1); https://docs.unity3d.com/ScriptReference/MonoBehaviour.StartCoroutine.html
That seems to work only question I have is why the clear flag dosent come with it but thats a triffle that can be dealt with thank you so much
thanks for that unfornatly that isnt working with PNG / JPG as its downloading everything only in UTF-8 String. Anyway i would like to give you my little upgrade for exporting FBX with all Childrens. using UnityEngine; using UnityFBXExporter; using System.IO; using System.Runtime.InteropServices; using System; using System.Text; public class EXPORTFBX1 : MonoBehaviour { public GameObject objectToExport; public void DownloadFBX() { string path = Path.Combine(Application.streamingAssetsPath, "data"); path = Path.Combine(path, "eled-screen"+ ".fbx"); if (!Directory.Exists(Path.GetDirectoryName(path))) { Directory.CreateDirectory(Path.GetDirectoryName(path)); } FBXExporter.ExportGameObjToFBX(objectToExport, path, false, false); //string content = FBXExporter.MeshToString(objectToExport, null, true, true); var bytes = System.IO.File.ReadAllBytes(path); string encodedText = Encoding.UTF8.GetString (bytes); //most important, cause Base64 Conversation doesnt work with this WEBGLFileSaver Plugin. Debug.Log (encodedText); // octet-stream is the right MIME Type for fbx files WebGLFileSaver.SaveFile(encodedText, "eled-screen-3dmodel.fbx", "application/octet-stream"); } }
im looking to create and download a zip file that contains an image and a text file could webglfilesaver do that? how?
User customizes an Avatar on WebGL Mobile, clicks download, model converts to GLTF, then you can load it natively in Augmented Reality or share the gltf model through native share. This could be a really powerful tool for that. @jonas-echterhoff tagging you, because this use case could really bring in a lot of eyeballs with AR.