2

I am using following helper class to handle sampling and rotation of the Camera Image.

object CaptureImageHelper {

/**
 * This method is responsible for solving the rotation issue if exist. Also scale the images to
 * 1024x1024 resolution
 *
 * @param context       The current context
 * @param selectedImage The Image URI
 * @return Bitmap image results
 * @throws IOException
 */
@Throws(IOException::class)
fun handleSamplingAndRotationBitmap(
    context: Context,
    selectedImage: Uri?,
    isFrontCamera: Boolean
): Bitmap? {
    val MAX_HEIGHT = 1024
    val MAX_WIDTH = 1024

    // First decode with inJustDecodeBounds=true to check dimensions
    val options = BitmapFactory.Options()
    options.inJustDecodeBounds = true
    var imageStream: InputStream = context.getContentResolver().openInputStream(selectedImage!!)!!
    BitmapFactory.decodeStream(imageStream, null, options)
    imageStream.close()

    // Calculate inSampleSize
    options.inSampleSize = calculateInSampleSize(options, MAX_WIDTH, MAX_HEIGHT)

    // Decode bitmap with inSampleSize set
    options.inJustDecodeBounds = false
    imageStream = context.getContentResolver().openInputStream(selectedImage!!)!!
    var img = BitmapFactory.decodeStream(imageStream, null, options)
    img = rotateImageIfRequired(img!!, selectedImage, isFrontCamera)
    return img
}

/**
 * Calculate an inSampleSize for use in a [BitmapFactory.Options] object when decoding
 * bitmaps using the decode* methods from [BitmapFactory]. This implementation calculates
 * the closest inSampleSize that will result in the final decoded bitmap having a width and
 * height equal to or larger than the requested width and height. This implementation does not
 * ensure a power of 2 is returned for inSampleSize which can be faster when decoding but
 * results in a larger bitmap which isn't as useful for caching purposes.
 *
 * @param options   An options object with out* params already populated (run through a decode*
 * method with inJustDecodeBounds==true
 * @param reqWidth  The requested width of the resulting bitmap
 * @param reqHeight The requested height of the resulting bitmap
 * @return The value to be used for inSampleSize
 */
private fun calculateInSampleSize(
    options: BitmapFactory.Options,
    reqWidth: Int, reqHeight: Int
): Int {
    // Raw height and width of image
    val height = options.outHeight
    val width = options.outWidth
    var inSampleSize = 1
    if (height > reqHeight || width > reqWidth) {

        // Calculate ratios of height and width to requested height and width
        val heightRatio =
            Math.round(height.toFloat() / reqHeight.toFloat())
        val widthRatio =
            Math.round(width.toFloat() / reqWidth.toFloat())

        // Choose the smallest ratio as inSampleSize value, this will guarantee a final image
        // with both dimensions larger than or equal to the requested height and width.
        inSampleSize = if (heightRatio < widthRatio) heightRatio else widthRatio

        // This offers some additional logic in case the image has a strange
        // aspect ratio. For example, a panorama may have a much larger
        // width than height. In these cases the total pixels might still
        // end up being too large to fit comfortably in memory, so we should
        // be more aggressive with sample down the image (=larger inSampleSize).
        val totalPixels = width * height.toFloat()

        // Anything more than 2x the requested pixels we'll sample down further
        val totalReqPixelsCap = reqWidth * reqHeight * 2.toFloat()
        while (totalPixels / (inSampleSize * inSampleSize) > totalReqPixelsCap) {
            inSampleSize++
        }
    }
    return inSampleSize
}

/**
 * Rotate an image if required.
 *
 * @param img           The image bitmap
 * @param selectedImage Image URI
 * @return The resulted Bitmap after manipulation
 */
@Throws(IOException::class)
private fun rotateImageIfRequired(
    img: Bitmap,
    selectedImage: Uri,
    isFrontCamera: Boolean
): Bitmap? {
    val ei = ExifInterface(selectedImage.path!!)
    val orientation: Int =
        ei.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL)
    return when (orientation) {
        ExifInterface.ORIENTATION_ROTATE_90 -> rotateImage(img, 90, isFrontCamera)
        ExifInterface.ORIENTATION_ROTATE_180 -> rotateImage(img, 180, isFrontCamera)
        ExifInterface.ORIENTATION_ROTATE_270 -> rotateImage(img, 270, isFrontCamera)
        else -> img
    }
}

private fun rotateImage(
    img: Bitmap,
    degree: Int,
    isFrontCamera: Boolean
): Bitmap? {
    val matrix = Matrix()
    if(isFrontCamera) {
        val matrixMirrorY = Matrix()
        val mirrorY = floatArrayOf(-1f, 0f, 0f, 0f, 1f, 0f, 0f, 0f, 1f)
        matrixMirrorY.setValues(mirrorY)
        matrix.postConcat(matrixMirrorY)
        matrix.preRotate(270f)
    } else {
        matrix.postRotate(degree.toFloat())
    }
    val rotatedImg =
        Bitmap.createBitmap(img, 0, 0, img.width, img.height, matrix, true)
    img.recycle()
    return rotatedImg
}
}

Calling helper class

val bitmap = CaptureImageHelper.handleSamplingAndRotationBitmap(requireContext(), Uri.fromFile(cameraImage!!), false)

The issue i am getting is random. Mostly if I get rotated image below statement

ei.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL)

returns ExifInterface.ORIENTATION_ROTATE_90 which is fine and code rotate that image correctly. But sometimes Image is rotated but it Exif getAttributeInt returns ExifInterface.ORIENTATION_NORMAL. Which i believe means there is no data of Exif/Orientation against this image and it returns default value.

Exit get Attribute method

    public int getAttributeInt(@NonNull String tag, int defaultValue) {
    if (tag == null) {
        throw new NullPointerException("tag shouldn't be null");
    }
    ExifAttribute exifAttribute = getExifAttribute(tag);
    if (exifAttribute == null) {
        return defaultValue;
    }

    try {
        return exifAttribute.getIntValue(mExifByteOrder);
    } catch (NumberFormatException e) {
        return defaultValue;
    }
}
Nouman Bhatti
  • 1,777
  • 4
  • 28
  • 55
  • Please tell the value of selectedImage.path!!. – blackapps Feb 18 '21 at 09:48
  • @blackapps I have debug and verify that image path is correct. /storage/emulated/0/Android/data/com.tech.appcustomer/files/Pictures/IMG_2021_02_18_14_50_19_322.jpg – Nouman Bhatti Feb 18 '21 at 09:53
  • A strange uri that delivers such a path. Where did you get it from? Wasn't it a content scheme uri? Please tell the value of that uri. – blackapps Feb 18 '21 at 09:55
  • yes it is the uri file:///storage/emulated/0/Android/data/com.tech.appcustomer/files/Pictures/IMG_2021_02_18_16_03_59_117.jpg – Nouman Bhatti Feb 18 '21 at 11:05
  • @blackapps also this uri is correct because i am showing the image on ImageView using same uri but that image is rotated – Nouman Bhatti Feb 18 '21 at 11:06
  • Your code does not distinguish between ExifInterface.ORIENTATION_NORMAL and yet another value. Are you logging the obtained value? Are not there more ORIENTATION_ROTATE_### values? Maybe minus? – blackapps Feb 18 '21 at 11:16
  • the issue is how do i distinguish between ExifInterface.ORIENTATION_NORMAL and rotated. Because getAttributeInt returns ORIENTATION_NORMAL even if image is rotated – Nouman Bhatti Feb 18 '21 at 12:06
  • I meant change ei.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL) to ei.getAttributeInt(ExifInterface.TAG_ORIENTATION, 3333) or something like that and then see what you get. – blackapps Feb 18 '21 at 12:25
  • obviously i'll get 3333 because currently it returns default value – Nouman Bhatti Feb 18 '21 at 12:27
  • `getAttributeInt returns ORIENTATION_NORMAL` Well you are not checking that in your code so how do you know? You are nowhere checking the return value to begin with. Well... not enough values.. – blackapps Feb 18 '21 at 12:27
  • ok i have changed code to ei.getAttributeInt(ExifInterface.TAG_ORIENTATION, 3333) and it returns 1 meaning ORIENTATION_NORMAL – Nouman Bhatti Feb 18 '21 at 12:34
  • I have updated my question and also added getAttributeInt method which is method of ExifInterface class – Nouman Bhatti Feb 18 '21 at 12:36
  • What i am missing in your when block is `ExifInterface.ORIENTATION_NORMAL-> img else .... unhandled value.. `And... do not put it as default value but take 333 or something else. – blackapps Feb 18 '21 at 12:42
  • If 1 is returned then that was valid exif info. I think you can do nothing then. Tested on how many devices? – blackapps Feb 18 '21 at 12:46
  • return exifAttribute.getIntValue(mExifByteOrder); Ok but what is mExifByteOrder ? – blackapps Feb 18 '21 at 13:10
  • cant debug getAttributeInt method. I have tested on Xiomi MiA2 – Nouman Bhatti Feb 18 '21 at 13:31
  • We still do not know who or which app created those images. If a camera app then try other camera apps. – blackapps Feb 18 '21 at 13:35

0 Answers0