Skip to content

Commit

Permalink
Added asynchronous LoadImageAtPathAsync and GetVideoThumbnailAsync fu…
Browse files Browse the repository at this point in the history
…nctions (requires 2018.4 or later)
  • Loading branch information
yasirkula committed Sep 22, 2022
1 parent f872708 commit 12a6ab5
Show file tree
Hide file tree
Showing 4 changed files with 192 additions and 9 deletions.
6 changes: 5 additions & 1 deletion .github/README.md
Expand Up @@ -99,11 +99,15 @@ Beginning with *6.0 Marshmallow*, Android apps must request runtime permissions
- **generateMipmaps** determines whether texture should have mipmaps or not
- **linearColorSpace** determines whether texture should be in linear color space or sRGB color space

`Texture2D NativeCamera.GetVideoThumbnail( string videoPath, int maxSize = -1, double captureTimeInSeconds = -1.0, bool markTextureNonReadable = true )`: creates a Texture2D thumbnail from a video file and returns it. Returns *null*, if something goes wrong.
`async Task<Texture2D> NativeCamera.LoadImageAtPathAsync( string imagePath, int maxSize = -1, bool markTextureNonReadable = true, bool generateMipmaps = true, bool linearColorSpace = false )`: asynchronous variant of *LoadImageAtPath* (requires Unity 2018.4 or later). Works best when *linearColorSpace* is *false*. It's also slightly faster when *generateMipmaps* is *false*. Note that it isn't possible to load multiple images simultaneously using this function.

`Texture2D NativeCamera.GetVideoThumbnail( string videoPath, int maxSize = -1, double captureTimeInSeconds = -1.0, bool markTextureNonReadable = true, bool generateMipmaps = true, bool linearColorSpace = false )`: creates a Texture2D thumbnail from a video file and returns it. Returns *null*, if something goes wrong.
- **maxSize** determines the maximum size of the returned Texture2D in pixels. Larger thumbnails will be down-scaled. If untouched, its value will be set to *SystemInfo.maxTextureSize*. It is recommended to set a proper maxSize for better performance
- **captureTimeInSeconds** determines the frame of the video that the thumbnail is captured from. If untouched, OS will decide this value
- **markTextureNonReadable** (see *LoadImageAtPath*)

`async Task<Texture2D> NativeCamera.GetVideoThumbnailAsync( string videoPath, int maxSize = -1, double captureTimeInSeconds = -1.0, bool markTextureNonReadable = true, bool generateMipmaps = true, bool linearColorSpace = false )`: asynchronous variant of *GetVideoThumbnail* (requires Unity 2018.4 or later). Works best when *linearColorSpace* is *false*. It's also slightly faster when *generateMipmaps* is *false*. Note that it isn't possible to generate multiple video thumbnails simultaneously using this function.

## EXAMPLE CODE

The following code has two functions:
Expand Down
185 changes: 180 additions & 5 deletions Plugins/NativeCamera/NativeCamera.cs
Expand Up @@ -2,10 +2,15 @@
using System.Globalization;
using System.IO;
using UnityEngine;
using Object = UnityEngine.Object;
#if UNITY_2018_4_OR_NEWER && !NATIVE_CAMERA_DISABLE_ASYNC_FUNCTIONS
using System.Threading.Tasks;
using Unity.Collections;
using UnityEngine.Networking;
#endif
#if UNITY_ANDROID || UNITY_IOS
using NativeCameraNamespace;
#endif
using Object = UnityEngine.Object;

public static class NativeCamera
{
Expand Down Expand Up @@ -287,8 +292,7 @@ public static bool IsCameraBusy()
#endregion

#region Utility Functions
public static Texture2D LoadImageAtPath( string imagePath, int maxSize = -1, bool markTextureNonReadable = true,
bool generateMipmaps = true, bool linearColorSpace = false )
public static Texture2D LoadImageAtPath( string imagePath, int maxSize = -1, bool markTextureNonReadable = true, bool generateMipmaps = true, bool linearColorSpace = false )
{
if( string.IsNullOrEmpty( imagePath ) )
throw new ArgumentException( "Parameter 'imagePath' is null or empty!" );
Expand Down Expand Up @@ -316,6 +320,8 @@ public static bool IsCameraBusy()
{
if( !result.LoadImage( File.ReadAllBytes( loadPath ), markTextureNonReadable ) )
{
Debug.LogWarning( "Couldn't load image at path: " + loadPath );

Object.DestroyImmediate( result );
return null;
}
Expand All @@ -342,7 +348,129 @@ public static bool IsCameraBusy()
return result;
}

public static Texture2D GetVideoThumbnail( string videoPath, int maxSize = -1, double captureTimeInSeconds = -1.0, bool markTextureNonReadable = true )
#if UNITY_2018_4_OR_NEWER && !NATIVE_CAMERA_DISABLE_ASYNC_FUNCTIONS
public static async Task<Texture2D> LoadImageAtPathAsync( string imagePath, int maxSize = -1, bool markTextureNonReadable = true, bool generateMipmaps = true, bool linearColorSpace = false )
{
if( string.IsNullOrEmpty( imagePath ) )
throw new ArgumentException( "Parameter 'imagePath' is null or empty!" );

if( !File.Exists( imagePath ) )
throw new FileNotFoundException( "File not found at " + imagePath );

if( maxSize <= 0 )
maxSize = SystemInfo.maxTextureSize;

#if !UNITY_EDITOR && UNITY_ANDROID
string loadPath = await TryCallNativeAndroidFunctionOnSeparateThread( () => AJC.CallStatic<string>( "LoadImageAtPath", Context, imagePath, TemporaryImagePath, maxSize ) );
#elif !UNITY_EDITOR && UNITY_IOS
string loadPath = await Task.Run( () => _NativeCamera_LoadImageAtPath( imagePath, TemporaryImagePath, maxSize ) );
#else
string loadPath = imagePath;
#endif

Texture2D result = null;

if( !linearColorSpace )
{
using( UnityWebRequest www = UnityWebRequestTexture.GetTexture( "file://" + loadPath, markTextureNonReadable && !generateMipmaps ) )
{
UnityWebRequestAsyncOperation asyncOperation = www.SendWebRequest();
while( !asyncOperation.isDone )
await Task.Yield();

#if UNITY_2020_1_OR_NEWER
if( www.result != UnityWebRequest.Result.Success )
#else
if( www.isNetworkError || www.isHttpError )
#endif
{
Debug.LogWarning( "Couldn't use UnityWebRequest to load image, falling back to LoadImage: " + www.error );
}
else
{
Texture2D texture = DownloadHandlerTexture.GetContent( www );

if( !generateMipmaps )
result = texture;
else
{
Texture2D mipmapTexture = null;
try
{
// Generate a Texture with mipmaps enabled
// Credits: https://forum.unity.com/threads/generate-mipmaps-at-runtime-for-a-texture-loaded-with-unitywebrequest.644842/#post-7571809
NativeArray<byte> textureData = texture.GetRawTextureData<byte>();

mipmapTexture = new Texture2D( texture.width, texture.height, texture.format, true );
#if UNITY_2019_3_OR_NEWER
mipmapTexture.SetPixelData( textureData, 0 );
#else
NativeArray<byte> mipmapTextureData = mipmapTexture.GetRawTextureData<byte>();
NativeArray<byte>.Copy( textureData, mipmapTextureData, textureData.Length );
mipmapTexture.LoadRawTextureData( mipmapTextureData );
#endif
mipmapTexture.Apply( true, markTextureNonReadable );

result = mipmapTexture;
}
catch( Exception e )
{
Debug.LogException( e );

if( mipmapTexture )
Object.DestroyImmediate( mipmapTexture );
}
finally
{
Object.DestroyImmediate( texture );
}
}
}
}
}

if( !result ) // Fallback to Texture2D.LoadImage if something goes wrong
{
string extension = Path.GetExtension( imagePath ).ToLowerInvariant();
TextureFormat format = ( extension == ".jpg" || extension == ".jpeg" ) ? TextureFormat.RGB24 : TextureFormat.RGBA32;

result = new Texture2D( 2, 2, format, generateMipmaps, linearColorSpace );

try
{
if( !result.LoadImage( File.ReadAllBytes( loadPath ), markTextureNonReadable ) )
{
Debug.LogWarning( "Couldn't load image at path: " + loadPath );

Object.DestroyImmediate( result );
return null;
}
}
catch( Exception e )
{
Debug.LogException( e );

Object.DestroyImmediate( result );
return null;
}
finally
{
if( loadPath != imagePath )
{
try
{
File.Delete( loadPath );
}
catch { }
}
}
}

return result;
}
#endif

public static Texture2D GetVideoThumbnail( string videoPath, int maxSize = -1, double captureTimeInSeconds = -1.0, bool markTextureNonReadable = true, bool generateMipmaps = true, bool linearColorSpace = false )
{
if( maxSize <= 0 )
maxSize = SystemInfo.maxTextureSize;
Expand All @@ -356,11 +484,58 @@ public static Texture2D GetVideoThumbnail( string videoPath, int maxSize = -1, d
#endif

if( !string.IsNullOrEmpty( thumbnailPath ) )
return LoadImageAtPath( thumbnailPath, maxSize, markTextureNonReadable );
return LoadImageAtPath( thumbnailPath, maxSize, markTextureNonReadable, generateMipmaps, linearColorSpace );
else
return null;
}

#if UNITY_2018_4_OR_NEWER && !NATIVE_CAMERA_DISABLE_ASYNC_FUNCTIONS
public static async Task<Texture2D> GetVideoThumbnailAsync( string videoPath, int maxSize = -1, double captureTimeInSeconds = -1.0, bool markTextureNonReadable = true, bool generateMipmaps = true, bool linearColorSpace = false )
{
if( maxSize <= 0 )
maxSize = SystemInfo.maxTextureSize;

#if !UNITY_EDITOR && UNITY_ANDROID
string thumbnailPath = await TryCallNativeAndroidFunctionOnSeparateThread( () => AJC.CallStatic<string>( "GetVideoThumbnail", Context, videoPath, TemporaryImagePath + ".png", false, maxSize, captureTimeInSeconds ) );
#elif !UNITY_EDITOR && UNITY_IOS
string thumbnailPath = await Task.Run( () => _NativeCamera_GetVideoThumbnail( videoPath, TemporaryImagePath + ".png", maxSize, captureTimeInSeconds ) );
#else
string thumbnailPath = null;
#endif

if( !string.IsNullOrEmpty( thumbnailPath ) )
return await LoadImageAtPathAsync( thumbnailPath, maxSize, markTextureNonReadable, generateMipmaps, linearColorSpace );
else
return null;
}

private static async Task<T> TryCallNativeAndroidFunctionOnSeparateThread<T>( Func<T> function )
{
T result = default( T );
bool hasResult = false;

await Task.Run( () =>
{
if( AndroidJNI.AttachCurrentThread() != 0 )
Debug.LogWarning( "Couldn't attach JNI thread, calling native function on the main thread" );
else
{
try
{
result = function();
hasResult = true;
}
finally
{
AndroidJNI.DetachCurrentThread();
}
}
} );

return hasResult ? result : function();
}
#endif

public static ImageProperties GetImageProperties( string imagePath )
{
if( !File.Exists( imagePath ) )
Expand Down
8 changes: 6 additions & 2 deletions Plugins/NativeCamera/README.txt
@@ -1,4 +1,4 @@
= Native Camera for Android & iOS =
= Native Camera for Android & iOS (v1.3.5) =

Online documentation & example code available at: https://github.com/yasirkula/UnityNativeCamera
E-mail: yasirkula@gmail.com
Expand Down Expand Up @@ -78,12 +78,16 @@ bool NativeCamera.CanOpenSettings();
// generateMipmaps: determines whether texture should have mipmaps or not
// linearColorSpace: determines whether texture should be in linear color space or sRGB color space
Texture2D NativeCamera.LoadImageAtPath( string imagePath, int maxSize = -1, bool markTextureNonReadable = true, bool generateMipmaps = true, bool linearColorSpace = false );
async Task<Texture2D> NativeCamera.LoadImageAtPathAsync( string imagePath, int maxSize = -1, bool markTextureNonReadable = true, bool generateMipmaps = true, bool linearColorSpace = false );

// Creates a Texture2D thumbnail from a video file and returns it. Returns null, if something goes wrong
// maxSize: determines the maximum size of the returned Texture2D in pixels. Larger thumbnails will be down-scaled. If untouched, its value will be set to SystemInfo.maxTextureSize. It is recommended to set a proper maxSize for better performance
// captureTimeInSeconds: determines the frame of the video that the thumbnail is captured from. If untouched, OS will decide this value
// markTextureNonReadable: see LoadImageAtPath
Texture2D NativeCamera.GetVideoThumbnail( string videoPath, int maxSize = -1, double captureTimeInSeconds = -1.0, bool markTextureNonReadable = true );
// generateMipmaps: see LoadImageAtPath
// linearColorSpace: see LoadImageAtPath
Texture2D NativeCamera.GetVideoThumbnail( string videoPath, int maxSize = -1, double captureTimeInSeconds = -1.0, bool markTextureNonReadable = true, bool generateMipmaps = true, bool linearColorSpace = false );
async Task<Texture2D> NativeCamera.GetVideoThumbnailAsync( string videoPath, int maxSize = -1, double captureTimeInSeconds = -1.0, bool markTextureNonReadable = true, bool generateMipmaps = true, bool linearColorSpace = false );

// Returns an ImageProperties instance that holds the width, height and mime type information of an image file without creating a Texture2D object. Mime type will be null, if it can't be determined
NativeCamera.ImageProperties NativeCamera.GetImageProperties( string imagePath );
Expand Down
2 changes: 1 addition & 1 deletion package.json
@@ -1,7 +1,7 @@
{
"name": "com.yasirkula.nativecamera",
"displayName": "Native Camera",
"version": "1.3.3",
"version": "1.3.5",
"documentationUrl": "https://github.com/yasirkula/UnityNativeCamera",
"changelogUrl": "https://github.com/yasirkula/UnityNativeCamera/releases",
"licensesUrl": "https://github.com/yasirkula/UnityNativeCamera/blob/master/LICENSE.txt",
Expand Down

0 comments on commit 12a6ab5

Please sign in to comment.