diff --git a/packages/react-native/ReactAndroid/api/ReactAndroid.api b/packages/react-native/ReactAndroid/api/ReactAndroid.api index 881ded1e9a2213..0e9cb196e12245 100644 --- a/packages/react-native/ReactAndroid/api/ReactAndroid.api +++ b/packages/react-native/ReactAndroid/api/ReactAndroid.api @@ -6199,15 +6199,16 @@ public class com/facebook/react/views/imagehelper/ImageSource { public fun isResource ()Z } -public class com/facebook/react/views/imagehelper/MultiSourceHelper { - public fun ()V - public static fun getBestSourceForSize (IILjava/util/List;)Lcom/facebook/react/views/imagehelper/MultiSourceHelper$MultiSourceResult; - public static fun getBestSourceForSize (IILjava/util/List;D)Lcom/facebook/react/views/imagehelper/MultiSourceHelper$MultiSourceResult; +public final class com/facebook/react/views/imagehelper/MultiSourceHelper { + public static final field INSTANCE Lcom/facebook/react/views/imagehelper/MultiSourceHelper; + public static final fun getBestSourceForSize (IILjava/util/List;)Lcom/facebook/react/views/imagehelper/MultiSourceHelper$MultiSourceResult; + public static final fun getBestSourceForSize (IILjava/util/List;D)Lcom/facebook/react/views/imagehelper/MultiSourceHelper$MultiSourceResult; } -public class com/facebook/react/views/imagehelper/MultiSourceHelper$MultiSourceResult { - public fun getBestResult ()Lcom/facebook/react/views/imagehelper/ImageSource; - public fun getBestResultInCache ()Lcom/facebook/react/views/imagehelper/ImageSource; +public final class com/facebook/react/views/imagehelper/MultiSourceHelper$MultiSourceResult { + public final field bestResult Lcom/facebook/react/views/imagehelper/ImageSource; + public final field bestResultInCache Lcom/facebook/react/views/imagehelper/ImageSource; + public fun (Lcom/facebook/react/views/imagehelper/ImageSource;Lcom/facebook/react/views/imagehelper/ImageSource;)V } public final class com/facebook/react/views/imagehelper/ResourceDrawableIdHelper { diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageView.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageView.java index da4fad4b4c7c97..50bb7684b9c4f8 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageView.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageView.java @@ -578,8 +578,8 @@ private void setSourceImage() { } else if (hasMultipleSources()) { MultiSourceResult multiSource = MultiSourceHelper.getBestSourceForSize(getWidth(), getHeight(), mSources); - mImageSource = multiSource.getBestResult(); - mCachedImageSource = multiSource.getBestResultInCache(); + mImageSource = multiSource.bestResult; + mCachedImageSource = multiSource.bestResultInCache; return; } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/imagehelper/MultiSourceHelper.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/imagehelper/MultiSourceHelper.java deleted file mode 100644 index 46810aaff4e956..00000000000000 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/imagehelper/MultiSourceHelper.java +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -package com.facebook.react.views.imagehelper; - -import androidx.annotation.Nullable; -import com.facebook.imagepipeline.core.ImagePipeline; -import com.facebook.imagepipeline.core.ImagePipelineFactory; -import com.facebook.infer.annotation.Nullsafe; -import java.util.List; - -/** Helper class for dealing with multisource images. */ -@Nullsafe(Nullsafe.Mode.LOCAL) -public class MultiSourceHelper { - - public static class MultiSourceResult { - private final @Nullable ImageSource bestResult; - private final @Nullable ImageSource bestResultInCache; - - private MultiSourceResult( - @Nullable ImageSource bestResult, @Nullable ImageSource bestResultInCache) { - this.bestResult = bestResult; - this.bestResultInCache = bestResultInCache; - } - - /** - * Get the best result overall (closest in size to the view's size). Can be null if there were - * no sources to choose from, or if there were more than 1 sources but width/height were 0. - */ - public @Nullable ImageSource getBestResult() { - return bestResult; - } - - /** - * Get the best result (closest in size to the view's size) that is also in cache. If this would - * be the same as the source from {@link #getBestResult()}, this will return {@code null} - * instead. - */ - public @Nullable ImageSource getBestResultInCache() { - return bestResultInCache; - } - } - - public static MultiSourceResult getBestSourceForSize( - int width, int height, List sources) { - return getBestSourceForSize(width, height, sources, 1.0d); - } - - /** - * Chooses the image source with the size closest to the target image size. - * - * @param width the width of the view that will be used to display this image - * @param height the height of the view that will be used to display this image - * @param sources the list of potential image sources to choose from - * @param multiplier the area of the view will be multiplied by this number before calculating the - * best source; this is useful if the image will be displayed bigger than the view (e.g. - * zoomed) - */ - public static MultiSourceResult getBestSourceForSize( - int width, int height, List sources, double multiplier) { - // no sources - if (sources.isEmpty()) { - return new MultiSourceResult(null, null); - } - - // single source - if (sources.size() == 1) { - return new MultiSourceResult(sources.get(0), null); - } - - // For multiple sources, we first need the view's size in order to determine the best source to - // load. If we haven't been measured yet, return null and wait for onSizeChanged. - if (width <= 0 || height <= 0) { - return new MultiSourceResult(null, null); - } - - ImagePipeline imagePipeline = ImagePipelineFactory.getInstance().getImagePipeline(); - ImageSource best = null; - ImageSource bestCached = null; - final double viewArea = width * height * multiplier; - double bestPrecision = Double.MAX_VALUE; - double bestCachePrecision = Double.MAX_VALUE; - for (ImageSource source : sources) { - double precision = Math.abs(1.0 - source.getSize() / viewArea); - if (precision < bestPrecision) { - bestPrecision = precision; - best = source; - } - if (precision < bestCachePrecision - && (imagePipeline.isInBitmapMemoryCache(source.getUri()) - || imagePipeline.isInDiskCacheSync(source.getUri()))) { - bestCachePrecision = precision; - bestCached = source; - } - } - if (bestCached != null && best != null && bestCached.getSource().equals(best.getSource())) { - bestCached = null; - } - return new MultiSourceResult(best, bestCached); - } -} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/imagehelper/MultiSourceHelper.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/imagehelper/MultiSourceHelper.kt new file mode 100644 index 00000000000000..bd2711ed81b60a --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/imagehelper/MultiSourceHelper.kt @@ -0,0 +1,89 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.facebook.react.views.imagehelper + +import com.facebook.imagepipeline.core.ImagePipelineFactory + +/** Helper class for dealing with multisource images. */ +public object MultiSourceHelper { + @JvmStatic + public fun getBestSourceForSize( + width: Int, + height: Int, + sources: List + ): MultiSourceResult = getBestSourceForSize(width, height, sources, 1.0) + + /** + * Chooses the image source with the size closest to the target image size. + * + * @param width the width of the view that will be used to display this image + * @param height the height of the view that will be used to display this image + * @param sources the list of potential image sources to choose from + * @param multiplier the area of the view will be multiplied by this number before calculating the + * best source; this is useful if the image will be displayed bigger than the view (e.g. zoomed) + */ + @JvmStatic + public fun getBestSourceForSize( + width: Int, + height: Int, + sources: List, + multiplier: Double + ): MultiSourceResult { + // no sources + if (sources.isEmpty()) { + return MultiSourceResult(null, null) + } + + // single source + if (sources.size == 1) { + return MultiSourceResult(sources[0], null) + } + + // For multiple sources, we first need the view's size in order to determine the best source to + // load. If we haven't been measured yet, return null and wait for onSizeChanged. + if (width <= 0 || height <= 0) { + return MultiSourceResult(null, null) + } + val imagePipeline = ImagePipelineFactory.getInstance().imagePipeline + var best: ImageSource? = null + var bestCached: ImageSource? = null + val viewArea = width * height * multiplier + var bestPrecision = Double.MAX_VALUE + var bestCachePrecision = Double.MAX_VALUE + for (source in sources) { + val precision = Math.abs(1.0 - source.size / viewArea) + if (precision < bestPrecision) { + bestPrecision = precision + best = source + } + if (precision < bestCachePrecision && + (imagePipeline.isInBitmapMemoryCache(source.uri) || + imagePipeline.isInDiskCacheSync(source.uri))) { + bestCachePrecision = precision + bestCached = source + } + } + if (bestCached != null && best != null && bestCached.source == best.source) { + bestCached = null + } + return MultiSourceResult(best, bestCached) + } + + public class MultiSourceResult( + /** + * Get the best result overall (closest in size to the view's size). Can be null if there were + * no sources to choose from, or if there were more than 1 sources but width/height were 0. + */ + @JvmField public val bestResult: ImageSource?, + /** + * Get the best result (closest in size to the view's size) that is also in cache. If this + * would be the same as the source from [.getBestResult], this will return `null` instead. + */ + @JvmField public val bestResultInCache: ImageSource? + ) +}