LoadImageAtPathAsync with recycling view using EnhancedScroller.
Hi,
First of all, thank you very much for this package and for making it free for everyone. I appreciate all the effort put into this library to maintain it and make it an easy API to quickly integrate with existing Unity projects.
I have been facing one problem, particularly with loading images while scrolling with EnhancedScroller. LoadImageAtPathAsync works smoothly except for tons of errors that the tmp file doesn't exist, which ends up generating broken textures or, many times, repeating the same image twice. (I guess it's due to the scrolling nature of my app that UnityNativeGallery is unable to fulfill multiple texture creation at once, as pointed out in the docs.)
However, when I use LoadImageAtPath, the images load exactly as I expect with proper positioning in the recycler cell, but with one caveat: if the image is a big file, there are massive frame drops and eventually the app crashes.
I am looking for a way to find the ease of LoadImageAtPathAsync with perhaps a way to cancel a Task when the recycler clears the cell, along with support for multiple image loads as available in LoadImageAtPath. In the future, I also wish to use GetVideoThumbnailAsync in my gallery view, so finding a way to support multiple async image loadings with task cancellation when the view is getting recycled would be great.
I am making the app for VR, so frame drops are a very important factor, and that's why I designed my custom gallery view window instead of using the native Android file picker with GetMixedMediaFromGallery.
LoadImageAtPath and LoadImageAtPathAsync may create an intermediate temporary image file. Multiple async calls will have conflicts as you've experienced since I don't give unique filenames to these temporary files (I want them to be overwritten for storage optimization). I may try passing the image's bytes directly to Unity but then how am I supposed to use UnityWebRequest with that byte[] (serious question)?
Regarding sending byte[] between Unity and Java:
- https://discussions.unity.com/t/androidjni-tosbytearray-intptr-usage-in-java-code/882893
- https://docs.unity3d.com/ScriptReference/AndroidJNI.GetDirectByteBuffer.html
- https://discussions.unity.com/t/efficient-sharing-of-large-payloads-between-c-and-java-runtimes-on-android/738884/7
@yasirkula , thanks for the reply. In my previous attempts I was able to convert android's byte array to Unity texture2d with this code:
Texture2D tex = new Texture2D(2, 2);
tex.LoadImage(bytesArr);
tex.Apply();
but this would only work with AndroidJavaProxy callbacks. For some reason bytes returned directly from android to C# wouldn't load the texture but I might've been doing something wrong.
this was my java code:
public class ImageUtils {
public interface GlideImageCallback {
void onResult(byte[] imageData);
}
private static final ExecutorService executor = Executors.newSingleThreadExecutor();
public static void getGlideImage(Context activity, String filePath, GlideImageCallback glideImageCallback) {
if (activity == null || filePath == null) {
glideImageCallback.onResult(null);
return;
}
executor.execute(() -> {
try {
Glide.with(activity)
.asBitmap()
.load(filePath)
.override(240, 240)
.diskCacheStrategy(DiskCacheStrategy.ALL)
.listener(new RequestListener<Bitmap>() {
@Override
public boolean onLoadFailed(@Nullable GlideException e, Object model, Target<Bitmap> target, boolean isFirstResource) {
glideImageCallback.onResult(null);
return false;
}
@Override
public boolean onResourceReady(Bitmap resource, Object model, Target<Bitmap> target, DataSource dataSource, boolean isFirstResource) {
try {
ByteArrayOutputStream stream = new ByteArrayOutputStream();
resource.compress(Bitmap.CompressFormat.JPEG, 80, stream);
byte[] imageBytes = stream.toByteArray();
glideImageCallback.onResult(imageBytes);
} catch (Exception e) {
glideImageCallback.onResult(null);
}
return false;
}
})
.submit();
} catch (Exception e) {
glideImageCallback.onResult(null);
}
});
}
}
and in C#:
private class GlideImageCallback : AndroidJavaProxy
{
private Action<byte[]> cb;
public GlideImageCallback (Action<byte[]> cb) : base("com.unity3d.player.ImageUtils$GlideImageCallback")
{
this.cb = cb;
}
public void onResult(byte[] arr)
{
javaInterface.Dispose();
cb(arr);
}
}
This code used to work but it would take too much time for Glide library to send the image bytes and by that time, the recycler would already recycle the cell and it ended up with wrong view for wrong cell. But if you can send the bytes, I might be able to test with loading it to Texture2D directly.
Texture2D.LoadImage is a blocking operation so it won't do. It must be called on the main thread which I assume is why your code was failing.
@yasirkula oh right, that makes sense.
If you wish, you can give temporaryImagePath a unique path for each call and make sure to delete it after the image is loaded: https://github.com/yasirkula/UnityNativeGallery/blob/4d27475bed66d56aca8b7d585b98fe503525e593/Plugins/NativeGallery/NativeGallery.cs#L838