diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/image/ImageLoaderModule.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/image/ImageLoaderModule.kt index 9f6b35189e31..c0a403771345 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/image/ImageLoaderModule.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/image/ImageLoaderModule.kt @@ -20,6 +20,7 @@ import com.facebook.drawee.backends.pipeline.Fresco import com.facebook.fbreact.specs.NativeImageLoaderAndroidSpec import com.facebook.imagepipeline.common.RotationOptions import com.facebook.imagepipeline.core.ImagePipeline +import com.facebook.imagepipeline.image.CloseableImage import com.facebook.imagepipeline.image.EncodedImage import com.facebook.imagepipeline.request.ImageRequest import com.facebook.imagepipeline.request.ImageRequestBuilder @@ -97,9 +98,7 @@ internal class ImageLoaderModule : NativeImageLoaderAndroidSpec, LifecycleEventL ImageRequestBuilder.newBuilderWithSource(source.uri) .setRotationOptions(RotationOptions.disableRotation()) .build() - val dataSource: DataSource> = - this.imagePipeline.fetchEncodedImage(request, this.callerContext) - dataSource.subscribe(createSizeSubscriber(promise), CallerThreadExecutor.getInstance()) + fetchImageSize(request, source.uri.scheme == "data", promise) } /** @@ -127,11 +126,57 @@ internal class ImageLoaderModule : NativeImageLoaderAndroidSpec, LifecycleEventL .setRotationOptions(RotationOptions.disableRotation()) val request: ImageRequest = ReactNetworkImageRequest.fromBuilderWithHeaders(imageRequestBuilder, headers) + fetchImageSize(request, source.uri.scheme == "data", promise) + } + + private fun fetchImageSize(request: ImageRequest, isDataUri: Boolean, promise: Promise) { + if (isDataUri) { + val dataSource: DataSource> = + this.imagePipeline.fetchDecodedImage(request, this.callerContext) + dataSource.subscribe( + createDecodedImageSizeSubscriber(promise), + CallerThreadExecutor.getInstance(), + ) + return + } + val dataSource: DataSource> = this.imagePipeline.fetchEncodedImage(request, this.callerContext) dataSource.subscribe(createSizeSubscriber(promise), CallerThreadExecutor.getInstance()) } + private fun createDecodedImageSizeSubscriber( + promise: Promise + ): DataSubscriber> = + object : BaseDataSubscriber>() { + override fun onNewResultImpl(dataSource: DataSource>) { + if (!dataSource.isFinished) { + return + } + val ref = dataSource.result + if (ref != null) { + try { + val image: CloseableImage = ref.get() + val sizes = buildReadableMap { + put("width", image.width) + put("height", image.height) + } + promise.resolve(sizes) + } catch (e: Exception) { + promise.reject(ERROR_GET_SIZE_FAILURE, e) + } finally { + CloseableReference.closeSafely(ref) + } + } else { + promise.reject(ERROR_GET_SIZE_FAILURE, "Failed to get the size of the image") + } + } + + override fun onFailureImpl(dataSource: DataSource>) { + promise.reject(ERROR_GET_SIZE_FAILURE, dataSource.failureCause) + } + } + private fun createSizeSubscriber( promise: Promise ): DataSubscriber> = diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/modules/image/ImageLoaderModuleTest.kt b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/modules/image/ImageLoaderModuleTest.kt index 9ba6d68b3d52..2a7d812366dc 100644 --- a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/modules/image/ImageLoaderModuleTest.kt +++ b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/modules/image/ImageLoaderModuleTest.kt @@ -9,10 +9,15 @@ package com.facebook.react.modules.image import android.content.res.Resources import android.graphics.drawable.Drawable +import com.facebook.common.references.CloseableReference +import com.facebook.datasource.DataSource +import com.facebook.imagepipeline.core.ImagePipeline +import com.facebook.imagepipeline.image.CloseableImage import com.facebook.react.bridge.Promise import com.facebook.react.bridge.ReactTestHelper import com.facebook.react.bridge.ReadableMap import com.facebook.react.bridge.WritableMap +import com.facebook.react.views.image.ReactCallerContextFactory import com.facebook.react.views.imagehelper.ResourceDrawableIdHelper import com.facebook.testutils.shadows.ShadowArguments import com.facebook.testutils.shadows.ShadowSoLoader @@ -26,6 +31,8 @@ import org.mockito.Mockito.mockStatic import org.mockito.kotlin.any import org.mockito.kotlin.eq import org.mockito.kotlin.mock +import org.mockito.kotlin.never +import org.mockito.kotlin.verify import org.mockito.kotlin.whenever import org.robolectric.RobolectricTestRunner import org.robolectric.annotation.Config @@ -177,6 +184,46 @@ class ImageLoaderModuleTest { assertThat(promise.errorMessage).contains("no intrinsic size") } + @Test + fun testGetSizeWithDataUriUsesDecodedImagePipeline() { + val imagePipeline = mock() + val dataSource = mock>>() + whenever(imagePipeline.fetchDecodedImage(any(), any())).thenReturn(dataSource) + val module = + ImageLoaderModule( + ReactTestHelper.createCatalystContextForTest(), + imagePipeline, + ReactCallerContextFactory { _, _ -> null }, + ) + + module.getSize(DATA_URI, SimplePromise()) + + verify(imagePipeline).fetchDecodedImage(any(), any()) + verify(imagePipeline, never()).fetchEncodedImage(any(), any()) + } + + @Test + fun testGetSizeWithHeadersWithDataUriUsesDecodedImagePipeline() { + val imagePipeline = mock() + val dataSource = mock>>() + whenever(imagePipeline.fetchDecodedImage(any(), any())).thenReturn(dataSource) + val module = + ImageLoaderModule( + ReactTestHelper.createCatalystContextForTest(), + imagePipeline, + ReactCallerContextFactory { _, _ -> null }, + ) + + module.getSizeWithHeaders( + DATA_URI, + null, + SimplePromise(), + ) + + verify(imagePipeline).fetchDecodedImage(any(), any()) + verify(imagePipeline, never()).fetchEncodedImage(any(), any()) + } + @Test fun testGetSizeWithEmptyUri() { val promise = SimplePromise() @@ -271,4 +318,9 @@ class ImageLoaderModuleTest { reject(null, message, null, null) } } + + companion object { + private const val DATA_URI = + "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=" + } }