I develop Web Player application.
I need to download *.png image and use this image in scene.
Download code:
public Material mat;
string fullFilename;
Texture2D texTmp;
Sprite spr;
void Awake()
{
fullFilename = "http://585649.workwork.web.hostingtest.net/Images/Logo.png";
StartCoroutine(Download());
texTmp = new Texture2D(50, 50);
spr = Sprite.Create(texTmp, new Rect(0, 0, texTmp.width, texTmp.height), Vector2.zero, 100);
spr.texture.wrapMode = TextureWrapMode.Clamp;
mat.mainTexture = spr.texture;
}
IEnumerator Download()
{
WWW www = new WWW(fullFilename);
yield return www;
www.LoadImageIntoTexture(texTmp);
}
This work fine,but after loading scene uploaded picture appears after a while.
How i can fix it ?
Sorry for my English :)
Thanks!
This is natural. Because you download picture from internet, and there are some delays. So you add loading screen or wait all scene until picture is downloaded by you. But i think it is not good solution because you only load picture. Maybe disabling other buttons/interactive elements before starting download and then enable them after download is finished is good solution.
For example:
void Awake()
{
fullFilename = "http://585649.workwork.web.hostingtest.net/Images/Logo.png";
disableButtons();
StartCoroutine(Download());
texTmp = new Texture2D(50, 50);
spr = Sprite.Create(texTmp, new Rect(0, 0, texTmp.width, texTmp.height), Vector2.zero, 100);
spr.texture.wrapMode = TextureWrapMode.Clamp;
mat.mainTexture = spr.texture;
}
IEnumerator Download()
{
WWW www = new WWW(fullFilename);
yield return www;
www.LoadImageIntoTexture(texTmp);
enableButtons();
}
Related
Its a VN style game with user generated content and I need to load the image without delay.
Due to it being user generated content the images will be siting in a folder with the game.
Due to the same reason I cant preload the images, since I cant know which will be the next image to load.
I have tried UnityWebRequest, WWW or File.ReadAllBytes and all have more delay than I expected, even thou Im running it on a SSD.
Is there a faster way?
The code im using for testing the loading time of the images
using UnityEngine;
using System.IO;
using UnityEngine.Networking;
using System.Collections;
using UnityEngine.UI;
using System.Threading.Tasks;
/// <summary>
/// 2020/19/05 -- Unity 2019.3.3f1 -- C#
/// </summary>
public class itemCreatorImageLoad : MonoBehaviour
{
public Image image; // this is referencing to a UI Panel
private Texture2D texture2D;
private UnityWebRequest uwr;
public RawImage rawImage; // this is referencing to a UI rawImage
// path = #"C:\UnityTests\Referencing\Referencing\Assets\StreamingAssets\Items\image.png"
// the # handles the / and \ conventions that seem to come from the program using paths for the web
// C:/.../.../... web
// C:\...\...\... pc
public void LoadImageWWW(string path)
{
if (texture2D)
{
Destroy(texture2D); // this follows the reference and destroys the texture. Else it would just get a new one and the old textures start piling up in your memory, without you being able to remove them.
}
texture2D = new Texture2D(1, 1);
texture2D = new WWW(path).textureNonReadable as Texture2D;
image.sprite = Sprite.Create(texture2D, new Rect(0.0f, 0.0f, texture2D.width, texture2D.height), new Vector2(0.5f, 0.5f), 100.0f);
image.preserveAspect = true;
}
public void LoadImageWWWv2(string path)
{
// "http://url/image.jpg"
StartCoroutine(setImage(path));
}
IEnumerator setImage(string url) // this comes from https://stackoverflow.com/questions/31765518/how-to-load-an-image-from-url-with-unity
{
Texture2D texture = image.canvasRenderer.GetMaterial().mainTexture as Texture2D;
WWW www = new WWW(url);
yield return www;
// calling this function with StartCoroutine solves the problem
Debug.Log("Why on earh is this never called?");
www.LoadImageIntoTexture(texture);
www.Dispose();
www = null;
}
public void LoadImageReadAllBytes(string path)
{
byte[] pngBytes = File.ReadAllBytes(path);
if (texture2D)
{
Destroy(texture2D); // this follows the reference and destroys the texture. Else it would just get a new one and the old textures start piling up in your memory, without you being able to remove them.
}
texture2D = new Texture2D(1, 1);
texture2D.LoadImage(pngBytes);
image.sprite = Sprite.Create(texture2D as Texture2D, new Rect(0.0f, 0.0f, texture2D.width, texture2D.height), new Vector2(0.5f, 0.5f), 100.0f);
image.preserveAspect = true;
}
public void LoadImageUnityWebRequest(string path)
{
StartCoroutine(LoadImageCorroutine());
IEnumerator LoadImageCorroutine()
{
using (uwr = UnityWebRequestTexture.GetTexture(#path))
{
yield return uwr.SendWebRequest();
// I would always check for errors first
if (uwr.isHttpError || uwr.isNetworkError)
{
Debug.LogError($"Could not load texture do to {uwr.responseCode} - \"{uwr.error}\"", this);
yield break;
}
// Destroy the current texture instance
if (rawImage.texture)
{
Destroy(texture2D); // this follows the reference and destroys the texture. Else it would just get a new one and the old textures start piling up in your memory, without you being able to remove them.
}
rawImage.texture = DownloadHandlerTexture.GetContent(uwr);
image.sprite = Sprite.Create(rawImage.texture as Texture2D, new Rect(0.0f, 0.0f, rawImage.texture.width, rawImage.texture.height), new Vector2(0.5f, 0.5f), 100.0f);
image.preserveAspect = true;
}
StopCoroutine(LoadImageCorroutine());
}
}
public void LoadImageUnityWebRequestv2(string path)
{
StartCoroutine(LoadImageUnityWebRequestv2Coroutine(path));
}
IEnumerator LoadImageUnityWebRequestv2Coroutine(string MediaUrl) // this comes from https://stackoverflow.com/questions/31765518/how-to-load-an-image-from-url-with-unity
{
UnityWebRequest request = UnityWebRequestTexture.GetTexture(MediaUrl);
yield return request.SendWebRequest();
if (request.isNetworkError || request.isHttpError)
{
Debug.Log(request.error);
}
else
{
rawImage.texture = ((DownloadHandlerTexture)request.downloadHandler).texture;
}
}
// a async version that I grabbed from somewhere, but I dont remember where anymore
[SerializeField] string _imageUrl;
[SerializeField] Material _material;
public async void MyFunction()
{
Texture2D texture = await GetRemoteTexture(_imageUrl);
_material.mainTexture = texture;
}
public static async Task<Texture2D> GetRemoteTexture(string url)
{
using (UnityWebRequest www = UnityWebRequestTexture.GetTexture(url))
{
//begin requenst:
var asyncOp = www.SendWebRequest();
//await until it's done:
while (asyncOp.isDone == false)
{
await Task.Delay(1000 / 30);//30 hertz
}
//read results:
if (www.isNetworkError || www.isHttpError)
{
//log error:
#if DEBUG
Debug.Log($"{ www.error }, URL:{ www.url }");
#endif
//nothing to return on error:
return null;
}
else
{
//return valid results:
return DownloadHandlerTexture.GetContent(www);
}
}
}
}
I have a png image inside of my StreamingAssets folder. When I try to retrieve the image, it returns an image with a red question mark (this is not the right image).
I am using this script:
public string url;
IEnumerator Start()
{
url = Application.dataPath + "/StreamingAssets/shareImage.png";
using (WWW www = new WWW(url))
{
yield return www;
Renderer renderer = GetComponent<Renderer>();
renderer.material.mainTexture = www.texture;
}
}
What should I do the get the right image?
Location of the image:
The Inspector tab of the image I want to change:
While reading files in the StreamingAssets folder, you have to use WWW on some platforms or File.ReadAllBytes some others. You check if the path contains :// or :/// then determine which one to use. This should solve the question mark issue. Note that you should now be using UnityWebRequest where WWW is required unless there is a bug with UnityWebRequest.
Another issue that will arise is the SpriteRenderer image not being upadte. The material will reflect the images changes but the SpriteRenderer will not. The proper way to change a sprite of a SpriteRenderer is to change the SpriteRenderer.sprite property. This means that you will have to Convert the Texture2D to Sprite then assign that sprite to the SpriteRenderer.sprite property.
Finally, use Application.streamingAssetsPath when accessing files in the StreamingAssets folder. This is not the issue now since you're running it in the Editor but will be in a build.
Below is what your code for reading image file in the StreamingAssets should look like:
using UnityEngine.Networking;
public string url;
IEnumerator Start()
{
///url = Application.dataPath + "/StreamingAssets/shareImage.png";
url = Path.Combine(Application.streamingAssetsPath, "shareImage.png");
byte[] imgData;
Texture2D tex = new Texture2D(2, 2);
//Check if we should use UnityWebRequest or File.ReadAllBytes
if (url.Contains("://") || url.Contains(":///"))
{
UnityWebRequest www = UnityWebRequest.Get(url);
yield return www.SendWebRequest();
imgData = www.downloadHandler.data;
}
else
{
imgData = File.ReadAllBytes(url);
}
Debug.Log(imgData.Length);
//Load raw Data into Texture2D
tex.LoadImage(imgData);
//Convert Texture2D to Sprite
Vector2 pivot = new Vector2(0.5f, 0.5f);
Sprite sprite = Sprite.Create(tex, new Rect(0.0f, 0.0f, tex.width, tex.height), pivot, 100.0f);
//Apply Sprite to SpriteRenderer
SpriteRenderer renderer = GetComponent<SpriteRenderer>();
renderer.sprite = sprite;
}
I am downloading some sprites from my server and store them in Application.persistentDataPath.
However, I cannot load the controller using Resources.Load (controllerPath) because the path is outside the Resources folder.
Additionally, I get a MissingComponentException when I try to add the animation controller to the GameObject.
Here is my code:
private GameObject SideSprite;
// ...
string controllerPath = Application.persistentDataPath+"/"+aux+"/"+aux+"Controller";
controller = (RuntimeAnimatorController)Resources.Load (controllerPath); // Returns null
// Below I get:
// MissingComponentException: There is no 'Animator' attached to the
// "Missing Prefab (Dummy)" game object, but a script is trying to access it.
SideSprite.GetComponent<Animator> ().runtimeAnimatorController = controller;
How should I load the resources from the persistent data path?
persistentDataPath is used as any regular folder. I would not store a Sprite but more likely a texture and next time you need it you unroll the process of applying a texture to a sprite:
public static void StoreCacheSprite(string url, Sprite sprite)
{
if(sprite == null || string.IsNullOrEmpty(url) == true) { return; }
SpriteRenderer spRend = sprite.GetComponent<SpriteRenderer>();
Texture2D tex = spRend.material.mainTexture;
byte[] bytes = tex.EncodeToPNG();
string path = Path.Combine(Application.persistentDataPath, url);
File.WriteAllBytes(Application.persistentDataPath, bytes);
}
public static Sprite GetCacheSprite(string url)
{
if( string.IsNullOrEmpty(url) == true) { return; }
string path = Path.Combine(Application.persistentDataPath, url);
if(File.Exists(path) == true)
{
bytes = File.ReadAllBytes(path);
Texture2D texture = new Texture2D(4, 4, TextureFormat.RGBA32, false);
texture.LoadImage(bytes);
Sprite sp = Sprite.Create(texture, new Rect(0,0 texture.width, texture.height, new Vector2(0.5f,0.5f));
return sp;
}
return null;
}
The first method stores the texture using the File class from .NET. It converts and writes a byte array onto the ROM of the device (File.WriteAllBytes). You need a path to a Sprite and a name for it. That name needs to comply with file and folder path naming.
The second method does the inverse process, checking if it is already stored and turning the byte array found on the RAM into a usable Sprite.
You could also simply use WWW to get the data.
string controllerPath = Application.persistentDataPath+"/"+aux+"/"+aux+"Controller";
IEnumerator Start ()
{
WWW www = new WWW("file:///" + controllerPath);
yield return www;
Debug.Log(www.texture); //or www.bytes
}
Please save me from going crazy.
No matter how many times I google, I always end up with (usually deprecated) versions of the following code:
IEnumerator setImage(string url) {
Texture2D texture = profileImage.canvasRenderer.GetMaterial().mainTexture as Texture2D;
WWW www = new WWW(url);
yield return www;
Debug.Log("Why on earh is this never called?");
www.LoadImageIntoTexture(texture);
www.Dispose();
www = null;
}
I'm using Unity 5 not 4. The URL I'm trying to load exists.
Please shine some light on me.
How do I load an image over HTTP and display it in a UnityEngine.UI.Image?
For Unity 2018+ use UnityWebRequest which replaces the WWW class.
void Start(){
StartCoroutine(DownloadImage(url));
}
IEnumerator DownloadImage(string MediaUrl)
{
UnityWebRequest request = UnityWebRequestTexture.GetTexture(MediaUrl);
yield return request.SendWebRequest();
if(request.isNetworkError || request.isHttpError)
Debug.Log(request.error);
else
YourRawImage.texture = ((DownloadHandlerTexture) request.downloadHandler).texture;
}
You can do that with async/await:
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.Networking;
public static async Task<Texture2D> GetRemoteTexture ( string url )
{
using( UnityWebRequest www = UnityWebRequestTexture.GetTexture(url) )
{
// begin request:
var asyncOp = www.SendWebRequest();
// await until it's done:
while( asyncOp.isDone==false )
await Task.Delay( 1000/30 );//30 hertz
// read results:
if( www.isNetworkError || www.isHttpError )
// if( www.result!=UnityWebRequest.Result.Success )// for Unity >= 2020.1
{
// log error:
#if DEBUG
Debug.Log( $"{www.error}, URL:{www.url}" );
#endif
// nothing to return on error:
return null;
}
else
{
// return valid results:
return DownloadHandlerTexture.GetContent(www);
}
}
}
Usage example:
[SerializeField] string _imageUrl;
[SerializeField] Material _material;
Texture2D _texture;
async void Start ()
{
_texture = await GetRemoteTexture(_imageUrl);
_material.mainTexture = _texture;
}
void OnDestroy () => Dispose();
public void Dispose () => Object.Destroy(_texture);// memory released, leak otherwise
quick clarification:
https://docs.unity3d.com/ScriptReference/MonoBehaviour.StartCoroutine.html
public void yourMethod()
{
StartCoroutine(setImage("http://url/image.jpg")); //balanced parens CAS
}
IEnumerator setImage(string url) {
Texture2D texture = profileImage.canvasRenderer.GetMaterial().mainTexture as Texture2D;
WWW www = new WWW(url);
yield return www;
// calling this function with StartCoroutine solves the problem
Debug.Log("Why on earh is this never called?");
www.LoadImageIntoTexture(texture);
www.Dispose();
www = null;
}
We can't directly apply texture material on the Image component in the canvas. So, we should create a sprite from the texture downloaded at runtime then apply that sprite in the Image component.
Try this one,
IEnumerator DownloadImage(string MediaUrl)
{
UnityWebRequest request = UnityWebRequestTexture.GetTexture(MediaUrl);
yield return request.SendWebRequest();
if(request.isNetworkError || request.isHttpError)
Debug.Log(request.error);
else{
// ImageComponent.texture = ((DownloadHandlerTexture) request.downloadHandler).texture;
Texture2D tex = ((DownloadHandlerTexture) request.downloadHandler).texture;
Sprite sprite = Sprite.Create(tex, new Rect(0, 0, tex.width, tex.height), new Vector2(tex.width / 2, tex.height / 2));
ImageComponent.GetComponent<Image>().overrideSprite = sprite;
}
}
Thanks to the question asked by Umair M, I figured out that this method needs to be called using unity's StartCoroutine.
public void OnStart()
{
StartCoroutine(setImage("http://drive.google.com/myimage.jpg"));
}
IEnumerator setImage(string url)
{
Texture2D texture = null;
WWW www = new WWW(url);
yield return www;
Debug.Log("Why on earth is this never called?");
texture = www.texture;
//end show Image in texture 2D
}
Hello i load texture this code:
settingSplit this is string array.
IEnumerator DownloadLogos()
{
WWW www = new WWW(settingsSplit[0]);
while (www.progress < 1)
{
slider.GetComponent<UISlider>().value = www.progress;
if (slider.GetComponent<UISlider>().value > 0.880f)
{
slider.GetComponent<UISlider>().value = 1;
}
yield return new WaitForEndOfFrame();
}
yield return www;
if (www.error == null)
{
fadein = true;
model.GetComponent<Animation>().Play();
texTmp = www.textureNonReadable;
spr = Sprite.Create(texTmp, new Rect(0, 0, texTmp.width, texTmp.height), Vector2.zero, 50);
spr.texture.wrapMode = TextureWrapMode.Clamp;
mat.mainTexture = spr.texture;
decal.sprite = spr;
yield return new WaitForEndOfFrame();
slider.SetActive(false);
float multipier = 1;
if (settingsSplit[2] != null)
{
multipier = float.Parse(settingsSplit[2]);
}
decal.transform.localScale = new Vector3(decal.transform.localScale.x * multipier,
decal.transform.localScale.y * multipier, decal.transform.localScale.z);
BuildDecal(decal);
}
Work fine but
When texture load MainThread stop for some time (1-2 second).
How i can fix this ?
Thanks!
I don't know if you have solved this, but I was facing the same problem. The problematic line is
spr = Sprite.Create(texTmp, new Rect(0, 0, texTmp.width, texTmp.height), Vector2.zero, 50);
Because the creation of the Sprite takes a while and runs in the main thread, which causes your game to freeze.
My solution was to use a RawImage instead of an Image to display the loaded texture, just delete the mentioned line and replace
decal.sprite = spr;
with
decal.texture = www.texture;
and set the rest of properties/values you want to use.
I hope this helps someone having this problem.
WaitForEndOfFrame resumes at the end of the frame. I'm fairly certain if you yield for the end of the frame, at the end of the frame, you still wont progress to the next frame. Just yield null and you will resume the next frame.
while (www.progress < 1)
{
slider.GetComponent<UISlider>().value = www.progress;
if (slider.GetComponent<UISlider>().value > 0.880f)
{
slider.GetComponent<UISlider>().value = 1;
}
yield return null;
}