Skip to content

Commit 12a6ab5

Browse files
committed
Added asynchronous LoadImageAtPathAsync and GetVideoThumbnailAsync functions (requires 2018.4 or later)
1 parent f872708 commit 12a6ab5

File tree

4 files changed

+192
-9
lines changed

4 files changed

+192
-9
lines changed

.github/README.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,11 +99,15 @@ Beginning with *6.0 Marshmallow*, Android apps must request runtime permissions
9999
- **generateMipmaps** determines whether texture should have mipmaps or not
100100
- **linearColorSpace** determines whether texture should be in linear color space or sRGB color space
101101

102-
`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.
102+
`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.
103+
104+
`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.
103105
- **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
104106
- **captureTimeInSeconds** determines the frame of the video that the thumbnail is captured from. If untouched, OS will decide this value
105107
- **markTextureNonReadable** (see *LoadImageAtPath*)
106108

109+
`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.
110+
107111
## EXAMPLE CODE
108112

109113
The following code has two functions:

Plugins/NativeCamera/NativeCamera.cs

Lines changed: 180 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,15 @@
22
using System.Globalization;
33
using System.IO;
44
using UnityEngine;
5-
using Object = UnityEngine.Object;
5+
#if UNITY_2018_4_OR_NEWER && !NATIVE_CAMERA_DISABLE_ASYNC_FUNCTIONS
6+
using System.Threading.Tasks;
7+
using Unity.Collections;
8+
using UnityEngine.Networking;
9+
#endif
610
#if UNITY_ANDROID || UNITY_IOS
711
using NativeCameraNamespace;
812
#endif
13+
using Object = UnityEngine.Object;
914

1015
public static class NativeCamera
1116
{
@@ -287,8 +292,7 @@ public static bool IsCameraBusy()
287292
#endregion
288293

289294
#region Utility Functions
290-
public static Texture2D LoadImageAtPath( string imagePath, int maxSize = -1, bool markTextureNonReadable = true,
291-
bool generateMipmaps = true, bool linearColorSpace = false )
295+
public static Texture2D LoadImageAtPath( string imagePath, int maxSize = -1, bool markTextureNonReadable = true, bool generateMipmaps = true, bool linearColorSpace = false )
292296
{
293297
if( string.IsNullOrEmpty( imagePath ) )
294298
throw new ArgumentException( "Parameter 'imagePath' is null or empty!" );
@@ -316,6 +320,8 @@ public static Texture2D LoadImageAtPath( string imagePath, int maxSize = -1, boo
316320
{
317321
if( !result.LoadImage( File.ReadAllBytes( loadPath ), markTextureNonReadable ) )
318322
{
323+
Debug.LogWarning( "Couldn't load image at path: " + loadPath );
324+
319325
Object.DestroyImmediate( result );
320326
return null;
321327
}
@@ -342,7 +348,129 @@ public static Texture2D LoadImageAtPath( string imagePath, int maxSize = -1, boo
342348
return result;
343349
}
344350

345-
public static Texture2D GetVideoThumbnail( string videoPath, int maxSize = -1, double captureTimeInSeconds = -1.0, bool markTextureNonReadable = true )
351+
#if UNITY_2018_4_OR_NEWER && !NATIVE_CAMERA_DISABLE_ASYNC_FUNCTIONS
352+
public static async Task<Texture2D> LoadImageAtPathAsync( string imagePath, int maxSize = -1, bool markTextureNonReadable = true, bool generateMipmaps = true, bool linearColorSpace = false )
353+
{
354+
if( string.IsNullOrEmpty( imagePath ) )
355+
throw new ArgumentException( "Parameter 'imagePath' is null or empty!" );
356+
357+
if( !File.Exists( imagePath ) )
358+
throw new FileNotFoundException( "File not found at " + imagePath );
359+
360+
if( maxSize <= 0 )
361+
maxSize = SystemInfo.maxTextureSize;
362+
363+
#if !UNITY_EDITOR && UNITY_ANDROID
364+
string loadPath = await TryCallNativeAndroidFunctionOnSeparateThread( () => AJC.CallStatic<string>( "LoadImageAtPath", Context, imagePath, TemporaryImagePath, maxSize ) );
365+
#elif !UNITY_EDITOR && UNITY_IOS
366+
string loadPath = await Task.Run( () => _NativeCamera_LoadImageAtPath( imagePath, TemporaryImagePath, maxSize ) );
367+
#else
368+
string loadPath = imagePath;
369+
#endif
370+
371+
Texture2D result = null;
372+
373+
if( !linearColorSpace )
374+
{
375+
using( UnityWebRequest www = UnityWebRequestTexture.GetTexture( "file://" + loadPath, markTextureNonReadable && !generateMipmaps ) )
376+
{
377+
UnityWebRequestAsyncOperation asyncOperation = www.SendWebRequest();
378+
while( !asyncOperation.isDone )
379+
await Task.Yield();
380+
381+
#if UNITY_2020_1_OR_NEWER
382+
if( www.result != UnityWebRequest.Result.Success )
383+
#else
384+
if( www.isNetworkError || www.isHttpError )
385+
#endif
386+
{
387+
Debug.LogWarning( "Couldn't use UnityWebRequest to load image, falling back to LoadImage: " + www.error );
388+
}
389+
else
390+
{
391+
Texture2D texture = DownloadHandlerTexture.GetContent( www );
392+
393+
if( !generateMipmaps )
394+
result = texture;
395+
else
396+
{
397+
Texture2D mipmapTexture = null;
398+
try
399+
{
400+
// Generate a Texture with mipmaps enabled
401+
// Credits: https://forum.unity.com/threads/generate-mipmaps-at-runtime-for-a-texture-loaded-with-unitywebrequest.644842/#post-7571809
402+
NativeArray<byte> textureData = texture.GetRawTextureData<byte>();
403+
404+
mipmapTexture = new Texture2D( texture.width, texture.height, texture.format, true );
405+
#if UNITY_2019_3_OR_NEWER
406+
mipmapTexture.SetPixelData( textureData, 0 );
407+
#else
408+
NativeArray<byte> mipmapTextureData = mipmapTexture.GetRawTextureData<byte>();
409+
NativeArray<byte>.Copy( textureData, mipmapTextureData, textureData.Length );
410+
mipmapTexture.LoadRawTextureData( mipmapTextureData );
411+
#endif
412+
mipmapTexture.Apply( true, markTextureNonReadable );
413+
414+
result = mipmapTexture;
415+
}
416+
catch( Exception e )
417+
{
418+
Debug.LogException( e );
419+
420+
if( mipmapTexture )
421+
Object.DestroyImmediate( mipmapTexture );
422+
}
423+
finally
424+
{
425+
Object.DestroyImmediate( texture );
426+
}
427+
}
428+
}
429+
}
430+
}
431+
432+
if( !result ) // Fallback to Texture2D.LoadImage if something goes wrong
433+
{
434+
string extension = Path.GetExtension( imagePath ).ToLowerInvariant();
435+
TextureFormat format = ( extension == ".jpg" || extension == ".jpeg" ) ? TextureFormat.RGB24 : TextureFormat.RGBA32;
436+
437+
result = new Texture2D( 2, 2, format, generateMipmaps, linearColorSpace );
438+
439+
try
440+
{
441+
if( !result.LoadImage( File.ReadAllBytes( loadPath ), markTextureNonReadable ) )
442+
{
443+
Debug.LogWarning( "Couldn't load image at path: " + loadPath );
444+
445+
Object.DestroyImmediate( result );
446+
return null;
447+
}
448+
}
449+
catch( Exception e )
450+
{
451+
Debug.LogException( e );
452+
453+
Object.DestroyImmediate( result );
454+
return null;
455+
}
456+
finally
457+
{
458+
if( loadPath != imagePath )
459+
{
460+
try
461+
{
462+
File.Delete( loadPath );
463+
}
464+
catch { }
465+
}
466+
}
467+
}
468+
469+
return result;
470+
}
471+
#endif
472+
473+
public static Texture2D GetVideoThumbnail( string videoPath, int maxSize = -1, double captureTimeInSeconds = -1.0, bool markTextureNonReadable = true, bool generateMipmaps = true, bool linearColorSpace = false )
346474
{
347475
if( maxSize <= 0 )
348476
maxSize = SystemInfo.maxTextureSize;
@@ -356,11 +484,58 @@ public static Texture2D GetVideoThumbnail( string videoPath, int maxSize = -1, d
356484
#endif
357485

358486
if( !string.IsNullOrEmpty( thumbnailPath ) )
359-
return LoadImageAtPath( thumbnailPath, maxSize, markTextureNonReadable );
487+
return LoadImageAtPath( thumbnailPath, maxSize, markTextureNonReadable, generateMipmaps, linearColorSpace );
488+
else
489+
return null;
490+
}
491+
492+
#if UNITY_2018_4_OR_NEWER && !NATIVE_CAMERA_DISABLE_ASYNC_FUNCTIONS
493+
public static async Task<Texture2D> GetVideoThumbnailAsync( string videoPath, int maxSize = -1, double captureTimeInSeconds = -1.0, bool markTextureNonReadable = true, bool generateMipmaps = true, bool linearColorSpace = false )
494+
{
495+
if( maxSize <= 0 )
496+
maxSize = SystemInfo.maxTextureSize;
497+
498+
#if !UNITY_EDITOR && UNITY_ANDROID
499+
string thumbnailPath = await TryCallNativeAndroidFunctionOnSeparateThread( () => AJC.CallStatic<string>( "GetVideoThumbnail", Context, videoPath, TemporaryImagePath + ".png", false, maxSize, captureTimeInSeconds ) );
500+
#elif !UNITY_EDITOR && UNITY_IOS
501+
string thumbnailPath = await Task.Run( () => _NativeCamera_GetVideoThumbnail( videoPath, TemporaryImagePath + ".png", maxSize, captureTimeInSeconds ) );
502+
#else
503+
string thumbnailPath = null;
504+
#endif
505+
506+
if( !string.IsNullOrEmpty( thumbnailPath ) )
507+
return await LoadImageAtPathAsync( thumbnailPath, maxSize, markTextureNonReadable, generateMipmaps, linearColorSpace );
360508
else
361509
return null;
362510
}
363511

512+
private static async Task<T> TryCallNativeAndroidFunctionOnSeparateThread<T>( Func<T> function )
513+
{
514+
T result = default( T );
515+
bool hasResult = false;
516+
517+
await Task.Run( () =>
518+
{
519+
if( AndroidJNI.AttachCurrentThread() != 0 )
520+
Debug.LogWarning( "Couldn't attach JNI thread, calling native function on the main thread" );
521+
else
522+
{
523+
try
524+
{
525+
result = function();
526+
hasResult = true;
527+
}
528+
finally
529+
{
530+
AndroidJNI.DetachCurrentThread();
531+
}
532+
}
533+
} );
534+
535+
return hasResult ? result : function();
536+
}
537+
#endif
538+
364539
public static ImageProperties GetImageProperties( string imagePath )
365540
{
366541
if( !File.Exists( imagePath ) )

Plugins/NativeCamera/README.txt

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
= Native Camera for Android & iOS =
1+
= Native Camera for Android & iOS (v1.3.5) =
22

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

8283
// Creates a Texture2D thumbnail from a video file and returns it. Returns null, if something goes wrong
8384
// 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
8485
// captureTimeInSeconds: determines the frame of the video that the thumbnail is captured from. If untouched, OS will decide this value
8586
// markTextureNonReadable: see LoadImageAtPath
86-
Texture2D NativeCamera.GetVideoThumbnail( string videoPath, int maxSize = -1, double captureTimeInSeconds = -1.0, bool markTextureNonReadable = true );
87+
// generateMipmaps: see LoadImageAtPath
88+
// linearColorSpace: see LoadImageAtPath
89+
Texture2D NativeCamera.GetVideoThumbnail( string videoPath, int maxSize = -1, double captureTimeInSeconds = -1.0, bool markTextureNonReadable = true, bool generateMipmaps = true, bool linearColorSpace = false );
90+
async Task<Texture2D> NativeCamera.GetVideoThumbnailAsync( string videoPath, int maxSize = -1, double captureTimeInSeconds = -1.0, bool markTextureNonReadable = true, bool generateMipmaps = true, bool linearColorSpace = false );
8791

8892
// 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
8993
NativeCamera.ImageProperties NativeCamera.GetImageProperties( string imagePath );

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "com.yasirkula.nativecamera",
33
"displayName": "Native Camera",
4-
"version": "1.3.3",
4+
"version": "1.3.5",
55
"documentationUrl": "https://github.com/yasirkula/UnityNativeCamera",
66
"changelogUrl": "https://github.com/yasirkula/UnityNativeCamera/releases",
77
"licensesUrl": "https://github.com/yasirkula/UnityNativeCamera/blob/master/LICENSE.txt",

0 commit comments

Comments
 (0)