I am trying to save a picture taken from the camera and making it appear in the device's gallery.
It's working well for Android Q, but for P the images are not showing up.
I tried to follow the Documentation as closely as I could
Manifest has:
android.permission.WRITE_EXTERNAL_STORAGE
I'm not getting any crashes or catches, I just don't know why it is not showing up in the gallery, can anyone please point me in the right direction to fix this? Much appreciated.
This method is called from the onActivityResult() when taking a picture from the camera.
private fun saveImageFromCamera(
activity: Activity,
imageView: ImageButton,
photoFile2: File?,
uriPath: (String?) -> Unit
) {
try {
// If on Android 10+ save to Pictures folder and private folder
val savedUri = Uri.fromFile(photoFile2)
val bitmap = getBitmap(activity, savedUri)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
if (bitmap != null) {
try {
saveImage(activity, savedUri, photoFile2!!.name)
} catch (e: Exception) {
e.printStackTrace()
}
}
} else {
if (bitmap != null) {
try {
if (PermissionsHelper.checkExternalStorageAccess(activity)) {
val newFile = createImageFile(activity)
addBitmapToFile(newFile, bitmap)
galleryAddPic(activity)
}
} catch (e: IOException) {
e.printStackTrace()
}
}
}
imageView.load(savedUri)
} catch (e: Exception) {
Toast.makeText(activity, "Failed to load", Toast.LENGTH_SHORT).show()
}
}
Function that writes a bitmap into destination file
fun addBitmapToFile(destinationFile: File, bitmap: Bitmap) {
val bos = ByteArrayOutputStream()
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, bos)
val bitmapData = bos.toByteArray()
//write the bytes in file
val fos = FileOutputStream(destinationFile)
fos.write(bitmapData)
fos.flush()
fos.close()
}
Creating a file to store the image in, using getExternalFilesDir.
#Throws(IOException::class)
fun createImageFile(activity: Activity): File {
// Create an image file name
val timeStamp: String = SimpleDateFormat(FILENAME, Locale.US).format(Date())
val storageDir: File = activity.getExternalFilesDir(Environment.DIRECTORY_PICTURES)!!
return File.createTempFile(
"JPEG_${timeStamp}_", /* prefix */
".jpg", /* suffix */
storageDir /* directory */
).apply { currentPhotoPath = absolutePath }
}
I do get an error when creating a file at this line:
return File.createTempFile(
"JPEG_${timeStamp}_", /* prefix */
".jpg", /* suffix */
storageDir /* directory */
).apply { currentPhotoPath = absolutePath }
2020-07-23 12:01:40.906 14705-14705/app.app W/System.err: java.io.IOException: Not a directory 2020-07-23
12:01:40.906 14705-14705/app.app W/System.err: at
java.io.UnixFileSystem.createFileExclusively0(Native Method)
2020-07-23 12:01:40.906 14705-14705/app.app W/System.err:
at
java.io.UnixFileSystem.createFileExclusively(UnixFileSystem.java:280)
2020-07-23 12:01:40.906 14705-14705/app.app W/System.err:
at java.io.File.createNewFile(File.java:948) 2020-07-23 12:01:40.906
14705-14705/app.app W/System.err: at
java.io.File.createTempFile(File.java:1862) 2020-07-23 12:01:40.906
14705-14705/app.app W/System.err: at
app.app.helpers.CameraAndGalleryHelper$Companion.createImageFile(CameraAndGalleryHelper.kt:338)
Getting the bitmap from a Uri, I am able to populate an ImageView from this bitmap, so I know that's not the issue.
fun getBitmap(context: Context, imageUri: Uri): Bitmap? {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
ImageDecoder.decodeBitmap(
ImageDecoder.createSource(
context.contentResolver, imageUri
)
)
} else {
context.contentResolver.openInputStream(imageUri)?.use { inputStream ->
BitmapFactory.decodeStream(inputStream)
}
}
}
Here is the scanner so that the image shows up in the gallery
private fun galleryAddPic(activity: Activity) {
Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE).also { mediaScanIntent ->
val f = File(currentPhotoPath)
mediaScanIntent.data = Uri.fromFile(f)
activity.sendBroadcast(mediaScanIntent)
}
}
When using the code above, I can take pictures on devices Android 8 and up without any problems.
But when using a Samsung S6 on Android 7, the images do not show up in the device's gallery.
Android:requestLegacyExternalStorage="true" for AndroidManifest.xml
How about trying to describe? However, this option will also be removed in the next android12.
Related
I save a png image to external storage using this block of code for sdk<=28
/**
* save image with this method if the sdk is 28 or lower
*/
private fun saveImageSdk28(fileName: String){
//declar the output stream variable outside of try/catch so that it can always be closed
var imageOutputStream: FileOutputStream? = null
var outputImageFile = getFile(fileName)
if (!outputImageFile.exists()) {
outputImageFile.createNewFile()
}
try {
imageOutputStream = FileOutputStream(outputImageFile)
encryptedBitmap.compress(Bitmap.CompressFormat.PNG, 100, imageOutputStream)
} catch (e: IOException) {
e.printStackTrace()
Timber.i(e.toString())
} finally {
if (imageOutputStream != null) {
imageOutputStream.flush()
imageOutputStream.close()
}
}
}
/**
* returns file from fileName
*/
fun getFile(fileName: String): File{
//open, or create the directory where the image will be stored
var directory = File(
Environment.getExternalStorageDirectory().toString() + "/AppNameOutput/"
)
if (!directory.exists()) {
directory.mkdir()
}
//create the file
var file: File = File(directory.absolutePath, fileName)
return file
}
and this code for when the sdk>28
/**
* save image with this method if the sdk is 29 or higher
*/
#RequiresApi(Build.VERSION_CODES.Q)
private fun saveImageSdk29(fileName: String){
val imageCollection = MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
val contentValues = ContentValues().apply {
put(MediaStore.Images.Media.DISPLAY_NAME, "$fileName")
put(MediaStore.Images.Media.MIME_TYPE, "image/png")
put(MediaStore.Images.Media.WIDTH, encryptedBitmap.width)
put(MediaStore.Images.Media.HEIGHT, encryptedBitmap.height)
}
try{
val contentResolver = getApplication<Application>().contentResolver
contentResolver.insert(imageCollection, contentValues)?.also {uri->
contentResolver.openOutputStream(uri).use {outputStream ->
encryptedBitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream)
}
}
}catch (e: IOException){
e.printStackTrace()
}
}
The image sucsessfully saves on the users device and can be accesed through files, however, the user can't access these images through the gallery, or Images tab.
I solved it. Turns out you just need to wait a while and reboot the phone for the gallery to show your images.
I have a page in my application where the user select an image from the gallery and save it as part of needed details. The image will be sent to a server through API request using Ktor. I have tried to convert the image bitmap to file but I get "operation not permitted" exception, and tired to look for a solution but nothing worked. One of the solutions recommended to add use permission for storage in the Manifest:
It didn't work as well.
The code is too long but here the block where I need to convert and save the file:
fun bitmapToFile(bitmap: Bitmap, fileNameToSave: String): File? { // File name like "image.png"
//create a file to write bitmap data
var file: File? = null
return try {
file = File(Environment.DIRECTORY_PICTURES + File.separator + fileNameToSave)
file.createNewFile()
//Convert bitmap to byte array
val bos = ByteArrayOutputStream()
bitmap.compress(Bitmap.CompressFormat.PNG, 0, bos) // YOU can also save it in JPEG
val bitmapData = bos.toByteArray()
//write the bytes in file
val fos = FileOutputStream(file)
fos.write(bitmapData)
fos.flush()
fos.close()
file
} catch (e: Exception) {
Toast.makeText( context, e.message, Toast.LENGTH_LONG ).show()
e.printStackTrace()
file // it will return null
}
}
fun handleChangeImage() {
try{
imageUri?.let {
if (Build.VERSION.SDK_INT < 28) {
bitmap = MediaStore.Images.Media.getBitmap(context.contentResolver, it)
} else {
val source = ImageDecoder.createSource(context.contentResolver, it)
bitmap = ImageDecoder.decodeBitmap(source)
}
}
//fixme: file conversion throw operation denied error
val fileName = imageUri?.lastPathSegment.toString().replace(":", ".")
val fileInput = if (bitmap != null)
bitmapToFile(bitmap!!, fileName)
else null
if(fileInput == null)
throw Exception("Error")
assetImageInput.value = fileInput
} catch (e: FileNotFoundException) {
Toast.makeText( context, e.message, Toast.LENGTH_LONG ).show()
} catch (e: Exception) {
Toast.makeText( context, e.message, Toast.LENGTH_LONG ).show()
}
}
After converting the image it should be saved as "assetImageInput.value" and be sent through the API as a parameter.
I am facing issue with Bitmap image quality when getting a image file from internal storage and show on imageView. How to show image file with original quality using bitmap.
Here is my code
fun renderPdf(renderer: PdfRenderer, pagesBitmap: MutableList<Bitmap>, dir: File) {
try {
for (i in 0 until PAGE_COUNT) {
val document: PDDocument = PDDocument.load(dir)
val pdfRenderer = PDFRenderer(document)
val bim = pdfRenderer.renderImage(i, 3.5f, Bitmap.Config.ARGB_8888!!)
pagesBitmap.add(bim!!)
document.close()
// Save the render result to an image
val path: String =
Environment.getExternalStorageDirectory().toString() + "/.AOF/aof_$i.jpg"
val renderFile = File(path)
val fileOut = FileOutputStream(renderFile)
pagesBitmap[i].compress(Bitmap.CompressFormat.JPEG, 100, fileOut)
fileOut.close()
}
ivForm.pagesBitmap = pagesBitmapFiles
ivForm.displayPDFDocument()
renderer.close()
} catch (e: IOException) {
Log.e("PdfBox-Android-Sample", "Exception thrown while rendering file", e)
} finally {
callDocumentType()
}
}
I am developing an Android app that saves bitmaps as jpeg images to the external storage. It occasionally happens that the JPEGs get corrupt (see image below). I have realized that corruption (eventually) only occurs when saveExif() is called. If I comment out saveExif(), the corruption never happened. This means that it is caused by something related to EXIF and not the compression process.
I have analyzed the jpeg with software (Bad Peggy) that detected the image as corrupt due to a premature end of data segment.
Any idea how to fix it?
This is how I save the image initially:
lateinit var uri: Uri
val imageOutStream: OutputStream
val contentResolver = context.contentResolver
val mimeType = "image/jpeg"
val mediaContentUri = MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
val values = ContentValues().apply {
put(MediaStore.Images.Media.DISPLAY_NAME, fileName)
put(MediaStore.Images.Media.MIME_TYPE, mimeType)
put(MediaStore.Images.Media.RELATIVE_PATH, directory)
}
contentResolver.run {
uri = context.contentResolver.insert(mediaContentUri, values)
?: return
imageOutStream = openOutputStream(uri) ?: return
}
try {
imageOutStream.use { bitmap.compress(Bitmap.CompressFormat.JPEG, photoCompression, it) }
} catch (e: java.lang.Exception) {
e.printStackTrace()
} finally {
imageOutStream.close()
}
try {
context.contentResolver.openInputStream(uri).use {
val exif = ExifInterface(context.contentResolver.openFileDescriptor(uri, "rw")!!.fileDescriptor)
saveExif(exif, context) //method ads exif metadata to image
}
}catch (e: java.lang.Exception){
}
This is how I add Exif metadata after the JPEG has been stored:
private fun saveExif(exif: ExifInterface, context: Context){
if (referenceWithCaptionExif != "" && notesExif != "") {
exif.setAttribute(ExifInterface.TAG_USER_COMMENT, "$referenceWithCaptionExif | $notesExif")
} else {
exif.setAttribute(ExifInterface.TAG_USER_COMMENT, "$referenceWithCaptionExif$notesExif")
}
if (companyExif != "") {
exif.setAttribute(ExifInterface.TAG_CAMERA_OWNER_NAME, companyExif)
val yearForExif = SimpleDateFormat("yyyy",
Locale.getDefault()).format(Date())
exif.setAttribute(ExifInterface.TAG_COPYRIGHT, "Copyright (c) $companyExif $yearForExif")
}
if (projectExif != "") {
exif.setAttribute(ExifInterface.TAG_IMAGE_DESCRIPTION, projectExif)
}
exif.setAttribute(ExifInterface.TAG_MAKER_NOTE, "Project[$projectExif] Company[$companyExif] " +
"Notes[$notesExif] Reference[$referenceExif] ReferenceType[$referenceTypeExif] Coordinates[$coordinatesExif] " +
"CoordinateSystem[$coordinateSystemExif] Accuracy[$accuracyExif] Altitude[$altitudeExif] " +
"Date[$dateTimeExif] Address[$addressExif]")
exif.setAttribute(ExifInterface.TAG_ARTIST, "${android.os.Build.MANUFACTURER} ${android.os.Build.MODEL}")
exif.setAttribute(ExifInterface.TAG_SOFTWARE, context.resources.getString(R.string.app_name))
exif.setAttribute(ExifInterface.TAG_MAKE, (android.os.Build.MANUFACTURER).toString())
exif.setAttribute(ExifInterface.TAG_MODEL, (android.os.Build.MODEL).toString())
exif.setAttribute(ExifInterface.TAG_COMPRESSION, 7.toString())
exif.setAttribute(ExifInterface.TAG_IMAGE_WIDTH, "${bitmapToProcess.width} px")
exif.setAttribute(ExifInterface.TAG_IMAGE_LENGTH, "${bitmapToProcess.height} px")
exif.setAttribute(ExifInterface.TAG_PIXEL_X_DIMENSION, "${bitmapToProcess.width} px")
exif.setAttribute(ExifInterface.TAG_PIXEL_Y_DIMENSION, "${bitmapToProcess.height} px")
exif.setAttribute(ExifInterface.TAG_GPS_ALTITUDE, altitudeExif)
exif.setAttribute(ExifInterface.TAG_GPS_ALTITUDE_REF, 0.toString())
exif.setAltitude(altitudeMetricExif)
exif.setLatLong(latitudeWGS84Exif, longitudeWGS84Exif)
exif.setAttribute(ExifInterface.TAG_GPS_TIMESTAMP, timeGPSExif)
exif.setAttribute(ExifInterface.TAG_GPS_DATESTAMP, dateGPSExif)
exif.setAttribute(ExifInterface.TAG_GPS_PROCESSING_METHOD, "GPS")
exif.setAttribute(ExifInterface.TAG_DATETIME, dateTimeOriginalExif)
exif.setAttribute(ExifInterface.TAG_DATETIME_ORIGINAL, dateTimeOriginalExif)
exif.setAttribute(ExifInterface.TAG_DATETIME_DIGITIZED, dateTimeOriginalExif)
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.N){
exif.setAttribute(ExifInterface.TAG_OFFSET_TIME_DIGITIZED, SimpleDateFormat("XXX", Locale.getDefault()).format(Date()))
exif.setAttribute(ExifInterface.TAG_OFFSET_TIME_ORIGINAL, SimpleDateFormat("XXX", Locale.getDefault()).format(Date()))
exif.setAttribute(ExifInterface.TAG_OFFSET_TIME, SimpleDateFormat("XXX", Locale.getDefault()).format(Date()))
}
exif.saveAttributes()
}
You could comment out each piece of metadata, one at a time and see if there’s a specific one that is causing the corruption. There’s a lot of programming happening for some of them so I wonder if it has to do with an incorrect string type or something.
I'm trying to create an application where i am required to add or delete an image simultaneously from image view and external storage. While doing the same, when I try adding the new image into the imageview using Uri, the old image keeps getting added again.
Here is my code
if (resultCode == Activity.RESULT_OK && requestCode == IMAGE_PICK_CODE_GALLERY) {
var selectedImage = data?.data
try {
val bitmap = MediaStore.Images.Media.getBitmap(context?.contentResolver,selectedImage)
if(bitmap!=null) {
val imageURI: String = getImageUri(context!!, bitmap)
}
private fun getImageUri(context: Context, inImage: Bitmap): String {
var fOut: OutputStream?
var path: String? = null
var fileName: String? = abc
var file: File? = null
file = File(
Environment.getExternalStorageDirectory().toString() + File.separator + "myDirectory",
"$fileName"
)
if (file.exists())
{
file.getCanonicalFile().delete()
if (file.exists())
{
context?.deleteFile(file.getName())
}
file.delete()
}
file.createNewFile() //If file already exists will do nothing
fOut = FileOutputStream(file)
inImage.compress(Bitmap.CompressFormat.JPEG, 40, fOut)
Glide.with(this).load(file).into(imageView!!)
fOut.flush()
fOut.close()
// path = MediaStore.Images.Media.insertImage(context.contentResolver,file.absolutePath,file.getName(),null);
} catch (ex: Exception) {
ex.printStackTrace()
}
return file.toString()
}
Glide caches your images, so probably you are loading a cached version of the old image.
As suggested in Glide's docs you should add a signature to handle cache invalidation:
Glide.with(yourFragment)
.load(yourFileDataModel)
.signature(new ObjectKey(yourVersionMetadata))
.into(yourImageView);