-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathImagePickerService.kt
More file actions
177 lines (155 loc) · 5.68 KB
/
Copy pathImagePickerService.kt
File metadata and controls
177 lines (155 loc) · 5.68 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
package com.github.familyvault.services
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.toComposeImageBitmap
import com.github.familyvault.models.ImageSize
import kotlinx.cinterop.BetaInteropApi
import kotlinx.cinterop.ExperimentalForeignApi
import kotlinx.cinterop.addressOf
import kotlinx.cinterop.memScoped
import kotlinx.cinterop.usePinned
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.IO
import kotlinx.coroutines.runBlocking
import org.jetbrains.skia.EncodedImageFormat
import org.jetbrains.skia.Image
import org.jetbrains.skia.impl.use
import platform.Foundation.NSData
import platform.Foundation.NSError
import platform.Foundation.NSItemProvider
import platform.PhotosUI.PHPickerConfiguration
import platform.PhotosUI.PHPickerFilter
import platform.PhotosUI.PHPickerResult
import platform.PhotosUI.PHPickerViewController
import platform.PhotosUI.PHPickerViewControllerDelegateProtocol
import platform.UIKit.UIApplication
import kotlin.coroutines.Continuation
import kotlin.coroutines.suspendCoroutine
import platform.UniformTypeIdentifiers.UTTypeImage
import platform.darwin.NSObject
import platform.posix.memcpy
import kotlin.coroutines.resume
class ImagePickerService : IImagePickerService {
private var continuation: Continuation<List<ByteArray>>? = null
private val selectedImages = mutableStateListOf<PHPickerResult>()
private val pickerController = PickerController(selectedImages)
private val config = PHPickerConfiguration().apply {
selectionLimit = 0
filter = PHPickerFilter.imagesFilter
}
private val scope = CoroutineScope(Dispatchers.Default)
private var cont: PHPickerViewController? = null
init {
clearSelectedImages()
}
override fun openMediaPickerForSelectingImages() {
if (cont == null) cont = PHPickerViewController(config)
cont!!.setDelegate(pickerController)
UIApplication.sharedApplication.keyWindow?.rootViewController?.presentViewController(
cont!!,
true,
null
)
}
override suspend fun pickImagesAndReturnByteArrays(): List<ByteArray> =
suspendCoroutine { cont ->
continuation = cont
openMediaPickerForSelectingImages()
}
override fun getBytesFromUri(uriString: String): ByteArray? {
return selectedImages.firstOrNull { phResult ->
phResult.ID == uriString
}?.let { phResult ->
return@let runBlocking(Dispatchers.IO) {
suspendCoroutine { cont ->
phResult.itemProvider.loadDataRepresentationForTypeIdentifier(UTTypeImage.identifier){ data, error ->
cont.resume(data!!.toByteArray())
}
}
}
}
}
@OptIn(ExperimentalForeignApi::class)
override fun getSelectedImageAsByteArrays(): List<ByteArray> {
return selectedImages.map { phResult ->
runBlocking {
suspendCoroutine { cont ->
phResult.itemProvider.loadDataRepresentationForTypeIdentifier(
UTTypeImage.identifier,
) { p1: NSData?,
p2: NSError? ->
val image = p1!!
cont.resume(image.toByteArray())
}
}
}
}
}
override fun getSelectedImageUrls(): List<String> = selectedImages.map { phResult ->
phResult.ID
}
override fun getBitmapFromBytes(imageBytes: ByteArray): ImageBitmap {
return Image.makeFromEncoded(imageBytes).use {
it.toComposeImageBitmap()
}
}
override fun clearSelectedImages() {
selectedImages.clear()
cont = null
}
override fun removeSelectedImage(uri: String) {
selectedImages.removeAll {
it.ID == uri
}
}
override fun compressAndRotateImage(
imageByteArray: ByteArray,
compressionQuality: Int?
): ByteArray {
return Image.makeFromEncoded(imageByteArray).use {
it.compress(quality = compressionQuality ?: 92)
}
}
override fun getImageAsByteArraySize(image: ByteArray): ImageSize {
val imageBitmap = getBitmapFromBytes(image)
return ImageSize(imageBitmap.height, imageBitmap.width)
}
private fun Image.compress(quality: Int): ByteArray {
return encodeToData(EncodedImageFormat.JPEG, quality)!!.bytes
}
//TODO: Check if this rotation method is needed
// private fun Image.fixRotation(): Image = this.apply{
//
// Bitmap.makeFromImage(Image.makeFromEncoded(imageBytes))
// }
}
@OptIn(BetaInteropApi::class)
class PickerController(
val selectedUris: MutableList<PHPickerResult>
) : NSObject(),
PHPickerViewControllerDelegateProtocol {
override fun picker(
picker: PHPickerViewController,
didFinishPicking: List<*>
) {
@Suppress("UNCHECKED_CAST")
didFinishPicking as List<PHPickerResult>
val itemsToAdd = didFinishPicking.filter {
it.itemProvider.hasItemConformingToTypeIdentifier(UTTypeImage.identifier)
}
selectedUris.clear()
selectedUris.addAll(itemsToAdd)
picker.dismissViewControllerAnimated(true,null)
}
}
@OptIn(ExperimentalForeignApi::class)
fun NSData.toByteArray(): ByteArray = memScoped {
ByteArray(length.toInt()).apply {
usePinned {
memcpy(it.addressOf(0), bytes, length)
}
}
}
val NSItemProvider.ID get() = "$suggestedName/$hash"
val PHPickerResult.ID get() = this.assetIdentifier ?: itemProvider.ID