This commit is contained in:
2020-07-18 21:12:14 +08:00
parent 1361db18a9
commit 33dc6cea60
214 changed files with 16596 additions and 3104 deletions

View File

@@ -1,12 +0,0 @@
fileFormatVersion: 2
guid: ce1403606c3629046a0147d3e705f7cc
timeCreated: 1498722610
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,79 +0,0 @@
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);
}
}

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: a032e025a802f4707bf50d1d51778b1f
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,9 +1,8 @@
fileFormatVersion: 2
guid: 0a607dcda26e7614f86300c6ca717295
guid: 880df38a865ba4f91981f0af6cafd817
folderAsset: yes
timeCreated: 1498722617
licenseType: Store
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,31 +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;
}
}
}
#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

View File

@@ -1,7 +1,7 @@
fileFormatVersion: 2
guid: 2d517fd0f2f85f24698df2775bee58e9
timeCreated: 1544889149
licenseType: Store
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences: []

View File

@@ -1,92 +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 );
}
}
}
}
#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

View File

@@ -1,7 +1,7 @@
fileFormatVersion: 2
guid: 4c18d702b07a63945968db47201b95c9
timeCreated: 1519060539
licenseType: Store
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences: []

View File

@@ -1,29 +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 );
}
}
}
}
#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

View File

@@ -1,7 +1,7 @@
fileFormatVersion: 2
guid: a07afac614af1294d8e72a3c083be028
timeCreated: 1519060539
licenseType: Store
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences: []

View File

@@ -1,7 +1,7 @@
fileFormatVersion: 2
guid: 07368f3d2020fe6439475cfaa8578fd8
timeCreated: 1544891451
licenseType: Store
guid: 8ed2b1cd685da484194ba437e57d1e49
timeCreated: 1570374101
licenseType: Free
PluginImporter:
serializedVersion: 2
iconMap: {}

View File

@@ -1,9 +1,8 @@
fileFormatVersion: 2
guid: 19fc6b8ce781591438a952d8aa9104f8
guid: d8aa34e33afa341a180dea50b2bdad62
folderAsset: yes
timeCreated: 1521452097
licenseType: Store
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,63 +1,72 @@
#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
#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 );
#if UNITY_2019_3_OR_NEWER
string targetGUID = pbxProject.GetUnityFrameworkTargetGuid();
#else
string targetGUID = pbxProject.TargetGuidByName( PBXProject.GetUnityTargetName() );
#endif
// Minimum supported iOS version on Unity 2018.1 and later is 8.0
#if !UNITY_2018_1_OR_NEWER
if( MINIMUM_TARGET_8_OR_ABOVE )
{
#endif
pbxProject.AddBuildProperty( targetGUID, "OTHER_LDFLAGS", "-framework Photos" );
pbxProject.AddBuildProperty( targetGUID, "OTHER_LDFLAGS", "-framework MobileCoreServices" );
pbxProject.AddBuildProperty( targetGUID, "OTHER_LDFLAGS", "-framework ImageIO" );
#if !UNITY_2018_1_OR_NEWER
}
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" );
}
#endif
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
}

View File

@@ -1,7 +1,7 @@
fileFormatVersion: 2
guid: dff1540cf22bfb749a2422f445cf9427
timeCreated: 1521452119
licenseType: Store
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences: []

View File

@@ -0,0 +1,15 @@
{
"name": "NativeGallery.Editor",
"references": [],
"includePlatforms": [
"Editor"
],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
}

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 3dffc8e654f00c545a82d0a5274d51eb
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,3 @@
{
"name": "NativeGallery.Runtime"
}

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 777e6aa34fa63442d979fd3ac881b77b
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: ddbd1086fc9d14bf6814614e75efda21
guid: 89147d0c91efd4310bf3b6ab773df35b
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

@@ -1,87 +1,113 @@
= 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
= 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
- (optional) change the value of PHOTO_LIBRARY_USAGE_DESCRIPTION in Plugins/NativeGallery/Editor/NGPostProcessBuild.cs
- (Unity 2017.4 or earlier) 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. FAQ
- How can I fetch the path of the saved image or the original path of the picked image?
You can't. On iOS, these files are stored in an internal directory that we have no access to (I don't think there is even a way to fetch that internal path). On Android, with Storage Access Framework, the absolute path is hidden behind the SAF-layer. There are some tricks here and there to convert SAF-path to absolute path but they don't work in all cases (most of the snippets that can be found on Stackoverflow can't return the absolute path if the file is stored on external SD card).
- Can't access the Gallery, it says "java.lang.ClassNotFoundException: com.yasirkula.unity.NativeGallery" in Logcat
If your project uses ProGuard, try adding the following line to ProGuard filters: -keep class com.yasirkula.unity.* { *; }
- Nothing happens when I try to access the Gallery on Android
Make sure that you've set the "Write Permission" to "External (SDCard)" in Player Settings.
- Saving image/video doesn't work properly
Make sure that the "filename" parameter of the Save function includes the file's extension, as well
4. 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/filename. On iOS, the image will be saved in the corresponding album.
// NOTE: Make sure that the filename parameter includes the file's extension, as well
// IMPORTANT: NativeGallery will never overwrite existing media on the Gallery. If there is a name conflict, NativeGallery will ensure a unique filename. So don't put '{0}' in filename anymore (for new users, putting {0} in filename was recommended in order to ensure unique filenames in earlier versions, this is no longer necessary).
// 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 filename, MediaSaveCallback callback = null );
NativeGallery.Permission NativeGallery.SaveImageToGallery( string existingMediaPath, string album, string filename, MediaSaveCallback callback = null );
NativeGallery.Permission NativeGallery.SaveImageToGallery( Texture2D image, string album, string filename, MediaSaveCallback callback = null );
NativeGallery.Permission NativeGallery.SaveVideoToGallery( byte[] mediaBytes, string album, string filename, MediaSaveCallback callback = null );
NativeGallery.Permission NativeGallery.SaveVideoToGallery( string existingMediaPath, string album, string filename, 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). Has no effect on iOS
NativeGallery.Permission NativeGallery.GetImageFromGallery( MediaPickCallback callback, string title = "", string mime = "image/*" );
NativeGallery.Permission NativeGallery.GetVideoFromGallery( MediaPickCallback callback, string title = "", string mime = "video/*" );
NativeGallery.Permission NativeGallery.GetImagesFromGallery( MediaPickMultipleCallback callback, string title = "", string mime = "image/*" );
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 ////
// Creates a Texture2D from the specified image file in correct orientation and returns it. Returns null, if something goes wrong
// 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 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
Texture2D NativeGallery.GetVideoThumbnail( string videoPath, int maxSize = -1, double captureTimeInSeconds = -1.0 );
// 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.ImageProperties NativeGallery.GetImageProperties( string imagePath );
// Returns a VideoProperties instance that holds the width, height, duration (in milliseconds) and rotation information of a video file. To play a video in correct orientation, you should rotate it by rotation degrees clockwise. For a 90-degree or 270-degree rotated video, values of width and height should be swapped to get the display size of the video
NativeGallery.VideoProperties NativeGallery.GetVideoProperties( string videoPath );

View File

@@ -1,8 +1,7 @@
fileFormatVersion: 2
guid: e5d70289d6cc2ac4294d703db236f8d0
timeCreated: 1520539180
licenseType: Store
guid: 1852f26c8592c453f8f7423bf2a283d7
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,9 +1,8 @@
fileFormatVersion: 2
guid: 9c623599351a41a4c84c20f73c9d8976
guid: c8af1e45d8c954cd0a87623adcbc246d
folderAsset: yes
timeCreated: 1498722622
licenseType: Store
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,72 +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;
}
}
}
#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 )
{
IsBusy = false;
if( callback != null )
{
callback( null );
callback = null;
}
}
}
}
}
public void OnMediaReceived( string path )
{
IsBusy = false;
if( string.IsNullOrEmpty( path ) )
path = null;
if( callback != null )
{
callback( path );
callback = null;
}
}
}
}
#endif

View File

@@ -1,7 +1,7 @@
fileFormatVersion: 2
guid: 71fb861c149c2d1428544c601e52a33c
timeCreated: 1519060539
licenseType: Store
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences: []

View File

@@ -1,46 +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;
}
}
}
}
#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

View File

@@ -1,7 +1,7 @@
fileFormatVersion: 2
guid: 9cbb865d0913a0d47bb6d2eb3ad04c4f
timeCreated: 1519060539
licenseType: Store
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences: []

View File

@@ -1,33 +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:
fileFormatVersion: 2
guid: 953e0b740eb03144883db35f72cad8a6
timeCreated: 1498722774
licenseType: Pro
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: