@@ -22,6 +22,15 @@ import android.text.TextUtils
2222import android.util.Base64
2323import androidx.exifinterface.media.ExifInterface
2424import 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
2534import com.facebook.infer.annotation.Assertions
2635import com.facebook.react.bridge.Arguments
2736import com.facebook.react.bridge.JSApplicationIllegalArgumentException
@@ -31,6 +40,9 @@ import com.facebook.react.bridge.ReadableMap
3140import com.facebook.react.bridge.ReadableType
3241import com.facebook.react.bridge.WritableMap
3342import 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
3446import java.io.ByteArrayInputStream
3547import java.io.File
3648import java.io.FileInputStream
@@ -51,7 +63,13 @@ object MimeType {
5163 const val WEBP = " image/webp"
5264}
5365
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+
5573 private val moduleCoroutineScope = CoroutineScope (Dispatchers .Default )
5674
5775 init {
@@ -65,6 +83,56 @@ class ImageEditorModuleImpl(private val reactContext: ReactApplicationContext) {
6583 cleanTask()
6684 }
6785
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+
68136 /* *
69137 * Asynchronous task that cleans up cache dirs (internal and, if available, external) of cropped
70138 * 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) {
102170 fun cropImage (uri : String? , options : ReadableMap , promise : Promise ) {
103171 val headers =
104172 if (options.hasKey(" headers" ) && options.getType(" headers" ) == ReadableType .Map )
105- options.getMap(" headers" )?.toHashMap()
173+ options.getMap(" headers" )
106174 else null
107175 val format = if (options.hasKey(" format" )) options.getString(" format" ) else null
108176 val offset = if (options.hasKey(" offset" )) options.getMap(" offset" ) else null
@@ -148,7 +216,7 @@ class ImageEditorModuleImpl(private val reactContext: ReactApplicationContext) {
148216 // memory
149217 val hasTargetSize = targetWidth > 0 && targetHeight > 0
150218 val cropped: Bitmap ? =
151- if (hasTargetSize) {
219+ if (hasTargetSize)
152220 cropAndResizeTask(
153221 outOptions,
154222 uri,
@@ -160,9 +228,8 @@ class ImageEditorModuleImpl(private val reactContext: ReactApplicationContext) {
160228 targetHeight,
161229 headers
162230 )
163- } else {
164- cropTask(outOptions, uri, x, y, width, height, headers)
165- }
231+ else cropTask(outOptions, uri, x, y, width, height, headers)
232+
166233 if (cropped == null ) {
167234 throw IOException (" Cannot decode bitmap: $uri " )
168235 }
@@ -196,7 +263,7 @@ class ImageEditorModuleImpl(private val reactContext: ReactApplicationContext) {
196263 y : Int ,
197264 width : Int ,
198265 height : Int ,
199- headers : HashMap < String , Any ?> ?
266+ headers : ReadableMap ?
200267 ): Bitmap ? {
201268 return openBitmapInputStream(uri, headers)?.use {
202269 // Efficiently crops image without loading full resolution into memory
@@ -258,7 +325,7 @@ class ImageEditorModuleImpl(private val reactContext: ReactApplicationContext) {
258325 rectHeight : Int ,
259326 outputWidth : Int ,
260327 outputHeight : Int ,
261- headers : HashMap < String , Any ?> ?
328+ headers : ReadableMap ?
262329 ): Bitmap ? {
263330 Assertions .assertNotNull(outOptions)
264331
@@ -337,20 +404,14 @@ class ImageEditorModuleImpl(private val reactContext: ReactApplicationContext) {
337404 }
338405 }
339406
340- private fun openBitmapInputStream (uri : String , headers : HashMap < String , Any ?> ? ): InputStream ? {
407+ private fun openBitmapInputStream (uri : String , headers : ReadableMap ? ): InputStream ? {
341408 return if (uri.startsWith(" data:" )) {
342409 val src = uri.substring(uri.indexOf(" ," ) + 1 )
343410 ByteArrayInputStream (Base64 .decode(src, Base64 .DEFAULT ))
344411 } else if (isLocalUri(uri)) {
345412 reactContext.contentResolver.openInputStream(Uri .parse(uri))
346413 } 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)
354415 }
355416 }
356417
0 commit comments