Search Unity

How do I let the user load an image from their harddrive into a WebGL app?

Discussion in 'Web' started by Mikael-H, Jan 21, 2016.

  1. Mikael-H

    Mikael-H

    Joined:
    Apr 26, 2013
    Posts:
    309
    TL;DR;
    I need to let the user load a photo of herself into a WebGL app, preferably directly from her harddrive.



    I am creating a system that makes a complete UMA avatar from one front view photo and one side view photo of a user. For anyone interested this system is further described in this thread: http://forum.unity3d.com/threads/wip-uma-master-thesis-player-avatar-creator.343331/#post-2422671
    And demonstrated in this youtube clip:


    What I want to do is create a WebGL client that performs the photo segmentation and all of that image analysis magic and then stores the user avatar and data in a database. My plan is then to create a client package other developers can use to import the finished user avatar into their apps/games.

    I just can't figure out how to actually load an image from the harddrive in WebGL, given the security limitations of the platform. I tried to use dropbox/onedrive/gdrive publicly shared file urls but I run into issues with CORS and since this was a workaround in the first place I think this is the wrong way to go other than as a last resort.

    I suspect one could create a popup window using jslib and upload an image to it, create a texture and send backa texture pointer to the webgl app but my javascript skills are simply not up to the task.

    If anyone could help me get this to work or point me to the correct resources/tutorials I would be very grateful!
     
    Muteking likes this.
  2. alexsuvorov

    alexsuvorov

    Unity Technologies

    Joined:
    Nov 15, 2015
    Posts:
    327
    Hello Mikael H.

    It is very much possible to load an image (or any file) from the user hard drive into your WebGL application.
    You can not do this directly from your application, but you can implement a JavaScript plugin that will do the job. Normally I would suggest to transfer data between JavaScript and WebGL using module heap, but for people not familiar with the asm.js infrastructure it might be more convenient to use blobs. The whole process can be split into two separate tasks:

    1) Uploading an image using html button

    First, you need to implement a JavaScript plugin that will handle the browser file dialog, create a blob for the selected file and transfer the blob address to the WebGL application. You can create the following Assets/Plugins/ImageUploader.jslib file for that:
    Code (JavaScript):
    1. var ImageUploaderPlugin = {
    2.   ImageUploaderInit: function() {
    3.     var fileInput = document.createElement('input');
    4.     fileInput.setAttribute('type', 'file');
    5.     fileInput.onclick = function (event) {
    6.       this.value = null;
    7.     };
    8.     fileInput.onchange = function (event) {
    9.       SendMessage('Canvas', 'FileSelected', URL.createObjectURL(event.target.files[0]));
    10.     }
    11.     document.body.appendChild(fileInput);
    12.   }
    13. };
    14. mergeInto(LibraryManager.library, ImageUploaderPlugin);
    Now you can attach the following script to your Canvas, that will initialize the plugin and load the texture from the generated blob address:
    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using System.Runtime.InteropServices;
    4.  
    5. public class CanvasScript : MonoBehaviour {
    6.     [DllImport("__Internal")]
    7.     private static extern void ImageUploaderInit();
    8.  
    9.     IEnumerator LoadTexture (string url) {
    10.         WWW image = new WWW (url);
    11.         yield return image;
    12.         Texture2D texture = new Texture2D (1, 1);
    13.         image.LoadImageIntoTexture (texture);
    14.         Debug.Log ("Loaded image size: " + texture.width + "x" + texture.height);
    15.     }
    16.  
    17.     void FileSelected (string url) {
    18.         StartCoroutine(LoadTexture (url));
    19.     }
    20.  
    21.     void Start () {
    22.         ImageUploaderInit ();
    23.     }
    24. }

    2) Uploading an image using UI button

    This one is quite tricky. The idea would be of course to make the html browse button hidden, and simulate its click programmatically. We are however facing a security restriction here, specifically, it is not possible to launch the <input type="file"> onclick event handler programmatically, unless you make the click() call from another event handler initiated by the user. And as in most complex systems, in Unity WebGL input events are not processed directly but instead are passed through an intermediate queue. This means that the initial JavaScript event handler initiated by the user has been already processed by the time when you receive the OnClick event in the managed code.
    Nevertheless, there is a trick that we can perform to make it work: as soon as the user pushes the UI button down, we can register onclick JavaScript event for the whole WebGL canvas, so that when the user releases the button, we will get a user-initiated onclick JavaScript event, and perform the input click simulation in the handler.

    You will need the following Assets/Plugins/ImageUploader.jslib plugin:
    Code (JavaScript):
    1. var ImageUploaderPlugin = {
    2.   ImageUploaderCaptureClick: function() {
    3.     if (!document.getElementById('ImageUploaderInput')) {
    4.       var fileInput = document.createElement('input');
    5.       fileInput.setAttribute('type', 'file');
    6.       fileInput.setAttribute('id', 'ImageUploaderInput');
    7.       fileInput.style.visibility = 'hidden';
    8.       fileInput.onclick = function (event) {
    9.         this.value = null;
    10.       };
    11.       fileInput.onchange = function (event) {
    12.         SendMessage('Canvas', 'FileSelected', URL.createObjectURL(event.target.files[0]));
    13.       }
    14.       document.body.appendChild(fileInput);
    15.     }
    16.     var OpenFileDialog = function() {
    17.       document.getElementById('ImageUploaderInput').click();
    18.       document.getElementById('canvas').removeEventListener('click', OpenFileDialog);
    19.     };
    20.     document.getElementById('canvas').addEventListener('click', OpenFileDialog, false);
    21.   }
    22. };
    23. mergeInto(LibraryManager.library, ImageUploaderPlugin);
    And the following script to interact with it:
    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using System.Runtime.InteropServices;
    4.  
    5. public class CanvasScript : MonoBehaviour {
    6.     [DllImport("__Internal")]
    7.     private static extern void ImageUploaderCaptureClick();
    8.  
    9.     IEnumerator LoadTexture (string url) {
    10.         WWW image = new WWW (url);
    11.         yield return image;
    12.         Texture2D texture = new Texture2D (1, 1);
    13.         image.LoadImageIntoTexture (texture);
    14.         Debug.Log ("Loaded image size: " + texture.width + "x" + texture.height);
    15.     }
    16.  
    17.     void FileSelected (string url) {
    18.         StartCoroutine(LoadTexture (url));
    19.     }
    20.  
    21.     public void OnButtonPointerDown () {
    22.         ImageUploaderCaptureClick ();
    23.     }
    24. }
    The OnButtonPointerDown should be triggered by the Pointer Down event (available through the Event Trigger component, that you can attach to your UI button)

    The second method is of course not 100% reliable (for example, user can push the button, move the mouse outside the canvas, then release the button, so that the attached onclick event handler remains unprocessed), but for most use cases it should be quite appropriate.
     
    Last edited: Jan 21, 2016
  3. Mikael-H

    Mikael-H

    Joined:
    Apr 26, 2013
    Posts:
    309
    Thank you so much for your detailed explanation! I'll try this out and see if even I can get it to work :)
     
    jamestrue likes this.
  4. Mikael-H

    Mikael-H

    Joined:
    Apr 26, 2013
    Posts:
    309
    I just tried version 1 and it works great, thanks!
     
  5. alexsuvorov

    alexsuvorov

    Unity Technologies

    Joined:
    Nov 15, 2015
    Posts:
    327
    Great news!
    You may also want to try method 2. Not only you will get a native Unity button for opening a file, but you will also be able to make it cross-platform, so that your open file dialog will work both in the editor and in the browser:
    Code (CSharp):
    1.     public void OnButtonPointerDown () {
    2. #if UNITY_EDITOR
    3.         string path = UnityEditor.EditorUtility.OpenFilePanel("Open image","","jpg,png,bmp");
    4.         if (!System.String.IsNullOrEmpty (path))
    5.             FileSelected ("file:///" + path);
    6. #else
    7.         ImageUploaderCaptureClick ();
    8. #endif
    9.     }
     
    Noisecrime and Mikael-H like this.
  6. Mikael-H

    Mikael-H

    Joined:
    Apr 26, 2013
    Posts:
    309
    Ha you read my mind, I was just scratching my head, thinking: Damn how do I make this work cross-platform :) Thanks!

    So if I understand this correctly, if WWW-class just has a url (or filepath in this case) then it CAN load from the user's HD even with WebGL? I was under the impression that it would not work due to security reasons but obviously I was wrong because it DOES load :)
     
  7. alexsuvorov

    alexsuvorov

    Unity Technologies

    Joined:
    Nov 15, 2015
    Posts:
    327
    Yes, it does load, but only if the requesting page has also been loaded from the local hard drive. You may think of local filesystem (i.e. file://) as of a separate domain (while in fact it is a different protocol). As soon as you upload the requesting WebGL page to the server, the file:// WWW request becomes cross-origin and therefore can not be performed (otherwise you would be able to scan local file system of any user visiting your website :))
     
    Last edited: Jan 23, 2016
    xeniaosense and Mikael-H like this.
  8. Mikael-H

    Mikael-H

    Joined:
    Apr 26, 2013
    Posts:
    309
    Thanks, it is all becoming more clear now.

    Some further research suggests that both onedrive and dropbox have CORS enabled but you need to make some url modifications first:
    http://stackoverflow.com/questions/...-image-load-from-cross-enabled-site-is-denied
    https://social.msdn.microsoft.com/F...ort-cors-for-webaplication-?forum=onedriveapi

    So with all the help from you @alexsuvorov and the info provided in those links I think even I can produce a system with the ability to load images from anywhere. Nice! :)
     
  9. alexsuvorov

    alexsuvorov

    Unity Technologies

    Joined:
    Nov 15, 2015
    Posts:
    327
    I would just like to mention that setting up a simple proxy server will give you even more freedom, as you will be able to upload an image from almost any location on the web (with or without proper CORS). So that the user will be able to both paste an url of an image from the web, or upload an image from the local hard drive (like in google image search for example), all implemented using native Unity UI.
     
  10. Mikael-H

    Mikael-H

    Joined:
    Apr 26, 2013
    Posts:
    309
    Thanks I'll have to look into that!

    Thank you for all the help, that's what I call above and beyond! :)
     
  11. Mikael-H

    Mikael-H

    Joined:
    Apr 26, 2013
    Posts:
    309
    So, thanks a lot for the help earlier with this. My project is progressing. I am experiencing huge memory problems though,

    Loading any image that is too large (I am talking about maybe 3MB here) crashes the web gl app. This seems very strange to me, I have tried increasing the memory by very large amounts and still this issue occurs.

    I mean regardless of how the image is stored, DXT or whatever, it should never take up that much memory?? Am I missing something here?

    You can try the app here if you want:
    http://vikingcrew.net/Selfie_to_Avatar/index.html
     
  12. alexsuvorov

    alexsuvorov

    Unity Technologies

    Joined:
    Nov 15, 2015
    Posts:
    327
    Hello Mikael H.

    Yes, this is indeed weird. Do you think it would be possible to reproduce the same issue using an empty project with just an image loader and nothing else? If this is the case, could you share the exact code you are using to load the texture, so that the issue can be analyzed?
     
    Mikael-H likes this.
  13. Mikael-H

    Mikael-H

    Joined:
    Apr 26, 2013
    Posts:
    309
    I'll try reproducing it in a simpler project! Thanks for not giving up on me :D
     
  14. Mikael-H

    Mikael-H

    Joined:
    Apr 26, 2013
    Posts:
    309
    So... I have been trying to do some experiments... I have constructed a simple webgl app where all you do is upload a set of images. I then tried to crash it. It required loading a handful of images, <10. Since I didn't set memory size to more than 256 MB this isn't terrible, but not great either. I decided to do some profiling to measure how much memory is consumed by loading an image.

    When loading one image of a moderately ugly face (~1MB jpg file) the total memory usage goes from 16.5MB to 61.7MB. Looking at the individual memories it seems that the memory is changed for both unity memory (RAM I guess?) and gfx memory. As the memory increases by this much for a single image it isn't vere surprising I run out pretty fast.

    Is this working as intended or may there be a bug somewhere? Or maybe there's something I could do to reduce memory load? I guess I could resize the image to a smaller size if needed as long as I know what limits I have to live with.

    I tried to attach the zip of the project but the 3.05MB size is apparently too large for the max 4MB limit on the site :p

    Instead I attached an image of the moderately ugly face causing this whole mess and supply a download link: https://onedrive.live.com/redir?res...4319&authkey=!AFSn42mOEJWi53U&ithint=file,zip
     

    Attached Files:

  15. alexsuvorov

    alexsuvorov

    Unity Technologies

    Joined:
    Nov 15, 2015
    Posts:
    327
    Actually, the size is not that big, considering that even 24-bit uncompressed is 2271 * 3452 * 3 = 23518476. The real question here, I suppose, is why the memory does not get deallocated, right?
     
    Mikael-H likes this.
  16. Mikael-H

    Mikael-H

    Joined:
    Apr 26, 2013
    Posts:
    309
    Good point... I would've thought it should get compressed to DXT on the gpu and take take no room in RAM once it was loaded though. One stupid question... Does texture memory count towards the memory allocated for the web gl app?

    I just tried running in editor and I get the same results. That should indicate it is all my fault I suppose :D I think I should probably resize the image after loading it at least and save some memory that way. I don't think my algorithm for face segmentation or final texture building needs much more than 1024x1024 anyway...

    Also, in my main project, I search the image structure of several scale spaces to find faces, so an image will take up about twice as much memory due to that. So the memory allocation for the webgl app starts filling up fast I guess.
     
  17. alexsuvorov

    alexsuvorov

    Unity Technologies

    Joined:
    Nov 15, 2015
    Posts:
    327
    Hello Mikael H.

    I believe dynamically created Texture2D would retain a shadow copy of the texture data on the heap (I can double check on that). Nevertheless, lets try one experiment. Could you crash the following image uploader and let me know which image sizes / number of images can crash it (it has default 256 MB heap and updates the texture in its original resolution, so the size of the image does matter):
    http://files.unity3d.com/alexsuvorov/test/uploadimage/
     
    Mikael-H likes this.
  18. Mikael-H

    Mikael-H

    Joined:
    Apr 26, 2013
    Posts:
    309
    Sorry, missed that you had replied.

    Umh it crashes when uploading just one image if I use one that is approx. 3MB. If I use one that is approx. 1 MB then I can upload it >10 times.

    I actually managed to solve the problem with my own app by resizing any uploaded image to be at the most 1k pixels on either side. I can then segment both images and create a resulting texture wihtout breaking the max memory limit. So I'm pretty happy for now :)

    I need to do a lot of work on user interaction and stuff but at least it is possible to do segmentation and start storing avatars! :D If anyone want to check it out here's a link (warning, very very very early alpha!) http://vikingcrew.net/Selfie_to_Avatar/index.html
     
  19. alexsuvorov

    alexsuvorov

    Unity Technologies

    Joined:
    Nov 15, 2015
    Posts:
    327
    Hello Mikael H.

    Yes, resizing the image should reduce the memory usage, however it does not resolve the actual problem. The reason why you run out of memory is because the texture in the following code does not get destroyed between uploads:
    Code (CSharp):
    1.     IEnumerator LoadTexture(string url) {
    2.         WWW image = new WWW(url);
    3.         //yields until loading is done
    4.         yield return image;
    5.         Texture2D texture = new Texture2D(1, 1);
    6.         image.LoadImageIntoTexture(texture);
    7.         Debug.Log("Loaded image size: " + texture.width + "x" + texture.height + " from " + url);
    8.         OnFinishedLoadingTexture.Invoke(texture);
    9.     }
    Assets are not garbage collected, therefore you should take care of this yourself using one of the following ways:

    1) Call Destroy() on the created texture when it is no longer used.
    2) Call Resources.UnloadUnusedAssets() to unload assets that are no longer referenced, including your texture.
    3) Reuse the same Texture2D object (probably the most convenient way to deal with this), consider the following code:
    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using System.Runtime.InteropServices;
    4.  
    5. public class upload : MonoBehaviour {
    6.     [DllImport("__Internal")]
    7.     private static extern void ImageUploaderCaptureClick();
    8.     private Texture2D displayedTexture;
    9.  
    10.     IEnumerator LoadTexture (string url) {
    11.         WWW image = new WWW (url);
    12.         yield return image;
    13.         image.LoadImageIntoTexture (displayedTexture);
    14.     }
    15.  
    16.     void FileSelected (string url) {
    17.         StartCoroutine(LoadTexture (url));
    18.     }
    19.  
    20.     public void OnButtonPointerDown () {
    21. #if UNITY_EDITOR
    22.         string path = UnityEditor.EditorUtility.OpenFilePanel("Open image","","jpg,png,bmp");
    23.         if (!System.String.IsNullOrEmpty (path))
    24.             FileSelected ("file:///" + path);
    25. #else
    26.         ImageUploaderCaptureClick ();
    27. #endif
    28.     }
    29.  
    30.     void Start () {
    31.         displayedTexture = new Texture2D (1, 1);
    32.         GameObject.Find ("Quad").GetComponent<MeshRenderer> ().material.mainTexture = displayedTexture;
    33.     }
    34. }
    Note that once associated with the mesh material, the texture will be automatically uploaded to the GPU each time you modify it (i.e. using LoadImageIntoTexture), and as it is the very same object being reused, the memory should no longer grow.

    I should have taken care of this in my initial code example, of course.

    P.S. A small note. When using blobs to transfer data from JavaScript to WebGL, make sure to call URL.revokeObjectURL() on no longer used blob url to get it garbage collected from the JavaScript memory (for example, you can do this by calling an additional jslib callback from the LoadTexture coroutine after the blob has been downloaded). You can monitor currently allocated blobs in the Firefox memory profiler. If you don't explicitly revoke them, they will remain allocated for the whole lifetime of the page.
     
    Last edited: May 25, 2016
    Noisecrime, Muteking and Mikael-H like this.
  20. nsmith1024

    nsmith1024

    Joined:
    Mar 18, 2014
    Posts:
    870
    If i use my way....

    IEnumerator GetPic(string url, GameObject go)
    {
    WWW loader;
    loader= new WWW(url);
    yield return loader;
    go.GetComponent<Renderer>().material.mainTexture = loader.texture;
    }

    it runs out of memory in WebGL in the browser, but works in the editor.


    I tried your way....

    IEnumerator GetPic(string url, GameObject go)
    {
    WWW loader;
    loader= new WWW(url);
    yield return loader;
    loader.LoadImageIntoTexture((Texture2D)go.GetComponent<Renderer>().material.mainTexture);
    }

    but it doesnt seem to work all the time, it sets the texture for some but not all, and the ones that gets the texture ends up setting the same texture for all the gameObjects using that script to the same texture when they should all be different.

    Im stuck because it runs out of memory when loading the scene in the browser. It first loads the scene, then goes around loading all the textures from different urls then it runs out of memory.

    Any ideas?

    I will repost this in a new thread to see if anybody has any ideas
     
  21. alexsuvorov

    alexsuvorov

    Unity Technologies

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

    Are you sure you create a separate texture for each object? I.e.:
    anotherGameObject.GetComponent<MeshRenderer>().material.mainTexture = new Texture2D(1, 1);
    Feel free to share your complete code here.
     
  22. nsmith1024

    nsmith1024

    Joined:
    Mar 18, 2014
    Posts:
    870
    Im new to all this and not sure the difference between texture and picture.... but anyways....

    There is a picture that is placed on the surface of the prefab of the game object as the default.

    When the game starts it creates a bunch of those prefabs all over the place, then loads new pictures from a URL using WWW and applies that picture to the prefabs to replace the default picture, so the result supposed to be each prefab in the scene has a different picture on it.

    But seems sometimes it applies the same picture to multiple prefabs for some reason when I used your code, but when I use my code, it works correctly but then runs out of memory.
     
  23. alexsuvorov

    alexsuvorov

    Unity Technologies

    Joined:
    Nov 15, 2015
    Posts:
    327
    It seems that all your game objects have the same texture object assigned to their material.mainTexture.

    The difference is the following:
    Code (CSharp):
    1. go.GetComponent<Renderer>().material.mainTexture = loader.texture;
    assigns a new managed texture object to the material texture

    while
    Code (CSharp):
    1. loader.LoadImageIntoTexture((Texture2D)go.GetComponent<Renderer>().material.mainTexture);
    modifies the managed texture object, which has been previously assigned to the material texture.

    Each time you modify a managed texture which has been assigned to some material texture, the associated texture on gpu is automatically updated. You are able to assign the same managed texture to different materials (or different objects), in which case all the associated material textures will simultaneously change as soon as you modify this managed texture. Feel free to share your code fragments here to check if that is the case.
     
  24. nsmith1024

    nsmith1024

    Joined:
    Mar 18, 2014
    Posts:
    870
    OK, now I understand, a texture is like a container/prefab that stores a picture, any gameObject using that texture gets automatically updated when the picture of that texture changes.

    So all my instantiated prefabs had the same texture so then any changes to any of the pictures would assign it to all the prefabs in the scene.

    So to have them all use a different picture I guess I could have to instantiate the prefab, and assign a new texture for each of them, then assign that a new picture.

    But it seems like thats what im accidentally doing, heres my actual code...

    IEnumerator GetPic(string url, GameObject go)
    {
    WWW loader;
    loader= new WWW(url);
    yield return loader;
    DestroyImmediate(go.GetComponent<Renderer>().material.mainTexture, true);
    go.GetComponent<Renderer>().material.mainTexture = loader.texture;
    DestroyImmediate(loader.texture);
    }

    Where loader is a new variable containing a new texture that gets assigned to that prefab after the image is loaded.

    Here you can see I destroy the previous texture before assigning it the one from the loader, then destroying the loader one too finally.
     
  25. pl_ayground

    pl_ayground

    Joined:
    Dec 17, 2015
    Posts:
    60
    @alexsuvorov - any chance your #2 approach could be improved to work on an item in a ScrollRect?

    I either make the ScrollRect stop scrolling (due to: "NOTE: Attaching this component to a GameObject will make that object intercept ALL events, and no events will propagate to parent objects.") or the event kicks when I start scrolling rather than on mouse up.
     
  26. nsmith1024

    nsmith1024

    Joined:
    Mar 18, 2014
    Posts:
    870
    Anybody knows how to check the type of file (png, jpg, bmp, text) that the user clicked on? For exampe if you want him to click on an image but he selected a text file by accident, you want to warn him, but the link that the javascript returns to the c# function is just a bunch of random characters with no indication of the file type. Would also need to know the file size so that you dont try to load a 100 meg photo into the browser for example.
     
    Last edited: Dec 15, 2016
  27. tomekkie2

    tomekkie2

    Joined:
    Jul 6, 2012
    Posts:
    973
    That is simple.
    Code (CSharp):
    1.   function checkExtention(fname){
    2.      var extentions = ["jpg", "JPG", "png", "PNG"];
    3.      ext = fname.split(".")[1];
    4.      if(extentions.indexOf(ext) > -1){
    5.        //alert("true");
    6.        return true;
    7.        }else{
    8.        alert("only .jpg and .png files allowed");
    9.        return false;
    10.      }
    11.    }
    12. .....
    13. input.onchange = function (event) {
    14.      var tmppath = URL.createObjectURL(event.target.files[0]);
    15.      if(checkExtention(event.target.files[0].name)) SendMessage(g, "_GetTexture", tmppath);
    16.   }
    You coud also check extension in the path returned to Unity.

    BTW I could be also a good idea to check the image size before upload.
    Images of size above 4096 px seem to cause out of memory error and break the webgl player.
    I found some info how to check size or downsample the image prior to uploading it into unity here:http://stackoverflow.com/questions/...ile-size-image-height-and-width-before-upload
     
    Last edited: Dec 22, 2016
    Noisecrime likes this.
  28. tomekkie2

    tomekkie2

    Joined:
    Jul 6, 2012
    Posts:
    973
    BTW.
    How to get the original filename into Unity?
    Is there a way to retrieve it from downloaded blob or www inside Unity WebGL?
     
  29. fwalkerCirca

    fwalkerCirca

    Joined:
    Apr 10, 2017
    Posts:
    57
    In
    Code (CSharp):
    1.  document.getElementById('canvas').removeEventListener('click', OpenFileDialog);
    What canvas does the code refer too? Where those the element come from. I am getting a "Cannot read property "addEventListener" of null.
    Which seems to indicate that it did not find the canvas element?
     
  30. sumpfkraut

    sumpfkraut

    Joined:
    Jan 18, 2013
    Posts:
    242
    alexu likes this.
  31. fwalkerCirca

    fwalkerCirca

    Joined:
    Apr 10, 2017
    Posts:
    57
    Thank you sumpfkraut! I found that the problem was the that id of the canvas element was actually #canvas and not just canvas. I mistakenly thought the # was a special character!
     
  32. fwalkerCirca

    fwalkerCirca

    Joined:
    Apr 10, 2017
    Posts:
    57
    But I still can't get this to work. I am so frustrated. I am running Chrome.
    Everything is firing well. However, when I try to create a new WWW( withTheURL ) I get a Request aborted. And I am now stuck.
    The url returned looked like this:
    blob:http://localhost:5799/c1a00c6-106c-4994-b2d-68eb3ef99a7b

    From what I can tell the code looks the same as what Alex posted for Option 2 :( ....
     
    Last edited: Dec 17, 2017
    Aiursrage2k likes this.
  33. sumpfkraut

    sumpfkraut

    Joined:
    Jan 18, 2013
    Posts:
    242
  34. fwalkerCirca

    fwalkerCirca

    Joined:
    Apr 10, 2017
    Posts:
    57
    Yep, I eventually found this out :(
    "They removed javascript so i dont even know if this works anymore"

    Who removed javascript?
     
  35. Marco-Trivellato

    Marco-Trivellato

    Unity Technologies

    Joined:
    Jul 9, 2013
    Posts:
    1,654
    I just want to point out that the deprecation of Unityscript is not specific to our web export (Unity WebGL).
     
  36. BuitengewoneZaak

    BuitengewoneZaak

    Joined:
    Jun 5, 2013
    Posts:
    2
    Just in case someone is struggling with this just as me: I was trying to get the OnButtonPointerDown () hack working while using the Unity Immediate GUI mode. To make it possible to call the File Input field, I found that I can use a RepeatButton instead of normal Button in the following way:

    Code (JavaScript):
    1.  
    2. var buttonIsPressed = false;
    3.  
    4. function OnGUI() {
    5.      if (GUILayout.RepeatButton ("Upload image")) {
    6.           if(!buttonIsPressed) {
    7.               buttonIsPressed = true;
    8.               ImageUploaderCaptureClick();
    9.           }
    10.      } else {
    11.           buttonIsPressed = false;
    12.      }
    13. }
     
  37. deeptiparachuri

    deeptiparachuri

    Joined:
    Jun 6, 2017
    Posts:
    1
    Hi Alex,

    I tried version 1, i am able to upload image to weggl app from firefox but i get download failed error in chrome. Please help.
     
  38. J-F

    J-F

    Joined:
    Nov 8, 2012
    Posts:
    101
    I have the same issue as mentioned above.
    Also, how would i go about implementing this to upload with OnMouseOver?
    I have gameobjects that the user needs to click in order to upload a texture to each one of them.
     
  39. sumpfkraut

    sumpfkraut

    Joined:
    Jan 18, 2013
    Posts:
    242
    Just a update, if someone want to resize images before uploading them. (because WebGL memory problems)
    My sources:
    -this thread
    -http://blog.collectivemass.com/2014/03/resizing-textures-in-unity/
    -another site i dont remember...for JS parts.

    jslib:
    Code (JavaScript):
    1. var ImageUploaderPlugin =
    2. {
    3.   ImageUploaderCaptureClick: function(gObj, vName, maxsize)
    4.   {
    5.     var gameObj = Pointer_stringify(gObj);
    6.     var voidName = Pointer_stringify(vName);
    7.     if(!document.getElementById('ImageUploaderInput'))
    8.     {
    9.       var fileInput = document.createElement('input');
    10.       fileInput.setAttribute('type', 'file');
    11.       fileInput.setAttribute('id', 'ImageUploaderInput');
    12.       fileInput.setAttribute('accept', 'image/x-png, image/jpeg, image/jpg');
    13.       fileInput.style.visibility = 'hidden';
    14.       fileInput.style.display = 'none';
    15.       fileInput.onclick = function (event)
    16.       {
    17.         this.value = null;
    18.       };
    19.       fileInput.onchange = function (event)
    20.       {
    21.         resize_image(event.target.files[0])
    22.         document.getElementById('ImageUploaderInput').remove();
    23.       };
    24.       document.body.appendChild(fileInput);
    25.     }
    26.     else
    27.     {
    28.       //alert('UploadInputField already exist...');
    29.     }
    30.     var blob = null;
    31.     var OpenFileDialog = function()
    32.     {
    33.       $("#ImageUploaderInput").click();
    34.       document.getElementById('gameContainer').removeEventListener('click', OpenFileDialog);
    35.     };
    36.     document.getElementById('gameContainer').addEventListener('click', OpenFileDialog, false);
    37.  
    38.     function resize_image(file)
    39.     {
    40.       var reader = new FileReader();
    41.       reader.onloadend = function()
    42.       {
    43.     var tempImg = new Image();
    44.     tempImg.src = reader.result;
    45.     tempImg.onload = function()
    46.     {
    47.       var tempW = tempImg.width;
    48.       var tempH = tempImg.height;
    49.       if (tempW > tempH)
    50.       {
    51.         if (tempW > maxsize)
    52.         {
    53.           tempH *= maxsize / tempW;
    54.           tempW = maxsize;
    55.         }
    56.       }
    57.       else
    58.       {
    59.         if (tempH > maxsize)
    60.         {
    61.           tempW *= maxsize / tempH;
    62.           tempH = maxsize;
    63.         }
    64.       }
    65.       var dataURL = "";
    66.       var newBLOB = null;
    67.       try
    68.       {
    69.         if(document.getElementById('img-canvas'))
    70.             {
    71.           document.getElementById('img-canvas').remove();
    72.         }
    73.         var canvas = document.createElement('canvas');
    74.             canvas.setAttribute('id', 'img-canvas');
    75.         canvas.width = tempW;
    76.         canvas.height = tempH;
    77.         var ctx = canvas.getContext("2d");
    78.         ctx.drawImage(this, 0, 0, tempW, tempH);
    79.         dataURL = canvas.toDataURL("image/jpeg");
    80.         function dataURItoBlob(dataURI)
    81.         {
    82.           var mime = dataURI.split(',')[0].split(':')[1].split(';')[0];
    83.           var binary = atob(dataURI.split(',')[1]);
    84.           var array = [];
    85.           for (var i = 0; i < binary.length; i++)
    86.           {
    87.             array.push(binary.charCodeAt(i));
    88.           }
    89.           return new Blob([new Uint8Array(array)], {type: mime});
    90.         }
    91.         newBLOB = ((window.URL || window.webkitURL) || URL).createObjectURL(dataURItoBlob(dataURL));
    92.       }
    93.       catch(err)
    94.       {
    95.         alert('Error: ' + err.message);
    96.       }
    97.       finally
    98.       {
    99.         if(newBLOB === null)
    100.             {
    101.           alert('Error: Empty URL...');
    102.         }
    103.         else
    104.             {
    105.           //alert('Blob content: ' + newBLOB);
    106.         }
    107.         gameInstance.SendMessage(gameObj, voidName, newBLOB);
    108.       }
    109.         }//end tempImg.onload
    110.       }//end reader.onloadend
    111.       reader.readAsDataURL(file);
    112.     }//end resize_image
    113.   }//end ImageUploaderCaptureClick
    114. };
    115. mergeInto(LibraryManager.library, ImageUploaderPlugin);
    116.  
    c# (how to use)
    Code (CSharp):
    1. using System.Runtime.InteropServices;
    2.  
    3. [DllImport("__Internal")]
    4. public static extern void ImageUploaderCaptureClick(string gObj, string vName, int maxsize);
    5.  
    6. private void UploadImage()
    7. {
    8.     ImageUploaderCaptureClick("GAME-OBJECT-NAME", "FileSelected", 600);
    9. }
    10.  
    11. private void FileSelected (String url)
    12. {
    13.     StartCoroutine(LoadTexture (url));
    14. }
    15.  
    16. IEnumerator LoadTexture (String url)
    17. {
    18.     WWW image = new WWW (url);
    19.     yield return image;
    20.     if (!string.IsNullOrEmpty(image.error))
    21.     {
    22.         Debug.Log("WWW streaming error: " + image.error);
    23.     }
    24.     else
    25.     {
    26.         Texture2D texture = new Texture2D (1, 1);
    27.         image.LoadImageIntoTexture (texture);
    28.     }
    29. }
    30.  

    Resize existing texture and upload to a server:
    c# (resize images in unity before sending them to a php script)
    Code (CSharp):
    1.     public enum ImageFilterMode : int
    2.     {
    3.         Nearest = 0,
    4.         Biliner = 1,
    5.         Average = 2
    6.     }
    7.     public static Texture2D ResizeTexture(Texture2D pSource, ImageFilterMode pFilterMode, float pScale)
    8.     {
    9.         //*** Get All the source pixels
    10.         Color[] aSourceColor = pSource.GetPixels(0);
    11.         Vector2 vSourceSize = new Vector2(pSource.width, pSource.height);
    12.  
    13.         //*** Calculate New Size
    14.         float xWidth = Mathf.RoundToInt((float)pSource.width * pScale);
    15.         float xHeight = Mathf.RoundToInt((float)pSource.height * pScale);
    16.  
    17.         //*** Make New
    18.         Texture2D oNewTex = new Texture2D((int)xWidth, (int)xHeight, TextureFormat.RGBA32, false);
    19.  
    20.         //*** Make destination array
    21.         int xLength = (int)xWidth * (int)xHeight;
    22.         Color[] aColor = new Color[xLength];
    23.  
    24.         Vector2 vPixelSize = new Vector2(vSourceSize.x / xWidth, vSourceSize.y / xHeight);
    25.  
    26.         //*** Loop through destination pixels and process
    27.         Vector2 vCenter = new Vector2();
    28.         for (int i = 0; i < xLength; i++)
    29.         {
    30.  
    31.             //*** Figure out x&y
    32.             float xX = (float)i % xWidth;
    33.             float xY = Mathf.Floor((float)i / xWidth);
    34.  
    35.             //*** Calculate Center
    36.             vCenter.x = (xX / xWidth) * vSourceSize.x;
    37.             vCenter.y = (xY / xHeight) * vSourceSize.y;
    38.  
    39.             //*** Do Based on mode
    40.             //*** Nearest neighbour (testing)
    41.             if (pFilterMode == ImageFilterMode.Nearest)
    42.             {
    43.  
    44.                 //*** Nearest neighbour (testing)
    45.                 vCenter.x = Mathf.Round(vCenter.x);
    46.                 vCenter.y = Mathf.Round(vCenter.y);
    47.  
    48.                 //*** Calculate source index
    49.                 int xSourceIndex = (int)((vCenter.y * vSourceSize.x) + vCenter.x);
    50.  
    51.                 //*** Copy Pixel
    52.                 aColor[i] = aSourceColor[xSourceIndex];
    53.             }
    54.  
    55.             //*** Bilinear
    56.             else if (pFilterMode == ImageFilterMode.Biliner)
    57.             {
    58.  
    59.                 //*** Get Ratios
    60.                 float xRatioX = vCenter.x - Mathf.Floor(vCenter.x);
    61.                 float xRatioY = vCenter.y - Mathf.Floor(vCenter.y);
    62.  
    63.                 //*** Get Pixel index's
    64.                 int xIndexTL = (int)((Mathf.Floor(vCenter.y) * vSourceSize.x) + Mathf.Floor(vCenter.x));
    65.                 int xIndexTR = (int)((Mathf.Floor(vCenter.y) * vSourceSize.x) + Mathf.Ceil(vCenter.x));
    66.                 int xIndexBL = (int)((Mathf.Ceil(vCenter.y) * vSourceSize.x) + Mathf.Floor(vCenter.x));
    67.                 int xIndexBR = (int)((Mathf.Ceil(vCenter.y) * vSourceSize.x) + Mathf.Ceil(vCenter.x));
    68.  
    69.                 //*** Calculate Color
    70.                 aColor[i] = Color.Lerp(
    71.                     Color.Lerp(aSourceColor[xIndexTL], aSourceColor[xIndexTR], xRatioX),
    72.                     Color.Lerp(aSourceColor[xIndexBL], aSourceColor[xIndexBR], xRatioX),
    73.                     xRatioY
    74.                 );
    75.             }
    76.  
    77.             //*** Average
    78.             else if (pFilterMode == ImageFilterMode.Average)
    79.             {
    80.  
    81.                 //*** Calculate grid around point
    82.                 int xXFrom = (int)Mathf.Max(Mathf.Floor(vCenter.x - (vPixelSize.x * 0.5f)), 0);
    83.                 int xXTo = (int)Mathf.Min(Mathf.Ceil(vCenter.x + (vPixelSize.x * 0.5f)), vSourceSize.x);
    84.                 int xYFrom = (int)Mathf.Max(Mathf.Floor(vCenter.y - (vPixelSize.y * 0.5f)), 0);
    85.                 int xYTo = (int)Mathf.Min(Mathf.Ceil(vCenter.y + (vPixelSize.y * 0.5f)), vSourceSize.y);
    86.  
    87.                 //*** Loop and accumulate
    88.                 //Vector4 oColorTotal = new Vector4();
    89.                 Color oColorTemp = new Color();
    90.                 float xGridCount = 0;
    91.                 for (int iy = xYFrom; iy < xYTo; iy++)
    92.                 {
    93.                     for (int ix = xXFrom; ix < xXTo; ix++)
    94.                     {
    95.  
    96.                         //*** Get Color
    97.                         oColorTemp += aSourceColor[(int)(((float)iy * vSourceSize.x) + ix)];
    98.  
    99.                         //*** Sum
    100.                         xGridCount++;
    101.                     }
    102.                 }
    103.  
    104.                 //*** Average Color
    105.                 aColor[i] = oColorTemp / (float)xGridCount;
    106.             }
    107.         }
    108.  
    109.         //*** Set Pixels
    110.         oNewTex.SetPixels(aColor);
    111.         oNewTex.Apply();
    112.  
    113.         //*** Return
    114.         return oNewTex;
    115.     }
    c# (how to use)
    Code (CSharp):
    1.  
    2. using System.Security.Cryptography;
    3. private void GetImage()
    4. {
    5.     Renderer r = gameObject.GetComponent<Renderer>();
    6.     Material m = r.material;
    7.     Texture2D mytexture = m.mainTexture as Texture2D;
    8.     List<byte[]> byteImages = new List<byte[]>();
    9.     List<string> byteImagesNames = new List<string>();
    10.     try
    11.     {
    12.        float w = (float)mytexture.width;
    13.        float h = (float)mytexture.height;
    14.         if (w > 600 || h > 600)
    15.         {
    16.            float scale = 1.0f;
    17.            if (w > h)
    18.            {
    19.                 scale = (float)(600 / w);
    20.            }
    21.             else
    22.            {
    23.                 scale = (float)(600 / h);
    24.             }
    25.             mytexture = ResizeTexture(mytexture, ImageFilterMode.Biliner, scale);
    26.         }
    27.         byteImages.Add(mytexture.EncodeToJPG());
    28.         byteImagesNames.Add("imagename");
    29.         string md5 = GetHash("password-or-something-else");
    30.         StartCoroutine(UploadTexture(byteImages[0], byteImagesNames[0], md5));
    31.     }
    32.     catch { }
    33. }
    34.  
    35. //EXAMPLE FOR UPLOAD - you have to edit this part!
    36. IEnumerator UploadTexture(byte[] data, string filename, string md5)
    37. {
    38.     Debug.Log("Image info: " + filename + ".jpg  |  byte size: " + data.Length.ToString());
    39.     string uploadURL = "uploadscript.php";
    40.     WWWForm form = new WWWForm();
    41.  
    42.     form.AddField("action", "SaveMyImage");
    43.     form.AddField("sec", md5);
    44.     form.AddField("folder", currentSaveFolderName);
    45.     form.AddField("filename", filename + ".jpg");
    46.     form.AddField("file", "file");
    47.     form.AddBinaryData("file", data, filename + ".jpg", "images/jpg");
    48.  
    49.     UnityWebRequest uwr = UnityWebRequest.Post(OnlineRootFolder + "/" + uploadURL, form);
    50.     uwr.chunkedTransfer = false;
    51.     yield return uwr.SendWebRequest();
    52.  
    53.     if (uwr.isNetworkError)
    54.     {
    55.         Debug.Log("Error while sending: " + uwr.error);
    56.     }
    57.     else
    58.     {
    59.         if (uwr.uploadProgress == 1 && uwr.isDone)
    60.         {
    61.             yield return new WaitForSeconds(0.1f);
    62.         }
    63.     }
    64. }
    65.  
    PHP (example)
    Code (CSharp):
    1. <?php
    2. header("Access-Control-Allow-Credentials: true");
    3. header('Access-Control-Allow-Origin: *');
    4. header('Access-Control-Allow-Methods: POST, GET, OPTIONS');
    5. header('Access-Control-Allow-Headers: Accept, X-Access-Token, X-Application-Name, X-Request-Sent-Time');
    6.  
    7.     //check if something its being sent to this script
    8.     if ($_POST)
    9.     {
    10.         //check if theres a field called action in the sent data
    11.         if ( isset ($_POST['action']) && isset ($_POST['folder']) && isset ($_POST['sec']) && $_POST['sec'] === md5("password-or-something-else"))
    12.         {
    13.             if($_POST['action'] === 'SaveMyImage')
    14.             {
    15.                 if(!isset($_FILES) && isset($HTTP_POST_FILES))
    16.                 {
    17.                     $_FILES = $HTTP_POST_FILES;
    18.                 }
    19.            
    20.                 if ($_FILES['file']['error'] === UPLOAD_ERR_OK)
    21.                 {
    22.                     if ($_FILES['file']['name'] !== "" && isset ($_POST['filename']))
    23.                     {
    24.                         //only accept jpg files
    25.                         if ($_FILES['file']['type'] === 'images/jpg')
    26.                         {
    27.                             $uploadfile =  $_FILES['file']['name'];
    28.                             $newimage = "Saves/" . $_POST['folder'] . "/" . $_POST['filename'];
    29.                             move_uploaded_file($_FILES['file']['tmp_name'], $newimage);      
    30.                         }
    31.                     }
    32.                 }
    33.             }
    34.         }
    35.     }
    36. ?>
     
    Last edited: Jul 23, 2018
  40. andyz

    andyz

    Joined:
    Jan 5, 2010
    Posts:
    2,276
    Is anyone doing this today (with latest browser security)?
    Method 2 only half works for me in firefox as you have to click twice on UI button and the second click can be anywhere.

    Edit: I missed putting the event on pointer down not click of ui button. Still a great solution!
     
    Last edited: Feb 8, 2019
  41. jazzykhanx

    jazzykhanx

    Joined:
    Apr 11, 2018
    Posts:
    6
    can i use that for php? i want to upload the files to my serer not just see them on the webgl app.
     
  42. jon_underwood

    jon_underwood

    Joined:
    Feb 12, 2018
    Posts:
    4
    If anyone, like me was, stuck trying to implement Alex's 2nd example from above marked
    "2) Uploading an image using UI button"

    Unity has changed since this was posted

    In the file Assets/Plugins/ImageUploader.jslib
    you will need to change lines 18 and 20 to refer to #canvas
    because the name of the element that the WebGL build creates now has a # symbol at the start of its name.

    without doing this you web browser will give an error in the console
    "Cannot read property addEventListener of null"

    Also on line 12 the reference to Canvas is the name of the Unity Gameobject upon which you have placed the script called CanvasScript.cs
     
    Mikael-H likes this.
  43. alexu

    alexu

    Joined:
    Jun 19, 2014
    Posts:
    8
    In 2019.1 the gameContainer is now unityContainer :)
     
  44. The_Roam

    The_Roam

    Joined:
    Nov 14, 2018
    Posts:
    1
    Excellent workaround. Got it up and running in Unity 2018.4. Here's the app I used it on, which you can try, but wont have a good result unless the texture uses the right UVmapping. Maybe try a plain color image (only jpg, and png supported):

    https://surviuspreview.blogspot.com

    You do need to update the code a bit from the first version by alexsuvorov. I'm pretty new to this, and not a programmer at all, but I will share my bit hoping it helps newbies like me.

    This is a revision of alexsuvorov's method 2 for Unity 2018.4. It also includes the later talk about applying the texture to the mesh.

    1- Make a Plugins folder in the Assets directory
    (although I believe that now Unity will auto-find the jslib file anywhere in your project)

    2- Create a ImageUploader.jslib file in the Plugins folder
    (if the note in point 1 is true, then you can just create it anywhere. I haven't tried myself).

    3- Make the updates to alexsuvorov's code:

    Code (JavaScript):
    1. var ImageUploaderPlugin = {
    2.   ImageUploaderCaptureClick: function() {
    3.     if (!document.getElementById('ImageUploaderInput')) {
    4.       var fileInput = document.createElement('input');
    5.       fileInput.setAttribute('type', 'file');
    6.       fileInput.setAttribute('id', 'ImageUploaderInput');
    7.       fileInput.style.visibility = 'hidden';
    8.       fileInput.onclick = function (event) {
    9.         this.value = null;
    10.       };
    11.       fileInput.onchange = function (event) {
    12.         SendMessage('Canvas', 'FileSelected', URL.createObjectURL(event.target.files[0]));
    13.       }
    14.       document.body.appendChild(fileInput);
    15.     }
    16.     var OpenFileDialog = function() {
    17.       document.getElementById('ImageUploaderInput').click();
    18.       document.getElementById('#canvas').removeEventListener('click', OpenFileDialog);
    19.     };
    20.     document.getElementById('#canvas').addEventListener('click', OpenFileDialog, false);
    21.   }
    22. };
    23. mergeInto(LibraryManager.library, ImageUploaderPlugin);
    'canvas' is now '#canvas' and make sure that SendMessage refers to the game object holding the C script with the FileSelected function (in my case it is 'Canvas')

    4- Make a C script and add it to the game object as explained by others (in my case, the code is in a general script, attached to a "Canvas" gameobject).

    5- Add the following code to the script. In my case, the script is called "FileMenuCommands.cs" and has much more than shown here, so flag up any issues using this bit only.

    This version is a bit different to alexsuvorov's as it uses UnityWebRequest instead of the obsolete WWW. It is also explained how to apply the image file to a specific object's material main texture using the DownloadHandler module in UnityWebRequest modules (no more WWW):

    Code (CSharp):
    1.    
    2. using UnityEngine;
    3. using System.Collections;
    4. using System.Runtime.InteropServices;
    5.  
    6. public class FileMenuCommands : MonoBehaviour
    7. {
    8. //Define variables
    9.     //For file browser
    10.     [DllImport("__Internal")]
    11.     private static extern void ImageUploaderCaptureClick();
    12.     public string FilePath;
    13.     //For Game Object where you want to apply the texture
    14.     public GameObject Player;
    15.     private Renderer renderer;
    16.     private int i=0;
    17.  
    18.     //  Call on Start
    19.     void Start()
    20.     {
    21. //  Set player object and textures
    22. //  In this case, I have assigned the tag "Player" to my game object of interest, where the texture is applied.
    23.         Player = GameObject.FindWithTag("Player");
    24.         renderer = Player.GetComponent<Renderer>();
    25.     }
    26.  
    27.     public void OnButtonPointerDown () {
    28.     //File browser when using Unity
    29.     #if UNITY_EDITOR
    30.     FilePath = UnityEditor.EditorUtility.OpenFilePanel("Open image","","jpg,png");
    31.     if (!System.String.IsNullOrEmpty (FilePath))
    32.         FileSelected (FilePath);
    33.     #else
    34.     //File browser when using WebGL
    35.     ImageUploaderCaptureClick ();
    36.     #endif
    37.     }
    38.     // Function reads your selected file and loads it as a texture, so it's ready to be used in a material.
    39.     void FileSelected (string url) {
    40.         StartCoroutine(LoadTexture (url));
    41.     }
    42.     IEnumerator LoadTexture (string url) {
    43.         using(UnityWebRequest www = UnityWebRequestTexture.GetTexture(url)){
    44.             yield return www.SendWebRequest();
    45.             if(www.isNetworkError || www.isHttpError) {
    46.                 Debug.Log(www.error);
    47.             }
    48.             else {
    49.                 //Apply texture to specific material slot
    50.             renderer.materials[i].mainTexture = DownloadHandlerTexture.GetContent(www);
    51.             }
    52.         }
    53.     }
    In my case, Im applying the selected image file to one specific material slot which is defined by the user when selecting the model. can be 0,1,2,3.., just check your mesh renderer. Or if there is only one material, you can apply:
    renderer.materials[0].mainTexture = DownloadHandlerTexture.GetContent(www);

    or
    renderer.material.mainTexture = DownloadHandlerTexture.GetContent(www);


    5- Create a UI button in you canvas and add an Event Trigger component.
    - Click on "Add New Event Type" and select "PointerDown".
    - Click the "+" button to add an element to the list.
    - In the Object field, select the object in the scene where you have dropped the C script. In my case it's 'Canvas'.
    - If the previous point was successful, you will find the script in the dropdown box. Browse through the list and select OnButtonPointerDown().

    You should be all set and ready to go and if it works, thank everyone who contributed to this topic. Great work guys.
     
    xeniaosense and jsleek like this.
  45. gretesh

    gretesh

    Joined:
    Feb 20, 2020
    Posts:
    1
    Thanks @The_Roam for the step-by-step tips and everyone else, this works in 2020 also well!
     
    jsleek likes this.
  46. intaj_cse

    intaj_cse

    Joined:
    Nov 25, 2014
    Posts:
    10
    @alexsuvorov Thank you for your help. I want to get the selected image name . kindly help.
     
  47. sumpfkraut

    sumpfkraut

    Joined:
    Jan 18, 2013
    Posts:
    242
    Here is a updated version for unity2020.
    Tested with Firefox, Chrom and (new)Edge

    .jslib
    Code (JavaScript):
    1. var UploadFilePlugin = {
    2.   UploadFile: function(gameObjectName, methodName) {
    3.     var gameObject = UTF8ToString(gameObjectName);
    4.     var method = UTF8ToString(methodName);
    5.     var unitycanvas = document.getElementById('unity-canvas');
    6.     if(!document.getElementById('UploadFileInput'))
    7.     {
    8.       var fileInput = document.createElement('input');
    9.       fileInput.setAttribute('type', 'file');
    10.       fileInput.setAttribute('id', 'UploadFileInput');
    11.       fileInput.setAttribute('accept', '.zip');
    12.       fileInput.style.visibility = 'hidden';
    13.       fileInput.style.display = 'none';
    14.       fileInput.onclick = function (event)
    15.       {
    16.         this.value = null;
    17.         //make sure all listener are removed even if you cancel the dialog
    18.         var element = document.getElementById('UploadFileInput');
    19.         element.parentNode.removeChild(element);
    20.         unitycanvas.removeEventListener('click', OpenFileDialog, false);
    21.       };
    22.       fileInput.onchange = function (event)
    23.       {
    24.         if(event.target.value != null){
    25.           SendMessage(gameObject, method, URL.createObjectURL(event.target.files[0]));
    26.         }
    27.       };
    28.       document.body.appendChild(fileInput);
    29.     }
    30.     var OpenFileDialog = function()
    31.     {
    32.       document.getElementById('UploadFileInput').click();
    33.     };
    34.     unitycanvas.addEventListener('click', OpenFileDialog, false);
    35.   }
    36. };
    37. mergeInto(LibraryManager.library, UploadFilePlugin);
    .jslib variant with resizing large textures
    Code (JavaScript):
    1. var UploadTexturePlugin = {
    2.   UploadTexture: function(gameObjectName, methodName, maxSize) {
    3.     var gameObject = UTF8ToString(gameObjectName);
    4.     var method = UTF8ToString(methodName);
    5.     var unitycanvas = document.getElementById('unity-canvas');
    6.     if(!document.getElementById('UploadTextureInput'))
    7.     {
    8.       var fileInput = document.createElement('input');
    9.       fileInput.setAttribute('type', 'file');
    10.       fileInput.setAttribute('id', 'UploadTextureInput');
    11.       fileInput.setAttribute('accept', '.jpg, .jpeg, .png');
    12.       fileInput.style.visibility = 'hidden';
    13.       fileInput.style.display = 'none';
    14.       fileInput.onclick = function (event)
    15.       {
    16.         this.value = null;
    17.         var element = document.getElementById('UploadTextureInput');
    18.         element.parentNode.removeChild(element);
    19.     unitycanvas.removeEventListener('click', OpenFileDialog, false);
    20.       };
    21.       fileInput.onchange = function (event)
    22.       {
    23.         if(event.target.value != null)
    24.         {
    25.           //resize instead of SendMessage()
    26.           resize_image(event.target.files[0])
    27.           //SendMessage(gameObject, method, URL.createObjectURL(event.target.files[0]));
    28.         }
    29.       };
    30.       document.body.appendChild(fileInput);
    31.     }
    32.     var OpenFileDialog = function()
    33.     {
    34.       document.getElementById('UploadTextureInput').click();
    35.     };
    36.     unitycanvas.addEventListener('click', OpenFileDialog, false);
    37.  
    38.     //resize image function
    39.     function resize_image(file)
    40.     {
    41.       var reader = new FileReader();
    42.       reader.onloadend = function()
    43.       {
    44.         var tempImg = new Image();
    45.         tempImg.src = reader.result;
    46.         tempImg.onload = function()
    47.         {
    48.           var tempW = tempImg.width;
    49.           var tempH = tempImg.height;
    50.           if (tempW > tempH)
    51.           {
    52.             if (tempW > maxSize)
    53.             {
    54.               tempH *= maxSize / tempW;
    55.               tempW = maxSize;
    56.             }
    57.           }
    58.           else
    59.           {
    60.             if (tempH > maxSize)
    61.             {
    62.               tempW *= maxSize / tempH;
    63.               tempH = maxSize;
    64.             }
    65.           }
    66.           var dataURL = "";
    67.           var newBLOB = null;
    68.           try
    69.           {
    70.             if(document.getElementById('img-canvas'))
    71.             {
    72.               document.getElementById('img-canvas').remove();
    73.             }
    74.             var canvas = document.createElement('canvas');
    75.             canvas.setAttribute('id', 'img-canvas');
    76.             canvas.width = tempW;
    77.             canvas.height = tempH;
    78.             var ctx = canvas.getContext("2d");
    79.             ctx.drawImage(this, 0, 0, tempW, tempH);
    80.             dataURL = canvas.toDataURL("image/jpeg");
    81.             function dataURItoBlob(dataURI)
    82.             {
    83.               var mime = dataURI.split(',')[0].split(':')[1].split(';')[0];
    84.               var binary = atob(dataURI.split(',')[1]);
    85.               var array = [];
    86.               for (var i = 0; i < binary.length; i++)
    87.               {
    88.                 array.push(binary.charCodeAt(i));
    89.               }
    90.               return new Blob([new Uint8Array(array)], {type: mime});
    91.             }
    92.             newBLOB = ((window.URL || window.webkitURL) || URL).createObjectURL(dataURItoBlob(dataURL));
    93.           }
    94.           catch(err)
    95.           {
    96.             alert('Error: ' + err.message);
    97.           }
    98.           finally
    99.           {
    100.             if(newBLOB === null)
    101.             {
    102.               alert('Error: Empty URL...');
    103.             }
    104.             else
    105.             {
    106.               //alert('Blob content: ' + newBLOB);
    107.             }
    108.             SendMessage(gameObject, method, newBLOB);
    109.           }
    110.         }//end tempImg.onload
    111.       }//end reader.onloadend
    112.       reader.readAsDataURL(file);
    113.     }//end resize_image
    114.   }//end UploadTexture
    115. };
    116. mergeInto(LibraryManager.library, UploadTexturePlugin);

    c# example for a byte[] result.
    (use LoadFrom() on a pointerDown event. Don't use the default onClick. onClick dont work for the first click, and show you a 2nd dialog if you click the 3rd time)
    Code (CSharp):
    1. [DllImport("__Internal")]
    2. private static extern void UploadFile(string gameObjectName, string methodName);
    3. [DllImport("__Internal")]
    4. private static extern void UploadTexture(string gameObjectName, string methodName, int maxSize);
    5.  
    6. //Show the file dialog
    7. public void LoadFrom()
    8. {
    9. #if UNITY_EDITOR
    10.     //do something different in the editor...
    11. #elif UNITY_WEBGL
    12.     UploadFile(gameObject.name, "FileSelected");
    13.     //variant with max. texture size
    14.     UploadTexture(gameObject.name, "FileSelected", 512);
    15. #endif
    16. }
    17.  
    18. //If a file selected (.jslib)
    19. public void FileSelected(string url)
    20. {
    21.     //load the file (blob)
    22.     StartCoroutine(LoadFile(url));
    23. }
    24.  
    25. //Load the file into a byte array
    26. public IEnumerator LoadFile(string url = "")
    27. {
    28.     byte[] byteArray = null;
    29.     yield return UploadFile(url, returnValue =>
    30.     {
    31.         byteArray = returnValue;
    32.     });
    33.  
    34.     //do something with the byteArray...
    35.     //Texture2D example (if byte[] array from a image)
    36.     Texture2D tex = new Texture2D(2, 2);
    37.     tex.LoadImage(byteArray);
    38. }
    39.  
    40. //Helper
    41. IEnumerator UploadFile(string url, System.Action<byte[]> callback = null)
    42. {
    43.     using (UnityWebRequest www = UnityWebRequest.Get(url))
    44.     {
    45.         yield return www.SendWebRequest();
    46.         if (www.error != null)
    47.         {
    48.             Debug.Log(www.error);
    49.         }
    50.         else
    51.         {
    52.             byte[] result = new byte[www.downloadHandler.data.Length];
    53.             Array.Copy(www.downloadHandler.data, 0, result, 0, www.downloadHandler.data.Length);
    54.             callback(result);
    55.         }
    56.     }
    57. }

    EDIT:
    if you need textures with alpha, you have to change one line in the .jslib resize_image() function:
    dataURL = canvas.toDataURL("image/jpeg");
    to:
    dataURL = canvas.toDataURL("image/png");
    !!!
     
    Last edited: Mar 22, 2021
  48. alejandroibrahimojea

    alejandroibrahimojea

    Joined:
    Jan 8, 2019
    Posts:
    1
    I've tried @sumpfkraut solution and it works on Unity 2020.2.3f1
    Thanks.
     
  49. erikvenhues

    erikvenhues

    Joined:
    Dec 4, 2020
    Posts:
    2
    trying to get it work in 2021 and Chrome on Mac.
    unfortnatly it doesnt open anything.

    can you maybe show your header of CS file?
    i also get an error on line 53 (doesnt find "Array")

    thanks in advance :)
     
  50. sumpfkraut

    sumpfkraut

    Joined:
    Jan 18, 2013
    Posts:
    242
    it's System.Array.Copy(...)

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using System;
    5. using UnityEngine.UI;
    6. using System.Linq;
    7. using System.Runtime.InteropServices;
    8. using UnityEngine.Networking;
    9. using UnityEngine.EventSystems;
    10. #if UNITY_STANDALONE || UNITY_EDITOR
    11. using System.IO;
    12. #endif