Silva
Silva

Reputation: 153

Load Image form PersistenceDataPath

I'm making an Android app that should, in the beginning, load data from a MySQL database and some images from the server. After that, the user will be in the outside, so the app will work offline.

I made the code to save all the database data (it works) and the image from the server to ApplicationPersistenceDataPath (not working). I already search for how I should do that. The code I have does not throw any error. But, when I tried to display it on the app the image was blank. That's when I looked in the folder where the images are being stored, and I cannot open them.

This is the method I have to save the images:

IEnumerator loadBgImage(string url, string file_name)
    {
        using (UnityWebRequest www = UnityWebRequest.Get(url))
        {
            yield return www.Send();
            if (www.isNetworkError || www.isHttpError)
            {
                print("erro");
            }
            else
            {
                string savePath = string.Format("{0}/{1}", Application.persistentDataPath, file_name);
                if (!File.Exists(savePath))
                {
                    System.IO.File.WriteAllText(savePath, www.downloadHandler.text);
                }
            }
        }
    }

And this is the code to show the image file in a Image:

string path = Application.persistentDataPath + "/" + nomeImagem;
imagem.GetComponent<SpriteRenderer>().sprite = LoadSprite(path);

This is the method called:

    private Sprite LoadSprite(string path)
    {
        if (string.IsNullOrEmpty(path)) return null;
        if (System.IO.File.Exists(path))
        {            
            byte[] bytes = File.ReadAllBytes(path);
            Texture2D texture = new Texture2D(900, 900, TextureFormat.RGB24, false);
            texture.filterMode = FilterMode.Trilinear;
            texture.LoadImage(bytes);
            Sprite sprite = Sprite.Create(texture, new Rect(0, 0, 8, 8), new Vector2(0.5f, 0.0f), 1.0f);

        }
        return null;
    }

I really need to save the images first, and then display it offline. Can someone please tell me what I am doing wrong?

Update After following the suggestions on @derHugo's answer, this is the code I have to save images:

IEnumerator loadBgImage(string url, string file_name)
    {
        using (UnityWebRequest www = UnityWebRequest.Get(url))
        {
            yield return www.Send();
            if (www.isNetworkError || www.isHttpError)
            {
                print("erro");
            }
            else
            {
                var savePath = Path.Combine(Application.persistentDataPath, file_name);
                var data = www.downloadHandler.data;
                using (var fs = new FileStream(savePath, FileMode.Create, FileAccess.Write))
                {
                    fs.Write(data, 0, data.Length);
                }
            }
        }
    }

It works, but I still can't see the images when running the app. In the method to show the sprite I added the return of the created sprite.

Second Update

I follow again the suggestions of @derHugo, and I use rawImage instead of Image. And it's now working both on Unity editor and on Android device. This is the method I use:

private void LoadSprite(string path)
    {
        if (string.IsNullOrEmpty(path)) print("erro");
        if (System.IO.File.Exists(path))
        {
            byte[] bytes = File.ReadAllBytes(path);
            Texture2D texture = new Texture2D(900, 900, TextureFormat.RGB24, false);
            texture.filterMode = FilterMode.Trilinear;
            texture.LoadImage(bytes);
            imagem.texture = texture;
        }
    }

Upvotes: 2

Views: 7213

Answers (1)

derHugo
derHugo

Reputation: 90862

Generally you should rather use Path.Combine like

var path = Path.Combine(Application.persistentDataPath, FileName);

for system paths instead! It might be that your target device simply doesn't understand / as path seperator.


And either write also if the file exists already (maybe newer version? And you downloaded it already anyway) or you shouldn't even start the download if it exists already to save bandwidth.


I also think that WriteAllText isn't quite the correct solution for binary image data because www.downloadHandler.text is already

interpreted as a UTF8 string

so since an image very probably has some bytes that are not representable in a UTF8 string you get corrupted data here!

You rather should directly use

www.downloadHandler.data

which returns the raw byte[] instead and than e.g. File.WriteAllBytes (only on UWP) or a proper FileStream for writing that byte[] to the file. Something like

var data = www.downloadHandler.data;
File.WriteAllBytes(path, data);

or

var data = www.downloadHandler.data;
using (var fs = new FileStream(path, FileMode.Create, FileAccess.Write))
{
    fs.Write(data, 0, data.Length);
}

Than a white image in the scene usually means that the sprite is null.

If it's no typo than your LoadSprite always returns null. You forgot to return the created sprite

private Sprite LoadSprite(string path)
{
    if (string.IsNullOrEmpty(path)) return null;
    if (System.IO.File.Exists(path))
    {            
        byte[] bytes = File.ReadAllBytes(path);
        Texture2D texture = new Texture2D(900, 900, TextureFormat.RGB24, false);
        texture.filterMode = FilterMode.Trilinear;
        texture.LoadImage(bytes);
        Sprite sprite = Sprite.Create(texture, new Rect(0, 0, 8, 8), new Vector2(0.5f, 0.0f), 1.0f);

        // You should return the sprite here!
        return sprite;
    }
    return null;
}

However note that a better way of loading also local images is actually UnityWebRequestTexture as well. It also takes a local file path as URL and

Using this class significantly reduces memory reallocation compared to downloading raw bytes and creating a texture manually in script. In addition, texture conversion will be performed on a worker thread.

But also

Only JPG and PNG formats are supported.

but this shouldn't be a problem I guess since the LoadImage you used so far underlies the same limitation.

This would be asynchronous, however, so you'll have to wait for the texture in order to create the sprite. So instead of retuning the Sprite I would actually rather pass in the reference of the according Image component and directly replace its sprite. Something like

private IEnumerator LoadLocalTexture(string path, Image receivingImage)
{
    UnityWebRequest www = UnityWebRequestTexture.GetTexture(path);
    yield return www.SendWebRequest();

    if(www.isNetworkError || www.isHttpError) 
    {
        Debug.Log(www.error);
    }
    else 
    {
        var texture = ((DownloadHandlerTexture)www.downloadHandler).texture;
        var sprite = Sprite.Create(texture, new Rect(0, 0, 8, 8), new Vector2(0.5f, 0.0f), 1.0f);

        receivingImage.sprite = sprite;
    }
}

If you are not bound to use actually Sprites I would always suggest to rather use RawImage components instead which can directly use Texture2D instead! And than do something like

private IEnumerator LoadLocalTexture(string path, RawImage receivingImage)
{
    UnityWebRequest www = UnityWebRequestTexture.GetTexture(path);
    yield return www.SendWebRequest();

    if(www.isNetworkError || www.isHttpError) 
    {
        Debug.Log(www.error);
    }
    else 
    {
        var texture = ((DownloadHandlerTexture)www.downloadHandler).texture;

        receivingImage.texture = texture;
    }
}

but also

Note : Keep in mind that using a RawImage creates an extra draw call with each RawImage present, so it's best to use it only for backgrounds or temporary visible graphics.

Upvotes: 5

Related Questions