From 0b73d4978c6869ef34b802884daa6d56f9ab0e7d Mon Sep 17 00:00:00 2001 From: Anatol Date: Thu, 11 Jan 2024 10:20:38 +0200 Subject: [PATCH 1/3] Adding compression quality as parameter of ResizeOption. Fixes #41. --- .../peekaboo/image/picker/ImagePickerLauncher.android.kt | 2 ++ .../com/preat/peekaboo/image/picker/PeekabooImageResizer.kt | 6 ++++-- .../com/preat/peekaboo/image/picker/ImagePickerLauncher.kt | 1 + .../preat/peekaboo/image/picker/ImagePickerLauncher.ios.kt | 6 +++--- .../src/commonMain/kotlin/com/preat/peekaboo/common/App.kt | 2 +- 5 files changed, 11 insertions(+), 6 deletions(-) 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..95fd7ad 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 @@ -40,12 +40,13 @@ internal object PeekabooImageResizer { width: Int, height: Int, resizeThresholdBytes: Long, + 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 +87,7 @@ internal object PeekabooImageResizer { uri: Uri, width: Int, height: Int, + compression: Double, filterOptions: FilterOptions, ): ByteArray? { val resizeCacheKey = "${uri}_w${width}_h$height" @@ -127,7 +129,7 @@ internal object PeekabooImageResizer { val filteredBitmap = applyFilter(rotatedBitmap, filterOptions) ByteArrayOutputStream().use { byteArrayOutputStream -> - filteredBitmap.compress(Bitmap.CompressFormat.JPEG, 100, byteArrayOutputStream) + filteredBitmap.compress(Bitmap.CompressFormat.JPEG, (100 * compression).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..a0bd636 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 @@ -45,6 +45,7 @@ data class ResizeOptions( val width: Int = DEFAULT_RESIZE_IMAGE_WIDTH, val height: Int = DEFAULT_RESIZE_IMAGE_HEIGHT, val resizeThresholdBytes: Long = DEFAULT_RESIZE_THRESHOLD_BYTES, + 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..eb609d1 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 @@ -88,7 +88,7 @@ actual fun rememberImagePickerLauncher( resizeOptions.resizeThresholdBytes, filterOptions, ) - val bytes = resizedImage?.toByteArray() + val bytes = resizedImage?.toByteArray(resizeOptions.compressionQuality) if (bytes != null) { imageData.add(bytes) } @@ -122,8 +122,8 @@ 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 jpegData = UIImageJPEGRepresentation(this, compressionQuality)!! return ByteArray(jpegData.length.toInt()).apply { memcpy(this.refTo(0), jpegData.bytes, jpegData.length) } 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, From bbb5fa8cd52e26dee755ee73fae47fa4fabe6a94 Mon Sep 17 00:00:00 2001 From: Anatol Date: Thu, 7 Mar 2024 14:22:33 +0200 Subject: [PATCH 2/3] Ensuring compression quality value is in range 0.0 .. 1.0 Adding annotation library and marking compressionQuality param as FloatRange. --- gradle/libs.versions.toml | 1 + peekaboo-image-picker/build.gradle.kts | 1 + .../preat/peekaboo/image/picker/PeekabooImageResizer.kt | 6 +++++- .../preat/peekaboo/image/picker/ImagePickerLauncher.kt | 2 ++ .../peekaboo/image/picker/ImagePickerLauncher.ios.kt | 9 +++++++-- 5 files changed, 16 insertions(+), 3 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 9350710..ddeab48 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -49,6 +49,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 = "1.7.1" } [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/PeekabooImageResizer.kt b/peekaboo-image-picker/src/androidMain/kotlin/com/preat/peekaboo/image/picker/PeekabooImageResizer.kt index 95fd7ad..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,6 +41,7 @@ internal object PeekabooImageResizer { width: Int, height: Int, resizeThresholdBytes: Long, + @FloatRange(from = 0.0, to = 1.0) compressionQuality: Double, filterOptions: FilterOptions, onResult: (ByteArray?) -> Unit, @@ -87,6 +89,7 @@ internal object PeekabooImageResizer { uri: Uri, width: Int, height: Int, + @FloatRange(from = 0.0, to = 1.0) compression: Double, filterOptions: FilterOptions, ): ByteArray? { @@ -129,7 +132,8 @@ internal object PeekabooImageResizer { val filteredBitmap = applyFilter(rotatedBitmap, filterOptions) ByteArrayOutputStream().use { byteArrayOutputStream -> - filteredBitmap.compress(Bitmap.CompressFormat.JPEG, (100 * compression).toInt(), 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 a0bd636..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,7 @@ 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, ) 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 eb609d1..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,6 +87,7 @@ actual fun rememberImagePickerLauncher( resizeOptions.width, resizeOptions.height, resizeOptions.resizeThresholdBytes, + resizeOptions.compressionQuality, filterOptions, ) val bytes = resizedImage?.toByteArray(resizeOptions.compressionQuality) @@ -123,7 +125,8 @@ actual fun rememberImagePickerLauncher( @OptIn(ExperimentalForeignApi::class) private fun UIImage.toByteArray(compressionQuality: Double): ByteArray { - val jpegData = UIImageJPEGRepresentation(this, compressionQuality)!! + 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 } From 6932b30fba705f333f557777d57951eca3c12522 Mon Sep 17 00:00:00 2001 From: Anatol Date: Fri, 8 Mar 2024 11:58:42 +0200 Subject: [PATCH 3/3] Fixing toml to follow convention --- gradle/libs.versions.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ddeab48..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,7 +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 = "1.7.1" } +annotation = { module = "androidx.annotation:annotation", version.ref = "androidx-version" } [plugins] androidApplication = { id = "com.android.application", version.ref = "agp" }