Skip to content

Commit

Permalink
Capture photo with Metadata (#82)
Browse files Browse the repository at this point in the history
  • Loading branch information
dimonovdd authored Dec 13, 2021
1 parent 4db269f commit b27483e
Show file tree
Hide file tree
Showing 18 changed files with 421 additions and 140 deletions.
13 changes: 11 additions & 2 deletions MediaGallery/MediaFile/MediaFile.android.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,27 @@ namespace NativeMedia
partial class MediaFile
{
readonly Uri uri;
readonly string tempFilePath;

internal MediaFile(string fileName, Uri uri)
internal MediaFile(string fileName, Uri uri, string tempFilePath = null)
{
this.tempFilePath = tempFilePath;
NameWithoutExtension = Path.GetFileNameWithoutExtension(fileName);
Extension = Path.GetExtension(fileName);
ContentType = MimeTypeMap.Singleton.GetMimeTypeFromExtension(Extension);
this.uri = uri;
Type = GetFileType(ContentType);
}

Task<Stream> PlatformOpenReadAsync()
=> Task.FromResult(Platform.AppActivity.ContentResolver.OpenInputStream(uri));

void PlatformDispose() { }
void PlatformDispose()
{
if(!string.IsNullOrWhiteSpace(tempFilePath) && File.Exists(tempFilePath))
File.Delete(tempFilePath);

uri?.Dispose();
}
}
}
78 changes: 74 additions & 4 deletions MediaGallery/MediaFile/MediaFile.ios.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using CoreGraphics;
using CoreImage;
using Foundation;
using ImageIO;
using MobileCoreServices;
using UIKit;

Expand Down Expand Up @@ -30,17 +33,15 @@ internal PHPickerFile(NSItemProvider provider)
{
this.provider = provider;
NameWithoutExtension = provider?.SuggestedName;
var identifiers = provider?.RegisteredTypeIdentifiers;

identifier = (identifiers?.Any(i => i.StartsWith(UTType.LivePhoto)) ?? false) && (identifiers?.Contains(UTType.JPEG) ?? false)
? identifiers?.FirstOrDefault(i => i == UTType.JPEG)
: identifiers?.FirstOrDefault();
identifier = GetIdentifier(provider?.RegisteredTypeIdentifiers);

if (string.IsNullOrWhiteSpace(identifier))
return;

Extension = GetExtension(identifier);
ContentType = GetMIMEType(identifier);
Type = GetFileType(ContentType);
}

protected override async Task<Stream> PlatformOpenReadAsync()
Expand All @@ -52,20 +53,34 @@ protected override void PlatformDispose()
provider = null;
base.PlatformDispose();
}

private string GetIdentifier(string[] identifiers)
{
if (!(identifiers?.Length > 0))
return null;
if (identifiers.Any(i => i.StartsWith(UTType.LivePhoto)) && identifiers.Contains(UTType.JPEG))
return identifiers.FirstOrDefault(i => i == UTType.JPEG);
if (identifiers.Contains(UTType.QuickTimeMovie))
return identifiers.FirstOrDefault(i => i == UTType.QuickTimeMovie);
return identifiers.FirstOrDefault();
}
}

class UIDocumentFile : MediaFile
{
UIDocument document;
NSUrl assetUrl;

internal UIDocumentFile(NSUrl assetUrl, string fileName)
{
this.assetUrl = assetUrl;
document = new UIDocument(assetUrl);
Extension = document.FileUrl.PathExtension;
ContentType = GetMIMEType(document.FileType);
NameWithoutExtension = !string.IsNullOrWhiteSpace(fileName)
? Path.GetFileNameWithoutExtension(fileName)
: null;
Type = GetFileType(ContentType);
}

protected override Task<Stream> PlatformOpenReadAsync()
Expand All @@ -75,7 +90,62 @@ protected override void PlatformDispose()
{
document?.Dispose();
document = null;
assetUrl?.Dispose();
assetUrl = null;
base.PlatformDispose();
}
}

class PhotoFile : MediaFile
{
UIImage img;
NSDictionary metadata;
NSMutableData imgWithMetadata;

internal PhotoFile(UIImage img, NSDictionary metadata, string name)
{
this.img = img;
this.metadata = metadata;
NameWithoutExtension = name;
ContentType = GetMIMEType(UTType.JPEG);
Extension = GetExtension(UTType.JPEG);
Type = GetFileType(ContentType);
}

protected override Task<Stream> PlatformOpenReadAsync()
{
imgWithMetadata ??= GetImageWithMeta();
return Task.FromResult(imgWithMetadata?.AsStream());
}

public NSMutableData GetImageWithMeta()
{
if (img == null || metadata == null)
return null;

using var source = CGImageSource.FromData(img.AsJPEG());
var destData = new NSMutableData();
using var destination = CGImageDestination.Create(destData, source.TypeIdentifier, 1, null);
destination.AddImage(source, 0, metadata);
destination.Close();
DisposeSources();
return destData;
}

protected override void PlatformDispose()
{
imgWithMetadata?.Dispose();
imgWithMetadata = null;
DisposeSources();
base.PlatformDispose();
}

void DisposeSources()
{
img?.Dispose();
img = null;
metadata?.Dispose();
metadata = null;
}
}
}
18 changes: 13 additions & 5 deletions MediaGallery/MediaFile/MediaFile.shared.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,24 @@ protected internal set

public string ContentType { get; protected internal set; }

public MediaFileType? Type => ContentType.StartsWith("image")
? MediaFileType.Image
: ContentType.StartsWith("video")
? MediaFileType.Video
: (MediaFileType?)null;
public MediaFileType? Type { get; protected set; }

public Task<Stream> OpenReadAsync()
=> PlatformOpenReadAsync();

public void Dispose()
=> PlatformDispose();

protected MediaFileType? GetFileType(string contentType)
{
if (string.IsNullOrWhiteSpace(contentType))
return null;
if (ContentType.StartsWith("image"))
return MediaFileType.Image;
if (ContentType.StartsWith("video"))
return MediaFileType.Video;

return null;
}
}
}
2 changes: 1 addition & 1 deletion MediaGallery/MediaGallery.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<PackageId>Xamarin.MediaGallery</PackageId>
<PackageTags>maui, xamarin, .net6, ios, android, toolkit, xamarin.forms, media, picker, photos, videos, mediapicker</PackageTags>
<Description>This plugin is designed for picking and saving photos and video files from the native gallery of Android and iOS devices</Description>
<Version>2.0.0</Version>
<Version>2.1.0-preview1</Version>
<Authors>dimonovdd</Authors>
<Owners>dimonovdd</Owners>
<RepositoryUrl>https://github.com/dimonovdd/Xamarin.MediaGallery</RepositoryUrl>
Expand Down
6 changes: 6 additions & 0 deletions MediaGallery/MediaGallery/MediaGallery.netstandard.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,11 @@ static Task PlatformSaveAsync(MediaFileType type, string filePath)

static Task PlatformSaveAsync(MediaFileType type, Stream fileStream, string fileName)
=> Task.CompletedTask;

static bool PlatformCheckCapturePhotoSupport()
=> false;

static Task<IMediaFile> PlatformCapturePhotoAsync(CancellationToken token)
=> Task.FromResult<IMediaFile>(null);
}
}
52 changes: 52 additions & 0 deletions MediaGallery/MediaGallery/MediaGallery.shared.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ namespace NativeMedia
/// <summary>Performs operations with media files.</summary>
public static partial class MediaGallery
{
static readonly string cacheDir = "XamarinMediaGalleryCacheDir";

/// <summary>Opens media files Picker</summary>
/// <returns>Media files selected by a user.</returns>
/// <inheritdoc cref = "MediaPickRequest(int, MediaFileType[])" path="/param"/>
Expand Down Expand Up @@ -63,6 +65,15 @@ public static async Task SaveAsync(MediaFileType type, string filePath)
await PlatformSaveAsync(type, filePath).ConfigureAwait(false);
}

public static bool CheckCapturePhotoSupport()
=> PlatformCheckCapturePhotoSupport();

public static async Task<IMediaFile> CapturePhotoAsync(CancellationToken token = default)
{
await CheckPossibilityCamera();
return await PlatformCapturePhotoAsync(token);
}

static void CheckFileName(string fileName)
{
if (string.IsNullOrWhiteSpace(fileName))
Expand All @@ -81,5 +92,46 @@ static async Task CheckPossibilitySave()
await Task.CompletedTask;
#endif
}

static async Task CheckPossibilityCamera()
{
ExceptionHelper.CheckSupport();
#if __MOBILE__
if (!CheckCapturePhotoSupport())
throw new FeatureNotSupportedException();


var status = await Permissions.CheckStatusAsync<Permissions.Camera>();

if (status != PermissionStatus.Granted)
throw ExceptionHelper.PermissionException(status);
#else
await Task.CompletedTask;
#endif
}

#if __MOBILE__
static void DeleteFile(string filePath)
{
if (!string.IsNullOrWhiteSpace(filePath) && File.Exists(filePath))
File.Delete(filePath);
}

static string GetFilePath(string fileName)
{
fileName = fileName.Trim();
var dirPath = Path.Combine(FileSystem.CacheDirectory, cacheDir);
var filePath = Path.Combine(dirPath, fileName);

if (!Directory.Exists(dirPath))
Directory.CreateDirectory(dirPath);
return filePath;
}
#endif
static string GetNewImageName(string imgName = null)
=> GetNewImageName(DateTime.Now, imgName);

static string GetNewImageName(DateTime val, string imgName = null)
=> $"{imgName ?? "IMG"}_{val:yyyyMMdd_HHmmss}";
}
}
Loading

0 comments on commit b27483e

Please sign in to comment.