add
This commit is contained in:
9
Assets/3rd/NativeGallery/Android.meta
Normal file
9
Assets/3rd/NativeGallery/Android.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0a607dcda26e7614f86300c6ca717295
|
||||
folderAsset: yes
|
||||
timeCreated: 1498722617
|
||||
licenseType: Store
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
31
Assets/3rd/NativeGallery/Android/NGCallbackHelper.cs
Normal file
31
Assets/3rd/NativeGallery/Android/NGCallbackHelper.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
#if !UNITY_EDITOR && UNITY_ANDROID
|
||||
using UnityEngine;
|
||||
|
||||
namespace NativeGalleryNamespace
|
||||
{
|
||||
public class NGCallbackHelper : MonoBehaviour
|
||||
{
|
||||
private System.Action mainThreadAction = null;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
DontDestroyOnLoad( gameObject );
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if( mainThreadAction != null )
|
||||
{
|
||||
System.Action temp = mainThreadAction;
|
||||
mainThreadAction = null;
|
||||
temp();
|
||||
}
|
||||
}
|
||||
|
||||
public void CallOnMainThread( System.Action function )
|
||||
{
|
||||
mainThreadAction = function;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
12
Assets/3rd/NativeGallery/Android/NGCallbackHelper.cs.meta
Normal file
12
Assets/3rd/NativeGallery/Android/NGCallbackHelper.cs.meta
Normal file
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2d517fd0f2f85f24698df2775bee58e9
|
||||
timeCreated: 1544889149
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,92 @@
|
||||
#if !UNITY_EDITOR && UNITY_ANDROID
|
||||
using UnityEngine;
|
||||
|
||||
namespace NativeGalleryNamespace
|
||||
{
|
||||
public class NGMediaReceiveCallbackAndroid : AndroidJavaProxy
|
||||
{
|
||||
private readonly NativeGallery.MediaPickCallback callback;
|
||||
private readonly NativeGallery.MediaPickMultipleCallback callbackMultiple;
|
||||
|
||||
private readonly NGCallbackHelper callbackHelper;
|
||||
|
||||
public NGMediaReceiveCallbackAndroid( NativeGallery.MediaPickCallback callback, NativeGallery.MediaPickMultipleCallback callbackMultiple ) : base( "com.yasirkula.unity.NativeGalleryMediaReceiver" )
|
||||
{
|
||||
this.callback = callback;
|
||||
this.callbackMultiple = callbackMultiple;
|
||||
callbackHelper = new GameObject( "NGCallbackHelper" ).AddComponent<NGCallbackHelper>();
|
||||
}
|
||||
|
||||
public void OnMediaReceived( string path )
|
||||
{
|
||||
callbackHelper.CallOnMainThread( () => MediaReceiveCallback( path ) );
|
||||
}
|
||||
|
||||
public void OnMultipleMediaReceived( string paths )
|
||||
{
|
||||
string[] result = null;
|
||||
if( !string.IsNullOrEmpty( paths ) )
|
||||
{
|
||||
string[] pathsSplit = paths.Split( '>' );
|
||||
|
||||
int validPathCount = 0;
|
||||
for( int i = 0; i < pathsSplit.Length; i++ )
|
||||
{
|
||||
if( !string.IsNullOrEmpty( pathsSplit[i] ) )
|
||||
validPathCount++;
|
||||
}
|
||||
|
||||
if( validPathCount == 0 )
|
||||
pathsSplit = new string[0];
|
||||
else if( validPathCount != pathsSplit.Length )
|
||||
{
|
||||
string[] validPaths = new string[validPathCount];
|
||||
for( int i = 0, j = 0; i < pathsSplit.Length; i++ )
|
||||
{
|
||||
if( !string.IsNullOrEmpty( pathsSplit[i] ) )
|
||||
validPaths[j++] = pathsSplit[i];
|
||||
}
|
||||
|
||||
pathsSplit = validPaths;
|
||||
}
|
||||
|
||||
result = pathsSplit;
|
||||
}
|
||||
|
||||
callbackHelper.CallOnMainThread( () => MediaReceiveMultipleCallback( result ) );
|
||||
}
|
||||
|
||||
private void MediaReceiveCallback( string path )
|
||||
{
|
||||
if( string.IsNullOrEmpty( path ) )
|
||||
path = null;
|
||||
|
||||
try
|
||||
{
|
||||
if( callback != null )
|
||||
callback( path );
|
||||
}
|
||||
finally
|
||||
{
|
||||
Object.Destroy( callbackHelper );
|
||||
}
|
||||
}
|
||||
|
||||
private void MediaReceiveMultipleCallback( string[] paths )
|
||||
{
|
||||
if( paths != null && paths.Length == 0 )
|
||||
paths = null;
|
||||
|
||||
try
|
||||
{
|
||||
if( callbackMultiple != null )
|
||||
callbackMultiple( paths );
|
||||
}
|
||||
finally
|
||||
{
|
||||
Object.Destroy( callbackHelper );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4c18d702b07a63945968db47201b95c9
|
||||
timeCreated: 1519060539
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,29 @@
|
||||
#if !UNITY_EDITOR && UNITY_ANDROID
|
||||
using System.Threading;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NativeGalleryNamespace
|
||||
{
|
||||
public class NGPermissionCallbackAndroid : AndroidJavaProxy
|
||||
{
|
||||
private object threadLock;
|
||||
public int Result { get; private set; }
|
||||
|
||||
public NGPermissionCallbackAndroid( object threadLock ) : base( "com.yasirkula.unity.NativeGalleryPermissionReceiver" )
|
||||
{
|
||||
Result = -1;
|
||||
this.threadLock = threadLock;
|
||||
}
|
||||
|
||||
public void OnPermissionResult( int result )
|
||||
{
|
||||
Result = result;
|
||||
|
||||
lock( threadLock )
|
||||
{
|
||||
Monitor.Pulse( threadLock );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a07afac614af1294d8e72a3c083be028
|
||||
timeCreated: 1519060539
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
BIN
Assets/3rd/NativeGallery/Android/NativeGallery.jar
Normal file
BIN
Assets/3rd/NativeGallery/Android/NativeGallery.jar
Normal file
Binary file not shown.
33
Assets/3rd/NativeGallery/Android/NativeGallery.jar.meta
Normal file
33
Assets/3rd/NativeGallery/Android/NativeGallery.jar.meta
Normal file
@@ -0,0 +1,33 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 07368f3d2020fe6439475cfaa8578fd8
|
||||
timeCreated: 1544891451
|
||||
licenseType: Store
|
||||
PluginImporter:
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
platformData:
|
||||
data:
|
||||
first:
|
||||
Android: Android
|
||||
second:
|
||||
enabled: 1
|
||||
settings: {}
|
||||
data:
|
||||
first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
data:
|
||||
first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
9
Assets/3rd/NativeGallery/Editor.meta
Normal file
9
Assets/3rd/NativeGallery/Editor.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 19fc6b8ce781591438a952d8aa9104f8
|
||||
folderAsset: yes
|
||||
timeCreated: 1521452097
|
||||
licenseType: Store
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
63
Assets/3rd/NativeGallery/Editor/NGPostProcessBuild.cs
Normal file
63
Assets/3rd/NativeGallery/Editor/NGPostProcessBuild.cs
Normal file
@@ -0,0 +1,63 @@
|
||||
#if UNITY_IOS
|
||||
using UnityEditor;
|
||||
using UnityEditor.Callbacks;
|
||||
using System.IO;
|
||||
using UnityEditor.iOS.Xcode;
|
||||
#endif
|
||||
|
||||
public class NGPostProcessBuild
|
||||
{
|
||||
private const bool ENABLED = true;
|
||||
|
||||
private const string PHOTO_LIBRARY_USAGE_DESCRIPTION = "Save media to Photos";
|
||||
private const bool MINIMUM_TARGET_8_OR_ABOVE = false;
|
||||
|
||||
#if UNITY_IOS
|
||||
#pragma warning disable 0162
|
||||
[PostProcessBuild]
|
||||
public static void OnPostprocessBuild( BuildTarget target, string buildPath )
|
||||
{
|
||||
if( !ENABLED )
|
||||
return;
|
||||
|
||||
if( target == BuildTarget.iOS )
|
||||
{
|
||||
string pbxProjectPath = PBXProject.GetPBXProjectPath( buildPath );
|
||||
string plistPath = Path.Combine( buildPath, "Info.plist" );
|
||||
|
||||
PBXProject pbxProject = new PBXProject();
|
||||
pbxProject.ReadFromFile( pbxProjectPath );
|
||||
|
||||
string targetGUID = pbxProject.TargetGuidByName( PBXProject.GetUnityTargetName() );
|
||||
|
||||
if( MINIMUM_TARGET_8_OR_ABOVE )
|
||||
{
|
||||
pbxProject.AddBuildProperty( targetGUID, "OTHER_LDFLAGS", "-framework Photos" );
|
||||
pbxProject.AddBuildProperty( targetGUID, "OTHER_LDFLAGS", "-framework MobileCoreServices" );
|
||||
pbxProject.AddBuildProperty( targetGUID, "OTHER_LDFLAGS", "-framework ImageIO" );
|
||||
}
|
||||
else
|
||||
{
|
||||
pbxProject.AddBuildProperty( targetGUID, "OTHER_LDFLAGS", "-weak_framework Photos" );
|
||||
pbxProject.AddBuildProperty( targetGUID, "OTHER_LDFLAGS", "-framework AssetsLibrary" );
|
||||
pbxProject.AddBuildProperty( targetGUID, "OTHER_LDFLAGS", "-framework MobileCoreServices" );
|
||||
pbxProject.AddBuildProperty( targetGUID, "OTHER_LDFLAGS", "-framework ImageIO" );
|
||||
}
|
||||
|
||||
pbxProject.RemoveFrameworkFromProject( targetGUID, "Photos.framework" );
|
||||
|
||||
File.WriteAllText( pbxProjectPath, pbxProject.WriteToString() );
|
||||
|
||||
PlistDocument plist = new PlistDocument();
|
||||
plist.ReadFromString( File.ReadAllText( plistPath ) );
|
||||
|
||||
PlistElementDict rootDict = plist.root;
|
||||
rootDict.SetString( "NSPhotoLibraryUsageDescription", PHOTO_LIBRARY_USAGE_DESCRIPTION );
|
||||
rootDict.SetString( "NSPhotoLibraryAddUsageDescription", PHOTO_LIBRARY_USAGE_DESCRIPTION );
|
||||
|
||||
File.WriteAllText( plistPath, plist.WriteToString() );
|
||||
}
|
||||
}
|
||||
#pragma warning restore 0162
|
||||
#endif
|
||||
}
|
||||
12
Assets/3rd/NativeGallery/Editor/NGPostProcessBuild.cs.meta
Normal file
12
Assets/3rd/NativeGallery/Editor/NGPostProcessBuild.cs.meta
Normal file
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: dff1540cf22bfb749a2422f445cf9427
|
||||
timeCreated: 1521452119
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
665
Assets/3rd/NativeGallery/NativeGallery.cs
Normal file
665
Assets/3rd/NativeGallery/NativeGallery.cs
Normal file
@@ -0,0 +1,665 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using UnityEngine;
|
||||
using Object = UnityEngine.Object;
|
||||
#if !UNITY_EDITOR && ( UNITY_ANDROID || UNITY_IOS )
|
||||
using NativeGalleryNamespace;
|
||||
#endif
|
||||
|
||||
public static class NativeGallery
|
||||
{
|
||||
public struct ImageProperties
|
||||
{
|
||||
public readonly int width;
|
||||
public readonly int height;
|
||||
public readonly string mimeType;
|
||||
public readonly ImageOrientation orientation;
|
||||
|
||||
public ImageProperties( int width, int height, string mimeType, ImageOrientation orientation )
|
||||
{
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.mimeType = mimeType;
|
||||
this.orientation = orientation;
|
||||
}
|
||||
}
|
||||
|
||||
public struct VideoProperties
|
||||
{
|
||||
public readonly int width;
|
||||
public readonly int height;
|
||||
public readonly long duration;
|
||||
public readonly float rotation;
|
||||
|
||||
public VideoProperties( int width, int height, long duration, float rotation )
|
||||
{
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.duration = duration;
|
||||
this.rotation = rotation;
|
||||
}
|
||||
}
|
||||
|
||||
public enum Permission { Denied = 0, Granted = 1, ShouldAsk = 2 };
|
||||
|
||||
// EXIF orientation: http://sylvana.net/jpegcrop/exif_orientation.html (indices are reordered)
|
||||
public enum ImageOrientation { Unknown = -1, Normal = 0, Rotate90 = 1, Rotate180 = 2, Rotate270 = 3, FlipHorizontal = 4, Transpose = 5, FlipVertical = 6, Transverse = 7 };
|
||||
|
||||
public delegate void MediaSaveCallback( string error );
|
||||
public delegate void MediaPickCallback( string path );
|
||||
public delegate void MediaPickMultipleCallback( string[] paths );
|
||||
|
||||
#region Platform Specific Elements
|
||||
#if !UNITY_EDITOR && UNITY_ANDROID
|
||||
private static AndroidJavaClass m_ajc = null;
|
||||
private static AndroidJavaClass AJC
|
||||
{
|
||||
get
|
||||
{
|
||||
if( m_ajc == null )
|
||||
m_ajc = new AndroidJavaClass( "com.yasirkula.unity.NativeGallery" );
|
||||
|
||||
return m_ajc;
|
||||
}
|
||||
}
|
||||
|
||||
private static AndroidJavaObject m_context = null;
|
||||
private static AndroidJavaObject Context
|
||||
{
|
||||
get
|
||||
{
|
||||
if( m_context == null )
|
||||
{
|
||||
using( AndroidJavaObject unityClass = new AndroidJavaClass( "com.unity3d.player.UnityPlayer" ) )
|
||||
{
|
||||
m_context = unityClass.GetStatic<AndroidJavaObject>( "currentActivity" );
|
||||
}
|
||||
}
|
||||
|
||||
return m_context;
|
||||
}
|
||||
}
|
||||
#elif !UNITY_EDITOR && UNITY_IOS
|
||||
[System.Runtime.InteropServices.DllImport( "__Internal" )]
|
||||
private static extern int _NativeGallery_CheckPermission();
|
||||
|
||||
[System.Runtime.InteropServices.DllImport( "__Internal" )]
|
||||
private static extern int _NativeGallery_RequestPermission();
|
||||
|
||||
[System.Runtime.InteropServices.DllImport( "__Internal" )]
|
||||
private static extern int _NativeGallery_CanOpenSettings();
|
||||
|
||||
[System.Runtime.InteropServices.DllImport( "__Internal" )]
|
||||
private static extern void _NativeGallery_OpenSettings();
|
||||
|
||||
[System.Runtime.InteropServices.DllImport( "__Internal" )]
|
||||
private static extern void _NativeGallery_ImageWriteToAlbum( string path, string album );
|
||||
|
||||
[System.Runtime.InteropServices.DllImport( "__Internal" )]
|
||||
private static extern void _NativeGallery_VideoWriteToAlbum( string path, string album );
|
||||
|
||||
[System.Runtime.InteropServices.DllImport( "__Internal" )]
|
||||
private static extern void _NativeGallery_PickImage( string imageSavePath, int maxSize );
|
||||
|
||||
[System.Runtime.InteropServices.DllImport( "__Internal" )]
|
||||
private static extern void _NativeGallery_PickVideo();
|
||||
|
||||
[System.Runtime.InteropServices.DllImport( "__Internal" )]
|
||||
private static extern string _NativeGallery_GetImageProperties( string path );
|
||||
|
||||
[System.Runtime.InteropServices.DllImport( "__Internal" )]
|
||||
private static extern string _NativeGallery_GetVideoProperties( string path );
|
||||
|
||||
[System.Runtime.InteropServices.DllImport( "__Internal" )]
|
||||
private static extern string _NativeGallery_LoadImageAtPath( string path, string temporaryFilePath, int maxSize );
|
||||
#endif
|
||||
|
||||
#if !UNITY_EDITOR && ( UNITY_ANDROID || UNITY_IOS )
|
||||
private static string m_temporaryImagePath = null;
|
||||
private static string TemporaryImagePath
|
||||
{
|
||||
get
|
||||
{
|
||||
if( m_temporaryImagePath == null )
|
||||
{
|
||||
m_temporaryImagePath = Path.Combine( Application.temporaryCachePath, "__tmpImG" );
|
||||
Directory.CreateDirectory( Application.temporaryCachePath );
|
||||
}
|
||||
|
||||
return m_temporaryImagePath;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if !UNITY_EDITOR && UNITY_IOS
|
||||
private static string m_iOSSelectedImagePath = null;
|
||||
private static string IOSSelectedImagePath
|
||||
{
|
||||
get
|
||||
{
|
||||
if( m_iOSSelectedImagePath == null )
|
||||
{
|
||||
m_iOSSelectedImagePath = Path.Combine( Application.temporaryCachePath, "tmp.png" );
|
||||
Directory.CreateDirectory( Application.temporaryCachePath );
|
||||
}
|
||||
|
||||
return m_iOSSelectedImagePath;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#endregion
|
||||
|
||||
#region Runtime Permissions
|
||||
public static Permission CheckPermission( bool readPermissionOnly = false )
|
||||
{
|
||||
#if !UNITY_EDITOR && UNITY_ANDROID
|
||||
Permission result = (Permission) AJC.CallStatic<int>( "CheckPermission", Context, readPermissionOnly );
|
||||
if( result == Permission.Denied && (Permission) PlayerPrefs.GetInt( "NativeGalleryPermission", (int) Permission.ShouldAsk ) == Permission.ShouldAsk )
|
||||
result = Permission.ShouldAsk;
|
||||
|
||||
return result;
|
||||
#elif !UNITY_EDITOR && UNITY_IOS
|
||||
return (Permission) _NativeGallery_CheckPermission();
|
||||
#else
|
||||
return Permission.Granted;
|
||||
#endif
|
||||
}
|
||||
|
||||
public static Permission RequestPermission( bool readPermissionOnly = false )
|
||||
{
|
||||
#if !UNITY_EDITOR && UNITY_ANDROID
|
||||
object threadLock = new object();
|
||||
lock( threadLock )
|
||||
{
|
||||
NGPermissionCallbackAndroid nativeCallback = new NGPermissionCallbackAndroid( threadLock );
|
||||
|
||||
AJC.CallStatic( "RequestPermission", Context, nativeCallback, readPermissionOnly, PlayerPrefs.GetInt( "NativeGalleryPermission", (int) Permission.ShouldAsk ) );
|
||||
|
||||
if( nativeCallback.Result == -1 )
|
||||
System.Threading.Monitor.Wait( threadLock );
|
||||
|
||||
if( (Permission) nativeCallback.Result != Permission.ShouldAsk && PlayerPrefs.GetInt( "NativeGalleryPermission", -1 ) != nativeCallback.Result )
|
||||
{
|
||||
PlayerPrefs.SetInt( "NativeGalleryPermission", nativeCallback.Result );
|
||||
PlayerPrefs.Save();
|
||||
}
|
||||
|
||||
return (Permission) nativeCallback.Result;
|
||||
}
|
||||
#elif !UNITY_EDITOR && UNITY_IOS
|
||||
return (Permission) _NativeGallery_RequestPermission();
|
||||
#else
|
||||
return Permission.Granted;
|
||||
#endif
|
||||
}
|
||||
|
||||
public static bool CanOpenSettings()
|
||||
{
|
||||
#if !UNITY_EDITOR && UNITY_IOS
|
||||
return _NativeGallery_CanOpenSettings() == 1;
|
||||
#else
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
public static void OpenSettings()
|
||||
{
|
||||
#if !UNITY_EDITOR && UNITY_ANDROID
|
||||
AJC.CallStatic( "OpenSettings", Context );
|
||||
#elif !UNITY_EDITOR && UNITY_IOS
|
||||
_NativeGallery_OpenSettings();
|
||||
#endif
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Save Functions
|
||||
public static Permission SaveImageToGallery( byte[] mediaBytes, string album, string filenameFormatted, MediaSaveCallback callback = null )
|
||||
{
|
||||
return SaveToGallery( mediaBytes, album, filenameFormatted, true, callback );
|
||||
}
|
||||
|
||||
public static Permission SaveImageToGallery( string existingMediaPath, string album, string filenameFormatted, MediaSaveCallback callback = null )
|
||||
{
|
||||
return SaveToGallery( existingMediaPath, album, filenameFormatted, true, callback );
|
||||
}
|
||||
|
||||
public static Permission SaveImageToGallery( Texture2D image, string album, string filenameFormatted, MediaSaveCallback callback = null )
|
||||
{
|
||||
if( image == null )
|
||||
throw new ArgumentException( "Parameter 'image' is null!" );
|
||||
|
||||
if( filenameFormatted.EndsWith( ".jpeg" ) || filenameFormatted.EndsWith( ".jpg" ) )
|
||||
return SaveToGallery( GetTextureBytes( image, true ), album, filenameFormatted, true, callback );
|
||||
else if( filenameFormatted.EndsWith( ".png" ) )
|
||||
return SaveToGallery( GetTextureBytes( image, false ), album, filenameFormatted, true, callback );
|
||||
else
|
||||
return SaveToGallery( GetTextureBytes( image, false ), album, filenameFormatted + ".png", true, callback );
|
||||
}
|
||||
|
||||
public static Permission SaveVideoToGallery( byte[] mediaBytes, string album, string filenameFormatted, MediaSaveCallback callback = null )
|
||||
{
|
||||
return SaveToGallery( mediaBytes, album, filenameFormatted, false, callback );
|
||||
}
|
||||
|
||||
public static Permission SaveVideoToGallery( string existingMediaPath, string album, string filenameFormatted, MediaSaveCallback callback = null )
|
||||
{
|
||||
return SaveToGallery( existingMediaPath, album, filenameFormatted, false, callback );
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Load Functions
|
||||
public static bool CanSelectMultipleFilesFromGallery()
|
||||
{
|
||||
#if !UNITY_EDITOR && UNITY_ANDROID
|
||||
return AJC.CallStatic<bool>( "CanSelectMultipleMedia" );
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
public static Permission GetImageFromGallery( MediaPickCallback callback, string title = "", string mime = "image/*", int maxSize = -1 )
|
||||
{
|
||||
return GetMediaFromGallery( callback, true, mime, title, maxSize );
|
||||
}
|
||||
|
||||
public static Permission GetVideoFromGallery( MediaPickCallback callback, string title = "", string mime = "video/*" )
|
||||
{
|
||||
return GetMediaFromGallery( callback, false, mime, title, -1 );
|
||||
}
|
||||
|
||||
public static Permission GetImagesFromGallery( MediaPickMultipleCallback callback, string title = "", string mime = "image/*", int maxSize = -1 )
|
||||
{
|
||||
return GetMultipleMediaFromGallery( callback, true, mime, title, maxSize );
|
||||
}
|
||||
|
||||
public static Permission GetVideosFromGallery( MediaPickMultipleCallback callback, string title = "", string mime = "video/*" )
|
||||
{
|
||||
return GetMultipleMediaFromGallery( callback, false, mime, title, -1 );
|
||||
}
|
||||
|
||||
public static bool IsMediaPickerBusy()
|
||||
{
|
||||
#if !UNITY_EDITOR && UNITY_IOS
|
||||
return NGMediaReceiveCallbackiOS.IsBusy;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Internal Functions
|
||||
private static Permission SaveToGallery( byte[] mediaBytes, string album, string filenameFormatted, bool isImage, MediaSaveCallback callback )
|
||||
{
|
||||
Permission result = RequestPermission( false );
|
||||
if( result == Permission.Granted )
|
||||
{
|
||||
if( mediaBytes == null || mediaBytes.Length == 0 )
|
||||
throw new ArgumentException( "Parameter 'mediaBytes' is null or empty!" );
|
||||
|
||||
if( album == null || album.Length == 0 )
|
||||
throw new ArgumentException( "Parameter 'album' is null or empty!" );
|
||||
|
||||
if( filenameFormatted == null || filenameFormatted.Length == 0 )
|
||||
throw new ArgumentException( "Parameter 'filenameFormatted' is null or empty!" );
|
||||
|
||||
string path = GetSavePath( album, filenameFormatted );
|
||||
|
||||
File.WriteAllBytes( path, mediaBytes );
|
||||
|
||||
SaveToGalleryInternal( path, album, isImage, callback );
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static Permission SaveToGallery( string existingMediaPath, string album, string filenameFormatted, bool isImage, MediaSaveCallback callback )
|
||||
{
|
||||
Permission result = RequestPermission( false );
|
||||
if( result == Permission.Granted )
|
||||
{
|
||||
if( !File.Exists( existingMediaPath ) )
|
||||
throw new FileNotFoundException( "File not found at " + existingMediaPath );
|
||||
|
||||
if( album == null || album.Length == 0 )
|
||||
throw new ArgumentException( "Parameter 'album' is null or empty!" );
|
||||
|
||||
if( filenameFormatted == null || filenameFormatted.Length == 0 )
|
||||
throw new ArgumentException( "Parameter 'filenameFormatted' is null or empty!" );
|
||||
|
||||
string path = GetSavePath( album, filenameFormatted );
|
||||
|
||||
File.Copy( existingMediaPath, path, true );
|
||||
|
||||
SaveToGalleryInternal( path, album, isImage, callback );
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static void SaveToGalleryInternal( string path, string album, bool isImage, MediaSaveCallback callback )
|
||||
{
|
||||
#if !UNITY_EDITOR && UNITY_ANDROID
|
||||
AJC.CallStatic( "MediaScanFile", Context, path );
|
||||
|
||||
if( callback != null )
|
||||
callback( null );
|
||||
|
||||
Debug.Log( "Saving to gallery: " + path );
|
||||
#elif !UNITY_EDITOR && UNITY_IOS
|
||||
NGMediaSaveCallbackiOS.Initialize( callback );
|
||||
if( isImage )
|
||||
_NativeGallery_ImageWriteToAlbum( path, album );
|
||||
else
|
||||
_NativeGallery_VideoWriteToAlbum( path, album );
|
||||
|
||||
Debug.Log( "Saving to Pictures: " + Path.GetFileName( path ) );
|
||||
#else
|
||||
if( callback != null )
|
||||
callback( null );
|
||||
#endif
|
||||
}
|
||||
|
||||
private static string GetSavePath( string album, string filenameFormatted )
|
||||
{
|
||||
string saveDir;
|
||||
#if !UNITY_EDITOR && UNITY_ANDROID
|
||||
saveDir = AJC.CallStatic<string>( "GetMediaPath", album );
|
||||
#else
|
||||
saveDir = Application.persistentDataPath;
|
||||
#endif
|
||||
|
||||
if( filenameFormatted.Contains( "{0}" ) )
|
||||
{
|
||||
int fileIndex = 0;
|
||||
string path;
|
||||
do
|
||||
{
|
||||
path = Path.Combine( saveDir, string.Format( filenameFormatted, ++fileIndex ) );
|
||||
} while( File.Exists( path ) );
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
saveDir = Path.Combine( saveDir, filenameFormatted );
|
||||
|
||||
#if !UNITY_EDITOR && UNITY_IOS
|
||||
// iOS internally copies images/videos to Photos directory of the system,
|
||||
// but the process is async. The redundant file is deleted by objective-c code
|
||||
// automatically after the media is saved but while it is being saved, the file
|
||||
// should NOT be overwritten. Therefore, always ensure a unique filename on iOS
|
||||
if( File.Exists( saveDir ) )
|
||||
{
|
||||
return GetSavePath( album,
|
||||
Path.GetFileNameWithoutExtension( filenameFormatted ) + " {0}" + Path.GetExtension( filenameFormatted ) );
|
||||
}
|
||||
#endif
|
||||
|
||||
return saveDir;
|
||||
}
|
||||
|
||||
private static Permission GetMediaFromGallery( MediaPickCallback callback, bool imageMode, string mime, string title, int maxSize )
|
||||
{
|
||||
Permission result = RequestPermission( true );
|
||||
if( result == Permission.Granted && !IsMediaPickerBusy() )
|
||||
{
|
||||
#if !UNITY_EDITOR && UNITY_ANDROID
|
||||
AJC.CallStatic( "PickMedia", Context, new NGMediaReceiveCallbackAndroid( callback, null ), imageMode, false, mime, title );
|
||||
#elif !UNITY_EDITOR && UNITY_IOS
|
||||
NGMediaReceiveCallbackiOS.Initialize( callback );
|
||||
if( imageMode )
|
||||
{
|
||||
if( maxSize <= 0 )
|
||||
maxSize = SystemInfo.maxTextureSize;
|
||||
|
||||
_NativeGallery_PickImage( IOSSelectedImagePath, maxSize );
|
||||
}
|
||||
else
|
||||
_NativeGallery_PickVideo();
|
||||
#else
|
||||
if( callback != null )
|
||||
callback( null );
|
||||
#endif
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static Permission GetMultipleMediaFromGallery( MediaPickMultipleCallback callback, bool imageMode, string mime, string title, int maxSize )
|
||||
{
|
||||
Permission result = RequestPermission( true );
|
||||
if( result == Permission.Granted && !IsMediaPickerBusy() )
|
||||
{
|
||||
if( CanSelectMultipleFilesFromGallery() )
|
||||
{
|
||||
#if !UNITY_EDITOR && UNITY_ANDROID
|
||||
AJC.CallStatic( "PickMedia", Context, new NGMediaReceiveCallbackAndroid( null, callback ), imageMode, true, mime, title );
|
||||
#else
|
||||
if( callback != null )
|
||||
callback( null );
|
||||
#endif
|
||||
}
|
||||
else if( callback != null )
|
||||
callback( null );
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static byte[] GetTextureBytes( Texture2D texture, bool isJpeg )
|
||||
{
|
||||
try
|
||||
{
|
||||
return isJpeg ? texture.EncodeToJPG( 100 ) : texture.EncodeToPNG();
|
||||
}
|
||||
catch( UnityException )
|
||||
{
|
||||
return GetTextureBytesFromCopy( texture, isJpeg );
|
||||
}
|
||||
catch( ArgumentException )
|
||||
{
|
||||
return GetTextureBytesFromCopy( texture, isJpeg );
|
||||
}
|
||||
|
||||
#pragma warning disable 0162
|
||||
return null;
|
||||
#pragma warning restore 0162
|
||||
}
|
||||
|
||||
private static byte[] GetTextureBytesFromCopy( Texture2D texture, bool isJpeg )
|
||||
{
|
||||
// Texture is marked as non-readable, create a readable copy and save it instead
|
||||
Debug.LogWarning( "Saving non-readable textures is slower than saving readable textures" );
|
||||
|
||||
Texture2D sourceTexReadable = null;
|
||||
RenderTexture rt = RenderTexture.GetTemporary( texture.width, texture.height );
|
||||
RenderTexture activeRT = RenderTexture.active;
|
||||
|
||||
try
|
||||
{
|
||||
Graphics.Blit( texture, rt );
|
||||
RenderTexture.active = rt;
|
||||
|
||||
sourceTexReadable = new Texture2D( texture.width, texture.height, texture.format, false );
|
||||
sourceTexReadable.ReadPixels( new Rect( 0, 0, texture.width, texture.height ), 0, 0, false );
|
||||
sourceTexReadable.Apply( false, false );
|
||||
}
|
||||
catch( Exception e )
|
||||
{
|
||||
Debug.LogException( e );
|
||||
|
||||
Object.DestroyImmediate( sourceTexReadable );
|
||||
return null;
|
||||
}
|
||||
finally
|
||||
{
|
||||
RenderTexture.active = activeRT;
|
||||
RenderTexture.ReleaseTemporary( rt );
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return isJpeg ? sourceTexReadable.EncodeToJPG( 100 ) : sourceTexReadable.EncodeToPNG();
|
||||
}
|
||||
catch( Exception e )
|
||||
{
|
||||
Debug.LogException( e );
|
||||
return null;
|
||||
}
|
||||
finally
|
||||
{
|
||||
Object.DestroyImmediate( sourceTexReadable );
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Utility Functions
|
||||
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!" );
|
||||
|
||||
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 = AJC.CallStatic<string>( "LoadImageAtPath", Context, imagePath, TemporaryImagePath, maxSize );
|
||||
#elif !UNITY_EDITOR && UNITY_IOS
|
||||
string loadPath = _NativeGallery_LoadImageAtPath( imagePath, TemporaryImagePath, maxSize );
|
||||
#else
|
||||
string loadPath = imagePath;
|
||||
#endif
|
||||
|
||||
String extension = Path.GetExtension( imagePath ).ToLowerInvariant();
|
||||
TextureFormat format = ( extension == ".jpg" || extension == ".jpeg" ) ? TextureFormat.RGB24 : TextureFormat.RGBA32;
|
||||
|
||||
Texture2D result = new Texture2D( 2, 2, format, generateMipmaps, linearColorSpace );
|
||||
|
||||
try
|
||||
{
|
||||
if( !result.LoadImage( File.ReadAllBytes( loadPath ), markTextureNonReadable ) )
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
public static ImageProperties GetImageProperties( string imagePath )
|
||||
{
|
||||
if( !File.Exists( imagePath ) )
|
||||
throw new FileNotFoundException( "File not found at " + imagePath );
|
||||
|
||||
#if !UNITY_EDITOR && UNITY_ANDROID
|
||||
string value = AJC.CallStatic<string>( "GetImageProperties", Context, imagePath );
|
||||
#elif !UNITY_EDITOR && UNITY_IOS
|
||||
string value = _NativeGallery_GetImageProperties( imagePath );
|
||||
#else
|
||||
string value = null;
|
||||
#endif
|
||||
|
||||
int width = 0, height = 0;
|
||||
string mimeType = null;
|
||||
ImageOrientation orientation = ImageOrientation.Unknown;
|
||||
if( !string.IsNullOrEmpty( value ) )
|
||||
{
|
||||
string[] properties = value.Split( '>' );
|
||||
if( properties != null && properties.Length >= 4 )
|
||||
{
|
||||
if( !int.TryParse( properties[0].Trim(), out width ) )
|
||||
width = 0;
|
||||
if( !int.TryParse( properties[1].Trim(), out height ) )
|
||||
height = 0;
|
||||
|
||||
mimeType = properties[2].Trim();
|
||||
if( mimeType.Length == 0 )
|
||||
{
|
||||
String extension = Path.GetExtension( imagePath ).ToLowerInvariant();
|
||||
if( extension == ".png" )
|
||||
mimeType = "image/png";
|
||||
else if( extension == ".jpg" || extension == ".jpeg" )
|
||||
mimeType = "image/jpeg";
|
||||
else if( extension == ".gif" )
|
||||
mimeType = "image/gif";
|
||||
else if( extension == ".bmp" )
|
||||
mimeType = "image/bmp";
|
||||
else
|
||||
mimeType = null;
|
||||
}
|
||||
|
||||
int orientationInt;
|
||||
if( int.TryParse( properties[3].Trim(), out orientationInt ) )
|
||||
orientation = (ImageOrientation) orientationInt;
|
||||
|
||||
#if !UNITY_EDITOR && UNITY_IOS
|
||||
if( orientation == ImageOrientation.Unknown ) // selected media is saved in correct orientation on iOS
|
||||
orientation = ImageOrientation.Normal;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
return new ImageProperties( width, height, mimeType, orientation );
|
||||
}
|
||||
|
||||
public static VideoProperties GetVideoProperties( string videoPath )
|
||||
{
|
||||
if( !File.Exists( videoPath ) )
|
||||
throw new FileNotFoundException( "File not found at " + videoPath );
|
||||
|
||||
#if !UNITY_EDITOR && UNITY_ANDROID
|
||||
string value = AJC.CallStatic<string>( "GetVideoProperties", Context, videoPath );
|
||||
#elif !UNITY_EDITOR && UNITY_IOS
|
||||
string value = _NativeGallery_GetVideoProperties( videoPath );
|
||||
#else
|
||||
string value = null;
|
||||
#endif
|
||||
|
||||
int width = 0, height = 0;
|
||||
long duration = 0L;
|
||||
float rotation = 0f;
|
||||
if( !string.IsNullOrEmpty( value ) )
|
||||
{
|
||||
string[] properties = value.Split( '>' );
|
||||
if( properties != null && properties.Length >= 4 )
|
||||
{
|
||||
if( !int.TryParse( properties[0].Trim(), out width ) )
|
||||
width = 0;
|
||||
if( !int.TryParse( properties[1].Trim(), out height ) )
|
||||
height = 0;
|
||||
if( !long.TryParse( properties[2].Trim(), out duration ) )
|
||||
duration = 0L;
|
||||
if( !float.TryParse( properties[3].Trim(), out rotation ) )
|
||||
rotation = 0f;
|
||||
}
|
||||
}
|
||||
|
||||
if( rotation == -90f )
|
||||
rotation = 270f;
|
||||
|
||||
return new VideoProperties( width, height, duration, rotation );
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
12
Assets/3rd/NativeGallery/NativeGallery.cs.meta
Normal file
12
Assets/3rd/NativeGallery/NativeGallery.cs.meta
Normal file
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ce1403606c3629046a0147d3e705f7cc
|
||||
timeCreated: 1498722610
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
79
Assets/3rd/NativeGallery/NativeGalleryUtl.cs
Normal file
79
Assets/3rd/NativeGallery/NativeGalleryUtl.cs
Normal file
@@ -0,0 +1,79 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using Coolape;
|
||||
|
||||
public class NativeGalleryUtl
|
||||
{
|
||||
public static IEnumerator TakeScreenshotAndSave(string album, string fileName)
|
||||
{
|
||||
yield return new WaitForEndOfFrame();
|
||||
|
||||
Texture2D ss = new Texture2D(Screen.width, Screen.height, TextureFormat.RGB24, false);
|
||||
ss.ReadPixels(new Rect(0, 0, Screen.width, Screen.height), 0, 0);
|
||||
ss.Apply();
|
||||
|
||||
// Save the screenshot to Gallery/Photos
|
||||
Debug.Log("Permission result: " + NativeGallery.SaveImageToGallery(ss, album, fileName));
|
||||
|
||||
// To avoid memory leaks
|
||||
Object.Destroy(ss);
|
||||
}
|
||||
|
||||
public static void PickImage(object callback, int maxSize = -1)
|
||||
{
|
||||
NativeGallery.Permission permission = NativeGallery.GetImageFromGallery((path) =>
|
||||
{
|
||||
Debug.Log("Image path: " + path);
|
||||
if (path != null)
|
||||
{
|
||||
// Create Texture from selected image
|
||||
Texture2D texture = NativeGallery.LoadImageAtPath(path, maxSize);
|
||||
if (texture == null)
|
||||
{
|
||||
Debug.Log("Couldn't load texture from " + path);
|
||||
return;
|
||||
}
|
||||
Utl.doCallback(callback, texture);
|
||||
|
||||
/*
|
||||
// Assign texture to a temporary quad and destroy it after 5 seconds
|
||||
GameObject quad = GameObject.CreatePrimitive(PrimitiveType.Quad);
|
||||
quad.transform.position = Camera.main.transform.position + Camera.main.transform.forward * 2.5f;
|
||||
quad.transform.forward = Camera.main.transform.forward;
|
||||
quad.transform.localScale = new Vector3(1f, texture.height / (float)texture.width, 1f);
|
||||
|
||||
Material material = quad.GetComponent<Renderer>().material;
|
||||
if (!material.shader.isSupported) // happens when Standard shader is not included in the build
|
||||
material.shader = Shader.Find("Legacy Shaders/Diffuse");
|
||||
|
||||
material.mainTexture = texture;
|
||||
|
||||
Destroy(quad, 5f);
|
||||
|
||||
// If a procedural texture is not destroyed manually,
|
||||
// it will only be freed after a scene change
|
||||
Destroy(texture, 5f);
|
||||
*/
|
||||
|
||||
}
|
||||
}, "Select a PNG image", "image/png");
|
||||
|
||||
Debug.Log("Permission result: " + permission);
|
||||
}
|
||||
|
||||
public static void PickVideo()
|
||||
{
|
||||
NativeGallery.Permission permission = NativeGallery.GetVideoFromGallery((path) =>
|
||||
{
|
||||
Debug.Log("Video path: " + path);
|
||||
if (path != null)
|
||||
{
|
||||
// Play the selected video
|
||||
Handheld.PlayFullScreenMovie("file://" + path);
|
||||
}
|
||||
}, "Select a video");
|
||||
|
||||
Debug.Log("Permission result: " + permission);
|
||||
}
|
||||
}
|
||||
11
Assets/3rd/NativeGallery/NativeGalleryUtl.cs.meta
Normal file
11
Assets/3rd/NativeGallery/NativeGalleryUtl.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ddbd1086fc9d14bf6814614e75efda21
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
87
Assets/3rd/NativeGallery/README.txt
Normal file
87
Assets/3rd/NativeGallery/README.txt
Normal file
@@ -0,0 +1,87 @@
|
||||
= Native Gallery for Android & iOS =
|
||||
|
||||
Online documentation & example code available at: https://github.com/yasirkula/UnityNativeGallery
|
||||
E-mail: yasirkula@gmail.com
|
||||
|
||||
1. ABOUT
|
||||
This plugin helps you interact with Gallery/Photos on Android & iOS.
|
||||
|
||||
2. HOW TO
|
||||
for Android: set "Write Permission" to "External (SDCard)" in Player Settings
|
||||
for iOS: there are two ways to set up the plugin on iOS:
|
||||
|
||||
a. Automated Setup for iOS
|
||||
- change the value of PHOTO_LIBRARY_USAGE_DESCRIPTION in Plugins/NativeGallery/Editor/NGPostProcessBuild.cs (optional)
|
||||
- if your minimum Deployment Target (iOS Version) is at least 8.0, set the value of MINIMUM_TARGET_8_OR_ABOVE to true in NGPostProcessBuild.cs
|
||||
|
||||
b. Manual Setup for iOS
|
||||
- set the value of ENABLED to false in NGPostProcessBuild.cs
|
||||
- build your project
|
||||
- enter a Photo Library Usage Description to Info.plist in Xcode
|
||||
- also enter a "Photo Library Additions Usage Description" to Info.plist in Xcode, if exists
|
||||
- insert "-weak_framework Photos -framework AssetsLibrary -framework MobileCoreServices -framework ImageIO" to the "Other Linker Flags" of Unity-iPhone Target (if your Deployment Target is at least 8.0, it is sufficient to insert "-framework Photos -framework MobileCoreServices -framework ImageIO")
|
||||
- lastly, remove Photos.framework from Link Binary With Libraries of Unity-iPhone Target in Build Phases, if exists
|
||||
|
||||
3. SCRIPTING API
|
||||
Please see the online documentation for a more in-depth documentation of the Scripting API: https://github.com/yasirkula/UnityNativeGallery
|
||||
|
||||
enum NativeGallery.Permission { Denied = 0, Granted = 1, ShouldAsk = 2 };
|
||||
enum NativeGallery.ImageOrientation { Unknown = -1, Normal = 0, Rotate90 = 1, Rotate180 = 2, Rotate270 = 3, FlipHorizontal = 4, Transpose = 5, FlipVertical = 6, Transverse = 7 }; // EXIF orientation: http://sylvana.net/jpegcrop/exif_orientation.html (indices are reordered)
|
||||
|
||||
delegate void MediaSaveCallback( string error );
|
||||
delegate void NativeGallery.MediaPickCallback( string path );
|
||||
delegate void MediaPickMultipleCallback( string[] paths );
|
||||
|
||||
//// Saving Media To Gallery/Photos ////
|
||||
|
||||
// On Android, your images are saved at DCIM/album/filenameFormatted. On iOS, the image will be saved in the corresponding album
|
||||
// filenameFormatted is string.Format'ed to avoid overwriting the same file on Android, if desired. If, for example, you want your images to be saved in a format like "My img 1.png", "My img 2.png" and etc., you can set the filenameFormatted as "My img {0}.png". {0} here is replaced with a unique number to avoid overwriting an existing file. If you don't use a {0} in your filenameFormatted parameter and a file with the same name does exist at that path, the file will be overwritten. On the other hand, a saved image is never overwritten on iOS
|
||||
// MediaSaveCallback takes a string parameter which stores an error string if something goes wrong while saving the image/video, or null if it is saved successfully
|
||||
NativeGallery.Permission NativeGallery.SaveImageToGallery( byte[] mediaBytes, string album, string filenameFormatted, MediaSaveCallback callback = null );
|
||||
NativeGallery.Permission NativeGallery.SaveImageToGallery( string existingMediaPath, string album, string filenameFormatted, MediaSaveCallback callback = null );
|
||||
NativeGallery.Permission NativeGallery.SaveImageToGallery( Texture2D image, string album, string filenameFormatted, MediaSaveCallback callback = null );
|
||||
NativeGallery.Permission NativeGallery.SaveVideoToGallery( byte[] mediaBytes, string album, string filenameFormatted, MediaSaveCallback callback = null );
|
||||
NativeGallery.Permission NativeGallery.SaveVideoToGallery( string existingMediaPath, string album, string filenameFormatted, MediaSaveCallback callback = null );
|
||||
|
||||
|
||||
//// Retrieving Media From Gallery/Photos ////
|
||||
|
||||
// This operation is asynchronous! After user selects an image/video or cancels the operation, the callback is called (on main thread)
|
||||
// MediaPickCallback takes a string parameter which stores the path of the selected image/video, or null if nothing is selected
|
||||
// MediaPickMultipleCallback takes a string[] parameter which stores the path(s) of the selected image(s)/video(s), or null if nothing is selected
|
||||
// title: determines the title of the image picker dialog on Android. Has no effect on iOS
|
||||
// mime: filters the available images/videos on Android. For example, to request a JPEG image from the user, mime can be set as "image/jpeg". Setting multiple mime types is not possible (in that case, you should leave mime as is). On iOS, selected images will always be in PNG format and thus, this parameter has no effect on iOS
|
||||
// maxSize: determines the maximum size of the returned image in pixels on iOS. A larger image will be down-scaled for better performance. If untouched, its value will be set to SystemInfo.maxTextureSize. Has no effect on Android
|
||||
NativeGallery.Permission NativeGallery.GetImageFromGallery( MediaPickCallback callback, string title = "", string mime = "image/*", int maxSize = -1 );
|
||||
NativeGallery.Permission NativeGallery.GetVideoFromGallery( MediaPickCallback callback, string title = "", string mime = "video/*" );
|
||||
NativeGallery.Permission NativeGallery.GetImagesFromGallery( MediaPickMultipleCallback callback, string title = "", string mime = "image/*", int maxSize = -1 );
|
||||
NativeGallery.Permission NativeGallery.GetVideosFromGallery( MediaPickMultipleCallback callback, string title = "", string mime = "video/*" );
|
||||
|
||||
// Returns true if selecting multiple images/videos from Gallery/Photos is possible on this device (only available on Android 18 and later; iOS not supported)
|
||||
bool NativeGallery.CanSelectMultipleFilesFromGallery();
|
||||
|
||||
// Returns true if the user is currently picking media from Gallery/Photos. In that case, another GetImageFromGallery or GetVideoFromGallery request will simply be ignored
|
||||
bool NativeGallery.IsMediaPickerBusy();
|
||||
|
||||
|
||||
//// Runtime Permissions ////
|
||||
|
||||
// Interacting with Gallery/Photos is only possible when permission state is Permission.Granted. Most of the functions request permission internally (and return the result) but you can also check/request the permissions manually
|
||||
NativeGallery.Permission NativeGallery.CheckPermission();
|
||||
NativeGallery.Permission NativeGallery.RequestPermission();
|
||||
|
||||
// If permission state is Permission.Denied, user must grant the necessary permission (Storage on Android and Photos on iOS) manually from the Settings. These functions help you open the Settings directly from within the app
|
||||
void NativeGallery.OpenSettings();
|
||||
bool NativeGallery.CanOpenSettings();
|
||||
|
||||
|
||||
//// Utility Functions ////
|
||||
|
||||
// maxSize: determines the maximum size of the returned Texture2D in pixels. Larger textures 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
|
||||
// markTextureNonReadable: marks the generated texture as non-readable for better memory usage. If you plan to modify the texture later (e.g. GetPixels/SetPixels), set its value to false
|
||||
// generateMipmaps: determines whether texture should have mipmaps or not
|
||||
// linearColorSpace: determines whether texture should be in linear color space or sRGB color space
|
||||
Texture2D NativeGallery.LoadImageAtPath( string imagePath, int maxSize = -1, bool markTextureNonReadable = true, bool generateMipmaps = true, bool linearColorSpace = false ): creates a Texture2D from the specified image file in correct orientation and returns it. Returns null, if something goes wrong
|
||||
|
||||
NativeGallery.ImageProperties NativeGallery.GetImageProperties( string imagePath ): 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
|
||||
NativeGallery.VideoProperties NativeGallery.GetVideoProperties( string videoPath ): returns a VideoProperties instance that holds the width, height, duration (in milliseconds) and rotation information of a video file
|
||||
8
Assets/3rd/NativeGallery/README.txt.meta
Normal file
8
Assets/3rd/NativeGallery/README.txt.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e5d70289d6cc2ac4294d703db236f8d0
|
||||
timeCreated: 1520539180
|
||||
licenseType: Store
|
||||
TextScriptImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
9
Assets/3rd/NativeGallery/iOS.meta
Normal file
9
Assets/3rd/NativeGallery/iOS.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9c623599351a41a4c84c20f73c9d8976
|
||||
folderAsset: yes
|
||||
timeCreated: 1498722622
|
||||
licenseType: Store
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
72
Assets/3rd/NativeGallery/iOS/NGMediaReceiveCallbackiOS.cs
Normal file
72
Assets/3rd/NativeGallery/iOS/NGMediaReceiveCallbackiOS.cs
Normal file
@@ -0,0 +1,72 @@
|
||||
#if !UNITY_EDITOR && UNITY_IOS
|
||||
using UnityEngine;
|
||||
|
||||
namespace NativeGalleryNamespace
|
||||
{
|
||||
public class NGMediaReceiveCallbackiOS : MonoBehaviour
|
||||
{
|
||||
private static NGMediaReceiveCallbackiOS instance;
|
||||
private NativeGallery.MediaPickCallback callback;
|
||||
|
||||
private float nextBusyCheckTime;
|
||||
|
||||
public static bool IsBusy { get; private set; }
|
||||
|
||||
[System.Runtime.InteropServices.DllImport( "__Internal" )]
|
||||
private static extern int _NativeGallery_IsMediaPickerBusy();
|
||||
|
||||
public static void Initialize( NativeGallery.MediaPickCallback callback )
|
||||
{
|
||||
if( IsBusy )
|
||||
return;
|
||||
|
||||
if( instance == null )
|
||||
{
|
||||
instance = new GameObject( "NGMediaReceiveCallbackiOS" ).AddComponent<NGMediaReceiveCallbackiOS>();
|
||||
DontDestroyOnLoad( instance.gameObject );
|
||||
}
|
||||
|
||||
instance.callback = callback;
|
||||
|
||||
instance.nextBusyCheckTime = Time.realtimeSinceStartup + 1f;
|
||||
IsBusy = true;
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if( IsBusy )
|
||||
{
|
||||
if( Time.realtimeSinceStartup >= nextBusyCheckTime )
|
||||
{
|
||||
nextBusyCheckTime = Time.realtimeSinceStartup + 1f;
|
||||
|
||||
if( _NativeGallery_IsMediaPickerBusy() == 0 )
|
||||
{
|
||||
if( callback != null )
|
||||
{
|
||||
callback( null );
|
||||
callback = null;
|
||||
}
|
||||
|
||||
IsBusy = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void OnMediaReceived( string path )
|
||||
{
|
||||
if( string.IsNullOrEmpty( path ) )
|
||||
path = null;
|
||||
|
||||
if( callback != null )
|
||||
{
|
||||
callback( path );
|
||||
callback = null;
|
||||
}
|
||||
|
||||
IsBusy = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 71fb861c149c2d1428544c601e52a33c
|
||||
timeCreated: 1519060539
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
46
Assets/3rd/NativeGallery/iOS/NGMediaSaveCallbackiOS.cs
Normal file
46
Assets/3rd/NativeGallery/iOS/NGMediaSaveCallbackiOS.cs
Normal file
@@ -0,0 +1,46 @@
|
||||
#if !UNITY_EDITOR && UNITY_IOS
|
||||
using UnityEngine;
|
||||
|
||||
namespace NativeGalleryNamespace
|
||||
{
|
||||
public class NGMediaSaveCallbackiOS : MonoBehaviour
|
||||
{
|
||||
private static NGMediaSaveCallbackiOS instance;
|
||||
private NativeGallery.MediaSaveCallback callback;
|
||||
|
||||
public static void Initialize( NativeGallery.MediaSaveCallback callback )
|
||||
{
|
||||
if( instance == null )
|
||||
{
|
||||
instance = new GameObject( "NGMediaSaveCallbackiOS" ).AddComponent<NGMediaSaveCallbackiOS>();
|
||||
DontDestroyOnLoad( instance.gameObject );
|
||||
}
|
||||
else if( instance.callback != null )
|
||||
instance.callback( null );
|
||||
|
||||
instance.callback = callback;
|
||||
}
|
||||
|
||||
public void OnMediaSaveCompleted( string message )
|
||||
{
|
||||
if( callback != null )
|
||||
{
|
||||
callback( null );
|
||||
callback = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void OnMediaSaveFailed( string error )
|
||||
{
|
||||
if( string.IsNullOrEmpty( error ) )
|
||||
error = "Unknown error";
|
||||
|
||||
if( callback != null )
|
||||
{
|
||||
callback( error );
|
||||
callback = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
12
Assets/3rd/NativeGallery/iOS/NGMediaSaveCallbackiOS.cs.meta
Normal file
12
Assets/3rd/NativeGallery/iOS/NGMediaSaveCallbackiOS.cs.meta
Normal file
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9cbb865d0913a0d47bb6d2eb3ad04c4f
|
||||
timeCreated: 1519060539
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
593
Assets/3rd/NativeGallery/iOS/NativeGallery.mm
Normal file
593
Assets/3rd/NativeGallery/iOS/NativeGallery.mm
Normal file
@@ -0,0 +1,593 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <Photos/Photos.h>
|
||||
#import <MobileCoreServices/UTCoreTypes.h>
|
||||
#import <ImageIO/ImageIO.h>
|
||||
#if __IPHONE_OS_VERSION_MIN_REQUIRED < 80000
|
||||
#import <AssetsLibrary/AssetsLibrary.h>
|
||||
#endif
|
||||
|
||||
#ifdef UNITY_4_0 || UNITY_5_0
|
||||
#import "iPhone_View.h"
|
||||
#else
|
||||
extern UIViewController* UnityGetGLViewController();
|
||||
#endif
|
||||
|
||||
@interface UNativeGallery:NSObject
|
||||
+ (int)checkPermission;
|
||||
+ (int)requestPermission;
|
||||
+ (int)canOpenSettings;
|
||||
+ (void)openSettings;
|
||||
+ (void)saveMedia:(NSString *)path albumName:(NSString *)album isImg:(BOOL)isImg;
|
||||
+ (void)pickMedia:(BOOL)imageMode savePath:(NSString *)imageSavePath;
|
||||
+ (void)pickMediaSetMaxSize:(int)maxSize;
|
||||
+ (int)isMediaPickerBusy;
|
||||
+ (char *)getImageProperties:(NSString *)path;
|
||||
+ (char *)getVideoProperties:(NSString *)path;
|
||||
+ (char *)loadImageAtPath:(NSString *)path tempFilePath:(NSString *)tempFilePath maximumSize:(int)maximumSize;
|
||||
@end
|
||||
|
||||
@implementation UNativeGallery
|
||||
|
||||
static NSString *pickedMediaSavePath;
|
||||
static UIPopoverController *popup;
|
||||
static UIImagePickerController *imagePicker;
|
||||
static int pickMediaMaxSize = -1;
|
||||
static int imagePickerState = 0; // 0 -> none, 1 -> showing (always in this state on iPad), 2 -> finished
|
||||
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||
+ (int)checkPermission {
|
||||
#if __IPHONE_OS_VERSION_MIN_REQUIRED < 80000
|
||||
if ([[[UIDevice currentDevice] systemVersion] compare:@"8.0" options:NSNumericSearch] != NSOrderedAscending)
|
||||
{
|
||||
#endif
|
||||
// version >= iOS 8: check permission using Photos framework
|
||||
PHAuthorizationStatus status = [PHPhotoLibrary authorizationStatus];
|
||||
if (status == PHAuthorizationStatusAuthorized)
|
||||
return 1;
|
||||
else if (status == PHAuthorizationStatusNotDetermined )
|
||||
return 2;
|
||||
else
|
||||
return 0;
|
||||
#if __IPHONE_OS_VERSION_MIN_REQUIRED < 80000
|
||||
}
|
||||
else
|
||||
{
|
||||
// version < iOS 8: check permission using AssetsLibrary framework (Photos framework not available)
|
||||
ALAuthorizationStatus status = [ALAssetsLibrary authorizationStatus];
|
||||
if (status == ALAuthorizationStatusAuthorized)
|
||||
return 1;
|
||||
else if (status == ALAuthorizationStatusNotDetermined)
|
||||
return 2;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
+ (int)requestPermission {
|
||||
#if __IPHONE_OS_VERSION_MIN_REQUIRED < 80000
|
||||
if ([[[UIDevice currentDevice] systemVersion] compare:@"8.0" options:NSNumericSearch] != NSOrderedAscending)
|
||||
{
|
||||
#endif
|
||||
// version >= iOS 8: request permission using Photos framework
|
||||
return [self requestPermissionNew];
|
||||
#if __IPHONE_OS_VERSION_MIN_REQUIRED < 80000
|
||||
}
|
||||
else
|
||||
{
|
||||
// version < iOS 8: request permission using AssetsLibrary framework (Photos framework not available)
|
||||
return [self requestPermissionOld];
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// Credit: https://stackoverflow.com/a/25453667/2373034
|
||||
+ (int)canOpenSettings {
|
||||
if (&UIApplicationOpenSettingsURLString != NULL)
|
||||
return 1;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Credit: https://stackoverflow.com/a/25453667/2373034
|
||||
+ (void)openSettings {
|
||||
if (&UIApplicationOpenSettingsURLString != NULL)
|
||||
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString]];
|
||||
}
|
||||
|
||||
+ (void)saveMedia:(NSString *)path albumName:(NSString *)album isImg:(BOOL)isImg {
|
||||
#if __IPHONE_OS_VERSION_MIN_REQUIRED < 80000
|
||||
if ([[[UIDevice currentDevice] systemVersion] compare:@"8.0" options:NSNumericSearch] != NSOrderedAscending)
|
||||
{
|
||||
#endif
|
||||
// version >= iOS 8: save to specified album using Photos framework
|
||||
[self saveMediaNew:path albumName:album isImage:isImg];
|
||||
#if __IPHONE_OS_VERSION_MIN_REQUIRED < 80000
|
||||
}
|
||||
else
|
||||
{
|
||||
// version < iOS 8: save using AssetsLibrary framework (Photos framework not available)
|
||||
[self saveMediaOld:path albumName:album isImage:isImg];
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// Credit: https://stackoverflow.com/a/26933380/2373034
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||
+ (int)requestPermissionOld {
|
||||
#if __IPHONE_OS_VERSION_MIN_REQUIRED < 80000
|
||||
ALAuthorizationStatus status = [ALAssetsLibrary authorizationStatus];
|
||||
|
||||
if (status == ALAuthorizationStatusAuthorized) {
|
||||
return 1;
|
||||
}
|
||||
else if (status == ALAuthorizationStatusNotDetermined) {
|
||||
__block BOOL authorized = NO;
|
||||
ALAssetsLibrary *lib = [[ALAssetsLibrary alloc] init];
|
||||
|
||||
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
|
||||
[lib enumerateGroupsWithTypes:ALAssetsGroupAll usingBlock:^(ALAssetsGroup *group, BOOL *stop) {
|
||||
*stop = YES;
|
||||
authorized = YES;
|
||||
dispatch_semaphore_signal(sema);
|
||||
} failureBlock:^(NSError *error) {
|
||||
dispatch_semaphore_signal(sema);
|
||||
}];
|
||||
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
|
||||
|
||||
if (authorized)
|
||||
return 1;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
else {
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
// Credit: https://stackoverflow.com/a/32989022/2373034
|
||||
+ (int)requestPermissionNew {
|
||||
PHAuthorizationStatus status = [PHPhotoLibrary authorizationStatus];
|
||||
|
||||
if (status == PHAuthorizationStatusAuthorized) {
|
||||
return 1;
|
||||
}
|
||||
else if (status == PHAuthorizationStatusNotDetermined) {
|
||||
__block BOOL authorized = NO;
|
||||
|
||||
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
|
||||
[PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) {
|
||||
authorized = (status == PHAuthorizationStatusAuthorized);
|
||||
dispatch_semaphore_signal(sema);
|
||||
}];
|
||||
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
|
||||
|
||||
if (authorized)
|
||||
return 1;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Credit: https://stackoverflow.com/a/22056664/2373034
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||
+ (void)saveMediaOld:(NSString *)path albumName:(NSString *)album isImage:(BOOL)isImage {
|
||||
#if __IPHONE_OS_VERSION_MIN_REQUIRED < 80000
|
||||
ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
|
||||
|
||||
if (!isImage && ![library videoAtPathIsCompatibleWithSavedPhotosAlbum:[NSURL fileURLWithPath:path]])
|
||||
{
|
||||
[[NSFileManager defaultManager] removeItemAtPath:path error:nil];
|
||||
UnitySendMessage("NGMediaSaveCallbackiOS", "OnMediaSaveFailed", "Video format is not compatible with Photos");
|
||||
return;
|
||||
}
|
||||
|
||||
void (^saveBlock)(ALAssetsGroup *assetCollection) = ^void(ALAssetsGroup *assetCollection) {
|
||||
void (^saveResultBlock)(NSURL *assetURL, NSError *error) = ^void(NSURL *assetURL, NSError *error) {
|
||||
[[NSFileManager defaultManager] removeItemAtPath:path error:nil];
|
||||
|
||||
if (error.code == 0) {
|
||||
[library assetForURL:assetURL resultBlock:^(ALAsset *asset) {
|
||||
[assetCollection addAsset:asset];
|
||||
UnitySendMessage("NGMediaSaveCallbackiOS", "OnMediaSaveCompleted", "");
|
||||
} failureBlock:^(NSError* error) {
|
||||
NSLog(@"Error moving asset to album: %@", error);
|
||||
UnitySendMessage("NGMediaSaveCallbackiOS", "OnMediaSaveFailed", [self getCString:[error localizedDescription]]);
|
||||
}];
|
||||
}
|
||||
else {
|
||||
NSLog(@"Error creating asset: %@", error);
|
||||
UnitySendMessage("NGMediaSaveCallbackiOS", "OnMediaSaveFailed", [self getCString:[error localizedDescription]]);
|
||||
}
|
||||
};
|
||||
|
||||
if (!isImage)
|
||||
[library writeImageDataToSavedPhotosAlbum:[NSData dataWithContentsOfFile:path] metadata:nil completionBlock:saveResultBlock];
|
||||
else
|
||||
[library writeVideoAtPathToSavedPhotosAlbum:[NSURL fileURLWithPath:path] completionBlock:saveResultBlock];
|
||||
};
|
||||
|
||||
__block BOOL albumFound = NO;
|
||||
[library enumerateGroupsWithTypes:ALAssetsGroupAlbum usingBlock:^(ALAssetsGroup *group, BOOL *stop) {
|
||||
if ([[group valueForProperty:ALAssetsGroupPropertyName] isEqualToString:album]) {
|
||||
*stop = YES;
|
||||
albumFound = YES;
|
||||
saveBlock(group);
|
||||
}
|
||||
else if (group == nil && albumFound==NO) { // Album doesn't exist
|
||||
[library addAssetsGroupAlbumWithName:album resultBlock:^(ALAssetsGroup *group) {
|
||||
saveBlock(group);
|
||||
}
|
||||
failureBlock:^(NSError *error) {
|
||||
NSLog(@"Error creating album: %@", error);
|
||||
[[NSFileManager defaultManager] removeItemAtPath:path error:nil];
|
||||
UnitySendMessage("NGMediaSaveCallbackiOS", "OnMediaSaveFailed", [self getCString:[error localizedDescription]]);
|
||||
}];
|
||||
}
|
||||
} failureBlock:^(NSError* error) {
|
||||
NSLog(@"Error listing albums: %@", error);
|
||||
[[NSFileManager defaultManager] removeItemAtPath:path error:nil];
|
||||
UnitySendMessage("NGMediaSaveCallbackiOS", "OnMediaSaveFailed", [self getCString:[error localizedDescription]]);
|
||||
}];
|
||||
#endif
|
||||
}
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
// Credit: https://stackoverflow.com/a/39909129/2373034
|
||||
+ (void)saveMediaNew:(NSString *)path albumName:(NSString *)album isImage:(BOOL)isImage {
|
||||
void (^saveBlock)(PHAssetCollection *assetCollection) = ^void(PHAssetCollection *assetCollection) {
|
||||
[[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
|
||||
PHAssetChangeRequest *assetChangeRequest;
|
||||
if (isImage)
|
||||
assetChangeRequest = [PHAssetChangeRequest creationRequestForAssetFromImageAtFileURL:[NSURL fileURLWithPath:path]];
|
||||
else
|
||||
assetChangeRequest = [PHAssetChangeRequest creationRequestForAssetFromVideoAtFileURL:[NSURL fileURLWithPath:path]];
|
||||
|
||||
PHAssetCollectionChangeRequest *assetCollectionChangeRequest = [PHAssetCollectionChangeRequest changeRequestForAssetCollection:assetCollection];
|
||||
[assetCollectionChangeRequest addAssets:@[[assetChangeRequest placeholderForCreatedAsset]]];
|
||||
|
||||
} completionHandler:^(BOOL success, NSError *error) {
|
||||
[[NSFileManager defaultManager] removeItemAtPath:path error:nil];
|
||||
|
||||
if (success)
|
||||
UnitySendMessage("NGMediaSaveCallbackiOS", "OnMediaSaveCompleted", "");
|
||||
else {
|
||||
NSLog(@"Error creating asset: %@", error);
|
||||
UnitySendMessage("NGMediaSaveCallbackiOS", "OnMediaSaveFailed", [self getCString:[error localizedDescription]]);
|
||||
}
|
||||
}];
|
||||
};
|
||||
|
||||
PHFetchOptions *fetchOptions = [[PHFetchOptions alloc] init];
|
||||
fetchOptions.predicate = [NSPredicate predicateWithFormat:@"localizedTitle = %@", album];
|
||||
PHFetchResult *fetchResult = [PHAssetCollection fetchAssetCollectionsWithType:PHAssetCollectionTypeAlbum subtype:PHAssetCollectionSubtypeAny options:fetchOptions];
|
||||
if (fetchResult.count > 0) {
|
||||
saveBlock(fetchResult.firstObject);
|
||||
}
|
||||
else {
|
||||
__block PHObjectPlaceholder *albumPlaceholder;
|
||||
[[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
|
||||
PHAssetCollectionChangeRequest *changeRequest = [PHAssetCollectionChangeRequest creationRequestForAssetCollectionWithTitle:album];
|
||||
albumPlaceholder = changeRequest.placeholderForCreatedAssetCollection;
|
||||
} completionHandler:^(BOOL success, NSError *error) {
|
||||
if (success) {
|
||||
PHFetchResult *fetchResult = [PHAssetCollection fetchAssetCollectionsWithLocalIdentifiers:@[albumPlaceholder.localIdentifier] options:nil];
|
||||
if (fetchResult.count > 0)
|
||||
saveBlock(fetchResult.firstObject);
|
||||
else {
|
||||
[[NSFileManager defaultManager] removeItemAtPath:path error:nil];
|
||||
UnitySendMessage("NGMediaSaveCallbackiOS", "OnMediaSaveFailed", "Album placeholder not found" );
|
||||
}
|
||||
}
|
||||
else {
|
||||
NSLog(@"Error creating album: %@", error);
|
||||
[[NSFileManager defaultManager] removeItemAtPath:path error:nil];
|
||||
UnitySendMessage("NGMediaSaveCallbackiOS", "OnMediaSaveFailed", [self getCString:[error localizedDescription]]);
|
||||
}
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
// Credit: https://stackoverflow.com/a/10531752/2373034
|
||||
+ (void)pickMedia:(BOOL)imageMode savePath:(NSString *)imageSavePath {
|
||||
imagePicker = [[UIImagePickerController alloc] init];
|
||||
imagePicker.delegate = self;
|
||||
imagePicker.allowsEditing = NO;
|
||||
imagePicker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
|
||||
|
||||
if (imageMode)
|
||||
imagePicker.mediaTypes = [NSArray arrayWithObject:(NSString *)kUTTypeImage];
|
||||
else
|
||||
imagePicker.mediaTypes = [NSArray arrayWithObjects:(NSString *)kUTTypeMovie, (NSString *)kUTTypeVideo, nil];
|
||||
|
||||
pickedMediaSavePath = imageSavePath;
|
||||
|
||||
imagePickerState = 1;
|
||||
UIViewController *rootViewController = UnityGetGLViewController();
|
||||
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) // iPhone
|
||||
[rootViewController presentViewController:imagePicker animated:YES completion:^{ imagePickerState = 0; }];
|
||||
else { // iPad
|
||||
popup = [[UIPopoverController alloc] initWithContentViewController:imagePicker];
|
||||
popup.delegate = self;
|
||||
[popup presentPopoverFromRect:CGRectMake( rootViewController.view.frame.size.width / 2, rootViewController.view.frame.size.height / 4, 0, 0 ) inView:rootViewController.view permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];
|
||||
}
|
||||
}
|
||||
|
||||
+ (void)pickMediaSetMaxSize:(int)maxSize {
|
||||
pickMediaMaxSize = maxSize;
|
||||
}
|
||||
|
||||
+ (int)isMediaPickerBusy {
|
||||
if (imagePickerState == 2)
|
||||
return 1;
|
||||
|
||||
if (imagePicker != nil) {
|
||||
if (imagePickerState == 1 || [imagePicker presentingViewController] == UnityGetGLViewController())
|
||||
return 1;
|
||||
else {
|
||||
imagePicker = nil;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Credit: https://stackoverflow.com/a/4170099/2373034
|
||||
+ (NSArray *)getImageMetadata:(NSString *)path {
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
int orientation = -1;
|
||||
|
||||
CGImageSourceRef imageSource = CGImageSourceCreateWithURL((__bridge CFURLRef)[NSURL fileURLWithPath:path], nil);
|
||||
if (imageSource != nil) {
|
||||
NSDictionary *options = [NSDictionary dictionaryWithObject:[NSNumber numberWithBool:NO] forKey:(__bridge NSString *)kCGImageSourceShouldCache];
|
||||
CFDictionaryRef imageProperties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, (__bridge CFDictionaryRef)options);
|
||||
CFRelease(imageSource);
|
||||
|
||||
CGFloat widthF = 0.0f, heightF = 0.0f;
|
||||
if (imageProperties != nil) {
|
||||
if (CFDictionaryContainsKey(imageProperties, kCGImagePropertyPixelWidth))
|
||||
CFNumberGetValue((CFNumberRef)CFDictionaryGetValue(imageProperties, kCGImagePropertyPixelWidth), kCFNumberCGFloatType, &widthF);
|
||||
|
||||
if (CFDictionaryContainsKey(imageProperties, kCGImagePropertyPixelHeight))
|
||||
CFNumberGetValue((CFNumberRef)CFDictionaryGetValue(imageProperties, kCGImagePropertyPixelHeight), kCFNumberCGFloatType, &heightF);
|
||||
|
||||
if (CFDictionaryContainsKey(imageProperties, kCGImagePropertyOrientation)) {
|
||||
CFNumberGetValue((CFNumberRef)CFDictionaryGetValue(imageProperties, kCGImagePropertyOrientation), kCFNumberIntType, &orientation);
|
||||
|
||||
if (orientation > 4) { // landscape image
|
||||
CGFloat temp = widthF;
|
||||
widthF = heightF;
|
||||
heightF = temp;
|
||||
}
|
||||
}
|
||||
|
||||
CFRelease(imageProperties);
|
||||
}
|
||||
|
||||
width = (int)roundf(widthF);
|
||||
height = (int)roundf(heightF);
|
||||
}
|
||||
|
||||
return [[NSArray alloc] initWithObjects:[NSNumber numberWithInt:width], [NSNumber numberWithInt:height], [NSNumber numberWithInt:orientation], nil];
|
||||
}
|
||||
|
||||
+ (char *)getImageProperties:(NSString *)path {
|
||||
NSArray *metadata = [self getImageMetadata:path];
|
||||
|
||||
int orientationUnity;
|
||||
int orientation = [metadata[2] intValue];
|
||||
|
||||
// To understand the magic numbers, see ImageOrientation enum in NativeGallery.cs
|
||||
// and http://sylvana.net/jpegcrop/exif_orientation.html
|
||||
if (orientation == 1)
|
||||
orientationUnity = 0;
|
||||
else if (orientation == 2)
|
||||
orientationUnity = 4;
|
||||
else if (orientation == 3)
|
||||
orientationUnity = 2;
|
||||
else if (orientation == 4)
|
||||
orientationUnity = 6;
|
||||
else if (orientation == 5)
|
||||
orientationUnity = 5;
|
||||
else if (orientation == 6)
|
||||
orientationUnity = 1;
|
||||
else if (orientation == 7)
|
||||
orientationUnity = 7;
|
||||
else if (orientation == 8)
|
||||
orientationUnity = 3;
|
||||
else
|
||||
orientationUnity = -1;
|
||||
|
||||
return [self getCString:[NSString stringWithFormat:@"%d>%d> >%d", [metadata[0] intValue], [metadata[1] intValue], orientationUnity]];
|
||||
}
|
||||
|
||||
+ (char *)getVideoProperties:(NSString *)path {
|
||||
CGSize size = CGSizeZero;
|
||||
float rotation = 0;
|
||||
long long duration = 0;
|
||||
|
||||
AVURLAsset *asset = [AVURLAsset URLAssetWithURL:[NSURL fileURLWithPath:path] options:nil];
|
||||
if (asset != nil) {
|
||||
duration = (long long) round(CMTimeGetSeconds([asset duration]) * 1000);
|
||||
CGAffineTransform transform = [asset preferredTransform];
|
||||
NSArray<AVAssetTrack *>* videoTracks = [asset tracksWithMediaType:AVMediaTypeVideo];
|
||||
if (videoTracks != nil && [videoTracks count] > 0) {
|
||||
size = [[videoTracks objectAtIndex:0] naturalSize];
|
||||
transform = [[videoTracks objectAtIndex:0] preferredTransform];
|
||||
}
|
||||
|
||||
rotation = atan2(transform.b, transform.a) * (180.0 / M_PI);
|
||||
}
|
||||
|
||||
return [self getCString:[NSString stringWithFormat:@"%d>%d>%lld>%f", (int)roundf(size.width), (int)roundf(size.height), duration, rotation]];
|
||||
}
|
||||
|
||||
+ (UIImage *)scaleImage:(UIImage *)image maxSize:(int)maxSize {
|
||||
CGFloat width = image.size.width;
|
||||
CGFloat height = image.size.height;
|
||||
|
||||
UIImageOrientation orientation = image.imageOrientation;
|
||||
if (width <= maxSize && height <= maxSize && orientation != UIImageOrientationDown &&
|
||||
orientation != UIImageOrientationLeft && orientation != UIImageOrientationRight &&
|
||||
orientation != UIImageOrientationLeftMirrored && orientation != UIImageOrientationRightMirrored &&
|
||||
orientation != UIImageOrientationUpMirrored && orientation != UIImageOrientationDownMirrored)
|
||||
return image;
|
||||
|
||||
CGFloat scaleX = 1.0f;
|
||||
CGFloat scaleY = 1.0f;
|
||||
if (width > maxSize)
|
||||
scaleX = maxSize / width;
|
||||
if (height > maxSize)
|
||||
scaleY = maxSize / height;
|
||||
|
||||
// Credit: https://github.com/mbcharbonneau/UIImage-Categories/blob/master/UIImage%2BAlpha.m
|
||||
CGImageAlphaInfo alpha = CGImageGetAlphaInfo(image.CGImage);
|
||||
BOOL hasAlpha = alpha == kCGImageAlphaFirst || alpha == kCGImageAlphaLast || alpha == kCGImageAlphaPremultipliedFirst || alpha == kCGImageAlphaPremultipliedLast;
|
||||
|
||||
CGFloat scaleRatio = scaleX < scaleY ? scaleX : scaleY;
|
||||
CGRect imageRect = CGRectMake(0, 0, width * scaleRatio, height * scaleRatio);
|
||||
UIGraphicsBeginImageContextWithOptions(imageRect.size, !hasAlpha, image.scale);
|
||||
[image drawInRect:imageRect];
|
||||
image = UIGraphicsGetImageFromCurrentImageContext();
|
||||
UIGraphicsEndImageContext();
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
+ (char *)loadImageAtPath:(NSString *)path tempFilePath:(NSString *)tempFilePath maximumSize:(int)maximumSize {
|
||||
NSArray *metadata = [self getImageMetadata:path];
|
||||
int orientationInt = [metadata[2] intValue]; // 1: correct orientation, [1,8]: valid orientation range
|
||||
if (( orientationInt <= 1 || orientationInt > 8 ) && [metadata[0] intValue] <= maximumSize && [metadata[1] intValue] <= maximumSize)
|
||||
return [self getCString:path];
|
||||
|
||||
UIImage *image = [UIImage imageWithContentsOfFile:path];
|
||||
if (image == nil)
|
||||
return [self getCString:path];
|
||||
|
||||
UIImage *scaledImage = [self scaleImage:image maxSize:maximumSize];
|
||||
if (scaledImage != image) {
|
||||
[UIImagePNGRepresentation(scaledImage) writeToFile:tempFilePath atomically:YES];
|
||||
return [self getCString:tempFilePath];
|
||||
}
|
||||
else
|
||||
return [self getCString:path];
|
||||
}
|
||||
|
||||
+ (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info {
|
||||
NSString *path;
|
||||
if ([info[UIImagePickerControllerMediaType] isEqualToString:(NSString *)kUTTypeImage]) { // image picked
|
||||
// Temporarily save image as PNG
|
||||
UIImage *image = info[UIImagePickerControllerOriginalImage];
|
||||
if (image == nil)
|
||||
path = nil;
|
||||
else {
|
||||
[UIImagePNGRepresentation([self scaleImage:image maxSize:pickMediaMaxSize]) writeToFile:pickedMediaSavePath atomically:YES];
|
||||
path = pickedMediaSavePath;
|
||||
}
|
||||
}
|
||||
else { // video picked
|
||||
NSURL *mediaUrl = info[UIImagePickerControllerMediaURL] ?: info[UIImagePickerControllerReferenceURL];
|
||||
if (mediaUrl == nil)
|
||||
path = nil;
|
||||
else
|
||||
path = [mediaUrl path];
|
||||
}
|
||||
|
||||
popup = nil;
|
||||
imagePicker = nil;
|
||||
imagePickerState = 2;
|
||||
UnitySendMessage("NGMediaReceiveCallbackiOS", "OnMediaReceived", [self getCString:path]);
|
||||
|
||||
[picker dismissViewControllerAnimated:NO completion:nil];
|
||||
}
|
||||
|
||||
+ (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker
|
||||
{
|
||||
popup = nil;
|
||||
imagePicker = nil;
|
||||
UnitySendMessage("NGMediaReceiveCallbackiOS", "OnMediaReceived", "");
|
||||
|
||||
[picker dismissViewControllerAnimated:YES completion:nil];
|
||||
}
|
||||
|
||||
+ (void)popoverControllerDidDismissPopover:(UIPopoverController *)popoverController {
|
||||
popup = nil;
|
||||
imagePicker = nil;
|
||||
UnitySendMessage("NGMediaReceiveCallbackiOS", "OnMediaReceived", "");
|
||||
}
|
||||
|
||||
// Credit: https://stackoverflow.com/a/37052118/2373034
|
||||
+ (char *)getCString:(NSString *)source {
|
||||
if (source == nil)
|
||||
source = @"";
|
||||
|
||||
const char *sourceUTF8 = [source UTF8String];
|
||||
char *result = (char*) malloc(strlen(sourceUTF8) + 1);
|
||||
strcpy(result, sourceUTF8);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
extern "C" int _NativeGallery_CheckPermission() {
|
||||
return [UNativeGallery checkPermission];
|
||||
}
|
||||
|
||||
extern "C" int _NativeGallery_RequestPermission() {
|
||||
return [UNativeGallery requestPermission];
|
||||
}
|
||||
|
||||
extern "C" int _NativeGallery_CanOpenSettings() {
|
||||
return [UNativeGallery canOpenSettings];
|
||||
}
|
||||
|
||||
extern "C" void _NativeGallery_OpenSettings() {
|
||||
[UNativeGallery openSettings];
|
||||
}
|
||||
|
||||
extern "C" void _NativeGallery_ImageWriteToAlbum(const char* path, const char* album) {
|
||||
[UNativeGallery saveMedia:[NSString stringWithUTF8String:path] albumName:[NSString stringWithUTF8String:album] isImg:YES];
|
||||
}
|
||||
|
||||
extern "C" void _NativeGallery_VideoWriteToAlbum(const char* path, const char* album) {
|
||||
[UNativeGallery saveMedia:[NSString stringWithUTF8String:path] albumName:[NSString stringWithUTF8String:album] isImg:NO];
|
||||
}
|
||||
|
||||
extern "C" void _NativeGallery_PickImage(const char* imageSavePath, int maxSize) {
|
||||
[UNativeGallery pickMediaSetMaxSize:maxSize];
|
||||
[UNativeGallery pickMedia:YES savePath:[NSString stringWithUTF8String:imageSavePath]];
|
||||
}
|
||||
|
||||
extern "C" void _NativeGallery_PickVideo() {
|
||||
[UNativeGallery pickMedia:NO savePath:nil];
|
||||
}
|
||||
|
||||
extern "C" int _NativeGallery_IsMediaPickerBusy() {
|
||||
return [UNativeGallery isMediaPickerBusy];
|
||||
}
|
||||
|
||||
extern "C" char* _NativeGallery_GetImageProperties(const char* path) {
|
||||
return [UNativeGallery getImageProperties:[NSString stringWithUTF8String:path]];
|
||||
}
|
||||
|
||||
extern "C" char* _NativeGallery_GetVideoProperties(const char* path) {
|
||||
return [UNativeGallery getVideoProperties:[NSString stringWithUTF8String:path]];
|
||||
}
|
||||
|
||||
extern "C" char* _NativeGallery_LoadImageAtPath(const char* path, const char* temporaryFilePath, int maxSize) {
|
||||
return [UNativeGallery loadImageAtPath:[NSString stringWithUTF8String:path] tempFilePath:[NSString stringWithUTF8String:temporaryFilePath] maximumSize:maxSize];
|
||||
}
|
||||
33
Assets/3rd/NativeGallery/iOS/NativeGallery.mm.meta
Normal file
33
Assets/3rd/NativeGallery/iOS/NativeGallery.mm.meta
Normal file
@@ -0,0 +1,33 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 953e0b740eb03144883db35f72cad8a6
|
||||
timeCreated: 1498722774
|
||||
licenseType: Store
|
||||
PluginImporter:
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
platformData:
|
||||
data:
|
||||
first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
data:
|
||||
first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
data:
|
||||
first:
|
||||
iPhone: iOS
|
||||
second:
|
||||
enabled: 1
|
||||
settings: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user