mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
0c9afec7dc
Summary: Use ImageRequestBuilder directly in Nodes, just like we do for non-Nodes. Reviewed By: sriramramani Differential Revision: D3660610 Ninja: Sandcastle is broken. 25 denizens of the Facebook republic are affected by this unrelated issue today.
306 lines
9.2 KiB
Java
306 lines
9.2 KiB
Java
/**
|
|
* 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.LinkedList;
|
|
import java.util.List;
|
|
|
|
import android.content.Context;
|
|
import android.graphics.Bitmap;
|
|
import android.graphics.BitmapShader;
|
|
import android.graphics.Canvas;
|
|
import android.graphics.Matrix;
|
|
import android.graphics.Paint;
|
|
import android.graphics.Path;
|
|
import android.graphics.PorterDuff;
|
|
import android.graphics.PorterDuffColorFilter;
|
|
import android.graphics.Shader;
|
|
|
|
import com.facebook.drawee.drawable.ScalingUtils.ScaleType;
|
|
import com.facebook.imagepipeline.request.ImageRequest;
|
|
import com.facebook.imagepipeline.request.ImageRequestBuilder;
|
|
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;
|
|
import com.facebook.react.views.imagehelper.ImageSource;
|
|
import com.facebook.react.views.imagehelper.MultiSourceHelper;
|
|
import com.facebook.react.views.imagehelper.MultiSourceHelper.MultiSourceResult;
|
|
|
|
/**
|
|
* DrawImageWithPipeline is DrawCommand that can draw a local or remote image.
|
|
* It uses PipelineRequestHelper internally to fetch and cache the images.
|
|
*/
|
|
/* package */ final class DrawImageWithPipeline extends AbstractDrawBorder
|
|
implements DrawImage, BitmapUpdateListener {
|
|
|
|
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 final List<ImageSource> mSources = new LinkedList<>();
|
|
private @Nullable Context mContext;
|
|
private final Matrix mTransform = new Matrix();
|
|
private ScaleType mScaleType = ImageResizeMode.defaultValue();
|
|
private @Nullable PipelineRequestHelper mRequestHelper;
|
|
private @Nullable PorterDuffColorFilter mColorFilter;
|
|
private @Nullable FlatViewGroup.InvalidateCallback mCallback;
|
|
private @Nullable Path mPathForRoundedBitmap;
|
|
private @Nullable BitmapShader mBitmapShader;
|
|
private float mHorizontalPadding;
|
|
private float mVerticalPadding;
|
|
private int mReactTag;
|
|
|
|
// variables used for fading the image in
|
|
private long mFirstDrawTime = -1;
|
|
private int mFadeDuration = ReactImageView.REMOTE_IMAGE_FADE_DURATION_MS;
|
|
|
|
@Override
|
|
public boolean hasImageRequest() {
|
|
return !mSources.isEmpty();
|
|
}
|
|
|
|
@Override
|
|
public void setSource(Context context, @Nullable ReadableArray sources) {
|
|
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) {
|
|
ReadableMap source = sources.getMap(0);
|
|
mSources.add(new ImageSource(context, source.getString("uri")));
|
|
} else {
|
|
for (int idx = 0; idx < sources.size(); idx++) {
|
|
ReadableMap source = sources.getMap(idx);
|
|
mSources.add(new ImageSource(
|
|
context,
|
|
source.getString("uri"),
|
|
source.getDouble("width"),
|
|
source.getDouble("height")));
|
|
}
|
|
}
|
|
}
|
|
mContext = context;
|
|
mBitmapShader = null;
|
|
}
|
|
|
|
@Override
|
|
public void setTintColor(int tintColor) {
|
|
if (tintColor == 0) {
|
|
mColorFilter = null;
|
|
} else {
|
|
mColorFilter = new PorterDuffColorFilter(tintColor, PorterDuff.Mode.SRC_ATOP);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void setScaleType(ScaleType scaleType) {
|
|
mScaleType = scaleType;
|
|
}
|
|
|
|
@Override
|
|
public ScaleType getScaleType() {
|
|
return mScaleType;
|
|
}
|
|
|
|
@Override
|
|
public void setReactTag(int reactTag) {
|
|
mReactTag = reactTag;
|
|
}
|
|
|
|
@Override
|
|
public void setFadeDuration(int fadeDuration) {
|
|
mFadeDuration = fadeDuration;
|
|
}
|
|
|
|
/**
|
|
* If the bitmap isn't loaded by the first draw, fade it in, otherwise don't fade.
|
|
*/
|
|
@Override
|
|
protected void onPreDraw(FlatViewGroup parent, Canvas canvas) {
|
|
super.onPreDraw(parent, canvas);
|
|
|
|
Bitmap bitmap = Assertions.assumeNotNull(mRequestHelper).getBitmap();
|
|
if (bitmap == null) {
|
|
mFirstDrawTime = 0;
|
|
} else {
|
|
if (mFirstDrawTime == -1) {
|
|
PAINT.setAlpha(255);
|
|
} else {
|
|
long currentDrawingTime = parent.getDrawingTime();
|
|
if (mFirstDrawTime == 0) {
|
|
mFirstDrawTime = currentDrawingTime - 16; // -16 to skip one draw cycle, so that we start
|
|
// with a non-zero alpha (otherwise, we waste 16ms doing nothing during this cycle).
|
|
}
|
|
int alpha = (int) (255.0f * (1.0f * (currentDrawingTime - mFirstDrawTime) / mFadeDuration));
|
|
if (alpha >= 255) {
|
|
mFirstDrawTime = -1;
|
|
alpha = 255;
|
|
} else {
|
|
Assertions.assumeNotNull(mCallback).invalidate();
|
|
}
|
|
PAINT.setAlpha(alpha);
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected void onDraw(Canvas canvas) {
|
|
Bitmap bitmap = Assertions.assumeNotNull(mRequestHelper).getBitmap();
|
|
if (bitmap == null) {
|
|
return;
|
|
}
|
|
|
|
PAINT.setColorFilter(mColorFilter);
|
|
|
|
if (getBorderRadius() < 0.5f) {
|
|
canvas.drawBitmap(bitmap, mTransform, PAINT);
|
|
} else {
|
|
if (mBitmapShader == null) {
|
|
mBitmapShader = new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
|
|
mBitmapShader.setLocalMatrix(mTransform);
|
|
}
|
|
PAINT.setShader(mBitmapShader);
|
|
canvas.drawPath(getPathForRoundedBitmap(), PAINT);
|
|
}
|
|
|
|
drawBorders(canvas);
|
|
}
|
|
|
|
@Override
|
|
protected boolean shouldClip() {
|
|
return getLeft() + mHorizontalPadding < getClipLeft() ||
|
|
getTop() + mVerticalPadding < getClipTop() ||
|
|
getRight() - mHorizontalPadding > getClipRight() ||
|
|
getBottom() - mVerticalPadding < getClipBottom();
|
|
}
|
|
|
|
@Override
|
|
public void setBorderRadius(float borderRadius) {
|
|
super.setBorderRadius(borderRadius);
|
|
setFlag(BORDER_BITMAP_PATH_DIRTY);
|
|
}
|
|
|
|
@Override
|
|
public void onAttached(FlatViewGroup.InvalidateCallback callback) {
|
|
mCallback = callback;
|
|
Assertions.assumeNotNull(mRequestHelper).attach(this);
|
|
}
|
|
|
|
@Override
|
|
public void onDetached() {
|
|
Assertions.assumeNotNull(mRequestHelper).detach();
|
|
|
|
if (mRequestHelper.isDetached()) {
|
|
// Make sure we don't hold on to the Bitmap.
|
|
mBitmapShader = null;
|
|
// this is optional
|
|
mCallback = null;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected void onBoundsChanged() {
|
|
super.onBoundsChanged();
|
|
setFlag(BORDER_BITMAP_PATH_DIRTY);
|
|
computeRequestHelper();
|
|
}
|
|
|
|
@Override
|
|
public void onSecondaryAttach(Bitmap bitmap) {
|
|
updateBounds(bitmap);
|
|
}
|
|
|
|
@Override
|
|
public void onBitmapReady(Bitmap bitmap) {
|
|
updateBounds(bitmap);
|
|
}
|
|
|
|
@Override
|
|
public void onImageLoadEvent(int imageLoadEvent) {
|
|
if (mReactTag != 0 && mCallback != null) {
|
|
mCallback.dispatchImageLoadEvent(mReactTag, imageLoadEvent);
|
|
}
|
|
}
|
|
|
|
private void computeRequestHelper() {
|
|
MultiSourceResult multiSource = MultiSourceHelper.getBestSourceForSize(
|
|
Math.round(getRight() - getLeft()),
|
|
Math.round(getBottom() - getTop()),
|
|
mSources);
|
|
ImageSource source = multiSource.getBestResult();
|
|
if (source == null) {
|
|
mRequestHelper = null;
|
|
return;
|
|
}
|
|
ImageRequest imageRequest = ImageRequestBuilder.newBuilderWithSource(source.getUri()).build();
|
|
|
|
// DrawImageWithPipeline does now support displaying low res cache images before request
|
|
mRequestHelper = new PipelineRequestHelper(Assertions.assertNotNull(imageRequest));
|
|
}
|
|
|
|
/* package */ void updateBounds(Bitmap bitmap) {
|
|
Assertions.assumeNotNull(mCallback).invalidate();
|
|
|
|
float left = getLeft();
|
|
float top = getTop();
|
|
|
|
float containerWidth = getRight() - left;
|
|
float containerHeight = getBottom() - top;
|
|
|
|
float imageWidth = (float) bitmap.getWidth();
|
|
float imageHeight = (float) bitmap.getHeight();
|
|
|
|
if (mScaleType == ScaleType.FIT_XY) {
|
|
mTransform.setScale(containerWidth / imageWidth, containerHeight / imageHeight);
|
|
mTransform.postTranslate(left, top);
|
|
mHorizontalPadding = mVerticalPadding = 0;
|
|
return;
|
|
}
|
|
|
|
final float scale;
|
|
|
|
if (mScaleType == ScaleType.CENTER_INSIDE) {
|
|
if (containerWidth >= imageWidth && containerHeight >= imageHeight) {
|
|
scale = 1.0f;
|
|
} else {
|
|
scale = Math.min(containerWidth / imageWidth, containerHeight / imageHeight);
|
|
}
|
|
} else {
|
|
scale = Math.max(containerWidth / imageWidth, containerHeight / imageHeight);
|
|
}
|
|
|
|
mHorizontalPadding = (containerWidth - imageWidth * scale) / 2;
|
|
mVerticalPadding = (containerHeight - imageHeight * scale) / 2;
|
|
|
|
mTransform.setScale(scale, scale);
|
|
mTransform.postTranslate(left + mHorizontalPadding, top + mVerticalPadding);
|
|
|
|
if (mBitmapShader != null) {
|
|
mBitmapShader.setLocalMatrix(mTransform);
|
|
}
|
|
}
|
|
|
|
private Path getPathForRoundedBitmap() {
|
|
if (isFlagSet(BORDER_BITMAP_PATH_DIRTY)) {
|
|
if (mPathForRoundedBitmap == null) {
|
|
mPathForRoundedBitmap = new Path();
|
|
}
|
|
|
|
updatePath(mPathForRoundedBitmap, 1.0f);
|
|
|
|
resetFlag(BORDER_BITMAP_PATH_DIRTY);
|
|
}
|
|
|
|
return mPathForRoundedBitmap;
|
|
}
|
|
}
|