diff --git a/README.md b/README.md
index 534b304..90afe5b 100644
--- a/README.md
+++ b/README.md
@@ -47,7 +47,7 @@ First, define the version in `libs.versions.toml`:
```toml
[versions]
-peekaboo = "0.3.0"
+peekaboo = "0.3.1"
[libraries]
peekaboo-ui = { module = "io.github.team-preat:peekaboo-ui", version.ref = "peekaboo" }
@@ -203,6 +203,54 @@ Button(
+## Image Resizing Options
+`peekaboo` offers customizable resizing options for both single and multiple image selections.
+This feature allows you to resize the selected images to specific dimensions, optimizing them for your application's requirements and enhancing performance.
+
+- The default resizing dimensions are set to `800 x 800` pixels.
+- You can customize the resizing dimensions according to your needs.
+
+### Usage
+Set the `resizeOptions` parameter in `rememberImagePickerLauncher` with your desired dimensions:
+
+```kotlin
+val resizeOptions = ResizeOptions(width = 1200, height = 1200) // Custom dimensions
+```
+
+#### Single Image Selection with Resizing
+```kotlin
+val singleImagePicker = rememberImagePickerLauncher(
+ selectionMode = SelectionMode.Single,
+ scope = rememberCoroutineScope(),
+ resizeOptions = resizeOptions,
+ onResult = { byteArrays ->
+ byteArrays.firstOrNull()?.let {
+ // Process the resized image's ByteArray
+ println(it)
+ }
+ }
+)
+```
+
+#### Multiple Images Selection with Resizing
+```kotlin
+val multipleImagePicker = rememberImagePickerLauncher(
+ selectionMode = SelectionMode.Multiple(maxSelection = 5),
+ scope = rememberCoroutineScope(),
+ resizeOptions = resizeOptions,
+ onResult = { byteArrays ->
+ byteArrays.forEach {
+ // Process the resized images' ByteArrays
+ println(it)
+ }
+ }
+)
+```
+
+>💡 Note: While resizing, the aspect ratio of the original images is preserved. The final dimensions may slightly vary to maintain the original proportions.
+
+
+
## ByteArray to ImageBitmap Conversion
We've added a new extension function `toImageBitmap()` to convert a `ByteArray` into an `ImageBitmap`.
This function simplifies the process of converting image data into a displayable format, enhancing the app's capability to handle image processing efficiently.
diff --git a/convention-plugins/src/main/kotlin/root.publication.gradle.kts b/convention-plugins/src/main/kotlin/root.publication.gradle.kts
index a3995a9..d3f3b04 100644
--- a/convention-plugins/src/main/kotlin/root.publication.gradle.kts
+++ b/convention-plugins/src/main/kotlin/root.publication.gradle.kts
@@ -19,7 +19,7 @@ plugins {
allprojects {
group = "io.github.team-preat"
- version = "0.3.0"
+ version = "0.3.1"
}
nexusPublishing {
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 66b38c2..735fd3d 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
@@ -15,6 +15,10 @@
*/
package com.preat.peekaboo.image.picker
+import android.content.Context
+import android.graphics.Bitmap
+import android.graphics.BitmapFactory
+import android.net.Uri
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.PickVisualMediaRequest
import androidx.activity.result.contract.ActivityResultContracts
@@ -23,23 +27,27 @@ import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalContext
import com.preat.peekaboo.image.picker.SelectionMode.Companion.INFINITY
import kotlinx.coroutines.CoroutineScope
+import java.io.ByteArrayOutputStream
@Composable
actual fun rememberImagePickerLauncher(
selectionMode: SelectionMode,
scope: CoroutineScope?,
+ resizeOptions: ResizeOptions,
onResult: (List) -> Unit,
): ImagePickerLauncher {
return when (selectionMode) {
SelectionMode.Single ->
pickSingleImage(
selectionMode = selectionMode,
+ resizeOptions = resizeOptions,
onResult = onResult,
)
is SelectionMode.Multiple ->
pickMultipleImages(
selectionMode = selectionMode,
+ resizeOptions = resizeOptions,
onResult = onResult,
)
}
@@ -48,6 +56,7 @@ actual fun rememberImagePickerLauncher(
@Composable
private fun pickSingleImage(
selectionMode: SelectionMode,
+ resizeOptions: ResizeOptions,
onResult: (List) -> Unit,
): ImagePickerLauncher {
val context = LocalContext.current
@@ -57,10 +66,15 @@ private fun pickSingleImage(
contract = ActivityResultContracts.PickVisualMedia(),
onResult = { uri ->
uri?.let {
- with(context.contentResolver) {
- openInputStream(uri)?.use {
- onResult(listOf(it.readBytes()))
- }
+ val resizedImage =
+ resizeImage(
+ context = context,
+ uri = uri,
+ width = resizeOptions.width,
+ height = resizeOptions.height,
+ )
+ if (resizedImage != null) {
+ onResult(listOf(resizedImage))
}
}
},
@@ -81,6 +95,7 @@ private fun pickSingleImage(
@Composable
fun pickMultipleImages(
selectionMode: SelectionMode.Multiple,
+ resizeOptions: ResizeOptions,
onResult: (List) -> Unit,
): ImagePickerLauncher {
val context = LocalContext.current
@@ -97,9 +112,12 @@ fun pickMultipleImages(
onResult = { uriList ->
val imageBytesList =
uriList.mapNotNull { uri ->
- context.contentResolver.openInputStream(uri)?.use { inputStream ->
- inputStream.readBytes()
- }
+ resizeImage(
+ context = context,
+ uri = uri,
+ width = resizeOptions.width,
+ height = resizeOptions.height,
+ )
}
if (imageBytesList.isNotEmpty()) {
onResult(imageBytesList)
@@ -127,3 +145,37 @@ actual class ImagePickerLauncher actual constructor(
onLaunch()
}
}
+
+fun resizeImage(
+ context: Context,
+ uri: Uri,
+ width: Int,
+ height: Int,
+): ByteArray? {
+ context.contentResolver.openInputStream(uri)?.use { inputStream ->
+ val options =
+ BitmapFactory.Options().apply {
+ inJustDecodeBounds = true
+ }
+ BitmapFactory.decodeStream(inputStream, null, options)
+
+ var inSampleSize = 1
+ while (options.outWidth / inSampleSize > width || options.outHeight / inSampleSize > height) {
+ inSampleSize *= 2
+ }
+
+ options.inJustDecodeBounds = false
+ options.inSampleSize = inSampleSize
+
+ context.contentResolver.openInputStream(uri)?.use { scaledInputStream ->
+ val scaledBitmap = BitmapFactory.decodeStream(scaledInputStream, null, options)
+ if (scaledBitmap != null) {
+ ByteArrayOutputStream().use { byteArrayOutputStream ->
+ scaledBitmap.compress(Bitmap.CompressFormat.JPEG, 100, byteArrayOutputStream)
+ return byteArrayOutputStream.toByteArray()
+ }
+ }
+ }
+ }
+ return null
+}
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 84a7096..d41f2a7 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
@@ -22,6 +22,7 @@ import kotlinx.coroutines.CoroutineScope
expect fun rememberImagePickerLauncher(
selectionMode: SelectionMode = SelectionMode.Single,
scope: CoroutineScope?,
+ resizeOptions: ResizeOptions = ResizeOptions(),
onResult: (List) -> Unit,
): ImagePickerLauncher
@@ -35,6 +36,11 @@ sealed class SelectionMode {
}
}
+data class ResizeOptions(
+ val width: Int = 800,
+ val height: Int = 800,
+)
+
expect class ImagePickerLauncher(
selectionMode: SelectionMode,
onLaunch: () -> Unit,
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 1382ced..67e711f 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
@@ -17,11 +17,16 @@ package com.preat.peekaboo.image.picker
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
+import kotlinx.cinterop.CValue
import kotlinx.cinterop.ExperimentalForeignApi
import kotlinx.cinterop.refTo
+import kotlinx.cinterop.useContents
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
+import platform.CoreGraphics.CGRectMake
+import platform.CoreGraphics.CGSize
+import platform.CoreGraphics.CGSizeMake
import platform.PhotosUI.PHPickerConfiguration
import platform.PhotosUI.PHPickerConfigurationSelectionOrdered
import platform.PhotosUI.PHPickerFilter
@@ -29,6 +34,11 @@ import platform.PhotosUI.PHPickerResult
import platform.PhotosUI.PHPickerViewController
import platform.PhotosUI.PHPickerViewControllerDelegateProtocol
import platform.UIKit.UIApplication
+import platform.UIKit.UIGraphicsBeginImageContextWithOptions
+import platform.UIKit.UIGraphicsEndImageContext
+import platform.UIKit.UIGraphicsGetImageFromCurrentImageContext
+import platform.UIKit.UIImage
+import platform.UIKit.UIImageJPEGRepresentation
import platform.darwin.NSObject
import platform.darwin.dispatch_group_create
import platform.darwin.dispatch_group_enter
@@ -40,9 +50,9 @@ import platform.posix.memcpy
actual fun rememberImagePickerLauncher(
selectionMode: SelectionMode,
scope: CoroutineScope?,
+ resizeOptions: ResizeOptions,
onResult: (List) -> Unit,
): ImagePickerLauncher {
- @OptIn(ExperimentalForeignApi::class)
val delegate =
object : NSObject(), PHPickerViewControllerDelegateProtocol {
override fun picker(
@@ -63,11 +73,14 @@ actual fun rememberImagePickerLauncher(
) { nsData, _ ->
scope?.launch(Dispatchers.Main) {
nsData?.let {
- val bytes = ByteArray(it.length.toInt())
- memcpy(bytes.refTo(0), it.bytes, it.length)
- imageData.add(bytes)
+ val image = UIImage.imageWithData(it)
+ val resizedImage = image?.fitInto(resizeOptions.width, resizeOptions.height)
+ val bytes = resizedImage?.toByteArray()
+ if (bytes != null) {
+ imageData.add(bytes)
+ }
+ dispatch_group_leave(dispatchGroup)
}
- dispatch_group_leave(dispatchGroup)
}
}
}
@@ -95,6 +108,33 @@ actual fun rememberImagePickerLauncher(
}
}
+@OptIn(ExperimentalForeignApi::class)
+private fun UIImage.toByteArray(): ByteArray {
+ val jpegData = UIImageJPEGRepresentation(this, 1.0)!!
+ return ByteArray(jpegData.length.toInt()).apply {
+ memcpy(this.refTo(0), jpegData.bytes, jpegData.length)
+ }
+}
+
+@OptIn(ExperimentalForeignApi::class)
+private fun UIImage.fitInto(
+ width: Int,
+ height: Int,
+): UIImage {
+ val targetSize = CGSizeMake(width.toDouble(), height.toDouble())
+ return this.resize(targetSize)
+}
+
+@OptIn(ExperimentalForeignApi::class)
+private fun UIImage.resize(targetSize: CValue): UIImage {
+ UIGraphicsBeginImageContextWithOptions(targetSize, false, 0.0)
+ this.drawInRect(CGRectMake(0.0, 0.0, targetSize.useContents { width }, targetSize.useContents { height }))
+ val newImage = UIGraphicsGetImageFromCurrentImageContext()
+ UIGraphicsEndImageContext()
+
+ return newImage!!
+}
+
private fun createPHPickerViewController(
delegate: PHPickerViewControllerDelegateProtocol,
selection: SelectionMode,
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 2e00f00..ccb0c6b 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
@@ -58,6 +58,7 @@ import com.preat.peekaboo.common.icon.IconCached
import com.preat.peekaboo.common.icon.IconClose
import com.preat.peekaboo.common.icon.IconWarning
import com.preat.peekaboo.common.style.PeekabooTheme
+import com.preat.peekaboo.image.picker.ResizeOptions
import com.preat.peekaboo.image.picker.SelectionMode
import com.preat.peekaboo.image.picker.rememberImagePickerLauncher
import com.preat.peekaboo.image.picker.toImageBitmap
@@ -90,6 +91,8 @@ fun App() {
// Default: No limit, depends on system's maximum capacity.
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),
onResult = { byteArrays ->
images =
byteArrays.map { byteArray ->