1

I am using Android's default camera to capture my intent. The images that come out are of really good quality and I cannot seem to find a way to lower the quality of the images.

Is that even possible without implementing a custom Camera ?

Is it possible to set like size limit of maximum 2MB or something like that? Or just take the image in the lowest quality possible as the images in my application do not need to be of good quality.

public class ImageCaptureIntent {
    public interface ImageCaptureResultListener {
        void onImageCaptured(File image);

        void onImageCaptureError(Exception exception);
    }

    static final int IMAGE_CAPTURE_REQUEST = 1;

    private enum BundleKeys {
        IMAGE_FILE
    }

    private File imageFile;

    public void onSaveInstanceState(@NonNull Bundle outState) {
        if (imageFile != null) {
            outState.putString(BundleKeys.IMAGE_FILE.name(), imageFile.getAbsolutePath());
        }
    }

    public void onRestoreInstanceState(@NonNull Bundle savedInstanceState) {
        if (savedInstanceState.containsKey(BundleKeys.IMAGE_FILE.name())) {
            imageFile = new File(savedInstanceState.getString(BundleKeys.IMAGE_FILE.name()));
        }
    }

    private static File createTempFile(File directory) throws IOException {
        String timestamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
        String filePrefix = "IMG_" + timestamp + "_";

        File file = File.createTempFile(filePrefix,".jpg", directory);
        if (file == null) {
            throw new IOException("Could not create a temp file");
        }

        return file;
    }

    public boolean initiateImageCapture(ImageCaptureResultListener listener, Activity activity, File directory) {
        if (listener == null) {
            return false;
        }

        Intent captureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);

        if (captureIntent.resolveActivity(activity.getPackageManager()) == null) {
            listener.onImageCaptureError(new ActivityNotFoundException("No app for ACTION_IMAGE_CAPTURE"));
            return false;
        }

        try {
            this.imageFile = createTempFile(directory);
        } catch (IOException e) {
            listener.onImageCaptureError(e);
            return false;
        }

        Uri imageUri = FileProvider.getUriForFile(activity,activity.getPackageName() + ".fileprovider", this.imageFile);
        captureIntent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);

        activity.startActivityForResult(captureIntent, IMAGE_CAPTURE_REQUEST);
        return true;
    }

    public boolean parseActivityResult(ImageCaptureResultListener listener, int requestCode, int resultCode, Intent data) {
        if (requestCode != IMAGE_CAPTURE_REQUEST) {
            return false;
        }

        if (listener == null) {
            return false;
        }

        if (resultCode == Activity.RESULT_OK) {
            listener.onImageCaptured(imageFile);
        } else {
            listener.onImageCaptureError(new RuntimeException("Image capturing was cancelled"));
        }

        return true;
    }
}

EDIT

I am not using Bitmaps in my application. I am taking images and then sending them to the backend. In perfect scenario I would like to capture low quality images and then save them to the phone if possible. If that is not possible then I would like to at least send the compressed images to backend.

Richard
  • 1,087
  • 18
  • 52

2 Answers2

1

When you get the path from intent then use it. CompressBitMap().execute(Uri.fromFile(File(mImagePath)))

inner class CompressBitMap : AsyncTask<Uri, Int, File>() {
    override fun doInBackground(vararg p0: Uri?): File? {
        val bitmap: Bitmap?
        val filename = "${Date().time}profile.png"
        val fileDir = File(Environment.getExternalStorageDirectory(), getString(R.string.app_name))
        if (!fileDir.exists()) {
            fileDir.mkdir()
        }

        val destPath = File(fileDir, filename)
        val outPutStream = FileOutputStream(destPath)
        try {
            bitmap = ScaledPicture(p0[0], activity.contentResolver).getBitmap(400, 400)
            bitmap.compress(Bitmap.CompressFormat.PNG, 100, outPutStream)
            outPutStream.flush()
            outPutStream.close()
        } catch (e: Exception) {
            e.printStackTrace()
        }
        return destPath
    }

    override fun onPostExecute(result: File?) {
        super.onPostExecute(result)
        result?.let {
            mImagePath = result.absolutePath
            setProfileImage(mImagePath, image_circle, null)
        }
    }
}

ScaledPicture and ImageScalingUtil are two important classes for reduce the size of image.

ScalePicture:=>

import android.content.ContentResolver
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Matrix
import android.graphics.RectF
import android.media.ExifInterface
import android.net.Uri
import com.silverskysoft.skysalon.imageUtils.ImageScalingUtils
import java.io.FileNotFoundException
import java.io.IOException
import java.io.InvalidObjectException


class ScaledPicture(private var uri: Uri?, private var resolver: ContentResolver) {
private var path: String? = null
private var orientation: Matrix? = null
private var storedHeight: Int = 0
private var storedWidth: Int = 0

@Throws(IOException::class)
private fun getInformation(): Boolean {
    /*if (getInformationFromMediaDatabase())
        return true;*/
    return getInformationFromFileSystem()
}

/* Support for file managers and dropbox */
@Throws(IOException::class)
private fun getInformationFromFileSystem(): Boolean {
    path = uri?.path
    if (path == null)
        return false
    val exif = ExifInterface(path.toString())
    val orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION,
            ExifInterface.ORIENTATION_NORMAL)
    this.orientation = Matrix()
    when (orientation) {
        ExifInterface.ORIENTATION_NORMAL -> {
        }
        ExifInterface.ORIENTATION_FLIP_HORIZONTAL -> this.orientation?.setScale(-1f, 1f)
        ExifInterface.ORIENTATION_ROTATE_180 -> this.orientation?.setRotate(180f)
        ExifInterface.ORIENTATION_FLIP_VERTICAL -> this.orientation?.setScale(1f, -1f)
        ExifInterface.ORIENTATION_TRANSPOSE -> {
            this.orientation?.setRotate(90f)
            this.orientation?.postScale(-1f, 1f)
        }
        ExifInterface.ORIENTATION_ROTATE_90 -> this.orientation?.setRotate(90f)
        ExifInterface.ORIENTATION_TRANSVERSE -> {
            this.orientation?.setRotate(-90f)
            this.orientation?.postScale(-1f, 1f)
        }
        ExifInterface.ORIENTATION_ROTATE_270 -> this.orientation?.setRotate(-90f)
    }/* Identity matrix */
    return true
}

@Throws(IOException::class)
private fun getStoredDimensions(): Boolean {
    val input = resolver.openInputStream(uri)
    val options = BitmapFactory.Options()
    options.inJustDecodeBounds = true
    BitmapFactory.decodeStream(resolver.openInputStream(uri), null, options)
    /* The input stream could be reset instead of closed and reopened if it were possible
       to reliably wrap the input stream on a buffered stream, but it's not possible because
       decodeStream() places an upper read limit of 1024 bytes for a reset to be made (it calls
       mark(1024) on the stream). */

    input?.close()
    if (options.outHeight <= 0 || options.outWidth <= 0)
        return false
    storedHeight = options.outHeight
    storedWidth = options.outWidth
    return true
}

@Throws(IOException::class)
fun getBitmap(reqWidth: Int, reqHeight: Int): Bitmap {
    val heightWidth = 1000
    if (!getInformation())
        throw FileNotFoundException()
    if (!getStoredDimensions())
        throw InvalidObjectException(null)
    val rect = RectF(0f, 0f, storedWidth.toFloat(), storedHeight.toFloat())
    orientation?.mapRect(rect)
    var width = rect.width().toInt()
    var height = rect.height().toInt()
    var subSample = 1
    while (width > heightWidth || height > heightWidth) {
        width /= 2
        height /= 2
        subSample *= 2
    }
    if (width == 0 || height == 0)
        throw InvalidObjectException(null)
    val options = BitmapFactory.Options()
    options.inSampleSize = subSample
    val subSampled = BitmapFactory.decodeStream(resolver.openInputStream(uri), null, options)
    val picture: Bitmap
    if (orientation?.isIdentity == false) {
        picture = Bitmap.createBitmap(subSampled, 0, 0, options.outWidth, options.outHeight,
                orientation, false)
        subSampled.recycle()
    } else

        picture = subSampled

    return ImageScalingUtils.decodeBitmap(picture, reqWidth, reqHeight, ImageScalingUtils.ScalingLogic.CROP)
 }

}

ImageScalingUtils:=>

import android.graphics.Bitmap
import android.graphics.BitmapFactory
import java.io.ByteArrayOutputStream

   /**
  * Created by Avinash on 7/8/19.
     * ImageScalingUtils responsible for compressing the bitmap    efficiently
*/
 object ImageScalingUtils {

/**
 * Utility function for decoding an image resource. The decoded bitmap will
 * be optimized for further scaling to the requested destination dimensions
 * and scaling logic.
 *
 * @param dstWidth Width of destination area
 * @param dstHeight Height of destination area
 * @param scalingLogic Logic to use to avoid image stretching
 * @return Decoded bitmap
 */


fun decodeBitmap(bm: Bitmap, dstWidth: Int, dstHeight: Int,
                 scalingLogic: ScalingLogic): Bitmap {
    val stream = ByteArrayOutputStream()
    bm.compress(Bitmap.CompressFormat.PNG, 100, stream)
    val byteArray = stream.toByteArray()
    val options = BitmapFactory.Options()
    options.inJustDecodeBounds = true
    BitmapFactory.decodeByteArray(byteArray, 0, byteArray.size, options)
    options.inJustDecodeBounds = false
    options.inSampleSize = calculateSampleSize(options.outWidth, options.outHeight, dstWidth,
            dstHeight, scalingLogic)
    return BitmapFactory.decodeByteArray(byteArray, 0, byteArray.size, options)
}

/**
 * ScalingLogic defines how scaling should be carried out if source and
 * destination image has different aspect ratio.
 *
 * CROP: Scales the image the minimum amount while making sure that at least
 * one of the two dimensions fit inside the requested destination area.
 * Parts of the source image will be cropped to realize this.
 *
 * FIT: Scales the image the minimum amount while making sure both
 * dimensions fit inside the requested destination area. The resulting
 * destination dimensions might be adjusted to a smaller size than
 * requested.
 */
enum class ScalingLogic {
    CROP, FIT
}

/**
 * Calculate optimal down-sampling factor given the dimensions of a source
 * image, the dimensions of a destination area and a scaling logic.
 *
 * @param srcWidth Width of source image
 * @param srcHeight Height of source image
 * @param dstWidth Width of destination area
 * @param dstHeight Height of destination area
 * @param scalingLogic Logic to use to avoid image stretching
 * @return Optimal down scaling sample size for decoding
 */
private fun calculateSampleSize(srcWidth: Int, srcHeight: Int, dstWidth: Int, dstHeight: Int,
                                scalingLogic: ScalingLogic): Int {
    if (scalingLogic == ScalingLogic.FIT) {
        val srcAspect = srcWidth.toFloat() / srcHeight.toFloat()
        val dstAspect = dstWidth.toFloat() / dstHeight.toFloat()

        return if (srcAspect > dstAspect) {
            srcWidth / dstWidth
        } else {
            srcHeight / dstHeight
        }
    } else {
        val srcAspect = srcWidth.toFloat() / srcHeight.toFloat()
        val dstAspect = dstWidth.toFloat() / dstHeight.toFloat()

        return if (srcAspect > dstAspect) {
            srcHeight / dstHeight
        } else {
            srcWidth / dstWidth
        }
    }
  }


}
Avinash
  • 867
  • 4
  • 11
  • I am not using `Bitmaps` in my application though. I am taking images and then sending them to the backend. Or does it work the way that I take the image, then make it into a `Bitmap` then resize it and then resave it? – Richard Jan 08 '20 at 12:08
  • So whats your issue?, you want to capture low quality images or sent to low quality image to backend. – Avinash Jan 08 '20 at 12:10
  • Well in perfect scenario I would like to capture low quality images and then save them to the phone if possible. If that is not possible then I would like to at least send the compressed images to backend. – Richard Jan 08 '20 at 12:11
  • if you want to capture low quality image then i think you need to create your custom camera , but its so long process, so in my case when i am getting path then i have compress the path of image through Bitmap and send to backend. – Avinash Jan 08 '20 at 12:14
  • Alright, thats what I also feared. Creating a custom camera is a pain. – Richard Jan 08 '20 at 12:31
  • i suggest you , Please implement my method , its working fine for my side. – Avinash Jan 08 '20 at 12:32
0

You should not use a file and fileprovider. Leave all empty.

Then you will get a Bitmap of a thumbnail in onActivityResult.

Bitmap bitmap = (Bitmap)data.getData();
blackapps
  • 8,011
  • 2
  • 11
  • 25