0

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()
    }

enter image description here

Rahul
  • 3,293
  • 2
  • 31
  • 43
Mike
  • 125
  • 1
  • 12
  • 1
    Please tell why and how you mess around with exif. What is your intention? – blackapps Jun 25 '21 at 15:13
  • 1
    And tell what uriImageCaptureIntent has to do with this all. You confuse us wirh it – blackapps Jun 25 '21 at 15:15
  • Hi @blackapps , thanks for your reply. I have amended the code to make it clearer. To answer your questions: uriImageCaptureIntent was in the code to check if the photo was taken with an Intent from a third-party app. In this case the storing would need to be handled differently. But I have removed it, because not relevant. The saveExif method adds exif data to the images, I have added the code). Thanks for your help! – Mike Jun 29 '21 at 16:31

1 Answers1

1

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.

user165881
  • 106
  • 2
  • 1
    Hi, thanks for your answer. I omitted the compression, width/height and pixel dimensions metadata and after a few tests, it seems to work and no corruption occurs anymore. While I have not 100% certainty that the issue has been solved (need to do further test), it really seems that this Exif metadata was the cause for corruption. Thanks! – Mike Jul 09 '21 at 08:22
  • @MichaelK That’s great! – user165881 Jul 09 '21 at 13:37
  • After further testing and experimenting, I can say that the corruptions have become less frequent (in my opinion), however, still occur occasionally. So I could not resolve the issue completely. If there is anyone out there with further suggestions, I would appreciate your help! Thanks! – Mike Oct 20 '21 at 14:15