As part of my program, I need to show images belonging to a folder (JPG files). To accomplish that, I have this code:
private ArrayList<ImageGalleryItem> getData() {
final ArrayList<ImageGalleryItem> imageItems = new ArrayList<>();
try {
Intent intent = getIntent();
String imagesFolder = intent.getStringExtra("SourceFolder");
String questionId = Integer.toString(intent.getIntExtra("QuestionId", 0));
File file = new File(imagesFolder);
if (file.exists() && file.isDirectory()) {
File[] files = file.listFiles();
for (File f : files) {
try {
if (f.getName().startsWith(questionId + "_")) {
Bitmap bitmap = BitmapFactory.decodeFile(f.getPath());
imageItems.add(new ImageGalleryItem(bitmap, f.getName()));
}
}
catch (Exception e)
{
Log.e("TDC#", e.getMessage());
}
}
}
}
catch (Exception e)
{
Log.e("TDC#", e.getMessage());
}
return imageItems;
}
The instruction "BitmapFactory.decodeFile(f.getPath());" creates the bitmap files for all files but for one of them. When that code is reached for the conflicting file, decodeFile throws an exception, but the stranger thing is that the exception is not catched by the try catch block.
When debugging using F7, the exception is thrown in BitmapFactory.java, in the throw shown in this code:
try {
bm = nativeDecodeByteArray(data, offset, length, opts);
if (bm == null && opts != null && opts.inBitmap != null) {
throw new IllegalArgumentException("Problem decoding into existing bitmap");
}
setDensityFromOptions(bm, opts);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_GRAPHICS);
}
So the questions are, why the bitmap cannot be correctly decoded and why the exception is not catched.
If I browse for the file in mobile file manager, and open the file, it is shown correctly, so, there is no problem with the image format. Furthermore, this image was taken with the camera, the same as the other images that do decode correctly.
How to solve this problem or, is there an alternate way to do this?
I don't know why that happens. Try this:
Bitmap bitmap = BitmapFactory.decodeStream(new FileInputStream(f));
Related
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.
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;
}
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.
Is there a way to read the orientation and rotate if needed?
I want the user to take a picture by the camera.
After that I check if the orientation is portrait. If not, rotate the Image and save it in internal storage for later use. (I have to store it in internal storage, for the case that the user deletes the image in gallery).
So here is my way to rotate the image:
int orientation = 0;
try {
ExifInterface exif = new ExifInterface(mCurrentPhotoPath);
orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION,ExifInterface.ORIENTATION_NORMAL);
} catch (IOException e) {
e.printStackTrace();
}
Matrix matrix = new Matrix();
matrix.postRotate(orientation);
Bitmap decodeFile = BitmapFactory.decodeFile(mCurrentPhotoPath);
Bitmap rotatedBitmap = Bitmap.createBitmap(decodeFile,0,0,decodeFile.getWidth(),decodeFile.getHeight(),matrix,true);
FileOutputStream out = null;
File pictureDir = new File(getFilesDir() + "/images/");
randomUUID = UUID.randomUUID().toString();
File file = new File(pictureDir.getAbsolutePath(), randomUUID + ".jpg");
try {
imagePath = file.getAbsolutePath();
out = new FileOutputStream(file);
rotatedBitmap.compress(Bitmap.CompressFormat.JPEG,100,out);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (out != null) {
out.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
At line Bitmap decodeFile = BitmapFactory.decodeFile(mCurrentPhotoPath), I get on old devices an OOM.
I tried to use BitmapFactory.Options and inJustDecodeBounds=true but the rotatedImage is null.
So how to just read the orientation and rotate the image while saving it in internal storage?
I've read many posts in stackoverflow but none of them had the same case.
Is it possible without loading it in memory?
Thank you for any help!
Kind Regards,
raymondis
I wrote the code loader images from assets folder following the example code
my code
I don't know why load *.png not work. JPG work.
JPG work
Bitmap bitmap = decodeStreamFromAssets("test.jpg", 64, 64);
if(bitmap != null){
imageViewTest.setImageBitmap(bitmap);
}
else {
Logs.e("error");
}
PNG not work ( is error )
Bitmap bitmap = decodeStreamFromAssets("test.png", 64, 64);
if(bitmap != null){
imageViewTest.setImageBitmap(bitmap);
}
else {
Logs.e("error");
}
There are two ways by which you can load image from assets folder.
Solution 1:
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.test, null);
imageViewTest.setImageBitmap(bitmap);
Solution 2:
InputStream ins = null;
try {
ins = getAssets().open("test.png");
Bitmap bitmap = BitmapFactory.decodeStream(ins);
imageViewTest.setImageBitmap(bitmap);
} catch (final IOException e) {
e.printStackTrace();
} finally {
if (ins != null)
try {
ins.close();
} catch (IOException e) { }
}
I suggest to use second one because good performance.
Running both functions 50 times to load a small PNG file (230*230) on Nexus
Galaxy running Android 4.2.2:
decodeResource: 1793ms
decodeStream: 188ms
where the decodeStreamFromAssets?
InputStream is = getAssets().open("ic_launcher.png");
Bitmap bitmap = BitmapFactory.decodeStream(is);
it work!