Get Exif data using Uri - android

I want to get Exif data for an image that I pick from Android gallery or similar apps like Google Photos. Here are the methods that I am using for getting image file from Uri:
public static String getActualPathFromUri(#NonNull Context context, #NonNull Uri selectedImageUri) {
Bitmap bitmap = null;
try {
bitmap = getBitmapFromUri(context, selectedImageUri);
} catch (IOException e) {
e.printStackTrace();
}
if (bitmap == null) {
return null;
}
File imageFileFolder = new File(context.getCacheDir(), "example");
if (!imageFileFolder.exists()) {
imageFileFolder.mkdir();
}
FileOutputStream out = null;
File imageFileName = new File(imageFileFolder, "example-" + System.currentTimeMillis() + ".jpg");
try {
out = new FileOutputStream(imageFileName);
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, out);
out.flush();
out.close();
} catch (IOException e) {
} finally {
if (out != null) {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return imageFileName.getAbsolutePath();
}
private static Bitmap getBitmapFromUri(#NonNull Context context, #NonNull Uri uri) throws IOException {
ParcelFileDescriptor parcelFileDescriptor =
context.getContentResolver().openFileDescriptor(uri, "r");
assert parcelFileDescriptor != null;
FileDescriptor fileDescriptor = parcelFileDescriptor.getFileDescriptor();
Bitmap image = BitmapFactory.decodeFileDescriptor(fileDescriptor);
parcelFileDescriptor.close();
}
The reason I am opting for Bitmap from the Uri instead of getting system filename is because the method is fragmented for different versions of Android. Plus, in apps like Google Photos, some photos are shown directly from server and are not stored on the device, so getting filename is not possible. Even if I get the filename, I get Security Exception when trying to use that in my app.
The problem with this approach is, when I get the Bitmap, I don't get the Exif data. But I need the Exif data and attach it to the file that I save in my cache folder so that when I upload the image to server, it gets the image along with Exif so that it can do some further processing. The reason, I need to save it as a file or at least get the file, if it already exists in the system is because Retrofit needs a File object which then gets converted to TypedFile and sent to server using Multipart request.
So, how can I accurately get the Exif information from any kind of image Uri(local or on server) and then save the image along with it's Exif data so that I can send it to the server.

Related

Writing EXIF data to image saved with DocumentFile class

I need to get a File from DocumentFile or Uri with correct scheme not the one with content://com.android.externalstorage.documents/tree/primary: if the device's main memory is selected.
To get File or absolute path of the image i need the one with file:///storage/emulated/0 or storage/emulated/0 but i could not find a way to get correct Uri for building a File to write EXIF data to image.
My scenario is:
User chooses a path to save images which returns Uri with content://com.android.externalstorage.documents onActivityResult(). I save this path with treeUri.toString() to SharedPreferences for using later.
User takes a picture and image is saved with DocumentFile.fromTreeUri(MainActivity.this, Uri.parse(uriString));
This where i fail, getting a File that correctly points to image, Uri with content:// does not return the existing image.Correct Uri should file:///storage/emulated/ and i can convert this Uri to file using File filePath = new File(URI.create(saveDir.getUri().toString()));
How can i get the Uri needed for consturcting File or File using Uri i got from SAF UI?
EDIT: ExifInterface Support Library is introduced for Android 7.1+ that can use InputStream or FileDescriptor.
Uri uri; // the URI you've received from the other app
InputStream in;
try {
in = getContentResolver().openInputStream(uri);
ExifInterface exifInterface = new ExifInterface(in);
// Now you can extract any Exif tag you want
// Assuming the image is a JPEG or supported raw format
} catch (IOException e) {
// Handle any errors
} finally {
if (in != null) {
try {
in.close();
} catch (IOException ignored) {}
}
}
Note: ExifInterface will not work with remote InputStreams, such as those returned from a HttpURLConnection. It is strongly recommended to only use them with content:// or file:// URIs.
Snippet above is reading data obviously since it opens an InputStream to read data. I need to be able to write EXIF data to JPEG files.
Answer for writing Exif data to an image previously saved and with known content Uri using FileDescriptor if Api is 24 or above
private void writeEXIFWithFileDescriptor(Uri uri) {
if (Build.VERSION.SDK_INT < 24) {
showToast("writeEXIFWithInputStream() API LOWER 24", Toast.LENGTH_SHORT);
return;
}
ParcelFileDescriptor parcelFileDescriptor = null;
try {
parcelFileDescriptor = mContext.getContentResolver().openFileDescriptor(uri, "rw");
FileDescriptor fileDescriptor = parcelFileDescriptor.getFileDescriptor();
showToast("writeEXIFWithFileDescriptor(): " + fileDescriptor.toString(), Toast.LENGTH_LONG);
ExifInterface exifInterface = new ExifInterface(fileDescriptor);
// TODO Create Exif Tags class to save Exif data
exifInterface.saveAttributes();
} catch (FileNotFoundException e) {
showToast("File Not Found " + e.getMessage(), Toast.LENGTH_LONG);
} catch (IOException e) {
// Handle any errors
e.printStackTrace();
showToast("IOEXception " + e.getMessage(), Toast.LENGTH_LONG);
} finally {
if (parcelFileDescriptor != null) {
try {
parcelFileDescriptor.close();
} catch (IOException ignored) {
ignored.printStackTrace();
}
}
}
}
If Api is lower than 24 it's necessary to use a buffer file and save that buffer file to actual location with DocumentFile after writing Exif data is finished.
private boolean exportImageWithEXIF(Bitmap bitmap, DocumentFile documentFile) {
OutputStream outputStream = null;
File bufFile = new File(Environment.getExternalStorageDirectory(), "buffer.jpg");
long freeSpace = Environment.getExternalStorageDirectory().getFreeSpace() / 1048576;
double bitmapSize = bitmap.getAllocationByteCount() / 1048576d;
showToast("exportImageWithEXIF() freeSpace " + freeSpace, Toast.LENGTH_LONG);
showToast("exportImageWithEXIF() bitmap size " + bitmapSize, Toast.LENGTH_LONG);
try {
outputStream = new FileOutputStream(bufFile);
// Compress image from bitmap with JPEG extension
if (mCameraSettings.getImageFormat().equals(Constants.IMAGE_FORMAT_JPEG)) {
isImageSaved = bitmap.compress(CompressFormat.JPEG, mCameraSettings.getImageQuality(), outputStream);
showToast("isImageSaved: " + isImageSaved, Toast.LENGTH_SHORT);
}
if (isImageSaved) {
writeEXIFWithFile(bufFile);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
if (outputStream != null) {
try {
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
OutputStream os = null;
InputStream is = null;
try {
int len;
byte[] buf = new byte[4096];
os = mContext.getContentResolver().openOutputStream(documentFile.getUri());
is = new FileInputStream(bufFile);
while ((len = is.read(buf)) > 0) {
os.write(buf, 0, len);
}
os.close();
is.close();
if (bufFile != null) {
bufFile.delete();
bufFile = null;
}
} catch (IOException e) {
e.printStackTrace();
}
return isImageSaved;
}
Both methods can be used to write Exif data to image saved to device's memory or SD card. You can also save image to SD card using valid Uri from Storage Access Framework.
I also found a way to get absolute path for memory and SD card from content Uri but it's irrelevant for this question and using Uri instead of absolute path is encouraged and does not lead to unnoticed errors, i also wasn't able to save image to SD card with absolute path, only able to read from it.

How To Convert A Bitmap Image To Uri

I have not found the answer to this question anywhere.
The Bitmap Image is processed in The application, meaning there is no File path to get the Image.
Below is how to convert a Uri to Bitmap
if (requestCode == RC_PHOTO_PICKER && resultCode == RESULT_OK) {
Uri selectedImageUri = data.getData();
imageview.setImageURI(selectedImageUri);
try {
bitmap1 = MediaStore.Images.Media.getBitmap(this.getContentResolver(), selectedImageUri);
} catch (IOException e) {
e.printStackTrace();
Toast.makeText(this, "" + e, Toast.LENGTH_SHORT).show();
}
bitmap1.compress(Bitmap.CompressFormat.JPEG, 7, bytearrayoutputstream);
BYTE = bytearrayoutputstream.toByteArray();
bitmap2 = BitmapFactory.decodeByteArray(BYTE, 0, BYTE.length);
imagetoo.setImageBitmap(bitmap2);
}
How do I now reconvert to a Uri
URI is super set of URL that means its a path to file . whereas Bitmap is a digital image composed of a matrix of dots.Bitmap represents a data and uri represents that location where data is saved .SO if you need to get a URI for a bitmap You just need to save it on a storage . In android you can do it by Java IO like below:First Create a file where you want to save it :
public File createImageFile() {
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US).format(new Date());
String imageFileName = "JPEG_" + timeStamp + "_";
File mFileTemp = null;
String root=activity.getDir("my_sub_dir",Context.MODE_PRIVATE).getAbsolutePath();
File myDir = new File(root + "/Img");
if(!myDir.exists()){
myDir.mkdirs();
}
try {
mFileTemp=File.createTempFile(imageFileName,".jpg",myDir.getAbsoluteFile());
} catch (IOException e1) {
e1.printStackTrace();
}
return mFileTemp;
}
Then flush it and you will get the URi
File file = createImageFile(context);
if (file != null) {
FileOutputStream fout;
try {
fout = new FileOutputStream(file);
currentImage.compress(Bitmap.CompressFormat.PNG, 70, fout);
fout.flush();
} catch (Exception e) {
e.printStackTrace();
}
Uri uri=Uri.fromFile(file);
}
This is just an example not idle code for all android version. To use Uri above and on android N you should use FileProvider to serve the file . Follow the Commonsware's answer.
Use compress() on Bitmap to write the bitmap to a file. Then, most likely, use FileProvider to serve that file, where getUriForFile() gives you the Uri corresponding to the file.
IOW, you do not "convert" a bitmap to a Uri. You save the bitmap somewhere that gives you a Uri.

How can I get a Picasa image folder on my Marshmallow device?

My application allows users to select an image to upload. When users select an image from a picasa album my data intent comes back with dat=content://com.sec.android.gallery3d.provider/picasa/item/....
Apparently when selecting an image from a picasa folder, I must handle getting the image differently as noted in this answer.
But before I implement a fix, I want to be able to reproduce the crash so I can verify my fix actually works. So how can I get a Picasa folder on my new (marshmallow) Android test device since Picasa has been killed by Google?
The most guaranteed way of getting a file send inside an intent, is to open a stream to it and copy it over to a private folder on your app.
This way works for local file, content uri, picasa, all of it.
Something like that:
private File getSharedFile() {
Uri uri = intent.getExtras().getParcelable(Intent.EXTRA_STREAM);
// or using the new compat lib
Uri uri = ShareCompat.IntentReader(this).getStream();
InputStream is = null;
OutputStream os = null;
try {
File f = ... define here a temp file // maybe getCacheDir();
is = getContentResolver().openInputStream(uri);
os = new BufferedOutputStream(new FileOutputStream(f));
int read;
byte[] bytes = new byte[2048];
while ((read = is.read(bytes)) != -1) {
os.write(bytes, 0, read);
}
return f;
} catch (Exception e) {
... handle exceptions, buffer underflow, NPE, etc
} finally {
try { is.close(); } catch (Exception e) { /* u never know */ }
try {
os.flush();
os.close();
} catch (Exception e) { /* seriously can happen */ }
}
return null;
}

crop image save in internal storage and get bitmap from uri

I write a code crop image.The image maybe a little big,so I use intent.putExtra("return-data", false); and intent.putExtra("output", outputUri); So I should decode the uri to get bitmap.Just like this(omActivityResult)
InputStream is = null;
is = getContentResolver().openInputStream(outputUri);
Bitmap avatar = BitmapFactory.decodeStream(is);
I first make External Storage to store the crop image.It work well.
outputUri = Uri.fromFile(new File("/storage/emulated/0/upload.jpg"));
But I need to store it in Internal Storage.I follow the Android developer
to save in Internal Storage.The code follow.
FileOutputStream os = null;
try
{
os = openFileOutput("upload.jpg", 0);
}
catch (FileNotFoundException e)
{
}
finally
{
if (os != null)
{
try
{
os.close();
}
catch (IOException e)
{
}
}
}
outputUri = Uri.fromFile(new File(getFilesDir().getAbsolutePath(), "upload.jpg"));
the avatar will be null.I have read many questions in stackoverflow but it doesn't work. Can you help me?

bitmap.compress from Uri resulting in OutOfMemoryError

I am trying to save a bitmap which user selects into my own App path.
Unfortunately, with very big images I get OutOfMemoryError error.
I am using the following code:
private String loadImage (Uri filePath) {
File fOut = new File(getFilesDir(),"own.jpg");
inStream = getContentResolver().openInputStream(filePath);
selectedImage = BitmapFactory.decodeStream(inStream);
selectedImage.compress(CompressFormat.JPEG, 100, new FileOutputStream(fOut));
}
Is there any way for me to save any image file of any size for an Uri to a file?
*I am not in a position to resize the image e.g. by using calculateInSampleSize method.
Is there any way for me to save any image file of any size for an Uri to a file?
Since it already is an image, just copy the bytes from the InputStream to the OutputStream:
private void copyInputStreamToFile( InputStream in, File file ) {
try {
FileOutputStream out = new FileOutputStream(file);
byte[] buf = new byte[8192];
int len;
while((len=in.read(buf))>0){
out.write(buf,0,len);
}
out.flush();
out.getFD().sync();
out.close();
in.close();
} catch (Exception e) {
e.printStackTrace();
}
}
(adapted from this SO answer)

Categories

Resources