Android cropping image from camera or gallery

Canato
3 min readMay 9, 2022

This is a copy from theartofdev.com with updates.
So we can be sure the text is still available and the library is still used.
Android Image Cropper open-source library

In 2019 I needed to update a cropped library image my company used when I realise this was not updated for more than a year. Tried to contact the owner but with no success so I forked the project to make it better, fix bugs, and update to Kotlin etc

Requirements:

  • Pick an image from the camera or gallery using a single chooser.
  • Select a circular crop window in the image for the avatar.
  • Limit output avatar image to 500×500 pixels.
  • Efficient memory usage.

Obstacles:

  • Creating single chooser intent for the camera and gallery.
  • Android crop activity is limited, unreliable and is an internal API.
  • The image taken by the camera may be rotated.
  • A picked image can be large and cause memory issues if loaded in full resolution.

From the cropper library, fork the fork, add a circular crop support option inspired by another fork, fix some of the reported bugs and most importantly add support for loading images from Android URI that are received from camera or gallery activity result.

  • The same code can be used for camera and gallery sources.
  • Automatically rotate the image by EXIF data.
  • Use sampling and lower density to conserve memory usage.
  • When a cropped image is requested it will again use sampling of the original image to create a bitmap of the required size without loading the full image or lowering the quality of the cropped image.

Implementation

See the code below on how to create image source chooser Intent and use the Android Image Cropper library to crop the picked image.

  • The library uses an AlertDialog for source choice with the option to use IntentChooser, both provide camera and gallery.
private fun showIntentChooser() {
val ciIntentChooser = CropImageIntentChooser(
activity = this,
callback = object : CropImageIntentChooser.ResultCallback {
override fun onSuccess(uri: Uri?) {
onPickImageResult(uri)
}

override fun onCancelled() {
setResultCancel()
}
}
)
cropImageOptions.let { options ->
options.intentChooserTitle
?.takeIf { title -> title.isNotBlank() }
?.let { icTitle ->
ciIntentChooser.setIntentChooserTitle(icTitle)
}
options.intentChooserPriorityList
?.takeIf { appPriorityList -> appPriorityList.isNotEmpty() }
?.let { appsList ->
ciIntentChooser.setupPriorityAppsList(appsList)
}
val cameraUri: Uri? = if (options.imageSourceIncludeCamera) getTmpFileUri() else null
ciIntentChooser.showChooserIntent(
includeCamera = options.imageSourceIncludeCamera,
includeGallery = options.imageSourceIncludeGallery,
cameraImgUri = cameraUri
)
}
}
open fun showImageSourceDialog(openSource: (Source) -> Unit) {
AlertDialog.Builder(this)
.setCancelable(false)
.setTitle(R.string.pick_image_chooser_title)
.setItems(
arrayOf(
getString(R.string.pick_image_camera),
getString(R.string.pick_image_gallery),
)
) { _, position -> openSource(if (position == 0) Source.CAMERA else Source.GALLERY) }
.show()
}
  • When getting the image from the gallery the contract returns the URI, when getting it from the camera it is necessary to provide the URI where it will be saved. To create the URI we have getUriForFile
private fun getTmpFileUri(): Uri {
val tmpFile = File.createTempFile("tmp_image_file", ".png", cacheDir).apply {
createNewFile()
deleteOnExit()
}

return getUriForFile(this, tmpFile)
}
fun setImageUriAsync(uri: Uri?) {
if (uri != null) {
val currentTask =
if (bitmapLoadingWorkerJob != null) bitmapLoadingWorkerJob!!.get() else null
currentTask?.cancel()
// either no existing task is working or we canceled it, need to load new URI
clearImageInt()
mCropOverlayView!!.initialCropWindowRect = null
bitmapLoadingWorkerJob =
WeakReference(BitmapLoadingWorkerJob(context, this, uri))
bitmapLoadingWorkerJob!!.get()!!.start()
setProgressBarVisibility()
}
}
  • The image will be sampled to the size of the device screen with a lower density.
  • When the user finishes cropping he/she will click on the “crop” button.
  • Retrieve the cropped image using getCroppedImage(500, 500)
  • We will use BitmapRegionDecoder to load only the cropped region
  • The image will be sampled to the requested size.

Code

For devices under OS 10 (SDK 29), we need to add external storage permissions to the manifest as some sources (DropBox, OneDrive, etc.) use external storage to return the image resulting in

java.io.FileNotFoundException… open failed: EACCES (Permission denied)

<uses-permissionandroid:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

Check the library

--

--

Canato

Android Developer, RPG players, DJ and producer and ex- many stuffs, let's have a coffee