A CMPImagePickNCrop is image selection & cropping library for Compose Multiplatform for Android and iOS.
Add the dependency to your build.gradle.kts file:
commonMain.dependencies {
implementation("network.chaintech:cmp-image-pick-n-crop:1.1.2")
}- Android : Include this at root level in your
AndroidManifest.xml:
<uses-feature android:name="android.hardware.camera"/>
<uses-feature android:name="android.hardware.camera.autofocus"/>
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.FLASHLIGHT"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="32" />- iOS : Add below key to the
Info.plistin your xcode project:
<key>NSCameraUsageDescription</key><string>$(PRODUCT_NAME) camera description.</string>
<key>NSPhotoLibraryUsageDescription</key><string>$(PRODUCT_NAME)photos description.</string>@Composable
fun CMPImagePickNCropDialog(
imageCropper: ImageCropper = rememberImageCropper(),
openImagePicker: Boolean,
cropEnable: Boolean = true,
showCameraOption: Boolean = true,
showGalleryOption: Boolean = true,
autoZoom: Boolean = true,
enableRotationOption: Boolean = true,
enabledFlipOption: Boolean = true,
shapes: List<ImageCropShape>? = DefaultCropShapes,
aspects: List<ImageAspectRatio>? = DefaultImageCropperAspectRatios,
imagePickerDialogStyle: ImagePickerDialogStyle = ImagePickerDialogStyle(),
defaultAspectRatio: ImageAspectRatio? = null,
imagePickerDialogHandler: (Boolean) -> Unit,
selectedImageCallback: (ImageBitmap) -> Unit,
selectedImageFileCallback: (SharedImage) -> Unit
)imageCropper: Manages the image cropping logic (default is rememberImageCropper()).openImagePicker: Controls whether the image picker dialog is open.cropEnable: Enables or disables the image cropping feature (default is true).showCameraOption: Displays the option to pick an image from the camera.showGalleryOption: Displays the option to pick an image from the gallery.autoZoom: Automatically zooms to fit the cropped region within view bounds.enableRotationOption: Show or hide rotation options.enabledFlipOption: Show or hide flip options.shapes: Specifies the list of cropping shapes (default is DefaultCropShapes).aspects: Defines the aspect ratios available for cropping (default is DefaultImageCropperAspectRatios).imagePickerDialogStyle: Styling options for the image picker dialog (default is ImagePickerDialogStyle()).defaultAspectRatio: Aspect ratio to be preselected when cropping starts (default is null).imagePickerDialogHandler: Handles the visibility of the image picker dialog.selectedImageCallback: Callback invoked with the cropped image as an ImageBitmap.selectedImageFileCallback: Callback invoked with the shared image object used for image compress.
@Composable
internal fun App() = AppTheme {
Surface(
modifier = Modifier.fillMaxSize(),
color = Color.White
) {
val imageCropper = rememberImageCropper()
var selectedImage by remember { mutableStateOf<ImageBitmap?>(null) }
var openImagePicker by remember { mutableStateOf(value = false) }
CMPImagePickNCropDialog(
imageCropper = imageCropper,
openImagePicker = openImagePicker,
defaultAspectRatio = ImageAspectRatio(16, 9),
imagePickerDialogStyle = ImagePickerDialogStyle(
title = "Choose from option",
txtCamera = "From Camera",
txtGallery = "From Gallery",
txtCameraColor = Color.DarkGray,
txtGalleryColor = Color.DarkGray,
cameraIconTint = Color.DarkGray,
galleryIconTint = Color.DarkGray,
backgroundColor = Color.White
),
autoZoom = true,
imagePickerDialogHandler = {
openImagePicker = it
},
selectedImageCallback = {
selectedImage = it
},
selectedImageFileCallback = { sharedImage ->
scope.launch {
val compressedFile = compressImage(
sharedImage = sharedImage,
targetFileSize = 200 * 1024 // In KB
)
println("Compressed File Path : $compressedFile")
}
}
)
Column(
modifier = Modifier
.fillMaxWidth()
.background(Color.White)
.safeContentPadding(),
horizontalAlignment = Alignment.CenterHorizontally
) {
selectedImage?.let {
Image(
bitmap = it,
contentDescription = null,
modifier = Modifier.weight(1f)
)
}
if (selectedImage == null)
Box(
contentAlignment = Alignment.Center,
modifier = Modifier.weight(1f)
) {
Text("No image selected !", color = Color.Black)
}
Button(
onClick = {
openImagePicker = true
},
) { Text("Choose Image") }
}
}
}- For Crop
val selectedBitmap = remember { mutableStateOf<ImageBitmap?>(null) }
val imageCropper = rememberImageCropper()
CMPImageCropDialog(imageCropper = imageCropper)
Button(
modifier = Modifier
.weight(2F)
.padding(start = 12.dp, end = 12.dp),
onClick = {
coroutineScope.launch {
selectedBitmap.value?.let { bitmap ->
when (val result = imageCropper.cropImage(bmp = bitmap)) {
ImageCropResult.Cancelled -> {
// Handle cancellation if needed
}
is ImageCropError -> {
// Handle error if needed
}
is ImageCropResult.Success -> {
selectedBitmap.value = result.bitmap
}
}
}
}
}
) {
Text(text = "CropImage")
}- ByteArray
If you want a bytearray then you can convert the ImageBitmap received in the callback using the ImageBitmap.toByteArray()
// function to convert ImageBitmap to ByteArray
fun ImageBitmap.toByteArray(
format: ImageFileFormat = ImageFileFormat.PNG,
quality: Float = 1.0f
): ByteArray?
// Usage
val imageBitmap = .. // your selected imageBitmap
val byteArray = imageBitmap.toByteArray() // converted byteArray- Image Compress
If you want to compress image then you can use the selectedImageFileCallback
selectedImageFileCallback = { sharedImage ->
scope.launch {
val compressedFile = compressImage(
sharedImage = sharedImage,
targetFileSize = 200 * 1024 // In KB
)
println("Compressed File Path : $compressedFile")
}
}-
For Demo Checkout This Class
-
Medium Article for detailed explaination.
Stay connected and keep up with our latest innovations! πΌ Let's innovate together!
Copyright 2023 Mobile Innovation Network
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
