Image cropper in Jetpack Compose?

Viewed 2598

I have searched everywhere but didn't got a single documentation about cropping images in Jetpack Compose
How to crop Image in Jetpack Compose?

3 Answers

You can actually just use those older Android libraries no problem.

I used this one: https://github.com/CanHub/Android-Image-Cropper

Project build.gradle:

allprojects {
    repositories {
        google()
        mavenCentral()
        maven(url = "https://jitpack.io")
    }
}

App build.gradle:

implementation("com.github.CanHub:Android-Image-Cropper:4.0.0")

Usage (CropImageContract is provided by the above):

@Composable
fun ImageSelectorAndCropper() {
    var imageUri by remember {
        mutableStateOf<Uri?>(null)
    }
    val context = LocalContext.current
    val bitmap =  remember {
        mutableStateOf<Bitmap?>(null)
    }

    val imageCropLauncher = rememberLauncherForActivityResult(CropImageContract()) { result ->
        if (result.isSuccessful) {
            // use the cropped image
            imageUri = result.uriContent
        } else {
            // an error occurred cropping
            val exception = result.error
        }
    }

    val imagePickerLauncher = rememberLauncherForActivityResult(contract = ActivityResultContracts.GetContent()) { uri: Uri? ->
        val cropOptions = CropImageContractOptions(uri, CropImageOptions())
        imageCropLauncher.launch(cropOptions)
    }

    if (imageUri != null) {
        if (Build.VERSION.SDK_INT < 28) {
            bitmap.value = MediaStore.Images.Media.getBitmap(context.contentResolver, imageUri)
        } else {
            val source = ImageDecoder.createSource(context.contentResolver, imageUri!!)
            bitmap.value = ImageDecoder.decodeBitmap(source)
        }
        Button(onClick = { imagePickerLauncher.launch("image/*") }) {
            Text("Pick image to crop")
        }
    }
}

Edit:

I also had to add this to the manifest so that the CropActivity picks up a theme

<activity android:name="com.canhub.cropper.CropImageActivity"
    android:theme="@style/Base.Theme.AppCompat"/>

Try this simple crop!

        Image(
            painter = painterResource(id = R.drawable.blue_bg),
            contentDescription = stringResource(R.string.background_image),
            contentScale = ContentScale.Crop,
            modifier = modifier
                .fillMaxWidth()
                .height(120.dp)
        )

It crops perfectly as per Height (this sample), as long as the height/width is < the Image size on the specific device.

I built one for Jetpack Compose that can crop with static, dynamic overlays with animations and custom shapes and many customization options.

It's in alpha with some bugs, and will optimize some functions and add free-hand crop. It would be ready for production in few days.

If you would like to test it out, help with issues, here is the link

https://github.com/SmartToolFactory/Compose-Cropper

enter image description here

@Composable
private fun MainContent(
    cropProperties: CropProperties,
    cropStyle: CropStyle,
    onSelectionPageMenuClicked: (SelectionPage) -> Unit
) {

    val imageBitmapLarge = ImageBitmap.imageResource(
        LocalContext.current.resources,
        R.drawable.landscape1
    )

    var imageBitmap by remember { mutableStateOf(imageBitmapLarge) }
    var croppedImage by remember { mutableStateOf<ImageBitmap?>(null) }


    var crop by remember { mutableStateOf(false) }
    var showDialog by remember { mutableStateOf(false) }
    var isCropping by remember { mutableStateOf(false) }

    Box(
        modifier = Modifier
            .fillMaxSize()
            .background(Color.DarkGray),
        contentAlignment = Alignment.Center
    ) {
        Column(modifier = Modifier.fillMaxSize()) {

            ImageCropper(
                modifier = Modifier.fillMaxWidth().weight(1f),
                imageBitmap = imageBitmap,
                contentDescription = "Image Cropper",
                cropStyle = cropStyle,
                cropProperties = cropProperties,
                crop = crop,
                onCropStart = {
                    isCropping = true
                }
            ) {
                croppedImage = it
                isCropping = false
                crop = false
                showDialog = true
            }
        }

        BottomAppBar(
            modifier = Modifier.align(Alignment.BottomStart),
            actions = {


                IconButton(
                    onClick = {
                        onSelectionPageMenuClicked(SelectionPage.Properties)
                    }
                ) {
                    Icon(
                        Icons.Filled.Settings,
                        contentDescription = "Settings",
                    )

                }
                IconButton(
                    onClick = {
                        onSelectionPageMenuClicked(SelectionPage.Style)
                    }
                ) {
                    Icon(Icons.Filled.Brush, contentDescription = "Style")
                }

                IconButton(
                    onClick = { crop = true }) {
                    Icon(Icons.Filled.Crop, contentDescription = "Crop Image")
                }
            },
            floatingActionButton = {
                ImageSelectionButton(
                    elevation = FloatingActionButtonDefaults.elevation(defaultElevation = 0.dp),
                    onImageSelected = { bitmap: ImageBitmap ->
                        imageBitmap = bitmap
                    }
                )
            }
        )

        if (isCropping) {
            CircularProgressIndicator()
        }
    }

    if (showDialog) {
        croppedImage?.let {
            ShowCroppedImageDialog(imageBitmap = it) {
                showDialog = !showDialog
                croppedImage = null
            }
        }
    }
}
Related