@@ -22,6 +22,15 @@ import android.text.TextUtils
22
22
import android.util.Base64
23
23
import androidx.exifinterface.media.ExifInterface
24
24
import com.facebook.common.logging.FLog
25
+ import com.facebook.common.memory.PooledByteBuffer
26
+ import com.facebook.common.memory.PooledByteBufferInputStream
27
+ import com.facebook.common.references.CloseableReference
28
+ import com.facebook.datasource.DataSource
29
+ import com.facebook.datasource.DataSources
30
+ import com.facebook.drawee.backends.pipeline.Fresco
31
+ import com.facebook.imagepipeline.core.ImagePipeline
32
+ import com.facebook.imagepipeline.request.ImageRequest
33
+ import com.facebook.imagepipeline.request.ImageRequestBuilder
25
34
import com.facebook.infer.annotation.Assertions
26
35
import com.facebook.react.bridge.Arguments
27
36
import com.facebook.react.bridge.JSApplicationIllegalArgumentException
@@ -31,6 +40,9 @@ import com.facebook.react.bridge.ReadableMap
31
40
import com.facebook.react.bridge.ReadableType
32
41
import com.facebook.react.bridge.WritableMap
33
42
import com.facebook.react.common.ReactConstants
43
+ import com.facebook.react.modules.fresco.ReactNetworkImageRequest
44
+ import com.facebook.react.views.image.ReactCallerContextFactory
45
+ import com.facebook.react.views.imagehelper.ImageSource
34
46
import java.io.ByteArrayInputStream
35
47
import java.io.File
36
48
import java.io.FileInputStream
@@ -51,7 +63,13 @@ object MimeType {
51
63
const val WEBP = " image/webp"
52
64
}
53
65
54
- class ImageEditorModuleImpl (private val reactContext : ReactApplicationContext ) {
66
+ class ImageEditorModuleImpl (
67
+ private val reactContext : ReactApplicationContext ,
68
+ private val callerContext : Any? ,
69
+ private val callerContextFactory : ReactCallerContextFactory ? ,
70
+ private val imagePipeline : ImagePipeline ?
71
+ ) {
72
+
55
73
private val moduleCoroutineScope = CoroutineScope (Dispatchers .Default )
56
74
57
75
init {
@@ -65,6 +83,56 @@ class ImageEditorModuleImpl(private val reactContext: ReactApplicationContext) {
65
83
cleanTask()
66
84
}
67
85
86
+ private fun getCallerContext (): Any? {
87
+ return callerContextFactory?.getOrCreateCallerContext(" " , " " ) ? : callerContext
88
+ }
89
+
90
+ private fun getImagePipeline (): ImagePipeline {
91
+ return imagePipeline ? : Fresco .getImagePipeline()
92
+ }
93
+
94
+ private fun fetchAndCacheImage (
95
+ uri : String ,
96
+ headers : ReadableMap ? ,
97
+ ): InputStream ? {
98
+ try {
99
+ val source = ImageSource (reactContext, uri)
100
+ val imageRequest: ImageRequest =
101
+ if (headers != null ) {
102
+ val imageRequestBuilder = ImageRequestBuilder .newBuilderWithSource(source.uri)
103
+ ReactNetworkImageRequest .fromBuilderWithHeaders(imageRequestBuilder, headers)
104
+ } else ImageRequestBuilder .newBuilderWithSource(source.uri).build()
105
+
106
+ val dataSource: DataSource <CloseableReference <PooledByteBuffer >> =
107
+ getImagePipeline().fetchEncodedImage(imageRequest, getCallerContext())
108
+
109
+ try {
110
+ val ref: CloseableReference <PooledByteBuffer >? =
111
+ DataSources .waitForFinalResult(dataSource)
112
+ if (ref != null ) {
113
+ try {
114
+ val result = ref.get()
115
+ return PooledByteBufferInputStream (result)
116
+ } finally {
117
+ CloseableReference .closeSafely(ref)
118
+ }
119
+ }
120
+ return null
121
+ } finally {
122
+ dataSource.close()
123
+ }
124
+ } catch (e: Exception ) {
125
+ // Fallback to default network requests
126
+ val connection = URL (uri).openConnection()
127
+ headers?.toHashMap()?.forEach { (key, value) ->
128
+ if (value is kotlin.String ) {
129
+ connection.setRequestProperty(key, value)
130
+ }
131
+ }
132
+ return connection.getInputStream()
133
+ }
134
+ }
135
+
68
136
/* *
69
137
* Asynchronous task that cleans up cache dirs (internal and, if available, external) of cropped
70
138
* image files. This is run when the module is invalidated (i.e. app is shutting down) and when
@@ -102,7 +170,7 @@ class ImageEditorModuleImpl(private val reactContext: ReactApplicationContext) {
102
170
fun cropImage (uri : String? , options : ReadableMap , promise : Promise ) {
103
171
val headers =
104
172
if (options.hasKey(" headers" ) && options.getType(" headers" ) == ReadableType .Map )
105
- options.getMap(" headers" )?.toHashMap()
173
+ options.getMap(" headers" )
106
174
else null
107
175
val format = if (options.hasKey(" format" )) options.getString(" format" ) else null
108
176
val offset = if (options.hasKey(" offset" )) options.getMap(" offset" ) else null
@@ -148,7 +216,7 @@ class ImageEditorModuleImpl(private val reactContext: ReactApplicationContext) {
148
216
// memory
149
217
val hasTargetSize = targetWidth > 0 && targetHeight > 0
150
218
val cropped: Bitmap ? =
151
- if (hasTargetSize) {
219
+ if (hasTargetSize)
152
220
cropAndResizeTask(
153
221
outOptions,
154
222
uri,
@@ -160,9 +228,8 @@ class ImageEditorModuleImpl(private val reactContext: ReactApplicationContext) {
160
228
targetHeight,
161
229
headers
162
230
)
163
- } else {
164
- cropTask(outOptions, uri, x, y, width, height, headers)
165
- }
231
+ else cropTask(outOptions, uri, x, y, width, height, headers)
232
+
166
233
if (cropped == null ) {
167
234
throw IOException (" Cannot decode bitmap: $uri " )
168
235
}
@@ -196,7 +263,7 @@ class ImageEditorModuleImpl(private val reactContext: ReactApplicationContext) {
196
263
y : Int ,
197
264
width : Int ,
198
265
height : Int ,
199
- headers : HashMap < String , Any ?> ?
266
+ headers : ReadableMap ?
200
267
): Bitmap ? {
201
268
return openBitmapInputStream(uri, headers)?.use {
202
269
// Efficiently crops image without loading full resolution into memory
@@ -258,7 +325,7 @@ class ImageEditorModuleImpl(private val reactContext: ReactApplicationContext) {
258
325
rectHeight : Int ,
259
326
outputWidth : Int ,
260
327
outputHeight : Int ,
261
- headers : HashMap < String , Any ?> ?
328
+ headers : ReadableMap ?
262
329
): Bitmap ? {
263
330
Assertions .assertNotNull(outOptions)
264
331
@@ -337,20 +404,14 @@ class ImageEditorModuleImpl(private val reactContext: ReactApplicationContext) {
337
404
}
338
405
}
339
406
340
- private fun openBitmapInputStream (uri : String , headers : HashMap < String , Any ?> ? ): InputStream ? {
407
+ private fun openBitmapInputStream (uri : String , headers : ReadableMap ? ): InputStream ? {
341
408
return if (uri.startsWith(" data:" )) {
342
409
val src = uri.substring(uri.indexOf(" ," ) + 1 )
343
410
ByteArrayInputStream (Base64 .decode(src, Base64 .DEFAULT ))
344
411
} else if (isLocalUri(uri)) {
345
412
reactContext.contentResolver.openInputStream(Uri .parse(uri))
346
413
} else {
347
- val connection = URL (uri).openConnection()
348
- headers?.forEach { (key, value) ->
349
- if (value is String ) {
350
- connection.setRequestProperty(key, value)
351
- }
352
- }
353
- connection.getInputStream()
414
+ fetchAndCacheImage(uri, headers)
354
415
}
355
416
}
356
417
0 commit comments