Search Unity

Question Can textures be extracted and assigned from a downloaded .zip?

Discussion in 'Scripting' started by DrJBN, Feb 13, 2024.

  1. DrJBN

    DrJBN

    Joined:
    Sep 28, 2014
    Posts:
    49
    I have several small textures that the user places on a server which the program (unity WebGL) downloads individually and assigns to texture2d objects. To alleviate server loads, if those textures were combined into a single zip, could the zip be downloaded to memory, then the individual textures extracted and assigned to Texture2d variables from the .zip? If that is doable, I'd sure appreciate seeing some code and reading materials to get me started.

    -I've managed to download the zip and put the downloadHandler.data in to a MemoryStream, and then make a new ziparchive in memory from that stream. The watch window shows the archive contains the correct files. Now- I need to discover how to read the files from the archive into their respective holders.
     
    Last edited: Feb 13, 2024
  2. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    3,998
    Sure, one of the easiest solutions is to use the SharpZipLib library. I used it in the past in webgl builds and it worked fine. It's a standalone library with no external dependencies which is generally great.

    You may be able to use the ZipFile class from the .NET / Mono framework. Though I'm not sure if you can include the necessary assemblies in Unity. I've found an old thread where people were struggling to include the compression library for the ZipFile class. Though I'm not sure if it will actually work in a WebGL build.

    Of course once you managed to get your files out of your zip files as a byte array, you can simply hand it to Texture2D.LoadImage. Keep in mind that Unity's ability to load images at runtime is limited. Not all file formats are supported in a build. That's because the editor ships with much more tooling, libraries and licenses of those tools than what is and can be shipped with the engine itself. At runtime Unity can only load png or jpg files. For other files types you need your own library.
     
    DrJBN and Ryiah like this.
  3. DevDunk

    DevDunk

    Joined:
    Feb 13, 2020
    Posts:
    5,058
    In general indeed look for how you can extract a file from zip in C#
    This is not Unity related perce

    ChatGTP helped me with this last time I had to make it haha
     
    DrJBN likes this.
  4. Ryiah

    Ryiah

    Joined:
    Oct 11, 2012
    Posts:
    21,175
    Last edited: Feb 13, 2024
    DrJBN and Bunny83 like this.
  5. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    3,998
    Yes, that's true. Though I wouldn't call them texture file formats as they are the actual representation in memory. So it's mainly a more efficient way to provide raw pixel data. So this is mostly about the order and size (in bits) of the different color channels and if a certain hardware supported compression scheme is applied to the data or not. There are some file formats which pretty much directly refect the memory layout of the texture in memory. Though usually all file formats have an additional header with meta information that would need to be parsed manually.
     
    Ryiah and DrJBN like this.
  6. Ryiah

    Ryiah

    Joined:
    Oct 11, 2012
    Posts:
    21,175
    Last edited: Feb 13, 2024
  7. DrJBN

    DrJBN

    Joined:
    Sep 28, 2014
    Posts:
    49
    Thank you guys very much- I have something working. I don't know if its the "right" or "best" way, but it works. I think the code will explain itself for most folks finding this thread. I'm not sure I could explain why it works anyway :(o_O

    Code (CSharp):
    1.  
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using UnityEngine;
    5. using System.IO.Compression;
    6. using System.IO;
    7. using UnityEngine.Networking;
    8.  
    9. public class Quad_Script : MonoBehaviour
    10. {
    11.     public Texture2D texture;
    12.  
    13.     UnityWebRequest www;
    14.  
    15.    //ZipArchive is in System.IO.Compression;
    16.     ZipArchive zipfile_stream;
    17.  
    18.     bool request_sent;
    19.     bool request_ok;
    20.     bool got_texture;
    21.  
    22.     IEnumerator getzip()
    23.     {
    24.         www = UnityWebRequest.Get(" my url goes here/Textures.zip");
    25.         yield return www.SendWebRequest();
    26.  
    27.         if (www.result == UnityWebRequest.Result.Success)
    28.         {
    29.             //convert the data to a memory stream
    30.             Stream zd = new MemoryStream(www.downloadHandler.data);
    31.  
    32.             //make a ziparchive out of the stream;
    33.             zipfile_stream = new ZipArchive(zd);
    34.  
    35.             request_ok = true;
    36.         }
    37.         else
    38.         {
    39.             print("download failed.." + www.error);
    40.         }
    41.     }
    42.  
    43.     // Update is called once per frame
    44.     void Update()
    45.     {
    46.         if (!request_sent)
    47.         {
    48.             StartCoroutine( getzip());
    49.             request_sent = true;
    50.         }
    51.  
    52.         if (request_ok)
    53.         {
    54.  
    55.             if (!got_texture)
    56.             {
    57.  
    58.                 // grab an entry from the zipfile, BM.png in this case
    59.                 ZipArchiveEntry tex1 = zipfile_stream.GetEntry("BM.png");
    60.  
    61.                 //create a new memory stream
    62.                 MemoryStream memstream = new MemoryStream();
    63.  
    64.                 //open the zip entry- it is open for reading and the reading will decompress the file
    65.                 //copy the open file to the memory stream
    66.                 tex1.Open().CopyTo(memstream);
    67.              
    68.                 //byte array for holding the bytes that consititute your BM.png texture file
    69.                 byte[] texbytes;
    70.  
    71.                 //put the memstream of the texture to the texbytes
    72.                 texbytes = memstream.ToArray();
    73.              
    74.                 //make your new texture
    75.                 texture = new Texture2D(10, 10);
    76.  
    77.                 //load the bytes into it
    78.                 texture.LoadImage(texbytes);
    79.              
    80.                 //display and see if it worked.  This script is attached to a quad.
    81.                 GetComponent<Renderer>().material.mainTexture = texture;
    82.  
    83.                 got_texture = true;
    84.             }
    85.  
    86.         }
    87.     }
    88. }
    89.  
     
  8. Nad_B

    Nad_B

    Joined:
    Aug 1, 2021
    Posts:
    730
    Looks good. Just don't forget to release (dispose) your ZipArchiveEntry stream (the Stream you get when you call ZipArchiveEntry.Open()) and the ZipArchive itself.

    Code (CSharp):
    1. using (var tex1Stream = tex1.Open())
    2. {
    3.     tex1Stream.CopyTo(memstream);
    4. }
    No need to do it for your MemoryStream, GC will take care of it.

    Also why putting this inside Update()? and why using a coroutine? I'd prefer to just create a public method GetTexture(zipFile) and call it when necessary. No need for a MonoBehaviour at all...


    Code (CSharp):
    1. public static class ZipTextureHelper
    2. {
    3.     public static Texture2D GetTextureFromZipFile(string zipFilePath, string zipEntryName)
    4.     {
    5.         using (var archive = new ZipArchive(File.OpenRead(zipFilePath)))
    6.         {
    7.             var entry = archive.GetEntry(zipEntryName) ??
    8.                 throw new Exception($"Cannot find zip entry '{zipEntryName}' in zip file '{zipFilePath}'");
    9.  
    10.             var memoryStream = new MemoryStream();
    11.  
    12.             using (var entryStream = entry.Open())
    13.             {
    14.                 entryStream.CopyTo(memoryStream);
    15.             }
    16.  
    17.             // create the texture and return it...
    18.         }
    19.     }
    20. }
     
    Last edited: Feb 14, 2024
    Ryiah and DrJBN like this.
  9. DrJBN

    DrJBN

    Joined:
    Sep 28, 2014
    Posts:
    49
    That's just my proof of concept code, so to speak. I'll tidy it up and encapsulate it when I put it to use.
     
    Ryiah, Bunny83 and Nad_B like this.
  10. Nad_B

    Nad_B

    Joined:
    Aug 1, 2021
    Posts:
    730
    Sorry you can ignore what I said, didn't notice you were downloading the file. Since everything is basically using MemoryStreams, then you don't need to dispose anything explicitly, but it's always a good idea to dispose any class implementing IDisposable.
     
    Last edited: Feb 13, 2024
    Ryiah, Bunny83 and DrJBN like this.
  11. DrJBN

    DrJBN

    Joined:
    Sep 28, 2014
    Posts:
    49
    Seems to be a mix of things- You can simply include IO.Compression from the .NET framework, at least now anyhow, and Unity doesn't seem to have any trouble lining it up and running fine, but it only seems to work through the editor. It doesn't seem to link up with WebGL. Once the webGL build is deployed, the .Net IO zip file code blows up in the browser.

    I followed the advice on the link you mentioned (and thank you for mentioning it!) that seemed to resolve the issue for non-webGL builds and added the file "csc.rsp" to the assets, with the following text inside:

    -r:System.IO.Compression.dll
    -r:System.IO.Compression.FileSystem.dll
    -r:System.IO.Compression.ZipFile.dll

    That seemed to fix the issue for WebGL. The code ran in the editor, and when compiled to WebGL and deployed. The following works on my system at least-
    https://learningchicken.com/JBN/TexTest/index.html
    If you give it a try, click the quad and then press "space".
     
    Last edited: Feb 14, 2024
    Bunny83 likes this.
  12. DrJBN

    DrJBN

    Joined:
    Sep 28, 2014
    Posts:
    49
    The issue of getting the .net IO.Compression to work in webGL is more complicated. What I discuss in the post above, including the csc.rsp file, did work for me in my office. But, the same program built at home did not. The difference was that in the office I was using VS Community 2019. At home, I was using VS Community 2022. I switched my home installation back to use VS 2019, and the WebGL program compiled and ran as expected when deployed. With 2022, the routines using the IO.Compression files failed.
     
    Bunny83 likes this.