'DEPLHI - Trying to get EXIF data on library images in Android
I am new at developing for Android, but not so new on Delphi development though. Anyway I am struggling to get EXIT data from an image (loaded from library) and show that image on the form.
Here is my code:
unit Unit1;
interface
uses
System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
System.Messaging,
{$IF CompilerVersion > 32}
System.Permissions,
{$ENDIF}
Androidapi.JNI.JavaTypes, FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics,
FMX.Dialogs, FMX.StdActns, FMX.Controls.Presentation, FMX.Objects, FMX.Layouts,
FMX.ScrollBox, FMX.Memo, FMX.Surfaces, FMX.ExtCtrls, FMX.StdCtrls,
FMX.Helpers.Android, System.Actions, FMX.ActnList, FMX.MediaLibrary.Actions;
type
TForm1 = class(TForm)
Button1: TButton;
Layout1: TLayout;
Memo1: TMemo;
img1: TImageControl;
procedure Button1Click(Sender: TObject);
private
FFileName: JString;
procedure GetEXIF(const AFileName: JInputStream);
procedure ResultNotificationMessageHandler(const Sender: TObject; const M: TMessage);
procedure TakePhoto;
{$IF CompilerVersion > 32}
procedure TakePhotoPermissionsResultHandler(Sender: TObject; const APermissions: TArray<string>; const AGrantResults: TArray<TPermissionStatus>);
{$ENDIF}
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
end;
var
Form1: TForm1;
implementation
{$R *.fmx}
uses
System.IOUtils, Androidapi.Helpers, Androidapi.JNI.Media, Androidapi.JNIBridge,
Androidapi.JNI.Provider, Androidapi.JNI.App, Androidapi.JNI.Os,
Androidapi.JNI.GraphicsContentViewText, Androidapi.JNI.Net,
FMX.Platform.Android, DW.Androidapi.JNI.Os;
const
cPermissionReadExternalStorage = 'android.permission.READ_EXTERNAL_STORAGE';
cPermissionWriteExternalStorage = 'android.permission.WRITE_EXTERNAL_STORAGE';
cPermissionCamera = 'android.permission.CAMERA';
{$IF CompilerVersion > 32}
type
TGrantResults = TArray<TPermissionStatus>;
TGrantResultsHelper = record helper for TGrantResults
public
function AreAllGranted: Boolean;
end;
{ TGrantResultsHelper }
function TGrantResultsHelper.AreAllGranted: Boolean;
var
LStatus: TPermissionStatus;
begin
for LStatus in Self do
begin
if LStatus <> TPermissionStatus.Granted then
Exit(False); // <======
end;
Result := True;
end;
{$ENDIF}
{ TForm1 }
procedure TForm1.Button1Click(Sender: TObject);
begin
{$IF CompilerVersion > 32}
TPermissionsService.DefaultService.RequestPermissions([cPermissionReadExternalStorage, cPermissionWriteExternalStorage, cPermissionCamera], TakePhotoPermissionsResultHandler);
{$ELSE}
TakePhoto;
{$ENDIF}
end;
constructor TForm1.Create(AOwner: TComponent);
begin
inherited;
TMessageManager.DefaultManager.SubscribeToMessage(TMessageResultNotification, ResultNotificationMessageHandler);
end;
destructor TForm1.Destroy;
begin
TMessageManager.DefaultManager.Unsubscribe(TMessageResultNotification, ResultNotificationMessageHandler);
inherited;
end;
procedure TForm1.GetEXIF(const AFileName: JInputStream);
var
LEXIF: JExifInterface;
LLatLong: TJavaArray<Single>;
LStream: JFileInputStream;
begin
try
LEXIF := TJExifInterface.JavaClass.init(AFileName);
Memo1.Lines.Clear;
Memo1.Lines.Add('Date Taken: ' + JStringToString(LEXIF.getAttribute(TJExifInterface.JavaClass.TAG_DATETIME)));
Memo1.Lines.Add('Camera Make: ' + JStringToString(LEXIF.getAttribute(TJExifInterface.JavaClass.TAG_MAKE)));
Memo1.Lines.Add('Camera Model: ' + JStringToString(LEXIF.getAttribute(TJExifInterface.JavaClass.TAG_MODEL)));
LLatLong := TJavaArray<Single>.Create(2);
try
if LEXIF.getLatLong(LLatLong) then
begin
Memo1.Lines.Add('Latitude: ' + LLatLong.Items[0].ToString);
Memo1.Lines.Add('Longitude: ' + LLatLong.Items[1].ToString);
end;
finally
LLatLong.Free;
end;
except
on E: Exception do
ShowMessage(e.Message);
end;
end;
procedure TForm1.ResultNotificationMessageHandler(const Sender: TObject; const M: TMessage);
var
LMessage: TMessageResultNotification;
Str: string;
FullPhotoUri: Jnet_Uri;
ms: TMemoryStream;
jis: JInputStream;
b: TJavaArray<Byte>;
NativeBitmap: JBitmap;
Bitmap: TBitmapSurface;
begin
if M is TMessageResultNotification then
begin
LMessage := TMessageResultNotification(M);
if LMessage.RequestCode = 10011 then
if (LMessage.ResultCode = TJActivity.JavaClass.RESULT_OK) then
if Assigned(LMessage.Value) then
try
try
FullPhotoUri := LMessage.Value.getData();
jis := TAndroidHelper.Context.getContentResolver.openInputStream(FullPhotoUri);
GetEXIF(jis);
ms := TMemoryStream.Create;
b := TJavaArray<Byte>.Create(jis.available);
jis.read(b);
ms.Write(b.Data^, b.Length);
img1.Bitmap.LoadFromStream(ms);
jis.close;
except
on E: Exception do
Application.ShowException(e);
end;
finally
ms.Free;
end;
end;
end;
procedure TForm1.TakePhotoPermissionsResultHandler(Sender: TObject; const APermissions: TArray<string>; const AGrantResults: TArray<TPermissionStatus>);
begin
if TGrantResults(AGrantResults).AreAllGranted then
TakePhoto
else
ShowMessage('Not all photo permissions granted!');
end;
// Based on: https://developer.android.com/training/camera/photobasics#java
procedure TForm1.TakePhoto;
var
LIntent: JIntent;
LFile, LDir: JFile;
LUri: Jnet_Uri;
LFileName: string;
begin
LIntent := TJIntent.Create;
LIntent.setAction(TJIntent.JavaClass.ACTION_OPEN_DOCUMENT).addCategory(TJIntent.JavaClass.CATEGORY_OPENABLE).setType(StringToJString('image/*'));
if LIntent.resolveActivity(TAndroidHelper.Context.getPackageManager) <> nil then
begin
MainActivity.startActivityForResult(LIntent, 10011);
end
else
ShowMessage('Cannot take a photo!');
end;
end.
Now, the error comes from the line:
img1.Bitmap.LoadFromStream(ms);
and the error is:
Project ObtainPhotoInfoDemo.apk raised exception class Segmentation fault (11).
Thanks
UPDATE
FOUND THE SOLUTION!!!!
blackapps commented and gave me an idea, the stream should be closed and reopened again to be used in an another call like this:
FullPhotoUri := LMessage.Value.getData();
//get input stream
jis := TAndroidHelper.Context.getContentResolver.openInputStream(FullPhotoUri);
GetEXIF(jis);
//have to close it because GetEXIF already consumed it
jis.close;
//open it again
jis := TAndroidHelper.Context.getContentResolver.openInputStream(FullPhotoUri);
NativeBitmap := TJBitmapFactory.JavaClass.decodeStream(jis);
Surf := TBitmapSurface.Create;
if JBitmapToSurface(NativeBitmap, Surf) then
img1.Bitmap.Assign(Surf);
jis.close;
Solution 1:[1]
Hi Dejan and blackapps learnt much about your code many thanks.
To contribute this topic, kindly be informed that Android updated image metadata and bitmap access with Android 29+ SAF:
Access documents and other files from shared storage Android Developers - Open a document - Bitmap
//Examine document metadata
procedure dumpImageMetaData(uri : JNet_Uri); (* GoruntuMetaVerisiDokumu *)
// The query, because it only applies to a single document, returns only
// one row. There's no need to filter, sort, or select fields,
// because we want all fields for one document.
var
displayName, size : JString;
sizeIndex : integer;
cursor : JCursor;
begin
cursor := TAndroidHelper.Activity.getContentResolver.query(uri,nil,nil,nil,nil,nil);
try
// moveToFirst() returns false if the cursor has 0 rows. Very handy for
// "if there's anything to look at, look at it" conditionals.
if (cursor<>nil) then
if (cursor.moveToFirst) then
begin
displayName := cursor.getString (cursor.getColumnIndex (TJOpenableColumns.JavaClass.DISPLAY_NAME));
Memo1.Lines.Add({TAG.ToString +} 'Display Name: ' + JStringToString (displayName));
sizeIndex:=cursor.getColumnIndex(TJOpenableColumns.JavaClass.SIZE);
size := nil;
if not (cursor.isNull(sizeIndex)) then
size := cursor.getString(sizeIndex)
else
size:=StringToJString ('Unknown');
Memo1.Lines.Add({TAG.ToString +} 'Size: ' + JStringToString (size));
end;
finally
cursor.close;
end;
end;
//Open a document - Bitmap
function getBitmapFromUri(uri : JNet_Uri): JBitmap; (* UridenBiteslemAl *)
var
fileDescriptor : JFileDescriptor;
parcelFileDescriptor : JParcelFileDescriptor;
image : JBitmap;
begin
Result := nil;
try
parcelFileDescriptor := TAndroidHelper.Activity
.getContentResolver.openFileDescriptor(uri,StringToJString('r'));
fileDescriptor := parcelFileDescriptor.getFileDescriptor;
image := TJBitmapFactory.JavaClass.decodeFileDescriptor(fileDescriptor);
parcelFileDescriptor.close;
result := image;
except
on E: Exception do
ShowMessage(e.Message);
end;
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 |
