diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 9350710..bda902b 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -8,6 +8,7 @@ compose-foundation = "1.5.4" compose-material3 = "1.1.2" androidx-activityCompose = "1.8.2" androidx-lifecycleViewmodelCompose = "2.7.0" +androidx-version = "1.7.1" nexus-publish = "2.0.0-rc-1" spotless = "6.25.0" ktlint = "1.0.1" @@ -49,6 +50,7 @@ androidx-exifinterface = { group = "androidx.exifinterface", name = "exifinterfa androidx-lifecycle-viewmodel-compose = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-compose", version.ref = "androidx-lifecycleViewmodelCompose" } paging-common = { module = "app.cash.paging:paging-common", version.ref = "multiplatform-paging" } paging-compose-common = { module = "app.cash.paging:paging-compose-common", version.ref = "multiplatform-paging" } +annotation = { module = "androidx.annotation:annotation", version.ref = "androidx-version" } [plugins] androidApplication = { id = "com.android.application", version.ref = "agp" } diff --git a/peekaboo-image-picker/build.gradle.kts b/peekaboo-image-picker/build.gradle.kts index 7053d97..6fbbfb0 100644 --- a/peekaboo-image-picker/build.gradle.kts +++ b/peekaboo-image-picker/build.gradle.kts @@ -39,6 +39,7 @@ kotlin { implementation(compose.runtime) implementation(compose.foundation) implementation(compose.material) + implementation(libs.annotation) } commonTest.dependencies { implementation(libs.kotlin.test) diff --git a/peekaboo-image-picker/src/androidMain/kotlin/com/preat/peekaboo/image/picker/ImagePickerLauncher.android.kt b/peekaboo-image-picker/src/androidMain/kotlin/com/preat/peekaboo/image/picker/ImagePickerLauncher.android.kt index 76d4688..3513c74 100644 --- a/peekaboo-image-picker/src/androidMain/kotlin/com/preat/peekaboo/image/picker/ImagePickerLauncher.android.kt +++ b/peekaboo-image-picker/src/androidMain/kotlin/com/preat/peekaboo/image/picker/ImagePickerLauncher.android.kt @@ -75,6 +75,7 @@ private fun pickSingleImage( width = resizeOptions.width, height = resizeOptions.height, resizeThresholdBytes = resizeOptions.resizeThresholdBytes, + compressionQuality = resizeOptions.compressionQuality, filterOptions = filterOptions, ) { resizedImage -> if (resizedImage != null) { @@ -129,6 +130,7 @@ private fun pickMultipleImages( width = resizeOptions.width, height = resizeOptions.height, resizeThresholdBytes = resizeOptions.resizeThresholdBytes, + compressionQuality = resizeOptions.compressionQuality, filterOptions = filterOptions, ) { resizedImage -> resizedImage?.let { diff --git a/peekaboo-image-picker/src/androidMain/kotlin/com/preat/peekaboo/image/picker/PeekabooImageResizer.kt b/peekaboo-image-picker/src/androidMain/kotlin/com/preat/peekaboo/image/picker/PeekabooImageResizer.kt index 638954f..57a88f3 100644 --- a/peekaboo-image-picker/src/androidMain/kotlin/com/preat/peekaboo/image/picker/PeekabooImageResizer.kt +++ b/peekaboo-image-picker/src/androidMain/kotlin/com/preat/peekaboo/image/picker/PeekabooImageResizer.kt @@ -25,6 +25,7 @@ import android.graphics.Matrix import android.graphics.Paint import android.net.Uri import android.provider.OpenableColumns +import androidx.annotation.FloatRange import androidx.exifinterface.media.ExifInterface import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -40,12 +41,14 @@ internal object PeekabooImageResizer { width: Int, height: Int, resizeThresholdBytes: Long, + @FloatRange(from = 0.0, to = 1.0) + compressionQuality: Double, filterOptions: FilterOptions, onResult: (ByteArray?) -> Unit, ) { coroutineScope.launch(Dispatchers.Default) { if (getImageSize(context, uri) > resizeThresholdBytes) { - val byteArray = resizeImage(context, uri, width, height, filterOptions) + val byteArray = resizeImage(context, uri, width, height, compressionQuality, filterOptions) withContext(Dispatchers.Main) { onResult(byteArray) } @@ -86,6 +89,8 @@ internal object PeekabooImageResizer { uri: Uri, width: Int, height: Int, + @FloatRange(from = 0.0, to = 1.0) + compression: Double, filterOptions: FilterOptions, ): ByteArray? { val resizeCacheKey = "${uri}_w${width}_h$height" @@ -127,7 +132,8 @@ internal object PeekabooImageResizer { val filteredBitmap = applyFilter(rotatedBitmap, filterOptions) ByteArrayOutputStream().use { byteArrayOutputStream -> - filteredBitmap.compress(Bitmap.CompressFormat.JPEG, 100, byteArrayOutputStream) + val validatedCompression = compression.coerceIn(0.0, 1.0) + filteredBitmap.compress(Bitmap.CompressFormat.JPEG, (100 * validatedCompression).toInt(), byteArrayOutputStream) val byteArray = byteArrayOutputStream.toByteArray() PeekabooBitmapCache.instance.put(filterCacheKey, filteredBitmap) return byteArray diff --git a/peekaboo-image-picker/src/commonMain/kotlin/com/preat/peekaboo/image/picker/ImagePickerLauncher.kt b/peekaboo-image-picker/src/commonMain/kotlin/com/preat/peekaboo/image/picker/ImagePickerLauncher.kt index 4dd8cf8..5dccb9a 100644 --- a/peekaboo-image-picker/src/commonMain/kotlin/com/preat/peekaboo/image/picker/ImagePickerLauncher.kt +++ b/peekaboo-image-picker/src/commonMain/kotlin/com/preat/peekaboo/image/picker/ImagePickerLauncher.kt @@ -15,6 +15,7 @@ */ package com.preat.peekaboo.image.picker +import androidx.annotation.FloatRange import androidx.compose.runtime.Composable import kotlinx.coroutines.CoroutineScope @@ -45,6 +46,8 @@ data class ResizeOptions( val width: Int = DEFAULT_RESIZE_IMAGE_WIDTH, val height: Int = DEFAULT_RESIZE_IMAGE_HEIGHT, val resizeThresholdBytes: Long = DEFAULT_RESIZE_THRESHOLD_BYTES, + @FloatRange(from = 0.0, to = 1.0) + val compressionQuality: Double = 1.0, ) sealed interface FilterOptions { diff --git a/peekaboo-image-picker/src/iosMain/kotlin/com/preat/peekaboo/image/picker/ImagePickerLauncher.ios.kt b/peekaboo-image-picker/src/iosMain/kotlin/com/preat/peekaboo/image/picker/ImagePickerLauncher.ios.kt index 228884a..bc95c46 100644 --- a/peekaboo-image-picker/src/iosMain/kotlin/com/preat/peekaboo/image/picker/ImagePickerLauncher.ios.kt +++ b/peekaboo-image-picker/src/iosMain/kotlin/com/preat/peekaboo/image/picker/ImagePickerLauncher.ios.kt @@ -15,6 +15,7 @@ */ package com.preat.peekaboo.image.picker +import androidx.annotation.FloatRange import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import kotlinx.cinterop.CValue @@ -86,9 +87,10 @@ actual fun rememberImagePickerLauncher( resizeOptions.width, resizeOptions.height, resizeOptions.resizeThresholdBytes, + resizeOptions.compressionQuality, filterOptions, ) - val bytes = resizedImage?.toByteArray() + val bytes = resizedImage?.toByteArray(resizeOptions.compressionQuality) if (bytes != null) { imageData.add(bytes) } @@ -122,8 +124,9 @@ actual fun rememberImagePickerLauncher( } @OptIn(ExperimentalForeignApi::class) -private fun UIImage.toByteArray(): ByteArray { - val jpegData = UIImageJPEGRepresentation(this, 1.0)!! +private fun UIImage.toByteArray(compressionQuality: Double): ByteArray { + val validCompressionQuality = compressionQuality.coerceIn(0.0, 1.0) + val jpegData = UIImageJPEGRepresentation(this, validCompressionQuality)!! return ByteArray(jpegData.length.toInt()).apply { memcpy(this.refTo(0), jpegData.bytes, jpegData.length) } @@ -134,9 +137,11 @@ private fun UIImage.fitInto( maxWidth: Int, maxHeight: Int, resizeThresholdBytes: Long, + @FloatRange(from = 0.0, to = 1.0) + compressionQuality: Double, filterOptions: FilterOptions, ): UIImage { - val imageData = this.toByteArray() + val imageData = this.toByteArray(compressionQuality) if (imageData.size > resizeThresholdBytes) { val originalWidth = this.size.useContents { width } val originalHeight = this.size.useContents { height } diff --git a/sample/common/src/commonMain/kotlin/com/preat/peekaboo/common/App.kt b/sample/common/src/commonMain/kotlin/com/preat/peekaboo/common/App.kt index 6231280..a942f86 100644 --- a/sample/common/src/commonMain/kotlin/com/preat/peekaboo/common/App.kt +++ b/sample/common/src/commonMain/kotlin/com/preat/peekaboo/common/App.kt @@ -78,7 +78,7 @@ fun App() { selectionMode = SelectionMode.Multiple(maxSelection = 5), scope = scope, // Resize options are customizable. Default is set to 800 x 800 pixels. - resizeOptions = ResizeOptions(width = 1200, height = 1200), + resizeOptions = ResizeOptions(width = 1200, height = 1200, compressionQuality = 1.0), // Default is 'Default', which applies no filter. // Other available options: GrayScale, Sepia, Invert. filterOptions = FilterOptions.GrayScale,