Home > database >  Singleton script to download images from web download and Malformed URL issue
Singleton script to download images from web download and Malformed URL issue

Time:09-16

Hi I have written a singleton script called DownloadImage which can be used by any class to download images from web. There is a scene with several images with an attached script WebImage. Webimage internally calls this DownloadImage Singleton to download the image and update its image. Webimage has url field in which we can pass in the image link.

This system works fine if url is fixed. However if if I change url for ecah image component in scene images are not downloaded properly or I get error saying Malfoemed url. What might be the issue here and how to solve this?

Below is singleton Download script, and this script is attached to empty gameobject component.

using System.Collections;
using System.Collections.Generic;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.Networking;
using System.IO;
using UnityEngine.UI;

public class DownloadImage : MonoBehaviour
{
    public static DownloadImage instance;

    [SerializeField] public string url;
    [SerializeField] public Texture2D texture;
    [SerializeField] public bool isCached;

    private void Awake()
    {
        if (instance != null)
        {
            Destroy(this.gameObject);
            return;
        }
        instance = this;
        DontDestroyOnLoad(this.gameObject);
    }


    void Start()
    {
        StartCoroutine(GetImageFromWeb(url));
    }

    public IEnumerator GetImageFromWeb(string refURL)
    {
        UnityWebRequest request = UnityWebRequestTexture.GetTexture(refURL);
        yield return request.SendWebRequest();

        if (request.isNetworkError || request.isHttpError || 
            File.Exists(Application.persistentDataPath   "Image.jpg"))
        {
            Debug.Log("-------------------------"    request.error);

            byte[] byteArray = File.ReadAllBytes(Application.persistentDataPath   "Image.jpg");
            Texture2D tempText = new Texture2D(60, 90);
            tempText.LoadImage(byteArray);
            texture = tempText;
        }

        else
        {
            texture = ((DownloadHandlerTexture)request.downloadHandler).texture;
            if (isCached)
            {
                SaveDownloadedTexture(texture);
            }
        }
            
    }

    public void SaveDownloadedTexture(Texture2D refTexture)
    {
        bool clearCache;
        byte[] byteArr = refTexture.EncodeToJPG();
        File.WriteAllBytes(Application.persistentDataPath   "Image.jpg", byteArr);
        clearCache = Caching.ClearCache();
        if (clearCache)
        {
            Debug.Log("Image deleted");
        }
    }
}

Below is script WebImage script which is attache on each of image component in scene that takes url and passes it to singleton.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class WebImage : MonoBehaviour
{
    [SerializeField] public string imageURL;
    [SerializeField] public bool cacheData;

    void Start()
    {
        this.gameObject.AddComponent<RawImage>();
        DownloadImage.instance.url = imageURL;
        DownloadImage.instance.isCached = cacheData;
    }

    void Update()
    {
        this.gameObject.GetComponent<RawImage>().texture = DownloadImage.instance.texture;
    }

}

CodePudding user response:

Besides the comments I made already timing can be a huge issue here!

You are starting the download right away in DownloadImage.Start.

But there is no guarantee that your WebImage.Start is actually executed before that => maybe the URL is even simply "".

Also check your local file path! By simply using what you get is a path like e.g.

some/local/filePathImage.jpg

what you want would rather be

some/local/filePath/Image.jpg

So for system paths always use Path.Combine which inserts the correct path separator depending on the OS the app is running on.

And then you probably would also not want to again and again overwrite the very same local file Image.jpg but rather have unique names (for now I will assume based on the last part of the Url)

As said I would simply rather merge these to together into a single component and do something like

public class LoadedImage : MonoBehaviour
{
    [SerializeField] private Image image;
    [SerializeField] private string url;
    [SerializeField] private bool isCached;

    // Your path was also invalid!
    // by simply using   you end up with a path like e.g.
    //     some/local/filePathImage.jpg
    // what you rather want is
    //     some/local/filePath/Image.jpg
    // And then still - if you have multiple instancesthey will all overwrite the same local file
    // I would simply use the url again
    private string localPath = Path.Combine(Application.persistentDataPath, url.Split('/').Last());

    // Unity will automatically run this as a Coroutine
    private IEnumerator Start()
    {
        Texture2D texture;

        // First of all why is the local file an error?
        // I would rather expect it to be the default case and only trigger a download
        // if it DOESN'T exist

        // So as first option go for local cached file
        if(File.Exists(localPath))
        {
            byte[] byteArray = File.ReadAllBytes(localPath);
            texture = new Texture2D(1,1);
            texture.LoadImage(byteArray);
        }
        // as fallback download
        else
        {
            using(var request = UnityWebRequestTexture.GetTexture(refURL))
            {
                yield return request.SendWebRequest();

            
                if (request.isNetworkError || request.isHttpError)
                {
                    Debug.LogError("-------------------------"    request.error); 
                    return;
                }
               
                texture = ((DownloadHandlerTexture)request.downloadHandler).texture;

                if (isCached)
                {
                    SaveDownloadedTexture(texture);
                }
            }
        }   

        if(texture != null)
        {
            // Finally use Image which is way more performant than RawImage which adds DrawCalls to your UI
            image.sprite = Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), Vector2.one * 0.5f);
        }
    }

    public void SaveDownloadedTexture(Texture2D refTexture)
    {
        var byteArr = refTexture.EncodeToJPG();
        File.WriteAllBytes(localPath, byteArr);
        var clearCache = Caching.ClearCache();
        if (clearCache)
        {
            Debug.Log("Image deleted");
        }
    }
}

There is no need for a singleton. Just have this attached to your image components, configure them and you should be fine.

  • Related