Jetpack Compose - Resizing Image after Image PIcker (ContentResolver Exception?) - android

I'm just trying to resize an image after the user launches the Image Picker from my app and chooses an image file on the local device (handling a remote image from Dropbox or something will be another battle) and while this has worked for me previously, now I'm getting this exception:
java.lang.RuntimeException: Failure delivering result ResultInfo{who=null, request=1105296364, result=-1, data=Intent { dat=content://com.android.externalstorage.documents/document/primary:Download/20170307_223207_cropped.jpg flg=0x1 }} to activity {my.app/MainActivity}: java.io.FileNotFoundException: No content provider: /document/primary:Download/20170307_223207_cropped.jpg
This occurs after the image is chosen in the Picker, because I'm running my "processing" code to locate the image, resize it, and copy it to a subfolder in the app's folder.
Like I said, this worked, but I'm not sure what's wrong now. I've tried this on the emulator as well as on my Galaxy S10 via USB debugging and it's the same result. The image is in the local storage "Download" folder on the emulator as well as my own device.
The URI looks weird (I mean the picture is just in the local storage "Download" folder) but I'm no URI expert so I assume it's fine, because that's what the Image Picker returns.
Here's the immediate code that's throwing the exception (specifically, the ImageDecoder.decodeBitmap call):
private fun copyFileToAppDataFolder(
context: Context,
imageTempPath: String
): String {
// ensure we are sent at least a non-empty path
if (imageTempPath.isEmpty()) {
return ""
}
val appDataFolder = "${context.dataDir.absolutePath}/images/firearms"
var filename = imageTempPath.substringAfterLast("/", "")
if (filename.isNullOrBlank()) {
filename = imageTempPath.substringAfterLast("%2F", "")
}
// couldn't parse filename from Uri; exit
if (filename.isNullOrBlank()) {
return ""
}
// get a bitmap of the selected image so it can be saved in an outputstream
var selectedImage: Bitmap? = null
selectedImage = if (Build.VERSION.SDK_INT <= 28) {
MediaStore.Images.Media.getBitmap(context.contentResolver, Uri.parse(imageTempPath))
} else {
ImageDecoder.decodeBitmap(ImageDecoder.createSource(context.contentResolver, Uri.parse(imageTempPath)))
}
if (selectedImage == null) {
return ""
}
val destinationImagePath: String = "$appDataFolder/$filename"
val destinationStream = FileOutputStream(destinationImagePath)
selectedImage.compress(Bitmap.CompressFormat.JPEG, 100, destinationStream)
destinationStream.close()
return destinationImagePath
}
That above function is called from my ViewModel (that processFirearmImage function is just calling the one above), where I send the result URI from the image Picker as well as the Application Context:
// this event is fired when the Image Picker returns
is AddEditFirearmEvent.AssignedPicture -> {
val resizedImagePath = ShotTrackerUtility.processFirearmImage(
event.applicationContext, // this is from LocalContext.current in Composable
event.value // result uri from image picker
)
_firearmImageUrl.value = resizedImagePath
}
I don't know, lol. I can't believe this is such a difficult thing but information for this sure seems sparse (for Compose especially, but even so) but I don't really consider launching an Image Picker and resizing the resulting image to be that weird. Any help would be great from you smart people.

Taking a step away from programming problems and coming back seems about the best bet sometimes, lol.
I came back tonight and within a couple minutes noticed that I was sending an improper Uri to the ImageDecoder.createSource method that was causing the exception. Basically this was happening:
val imageTempPath = theUriReturnedFromImagePicker.path ?: ""
ImageDecoder.decodeBitmap(ImageDecoder.createSource(context.contentResolver, Uri.parse(imageTempPath)))
And it should've been:
val imageUrl = theUriReturnedFromImagePicker
ImageDecoder.decodeBitmap(ImageDecoder.createSource(context.contentResolver, imageUri))
As I mentioned in the OP, this originally worked but I must've changed code around a bit (arguments I'm sending to various methods/classes, mostly). I'm also using that Uri.path part to get the filename of the image chosen so I overlooked and/or got confused to what I was sending to ImageDecoder.createSource.
Doh. Maybe someone else will do something dumb like me and this can help.

Related

Transfer Images using IPC app to app communication on the same device

I want to know if there is any efficient way to make app to app communication using IPC. I went to the guide of services that uses AIDL from the docs. But what I really want is to have an image to transfer between them.(Images are high quality) The only issue is android only let us use 1024 kB or even less to pass data through bundle. if you do more than that you'll end up with TransactionTooLargeException. Right now I'm compressing the image and passing base64 string between the two apps and it works fine. But sometimes some images can not be compressed at all. How can I do something like that. I'm compressing image using
bitmap.compress(imageformat=webp,quality=90,compress.nowrap)
the quality will reduce by 10 if I get TransactionTooLargeException. Any ideas on how to do something like that working on android?
But the thing is I don't want to open any other app. The image that I'm receiving will be processed and send a status to the image that was sent from the application. Like 'very cool image' 'very bad image'.in a string status.
Aidl link
Thanks.
var quality = 100
private fun sendImageToDevice(quality: Int, icon: Bitmap) {
Log.e(TAG, "quality of image is $quality ")
runOnUiThread {
Toast.makeText(
this#MainActivity,
"Image Quality $quality",
Toast.LENGTH_SHORT
)
.show()
}
if (quality > 0)
try {
mProcessImageService?.sendImageFormat(icon.toBase64(quality))
} catch (e: TransactionTooLargeException) {
e.message
this.quality = quality - 20
sendImageToDevice(this.quality, icon)
}
else {
}
}
fun Bitmap.toBase64(quality: Int): String {
val outputStream = ByteArrayOutputStream()
this.compress(Bitmap.CompressFormat.JPEG, quality, outputStream)
val base64String: String = Base64.encodeToString(outputStream.toByteArray(), Base64.NO_WRAP)
Log.d("MainActivity", "outputstream size is ${outputStream.size()}")
return base64String
}

Flutters resource load faster than native Android

I'm trying to convert image taken from resources to ByteArray which
will later be send through Socket. I've been measuring time of each of this conversion.
I've done it on both Flutter and native Android (Kotlin). All of the test were done on the same image which was about 1-2MB.
Flutter code :
sendMessage() async {
if (socket != null) {
Stopwatch start = Stopwatch()..start();
final imageBytes = await rootBundle.load('assets/images/stars.jpg');
final image = base64Encode(imageBytes.buffer.asUint8List(imageBytes.offsetInBytes, imageBytes.lengthInBytes));
print('Converting took ${start.elapsedMilliseconds}');
socket.emit("message", [image]);
}
}
Kotlin code:
private fun sendMessage() {
var message = ""
val thread = Thread(Runnable {
val start = SystemClock.elapsedRealtime()
val bitmap = BitmapFactory.decodeResource(resources, R.drawable.stars)
message = Base64.encodeToString(getBytesFromBitmap(bitmap), Base64.DEFAULT)
Log.d("Tag", "Converting time was : ${SystemClock.elapsedRealtime() - start}")
})
thread.start()
thread.join()
socket.emit("message", message)
}
private fun getBytesFromBitmap(bitmap: Bitmap): ByteArray? {
val stream = ByteArrayOutputStream()
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, stream)
return stream.toByteArray()
}
I've been actually expecting native code to be much much faster than Flutter's but thats not the case.. Conversion for Flutter takes about 50ms and its around 2000-3000ms for native.
I thought that Threading may be the case, so I've tried to run this conversion on background thread for native code but it didn't help.
Can you please tell me why is there such a different in time, and how I can implement it better in native code? Is there a way to omit casting to Bitmap etc.? Maybe this makes it so long.
EDIT. Added getBytesFromBitmap function
the difference you see is that in flutter code you just read your data without any image decoding, while in kotlin you are first decoding to Bitmap and then you are compress()ing it back - if you want to speed it up simply get an InputStream by calling Resources#openRawResource and read your image resource without any decoding
It have something to do with the way you convert it to bytes... Can you please post your
getBytesFromBitmap func? Plus, the conversion in native code really should be done in background thread, please upload the your results in this case.

Android 4.4.2 crash during picking image

During developing application in Xamarin Android we encountered strange error. The pick image/video (whether its from camera or documents UI) application sometimes crashes with no error. Scenario is that we open application, make steps to the activity, where the images are chosen, and then open the camera or document UI by standard way:
public void ChooseMediaAfterTypeChose (bool photo, bool camera) {
try {
string title = "";
int id;
Intent iIntent;
if (camera) {
if (photo) {
iIntent = new Intent("android.media.action.IMAGE_CAPTURE");
id = Const.AND_pickImageID_camera;
App._file = new File (App._dir, "myPhoto.jpg");
iIntent.PutExtra (Android.Provider.MediaStore.ExtraOutput, Uri.FromFile (App._file));
} else {
iIntent = new Intent("android.media.action.VIDEO_CAPTURE");
id = Const.AND_pickVideoID_camera;
App._file = new File (App._dir, "myVideo.mp4");
iIntent.PutExtra (Android.Provider.MediaStore.ExtraOutput, Uri.FromFile (App._file));
}
} else {
iIntent = new Intent ();
iIntent.SetAction (Intent.ActionGetContent);
if (photo) {
iIntent.SetType ("image/jpg");
title = Static.mainData.currentTexts.ChoosePhoto;
id = Const.AND_pickImageID_galery;
} else {
iIntent.SetType ("video/mp4");
title = Static.mainData.currentTexts.ChooseVideo;
id = Const.AND_pickVideoID_galery;
}
}
if (camera) {
StartActivityForResult (iIntent, id);
} else {
StartActivityForResult (Intent.CreateChooser (iIntent, title), id);
}
} catch (Exception ex) {
ShowError (Static.mainData.currentTexts.MediaError);
W.L(ex.Message);
}
}
The chosen application crashes during image/video picking (E.g. during browsing images in gallery wthout picking any). When that happens we never get to any method in our code.
What we have:
When user don't pick photos, the application runs without any memory crash
Sometimes the picking proceedes without error.
We think that the issue is mainly on Android 4.4.2 (It happend to us only on devices with this android - on other android version devices we didn't encounter it)
What we tried:
We put all permissions to manifest (read/write external storage, hardware.camera etc.)
We delete almost all memory stored content to save up memory
All Activities are unload from memory when they leave the screen
Logcat says that every process connected (Application and the active application) just died with no trace to any error
Our question is if anyone stumbled to similar issue and how to handle it. The second question is if there is any way to find out whats happening.
EDIT:
App._dir code added.
App._dir = new Java.IO.File (
Environment.GetExternalStoragePublicDirectory (
Environment.DirectoryPictures), "Camera");
if (!App._dir.Exists ())
{
App._dir.Mkdirs( );
}

What is MINI_THUMB_MAGIC and how to use it?

Background
I've noticed a weird column for MediaStore.Images.ImageColumns called "MINI_THUMB_MAGIC" .
the documentation says just that :
The mini thumb id.
Type: INTEGER
Constant Value: "mini_thumb_magic"
The question
my guess is that this field is related to MediaStore.Images.Thumbnails .
Is it correct ? if not, what is this and how do you use it?
if it is correct , i have other questions related to it:
Is it a mini sized image of the original one? does it use the same aspect ratio or does it do center-cropping on it?
how come the size of "MICRO" is square (96 x 96) and the size of "MINI" is a non-square rectangle ( 512 x 384 ) ?
How do you use it? My guess is that it's done by using "THUMB_DATA", which is a blob, so you use it like this, but then what is the purpose of using "getThumbnail" if you already have this field?
does it get a rotated thumbnail in case the orientation value is not 0 ? meaning that if I wish to show it, I won't need to rotate the image?
Is it possible to do a query of the images together with their thumbnails? maybe using inner join?
Is it available for all Android devices and versions?
Why is it even called "magic" ? Is it because it's also available for videos (and for some reason doesn't exist for music, as it could be the album's cover photo, for example) ?
Check this file: https://github.com/android/platform_packages_providers_mediaprovider/blob/master/src/com/android/providers/media/MediaThumbRequest.java in the Android source code. This value is some magic number which allows to determine if the thumbnail is still valid. I didn't investigate that file further, but it should be no bit issue to dive deeper.
To your questions:
No, no mini-sized image
Well, I guess it's a definition by Google who want to have a square thumbnail for some lists, where only very small previews should be visible and where many items should fit on the screen and there's another thumbnail format where the images are bigger...
I don't know that, but according to Google's doc, one (THUMB_DATA) is only some raw byte array of the thumbnail (dunno in which format) and the other one (getThumbnail) retrieves a full-fledged bitmap object...
don't know
don't know
I guess so, as it's part of AOSP source code.
The word "magic" is often used for some kind of identifier. There are "magic packets" who can wake up a computer from sleep or shutdown over the network, there are magic numbers on hard disks, where some sectors (e.g. the MBR) has the hexadecimal values AA 55 on its last two byte positions, there are also magic numbers in image files which help software packages determine the image type (e.g. GIF files begin with GIF89a or GIF87a (ASCII), JPEG files begin with FF D8 hexadecimal) and there are many, many more examples. So, magic numbers are a very common term here :-)
According to the source code at the following URL, the Magic Number is the Id of the original image * a constant. That value is then used to check for a long int. If the int isn't as expected, it's considered out of sync with the image media.
http://grepcode.com/file/repo1.maven.org/maven2/org.robolectric/android-all/4.4_r1-robolectric-0/android/media/MiniThumbFile.java#MiniThumbFile.getMagic%28long%29
// Get the magic number for the specified id in the mini-thumb file.
// Returns 0 if the magic is not available.
public synchronized long getMagic(long id) {
// check the mini thumb file for the right data. Right is
// defined as having the right magic number at the offset
// reserved for this "id".
RandomAccessFile r = miniThumbDataFile();
if (r != null) {
long pos = id * BYTES_PER_MINTHUMB;
FileLock lock = null;
try {
mBuffer.clear();
mBuffer.limit(1 + 8);
lock = mChannel.lock(pos, 1 + 8, true);
// check that we can read the following 9 bytes
// (1 for the "status" and 8 for the long)
if (mChannel.read(mBuffer, pos) == 9) {
mBuffer.position(0);
if (mBuffer.get() == 1) {
return mBuffer.getLong();
}
}
} catch (IOException ex) {
Log.v(TAG, "Got exception checking file magic: ", ex);
} catch (RuntimeException ex) {
// Other NIO related exception like disk full, read only channel..etc
Log.e(TAG, "Got exception when reading magic, id = " + id +
", disk full or mount read-only? " + ex.getClass());
} finally {
try {
if (lock != null) lock.release();
}
catch (IOException ex) {
// ignore it.
}
}
}
return 0;
}
I got the runtime exception when trying to get the original Id of a thumbnail by looking up the thumbnail's path. (BTW, the disk isn't full and it's not read-only.)
It's a bit strange parameter...
While exploring the Gallery source code,
noticed that the value is being read from the cursor, but then is Never used:
#Override
protected BaseImage loadImageFromCursor(Cursor cursor) {
long id = cursor.getLong(INDEX_ID);
String dataPath = cursor.getString(INDEX_DATA_PATH);
long dateTaken = cursor.getLong(INDEX_DATE_TAKEN);
if (dateTaken == 0) {
dateTaken = cursor.getLong(INDEX_DATE_MODIFIED) * 1000;
}
// here they read it ====>>
long miniThumbMagic = cursor.getLong(INDEX_MINI_THUMB_MAGIC);
int orientation = cursor.getInt(INDEX_ORIENTATION);
String title = cursor.getString(INDEX_TITLE);
String mimeType = cursor.getString(INDEX_MIME_TYPE);
if (title == null || title.length() == 0) {
title = dataPath;
}
// and not use at all ==>>>
return new Image(this, mContentResolver, id, cursor.getPosition(),
contentUri(id), dataPath, mimeType, dateTaken, title,
orientation);
}
Maybe it was used on the previous APIs.
ref: https://android.googlesource.com/platform/packages/apps/Gallery/+/android-8.0.0_r12/src/com/android/camera/gallery/ImageList.java?autodive=0%2F%2F.
and videos list:
https://android.googlesource.com/platform/packages/apps/Gallery/+/android-8.0.0_r12/src/com/android/camera/gallery/VideoList.java?autodive=0%2F%2F

How to Save Full Resolution Image Back to CameraRoll (Android)

Device - Nexus One
OS - Android 2.3.4
Class - CameraRoll
Method - addBitmapData()
Error - [ErrorEvent type="error" bubbles=false
cancelable=false eventPhase=2 text="Error #2038: File I/O Error."
errorID=2038]
I'm trying to develop a photo app but am having problems saving the full sized version of the image back to the CameraRoll. This is very frustrating as I've only seen examples saving the stage to CameraRoll (which I can get to work).
Is there a limitation to saving back to CameraRoll?? When I try to load an image (2592 x 1944) and save it directly back to CameraRoll using addBitmapData(), I get the following error.
[ErrorEvent type="error" bubbles=false cancelable=false eventPhase=2 text="Error #2038: File I/O Error." errorID=2038]
Here's a code sample.
// class vars for CameraRoll and Loader
private var _cameraRoll:CameraRoll = new CameraRoll();
private var _loader:Loader = new Loader();
// launch _cameraRoll
private function launchCameraRoll(e:MouseEvent):void {
_cameraRoll.addEventListener(MediaEvent.SELECT, loadImg);
_cameraRoll.browseForImage();
}
// open selected image using _loader
private function loadImg(e:MediaEvent):void {
if (e.data.isAsync) {
_loader.contentLoaderInfo.addEventListener(Event.COMPLETE, saveImage);
_loader.loadFilePromise(e.data);
} else {
_loader.loadFilePromise(e.data);
saveImage();
}
}
// once loaded, save image immediately back to _cameraRoll
private function saveImage(e:Event = null):void {
_cameraRoll.addEventListener(ErrorEvent.ERROR, onError);
_cameraRoll.addEventListener(Event.COMPLETE, onComplete);
var bmd:BitmapData = new BitmapData(_loader.width, _loader.height);
bmd.draw(_loader);
_cameraRoll.addBitmapData(bmd);
}
// trace error
private function onError(e:ErrorEvent):void {
trace(e); // [ErrorEvent type="error" bubbles=false cancelable=false eventPhase=2 text="Error #2038: File I/O Error." errorID=2038]
}
// show complete status
private function onComplete(e:Event):void {
trace("complete");
}
I had the same problem (even with real small images).
I solved it by uncommenting this line in the <android><manifestAdditions> section of the [Application]-app.xml file:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
have you tried to save image in lower res? e.g. 1024x768? Maybe there is a limitation on the size that your device supports, I've found this or maybe the disk space is problem like mentioned here
best regards

Categories

Resources