diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/image/BUCK b/ReactAndroid/src/main/java/com/facebook/react/views/image/BUCK index 60239e6721d..3eb51d8180b 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/image/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/views/image/BUCK @@ -9,7 +9,7 @@ android_library( react_native_target('java/com/facebook/csslayout:csslayout'), react_native_target('java/com/facebook/react/uimanager:uimanager'), react_native_target('java/com/facebook/react/uimanager/annotations:annotations'), - react_native_target('java/com/facebook/react/views/imagehelper:imagehelper'), + react_native_target('java/com/facebook/react/views/imagehelper:withmultisource'), react_native_dep('libraries/fresco/fresco-react-native:fbcore'), react_native_dep('libraries/fresco/fresco-react-native:fresco-react-native'), react_native_dep('libraries/fresco/fresco-react-native:fresco-drawee'), diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageView.java b/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageView.java index 9cda9d0f156..a864b140c81 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageView.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageView.java @@ -43,8 +43,6 @@ import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder; import com.facebook.drawee.generic.RoundingParams; import com.facebook.drawee.view.GenericDraweeView; import com.facebook.imagepipeline.common.ResizeOptions; -import com.facebook.imagepipeline.core.ImagePipeline; -import com.facebook.imagepipeline.core.ImagePipelineFactory; import com.facebook.imagepipeline.image.ImageInfo; import com.facebook.imagepipeline.request.BasePostprocessor; import com.facebook.imagepipeline.request.ImageRequest; @@ -58,7 +56,9 @@ import com.facebook.react.uimanager.PixelUtil; import com.facebook.react.uimanager.UIManagerModule; import com.facebook.react.uimanager.events.EventDispatcher; import com.facebook.react.views.imagehelper.ImageSource; +import com.facebook.react.views.imagehelper.MultiSourceHelper; import com.facebook.react.views.imagehelper.ResourceDrawableIdHelper; +import com.facebook.react.views.imagehelper.MultiSourceHelper.MultiSourceResult; /** * Wrapper class around Fresco's GenericDraweeView, enabling persisting props across multiple view @@ -441,45 +441,16 @@ public class ReactImageView extends GenericDraweeView { return; } if (hasMultipleSources()) { - setImageSourceFromMultipleSources(); + MultiSourceResult multiSource = + MultiSourceHelper.getBestSourceForSize(getWidth(), getHeight(), mSources); + mImageSource = multiSource.getBestResult(); + mCachedImageSource = multiSource.getBestResultInCache(); return; } mImageSource = mSources.get(0); } - /** - * Chooses the image source with the size closest to the target image size. Must be called only - * after the layout pass when the sizes of the target image have been computed, and when there - * are at least two sources to choose from. - */ - private void setImageSourceFromMultipleSources() { - ImagePipeline imagePipeline = ImagePipelineFactory.getInstance().getImagePipeline(); - final double targetImageSize = getWidth() * getHeight(); - double bestPrecision = Double.MAX_VALUE; - double bestCachePrecision = Double.MAX_VALUE; - for (ImageSource source : mSources) { - final double precision = Math.abs(1.0 - (source.getSize()) / targetImageSize); - if (precision < bestPrecision) { - bestPrecision = precision; - mImageSource = source; - } - - if (precision < bestCachePrecision && - (imagePipeline.isInBitmapMemoryCache(source.getUri()) || - imagePipeline.isInDiskCacheSync(source.getUri()))) { - bestCachePrecision = precision; - mCachedImageSource = source; - } - } - - // don't use cached image source if it's the same as the image source - if (mCachedImageSource != null && - mImageSource.getSource().equals(mCachedImageSource.getSource())) { - mCachedImageSource = null; - } - } - private static boolean shouldResize(ImageSource imageSource) { // Resizing is inferior to scaling. See http://frescolib.org/docs/resizing-rotating.html#_ // We resize here only for images likely to be from the device's camera, where the app developer diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/imagehelper/BUCK b/ReactAndroid/src/main/java/com/facebook/react/views/imagehelper/BUCK index baf75d4f656..c945a1f8eea 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/imagehelper/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/views/imagehelper/BUCK @@ -2,7 +2,7 @@ include_defs('//ReactAndroid/DEFS') android_library( name = 'imagehelper', - srcs = glob(['*.java']), + srcs = glob(['*.java'], excludes=['MultiSourceHelper.java']), deps = [ react_native_dep('third-party/java/infer-annotations:infer-annotations'), react_native_dep('third-party/java/jsr-305:jsr-305'), @@ -12,6 +12,22 @@ android_library( ], ) +android_library( + name = 'withmultisource', + srcs = ['MultiSourceHelper.java'], + exported_deps = [ + ':imagehelper', + ], + deps = [ + react_native_dep('libraries/fresco/fresco-react-native:imagepipeline'), + react_native_dep('third-party/java/infer-annotations:infer-annotations'), + react_native_dep('third-party/java/jsr-305:jsr-305'), + ], + visibility = [ + 'PUBLIC', + ], +) + project_config( src_target = ':imagehelper', ) diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/imagehelper/MultiSourceHelper.java b/ReactAndroid/src/main/java/com/facebook/react/views/imagehelper/MultiSourceHelper.java new file mode 100644 index 00000000000..5dba715ab7d --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/views/imagehelper/MultiSourceHelper.java @@ -0,0 +1,105 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +package com.facebook.react.views.imagehelper; + +import javax.annotation.Nullable; + +import java.util.List; + +import com.facebook.imagepipeline.core.ImagePipeline; +import com.facebook.imagepipeline.core.ImagePipelineFactory; +import com.facebook.react.views.imagehelper.ImageSource; + +/** + * Helper class for dealing with multisource images. + */ +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; + } + } + + /** + * 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 + */ + public static MultiSourceResult getBestSourceForSize( + int width, + int height, + List sources) { + // 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; + 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); + } +}