-
Notifications
You must be signed in to change notification settings - Fork 24.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Support Image resizeMode=repeat on Android
Summary: `<Image resizeMode="repeat" />` for Android, matching the iOS implementation (#7968). (Non-goal: changing the component's API for finer-grained control / feature parity with CSS - this would be nice in the future) As requested in e.g. #14158. Given facebook/fresco#1575, and lacking the context to follow the specific recommendations in facebook/fresco#1575 (comment), I've opted for a minimal change within RN itself. It's likely that performance can be improved by offloading this work to Fresco in some clever way; but I'm assuming that the present naive approach is still an improvement over a userland implementation with `onLayout` and multiple `<Image>` instances. - Picking up on a TODO note in the existing code, I implemented `MultiPostprocessor` to allow arbitrary chaining of Fresco-compatible postprocessors inside `ReactImageView`. - Rather than extensively refactor `ImageResizeMode`, `ReactImageManager` and `ReactImageView`, I mostly preserved the existing API that maps `resizeMode` values to [`ScaleType`](http://frescolib.org/javadoc/reference/com/facebook/drawee/drawable/ScalingUtils.ScaleType.html) instances, and simply added a second mapping, to [`TileMode`](https://developer.android.com/reference/android/graphics/Shader.TileMode.html). - To match the iOS rendering exactly for oversized images, I found that scaling with a custom `ScaleType` was required - a kind of combination of `CENTER_INSIDE` and `FIT_START` which Fresco doesn't provide - so I implemented that as `ScaleTypeStartInside`. (This is, frankly, questionable as the default behaviour on iOS to begin with - but I am aiming for parity here) - `resizeMode="repeat"` is therefore unpacked by the view manager to the effect of: ```js view.setScaleType(ScaleTypeStartInside.INSTANCE); view.setTileMode(Shader.TileMode.REPEAT); ``` And the added postprocessing in the view (in case of a non-`CLAMP` tile mode) consists of waiting for layout, allocating a destination bitmap and painting the source bitmap with the requested tile mode and scale type. Note that as in #17398 (comment), I have neither updated nor tested the "Flat" UI implementation - everything compiles but I've taken [this comment](#12770 (comment)) to mean there's no point in trying to wade through it on my own right now; I'm happy to tackle it if given some pointers. Also, I'm happy to address any code style issues or other feedback; I'm new to this codebase and a very infrequent Android/Java coder. Tested by enabling the relevant case in RNTester on Android. | iOS | Android | |-|-| | <img src=https://user-images.githubusercontent.com/2246565/34461897-4e12008e-ee2f-11e7-8581-1dc0cc8f2779.png width=300>| <img src=https://user-images.githubusercontent.com/2246565/34461894-40b2c8ec-ee2f-11e7-8a8f-96704f3c8caa.png width=300> | Docs update: facebook/react-native-website#106 [ANDROID] [FEATURE] [Image] - Implement resizeMode=repeat Closes #17404 Reviewed By: achen1 Differential Revision: D7070329 Pulled By: mdvacca fbshipit-source-id: 6a72fcbdcc7c7c2daf293dc1d8b6728f54ad0249
- Loading branch information
1 parent
1dde989
commit 0459e4f
Showing
7 changed files
with
227 additions
and
19 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
79 changes: 79 additions & 0 deletions
79
ReactAndroid/src/main/java/com/facebook/react/views/image/MultiPostprocessor.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
/** | ||
* Copyright (c) 2017-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.image; | ||
|
||
import android.graphics.Bitmap; | ||
|
||
import com.facebook.cache.common.CacheKey; | ||
import com.facebook.cache.common.MultiCacheKey; | ||
import com.facebook.common.references.CloseableReference; | ||
import com.facebook.imagepipeline.bitmaps.PlatformBitmapFactory; | ||
import com.facebook.imagepipeline.request.Postprocessor; | ||
|
||
import java.util.LinkedList; | ||
import java.util.List; | ||
|
||
public class MultiPostprocessor implements Postprocessor { | ||
private final List<Postprocessor> mPostprocessors; | ||
|
||
public static Postprocessor from(List<Postprocessor> postprocessors) { | ||
switch (postprocessors.size()) { | ||
case 0: | ||
return null; | ||
case 1: | ||
return postprocessors.get(0); | ||
default: | ||
return new MultiPostprocessor(postprocessors); | ||
} | ||
} | ||
|
||
private MultiPostprocessor(List<Postprocessor> postprocessors) { | ||
mPostprocessors = new LinkedList<>(postprocessors); | ||
} | ||
|
||
@Override | ||
public String getName () { | ||
StringBuilder name = new StringBuilder(); | ||
for (Postprocessor p: mPostprocessors) { | ||
if (name.length() > 0) { | ||
name.append(","); | ||
} | ||
name.append(p.getName()); | ||
} | ||
name.insert(0, "MultiPostProcessor ("); | ||
name.append(")"); | ||
return name.toString(); | ||
} | ||
|
||
@Override | ||
public CacheKey getPostprocessorCacheKey () { | ||
LinkedList<CacheKey> keys = new LinkedList<>(); | ||
for (Postprocessor p: mPostprocessors) { | ||
keys.push(p.getPostprocessorCacheKey()); | ||
} | ||
return new MultiCacheKey(keys); | ||
} | ||
|
||
@Override | ||
public CloseableReference<Bitmap> process(Bitmap sourceBitmap, PlatformBitmapFactory bitmapFactory) { | ||
CloseableReference<Bitmap> prevBitmap = null, nextBitmap = null; | ||
|
||
try { | ||
for (Postprocessor p : mPostprocessors) { | ||
nextBitmap = p.process(prevBitmap != null ? prevBitmap.get() : sourceBitmap, bitmapFactory); | ||
CloseableReference.closeSafely(prevBitmap); | ||
prevBitmap = nextBitmap.clone(); | ||
} | ||
return nextBitmap.clone(); | ||
} finally { | ||
CloseableReference.closeSafely(nextBitmap); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
40 changes: 40 additions & 0 deletions
40
ReactAndroid/src/main/java/com/facebook/react/views/image/ScaleTypeStartInside.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
/** | ||
* Copyright (c) 2017-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.image; | ||
|
||
import android.graphics.Matrix; | ||
import android.graphics.Rect; | ||
import com.facebook.drawee.drawable.ScalingUtils; | ||
|
||
public class ScaleTypeStartInside extends ScalingUtils.AbstractScaleType { | ||
public static final ScalingUtils.ScaleType INSTANCE = new ScaleTypeStartInside(); | ||
|
||
@Override | ||
public void getTransformImpl( | ||
Matrix outTransform, | ||
Rect parentRect, | ||
int childWidth, | ||
int childHeight, | ||
float focusX, | ||
float focusY, | ||
float scaleX, | ||
float scaleY) { | ||
float scale = Math.min(Math.min(scaleX, scaleY), 1.0f); | ||
float dx = parentRect.left; | ||
float dy = parentRect.top; | ||
outTransform.setScale(scale, scale); | ||
outTransform.postTranslate((int) (dx + 0.5f), (int) (dy + 0.5f)); | ||
} | ||
|
||
@Override | ||
public String toString() { | ||
return "start_inside"; | ||
} | ||
} |