Android-Image-Cropper icon indicating copy to clipboard operation
Android-Image-Cropper copied to clipboard

How to use it inside Compose?

Open Franky4242 opened this issue 9 months ago • 5 comments

I have tried : @Composable fun ComposableCropImageView(position: Int, setCropImageView: (CropImageView) -> Unit, force16_9: Boolean){ // Image Cropper fill the remaining available height AndroidView( factory = { context -> Log.d(TAG, "in factory") val uri = getPhotoUri(position) var anaBitmap = getAnaBitmap(uri) if (isHalfWidth){ anaBitmap = anaBitmap.scale(anaBitmap.width * 2, anaBitmap.height, false) } Log.d(TAG, "HERE") CropImageView(context).apply { layoutParams = ViewGroup.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT ) setImageBitmap(anaBitmap) setOnCropWindowChangedListener { onCropWindowChangedCallback(cropRect) } setCropImageView(this) // Store the instance Log.d(TAG, "end factory") }

        },
        modifier = Modifier
            .fillMaxSize(),
        update = {cropImageView ->
            Log.d(TAG, "start update")
            if (force16_9) {
                cropImageView.setAspectRatio(16, 9)
                cropImageView.setFixedAspectRatio(true)
                sharedViewModel.setCropAspectRatio16x9(true)
            }
            else {
                cropImageView.setFixedAspectRatio(false)
                sharedViewModel.setCropAspectRatio16x9(false)
            }
            Log.d(TAG, "end update")
        }
    )
}

but adding this Composable to my screen makes a timeout while composition. and then a crash and a tombstone file.

Franky4242 avatar May 08 '25 14:05 Franky4242

@Franky4242 Did you figure out how to use this in Compose? I can use the launcher. But I don't see the crop button and I'm not sure how to add a button to crop the photo?

`@Composable fun ImageCropLauncher( onCropResult: (Uri?) -> Unit ) { val cropImage = rememberLauncherForActivityResult(CropImageContract()) { result -> if (result.isSuccessful) { onCropResult(result.uriContent) } else { onCropResult(null) } }

Button(onClick = {
    val cropOptions = CropImageOptions(
        fixAspectRatio = true,
        aspectRatioX = 1,
        aspectRatioY = 1
    )
    val options = CropImageContractOptions(
        uri = null,
        cropImageOptions = cropOptions,
    )
    cropImage.launch(options)

}) {
    Text(text = "select_image")
}

}`

brian-smith723 avatar Jul 23 '25 11:07 brian-smith723

Hello Brian,

I have redeveloped a code with the functions I need for my app in Compose. If you are interested I can share it with you.

Kind regards

Frank

De : brian-smith723 @.> Envoyé : mercredi 23 juillet 2025 13:19 À : CanHub/Android-Image-Cropper @.> Cc : DERVILLE Frank INNOV/M-D @.>; Mention @.> Objet : Re: [CanHub/Android-Image-Cropper] How to use it inside Compose? (Issue #672)

CAUTION : This email originated outside the company. Do not click on any links or open attachments unless you are expecting them from the sender. ATTENTION : Cet e-mail provient de l'extérieur de l'entreprise. Ne cliquez pas sur les liens ou n'ouvrez pas les pièces jointes à moins de connaitre l'expéditeur.

[Image supprimée par l'expéditeur.]brian-smith723 left a comment (CanHub/Android-Image-Cropper#672)https://github.com/CanHub/Android-Image-Cropper/issues/672#issuecomment-3107169207

@Franky4242https://github.com/Franky4242 Did you figure out how to use this in Compose? I can use the launcher. But I don't see the crop button and I'm not sure how to add a button to crop the photo?

@.*** fun ImageCropLauncher( onCropResult: (Uri?) -> Unit ) { val cropImage = rememberLauncherForActivityResult(CropImageContract()) { result -> if (result.isSuccessful) { onCropResult(result.uriContent) } else { onCropResult(null) } }

Button(onClick = {

val cropOptions = CropImageOptions(

    fixAspectRatio = true,

    aspectRatioX = 1,

    aspectRatioY = 1

)

val options = CropImageContractOptions(

    uri = null,

    cropImageOptions = cropOptions,

)

cropImage.launch(options)

}) {

Text(text = "select_image")

}

}`

Reply to this email directly, view it on GitHubhttps://github.com/CanHub/Android-Image-Cropper/issues/672#issuecomment-3107169207, or unsubscribehttps://github.com/notifications/unsubscribe-auth/AVQWLSA4SQX7I2J2LWVZZ7T3J5VSFAVCNFSM6AAAAAB4WXWYO2VHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZTCMBXGE3DSMRQG4. You are receiving this because you were mentioned.Message ID: @.***>


Ce message et ses pieces jointes peuvent contenir des informations confidentielles ou privilegiees et ne doivent donc pas etre diffuses, exploites ou copies sans autorisation. Si vous avez recu ce message par erreur, veuillez le signaler a l'expediteur et le detruire ainsi que les pieces jointes. Les messages electroniques etant susceptibles d'alteration, Orange decline toute responsabilite si ce message a ete altere, deforme ou falsifie. Merci.

This message and its attachments may contain confidential or privileged information that may be protected by law; they should not be distributed, used or copied without authorisation. If you have received this email in error, please notify the sender and delete this message and its attachments. As emails may be altered, Orange is not liable for messages that have been modified, changed or falsified. Thank you.

Franky4242 avatar Jul 23 '25 12:07 Franky4242

@Franky4242 Can you share your code? That would be very helpful.

brian-smith723 avatar Jul 23 '25 12:07 brian-smith723

Hello Will do as soon as I can. I hope today. Kr

Frank

De : brian-smith723 @.> Envoyé : mercredi 23 juillet 2025 14:33 À : CanHub/Android-Image-Cropper @.> Cc : DERVILLE Frank INNOV/M-D @.>; Mention @.> Objet : Re: [CanHub/Android-Image-Cropper] How to use it inside Compose? (Issue #672)

CAUTION : This email originated outside the company. Do not click on any links or open attachments unless you are expecting them from the sender. ATTENTION : Cet e-mail provient de l'extérieur de l'entreprise. Ne cliquez pas sur les liens ou n'ouvrez pas les pièces jointes à moins de connaitre l'expéditeur.

[https://avatars.githubusercontent.com/u/9436849?s=20&v=4]brian-smith723 left a comment (CanHub/Android-Image-Cropper#672)https://github.com/CanHub/Android-Image-Cropper/issues/672#issuecomment-3107840980

@Franky4242https://github.com/Franky4242 Can you share your code? That would be very helpful.

Reply to this email directly, view it on GitHubhttps://github.com/CanHub/Android-Image-Cropper/issues/672#issuecomment-3107840980, or unsubscribehttps://github.com/notifications/unsubscribe-auth/AVQWLSAS7XD2IBEY2P6CE433J56HPAVCNFSM6AAAAAB4WXWYO2VHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZTCMBXHA2DAOJYGA. You are receiving this because you were mentioned.Message ID: @.@.>>


Ce message et ses pieces jointes peuvent contenir des informations confidentielles ou privilegiees et ne doivent donc pas etre diffuses, exploites ou copies sans autorisation. Si vous avez recu ce message par erreur, veuillez le signaler a l'expediteur et le detruire ainsi que les pieces jointes. Les messages electroniques etant susceptibles d'alteration, Orange decline toute responsabilite si ce message a ete altere, deforme ou falsifie. Merci.

This message and its attachments may contain confidential or privileged information that may be protected by law; they should not be distributed, used or copied without authorisation. If you have received this email in error, please notify the sender and delete this message and its attachments. As emails may be altered, Orange is not liable for messages that have been modified, changed or falsified. Thank you.

Franky4242 avatar Jul 24 '25 07:07 Franky4242


import android.content.Context
import android.graphics.Bitmap
import android.net.Uri
import android.widget.FrameLayout
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.Stable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.viewinterop.AndroidView
import androidx.core.net.toUri
import com.canhub.cropper.CropImageView
import com.canhub.cropper.CropImageView.RequestSizeOptions
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext

@Composable
fun rememberImageCropperState(uri: String? = null): ImageCropperState {
    val state = remember { ImageCropperState() }
    if (uri != null) {
        LaunchedEffect(uri) {
            state.load(uri.toUri())
        }

        LaunchedEffect(
            state.aspectRatio,
            state.isAutoZoomEnabled,
            state.cropShape,
            state.guidelines
        ) {
            state.applyConfig()
        }
    }
    return state
}

@Stable
@Suppress("unused")
class ImageCropperState() {
    internal var cropper by mutableStateOf<CropImageView?>(null)
        private set

    private var uri: Uri = Uri.EMPTY

    /**
     * Configs
     */
    var aspectRatio by mutableStateOf(1 to 1)
    var isAutoZoomEnabled by mutableStateOf(true)
    var isFixAspectRatio by mutableStateOf(true)
    var cropShape by mutableStateOf(CropImageView.CropShape.RECTANGLE)
    var guidelines by mutableStateOf(CropImageView.Guidelines.ON)

    fun load(imageUri: Uri) {
        uri = imageUri

        if (cropper?.imageUri != imageUri) {
            cropper?.setImageUriAsync(imageUri)
        }
    }

    suspend fun crop(
        context: Context,
        saveUri: Uri,
        reqWidth: Int = 0,
        reqHeight: Int = 0,
        format: Bitmap.CompressFormat = Bitmap.CompressFormat.PNG,
        quality: Int = 90,
        options: RequestSizeOptions = RequestSizeOptions.RESIZE_INSIDE,
    ): Unit = withContext(Dispatchers.IO) {
        val bitmap = cropper?.getCroppedImage(reqWidth, reqHeight, options)

        bitmap?.let { bmp ->
            context.contentResolver.openOutputStream(saveUri)?.use { out ->
                bmp.compress(format, quality, out)
            }
        }
    }

    internal fun applyConfig() {
        cropper?.setAspectRatio(aspectRatio.first, aspectRatio.second)
        cropper?.setFixedAspectRatio(isFixAspectRatio)
        cropper?.isAutoZoomEnabled = isAutoZoomEnabled
        cropper?.cropShape = cropShape
        cropper?.guidelines = guidelines
    }

    internal fun viewFactory(context: Context): FrameLayout {
        val layout = FrameLayout(context)
        val cropperView = CropImageView(context)
        layout.addView(
            cropperView, FrameLayout.LayoutParams(
                FrameLayout.LayoutParams.MATCH_PARENT,
                FrameLayout.LayoutParams.MATCH_PARENT,
            )
        )
        cropper = cropperView
        return layout
    }

    internal fun viewUpdate(view: FrameLayout) {
        load(uri)
        applyConfig()
    }

    internal fun viewRelease(view: FrameLayout) {
        cropper?.clearImage()
        cropper = null
    }
}


@Composable
fun ImageCropper(
    state: ImageCropperState,
    modifier: Modifier = Modifier,
) {
    Box(modifier = modifier) {
        AndroidView(
            modifier = Modifier
                .fillMaxSize()
                .background(Color.Black),
            factory = state::viewFactory,
            update = state::viewUpdate,
            onRelease = state::viewRelease
        )
    }
}

Usage


    Column(modifier = Modifier.fillMaxSize()) {
        val context = LocalContext.current
        val cropperState = rememberImageCropperState(state.uri)
        val scope = rememberCoroutineScope()

        ImageCropper(
            modifier = Modifier
                .fillMaxWidth()
                .weight(1f),
            state = cropperState
        )

        Button(
            onClick = {
                scope.launch {
                    cropperState.crop(context, "save-path".toUri())
                }
            }
        ) { Text(text = "Crop") }
    }

xiaoyvyv avatar Aug 13 '25 19:08 xiaoyvyv