From c61aafe95db4ad26b78650c4156250a6e730f736 Mon Sep 17 00:00:00 2001 From: Andrei Coman Date: Thu, 16 Jun 2016 07:13:57 -0700 Subject: [PATCH] Support multiple sources Summary: The nodes version of D3364550. The only difference is that here we don't get `onSizeChanged` but `onBoundsChanged`, and we need to compute the height/width of the target image from those bounds. ahmedre please let me know if any of these assumptions are in any way incorrect. Reviewed By: ahmedre Differential Revision: D3424843 --- .../com/facebook/react/flat/DrawImage.java | 3 +- .../react/flat/DrawImageWithDrawee.java | 59 +++++++++++++++---- .../react/flat/DrawImageWithPipeline.java | 57 ++++++++++++++---- .../react/flat/MultiSourceImageHelper.java | 40 +++++++++++++ .../com/facebook/react/flat/RCTImageView.java | 4 +- 5 files changed, 136 insertions(+), 27 deletions(-) create mode 100644 ReactAndroid/src/main/java/com/facebook/react/flat/MultiSourceImageHelper.java diff --git a/ReactAndroid/src/main/java/com/facebook/react/flat/DrawImage.java b/ReactAndroid/src/main/java/com/facebook/react/flat/DrawImage.java index 03f5f28d47..96c9d4dc0a 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/flat/DrawImage.java +++ b/ReactAndroid/src/main/java/com/facebook/react/flat/DrawImage.java @@ -14,6 +14,7 @@ import javax.annotation.Nullable; import android.content.Context; import com.facebook.drawee.drawable.ScalingUtils.ScaleType; +import com.facebook.react.bridge.ReadableArray; /** * Common interface for DrawImageWithPipeline and DrawImageWithDrawee. @@ -28,7 +29,7 @@ import com.facebook.drawee.drawable.ScalingUtils.ScaleType; /** * Assigns a new image source to the DrawImage, or null to clear the image request. */ - void setSource(Context context, @Nullable String source); + void setSource(Context context, @Nullable ReadableArray sources); /** * Assigns a tint color to apply to the image drawn. diff --git a/ReactAndroid/src/main/java/com/facebook/react/flat/DrawImageWithDrawee.java b/ReactAndroid/src/main/java/com/facebook/react/flat/DrawImageWithDrawee.java index 1f2b5a3b57..b5a477b9bd 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/flat/DrawImageWithDrawee.java +++ b/ReactAndroid/src/main/java/com/facebook/react/flat/DrawImageWithDrawee.java @@ -11,6 +11,9 @@ package com.facebook.react.flat; import javax.annotation.Nullable; +import java.util.HashMap; +import java.util.Map; + import android.content.Context; import android.graphics.Canvas; import android.graphics.PorterDuff; @@ -23,6 +26,8 @@ import com.facebook.drawee.generic.GenericDraweeHierarchy; import com.facebook.drawee.generic.RoundingParams; import com.facebook.imagepipeline.request.ImageRequest; import com.facebook.infer.annotation.Assertions; +import com.facebook.react.bridge.ReadableArray; +import com.facebook.react.bridge.ReadableMap; import com.facebook.react.views.image.ImageLoadEvent; import com.facebook.react.views.image.ImageResizeMode; import com.facebook.react.views.image.ReactImageView; @@ -34,7 +39,8 @@ import com.facebook.react.views.image.ReactImageView; /* package */ final class DrawImageWithDrawee extends AbstractDrawCommand implements DrawImage, ControllerListener { - private @Nullable String mSource; + private @Nullable Map mSources; + private @Nullable String mImageSource; private @Nullable Context mContext; private @Nullable DraweeRequestHelper mRequestHelper; private @Nullable PorterDuffColorFilter mColorFilter; @@ -48,12 +54,28 @@ import com.facebook.react.views.image.ReactImageView; @Override public boolean hasImageRequest() { - return mSource != null; + return mSources != null && !mSources.isEmpty(); } @Override - public void setSource(Context context, @Nullable String source) { - mSource = source; + public void setSource(Context context, @Nullable ReadableArray sources) { + if (mSources == null) { + mSources = new HashMap<>(); + } + mSources.clear(); + if (sources != null && sources.size() != 0) { + // Optimize for the case where we have just one uri, case in which we don't need the sizes + if (sources.size() == 1) { + mSources.put(sources.getMap(0).getString("uri"), 0.0d); + } else { + for (int idx = 0; idx < sources.size(); idx++) { + ReadableMap source = sources.getMap(idx); + mSources.put( + source.getString("uri"), + source.getDouble("width") * source.getDouble("height")); + } + } + } mContext = context; } @@ -202,23 +224,36 @@ import com.facebook.react.views.image.ReactImageView; @Override protected void onBoundsChanged() { super.onBoundsChanged(); - maybeComputeRequestHelper(); + computeRequestHelper(); } - private void maybeComputeRequestHelper() { - if (mRequestHelper != null) { - return; - } - - if (mSource == null) { + private void computeRequestHelper() { + mImageSource = getSourceImage(); + if (mImageSource == null) { mRequestHelper = null; return; } ImageRequest imageRequest = - ImageRequestHelper.createImageRequest(Assertions.assertNotNull(mContext), mSource); + ImageRequestHelper.createImageRequest(Assertions.assertNotNull(mContext), + mImageSource); mRequestHelper = new DraweeRequestHelper(Assertions.assertNotNull(imageRequest), this); } + private @Nullable String getSourceImage() { + if (mSources == null || mSources.isEmpty()) { + return null; + } + if (hasMultipleSources()) { + final double targetImageSize = (getRight() - getLeft()) * (getBottom() - getTop()); + return MultiSourceImageHelper.getImageSourceFromMultipleSources(targetImageSize, mSources); + } + return mSources.keySet().iterator().next(); + } + + private boolean hasMultipleSources() { + return Assertions.assertNotNull(mSources).size() > 1; + } + private boolean shouldDisplayBorder() { return mBorderColor != 0 || mBorderRadius >= 0.5f; } diff --git a/ReactAndroid/src/main/java/com/facebook/react/flat/DrawImageWithPipeline.java b/ReactAndroid/src/main/java/com/facebook/react/flat/DrawImageWithPipeline.java index 7da5e4d336..070b8f1593 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/flat/DrawImageWithPipeline.java +++ b/ReactAndroid/src/main/java/com/facebook/react/flat/DrawImageWithPipeline.java @@ -11,6 +11,9 @@ package com.facebook.react.flat; import javax.annotation.Nullable; +import java.util.HashMap; +import java.util.Map; + import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapShader; @@ -25,6 +28,8 @@ import android.graphics.Shader; import com.facebook.drawee.drawable.ScalingUtils.ScaleType; import com.facebook.imagepipeline.request.ImageRequest; import com.facebook.infer.annotation.Assertions; +import com.facebook.react.bridge.ReadableArray; +import com.facebook.react.bridge.ReadableMap; import com.facebook.react.views.image.ImageResizeMode; import com.facebook.react.views.image.ReactImageView; @@ -38,7 +43,8 @@ import com.facebook.react.views.image.ReactImageView; private static final Paint PAINT = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG); private static final int BORDER_BITMAP_PATH_DIRTY = 1 << 1; - private @Nullable String mSource; + private @Nullable Map mSources; + private @Nullable String mImageSource; private @Nullable Context mContext; private final Matrix mTransform = new Matrix(); private ScaleType mScaleType = ImageResizeMode.defaultValue(); @@ -61,8 +67,24 @@ import com.facebook.react.views.image.ReactImageView; } @Override - public void setSource(Context context, @Nullable String source) { - mSource = source; + public void setSource(Context context, @Nullable ReadableArray sources) { + if (mSources == null) { + mSources = new HashMap<>(); + } + mSources.clear(); + if (sources != null && sources.size() != 0) { + // Optimize for the case where we have just one uri, case in which we don't need the sizes + if (sources.size() == 1) { + mSources.put(sources.getMap(0).getString("uri"), 0.0d); + } else { + for (int idx = 0; idx < sources.size(); idx++) { + ReadableMap source = sources.getMap(idx); + mSources.put( + source.getString("uri"), + source.getDouble("width") * source.getDouble("height")); + } + } + } mContext = context; mBitmapShader = null; } @@ -183,7 +205,7 @@ import com.facebook.react.views.image.ReactImageView; protected void onBoundsChanged() { super.onBoundsChanged(); setFlag(BORDER_BITMAP_PATH_DIRTY); - maybeComputeRequestHelper(); + computeRequestHelper(); } @Override @@ -203,20 +225,33 @@ import com.facebook.react.views.image.ReactImageView; } } - private void maybeComputeRequestHelper() { - if (mRequestHelper == null) { - return; - } - - if (mSource == null) { + private void computeRequestHelper() { + mImageSource = getSourceImage(); + if (mImageSource == null) { mRequestHelper = null; return; } ImageRequest imageRequest = - ImageRequestHelper.createImageRequest(Assertions.assertNotNull(mContext), mSource); + ImageRequestHelper.createImageRequest(Assertions.assertNotNull(mContext), + mImageSource); mRequestHelper = new PipelineRequestHelper(Assertions.assertNotNull(imageRequest)); } + private String getSourceImage() { + if (mSources == null || mSources.isEmpty()) { + return null; + } + if (hasMultipleSources()) { + final double targetImageSize = (getRight() - getLeft()) * (getBottom() - getTop()); + return MultiSourceImageHelper.getImageSourceFromMultipleSources(targetImageSize, mSources); + } + return mSources.keySet().iterator().next(); + } + + private boolean hasMultipleSources() { + return Assertions.assertNotNull(mSources).size() > 1; + } + /* package */ void updateBounds(Bitmap bitmap) { Assertions.assumeNotNull(mCallback).invalidate(); diff --git a/ReactAndroid/src/main/java/com/facebook/react/flat/MultiSourceImageHelper.java b/ReactAndroid/src/main/java/com/facebook/react/flat/MultiSourceImageHelper.java new file mode 100644 index 0000000000..0820940533 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/flat/MultiSourceImageHelper.java @@ -0,0 +1,40 @@ +/** + * 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.flat; + +import javax.annotation.Nullable; + +import java.util.Map; + +/** + * Helper class for computing the source to be used for an Image. + */ +/* package */ class MultiSourceImageHelper { + + /** + * 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. + */ + public static @Nullable String getImageSourceFromMultipleSources( + double targetImageSize, + Map sources) { + double bestPrecision = Double.MAX_VALUE; + String imageSource = null; + for (Map.Entry source : sources.entrySet()) { + final double precision = Math.abs(1.0 - (source.getValue()) / targetImageSize); + if (precision < bestPrecision) { + bestPrecision = precision; + imageSource = source.getKey(); + } + } + return imageSource; + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/flat/RCTImageView.java b/ReactAndroid/src/main/java/com/facebook/react/flat/RCTImageView.java index 75b32c4786..c179d3e6a9 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/flat/RCTImageView.java +++ b/ReactAndroid/src/main/java/com/facebook/react/flat/RCTImageView.java @@ -88,9 +88,7 @@ import com.facebook.react.views.image.ImageResizeMode; @ReactProp(name = "src") public void setSource(@Nullable ReadableArray sources) { - final String source = - (sources == null || sources.size() == 0) ? null : sources.getMap(0).getString("uri"); - getMutableDrawImage().setSource(getThemedContext(), source); + getMutableDrawImage().setSource(getThemedContext(), sources); } @ReactProp(name = "tintColor")