react-native-fast-image 9.2.2 → 9.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +36 -7
- package/android/src/main/java/com/dylanvann/fastimage/FastImageBlur.java +18 -5
- package/android/src/main/java/com/dylanvann/fastimage/FastImageRequestListener.java +5 -1
- package/android/src/main/java/com/dylanvann/fastimage/FastImageSourceSize.java +22 -0
- package/android/src/main/java/com/dylanvann/fastimage/FastImageSvg.java +27 -0
- package/android/src/main/java/com/dylanvann/fastimage/FastImageSvgRenderer.java +10 -0
- package/android/src/main/java/com/dylanvann/fastimage/FastImageViewConverter.java +5 -0
- package/android/src/main/java/com/dylanvann/fastimage/FastImageViewManager.java +1 -1
- package/android/src/main/java/com/dylanvann/fastimage/FastImageViewWithUrl.java +157 -14
- package/dist/index.cjs.js +50 -18
- package/dist/index.cjs.js.flow +3 -0
- package/dist/index.cjs.web.js +241 -130
- package/dist/index.cjs.web.js.flow +3 -0
- package/dist/index.d.ts +38 -6
- package/dist/index.js +50 -18
- package/dist/index.js.flow +3 -0
- package/dist/index.web.js +243 -132
- package/dist/index.web.js.flow +3 -0
- package/dist/objectFit.d.ts +3 -0
- package/ios/FastImage/FFFDownsampledImage.h +8 -0
- package/ios/FastImage/FFFDownsampledImage.m +16 -0
- package/ios/FastImage/FFFastImageView.h +7 -1
- package/ios/FastImage/FFFastImageView.m +10 -5
- package/ios/FastImage/FFFastImageViewManager.m +3 -1
- package/ios/FastImage/RCTConvert+FFFastImage.h +3 -0
- package/ios/FastImage/RCTConvert+FFFastImage.m +11 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -91,7 +91,7 @@ const YourImage = () => (
|
|
|
91
91
|
headers: { Authorization: 'someAuthToken' },
|
|
92
92
|
priority: 'normal',
|
|
93
93
|
}}
|
|
94
|
-
|
|
94
|
+
objectFit="contain"
|
|
95
95
|
/>
|
|
96
96
|
)
|
|
97
97
|
```
|
|
@@ -235,16 +235,36 @@ On Android, `defaultSource` doesn't show in debug builds: there the dev server s
|
|
|
235
235
|
|
|
236
236
|
---
|
|
237
237
|
|
|
238
|
+
#### `objectFit`
|
|
239
|
+
|
|
240
|
+
**Type:** `ObjectFit` · **Default:** `'cover'`
|
|
241
|
+
|
|
242
|
+
How the image fits the view, as CSS's `object-fit` does.
|
|
243
|
+
|
|
244
|
+
- `'cover'`: scales it uniformly (keeping its aspect ratio) so it covers the view (minus padding), cropping what doesn't fit.
|
|
245
|
+
- `'contain'`: scales it uniformly (keeping its aspect ratio) so all of it fits in the view (minus padding).
|
|
246
|
+
- `'fill'`: scales its width and height separately to fill the view, which can change its aspect ratio.
|
|
247
|
+
- `'none'`: shows it at its own size, centered, cropped if it's larger than the view.
|
|
248
|
+
- `'scale-down'`: shows it at its own size, centered, or scaled down uniformly to fit if it's larger than the view (the smaller of `'none'` and `'contain'`).
|
|
249
|
+
|
|
250
|
+
An image's own size is its size in pixels, as points (dp on Android), as CSS counts an image's pixels: a 300 × 200 image is 300 × 200 points. A bundled image (`require()`) is its size in points.
|
|
251
|
+
|
|
252
|
+
It can also be set in `style`; the prop wins. Either one overrides `resizeMode`.
|
|
253
|
+
|
|
254
|
+
---
|
|
255
|
+
|
|
238
256
|
#### `resizeMode`
|
|
239
257
|
|
|
240
258
|
**Type:** `ResizeMode` · **Default:** `'cover'`
|
|
241
259
|
|
|
260
|
+
**Deprecated:** Use `objectFit` instead, except for `'repeat'`, which has no `objectFit` value and isn't deprecated.
|
|
261
|
+
|
|
242
262
|
How the image fills the view.
|
|
243
263
|
|
|
244
|
-
- `'contain'`:
|
|
245
|
-
- `'cover'`:
|
|
246
|
-
- `'stretch'`:
|
|
247
|
-
- `'center'`:
|
|
264
|
+
- `'contain'`: as `objectFit="contain"`.
|
|
265
|
+
- `'cover'`: as `objectFit="cover"`.
|
|
266
|
+
- `'stretch'`: as `objectFit="fill"`.
|
|
267
|
+
- `'center'`: as `objectFit="scale-down"`, except that on Android an image smaller than the view is shown at its size in pixels on the screen, so smaller than on iOS and the web.
|
|
248
268
|
- `'repeat'`: repeats it to cover the view, from its top-left corner, at the image's own size in pixels (a bundled image at its size in points), scaled down to fit if it's larger than the view. An animated image repeats its first frame, and `defaultSource` repeats too.
|
|
249
269
|
|
|
250
270
|
---
|
|
@@ -686,6 +706,14 @@ A load's result, as `onLoadEnd` gets it: `ok` with the image's size, or not `ok`
|
|
|
686
706
|
|
|
687
707
|
---
|
|
688
708
|
|
|
709
|
+
### `ObjectFit`
|
|
710
|
+
|
|
711
|
+
**Type:** `'fill' | 'contain' | 'cover' | 'none' | 'scale-down'`
|
|
712
|
+
|
|
713
|
+
How the image fits the view: see [`objectFit`](#objectfit).
|
|
714
|
+
|
|
715
|
+
---
|
|
716
|
+
|
|
689
717
|
### `OnErrorEvent`
|
|
690
718
|
|
|
691
719
|
`onError`'s event: `nativeEvent.error` says what went wrong, e.g. an HTTP status code or an image that can't be decoded.
|
|
@@ -826,9 +854,10 @@ FastImage works on the web with [react-native-web](https://necolas.github.io/rea
|
|
|
826
854
|
|
|
827
855
|
Support is minimal:
|
|
828
856
|
|
|
829
|
-
- Props that work: `source` (a `uri`, a `require()`d image, or several sizes), `defaultSource`, `resizeMode`, `tintColor`, `blurRadius`, `style`, children, `onLoadStart`, `onLoad`, `onError`, `onLoadEnd`, and View props such as `testID`, accessibility props, `onLayout` and `pointerEvents`.
|
|
857
|
+
- Props that work: `source` (a `uri`, a `require()`d image, or several sizes), `defaultSource`, `objectFit`, `resizeMode`, `tintColor`, `blurRadius`, `style`, children, `onLoadStart`, `onLoad`, `onError`, `onLoadEnd`, and View props such as `testID`, accessibility props, `onLayout` and `pointerEvents`.
|
|
830
858
|
- Ignored: `source.headers` (a browser can't send them for an image), `source.priority`, `source.cache`, `source.cacheKey`, `source.memoryCache`, `recyclingKey`, `loop`, `imageRendering`, `paused`, `transition`, `downsample`, `fallback` and `onProgress`.
|
|
831
|
-
-
|
|
859
|
+
- The image is an `<img>` element that fills the view inside its borders, with `objectFit` as its CSS `object-fit` (and `resizeMode`'s `stretch` as `fill`, `center` as `scale-down`). With `resizeMode="repeat"`, it's a repeated CSS background, with a transparent `<img>` over it. `tintColor` is an SVG filter and `blurRadius` a CSS `blur()`, and `defaultSource` is another `<img>`, under the image until it has loaded. A new `source` gets a new `<img>`, so the previous image doesn't stay while it loads (with several sizes, the browser shows it until the new one has loaded).
|
|
860
|
+
- Several sizes are shown with a lazily loaded `<img>` whose `srcset` lists them, with `sizes="auto, 100vw"`: the browser loads the one for the width the image is shown at, in device pixels (usually the smallest that's at least as wide). Browsers that don't support `sizes="auto"` use the viewport's width. `resizeMode="repeat"` shows them as `cover`.
|
|
832
861
|
- `FastImage.preload` loads the images into the browser's cache and resolves with a result per source. `clearMemoryCache`, `clearDiskCache` and `configureCache` resolve without doing anything (the browser manages its cache), and `getCachePath` and `writeToCache` resolve with `{ ok: false, error: 'Not supported on the web' }`.
|
|
833
862
|
|
|
834
863
|
## Troubleshooting
|
|
@@ -32,10 +32,17 @@ final class FastImageBlur extends BitmapTransformation {
|
|
|
32
32
|
// In pixels on screen.
|
|
33
33
|
private final float radius;
|
|
34
34
|
private final ImageView.ScaleType scaleType;
|
|
35
|
+
// Pixels on screen per pixel of the image at its own size (scale-down and
|
|
36
|
+
// none show it at that size: see FastImageViewWithUrl.updateImageMatrix;
|
|
37
|
+
// 1 for resizeMode center).
|
|
38
|
+
private final float density;
|
|
35
39
|
|
|
36
|
-
FastImageBlur(float radius, ImageView.ScaleType scaleType) {
|
|
40
|
+
FastImageBlur(float radius, ImageView.ScaleType scaleType, float density) {
|
|
37
41
|
this.radius = radius;
|
|
38
42
|
this.scaleType = scaleType;
|
|
43
|
+
this.density = scaleType == ImageView.ScaleType.CENTER_INSIDE || scaleType == ImageView.ScaleType.CENTER
|
|
44
|
+
? density
|
|
45
|
+
: 1;
|
|
39
46
|
}
|
|
40
47
|
|
|
41
48
|
@Override
|
|
@@ -80,7 +87,9 @@ final class FastImageBlur extends BitmapTransformation {
|
|
|
80
87
|
case FIT_XY:
|
|
81
88
|
return (float) Math.sqrt(x * y);
|
|
82
89
|
case CENTER_INSIDE:
|
|
83
|
-
return Math.min(Math.min(x, y),
|
|
90
|
+
return Math.min(Math.min(x, y), density);
|
|
91
|
+
case CENTER:
|
|
92
|
+
return density;
|
|
84
93
|
default:
|
|
85
94
|
return 1;
|
|
86
95
|
}
|
|
@@ -197,17 +206,21 @@ final class FastImageBlur extends BitmapTransformation {
|
|
|
197
206
|
public boolean equals(Object o) {
|
|
198
207
|
return o instanceof FastImageBlur
|
|
199
208
|
&& ((FastImageBlur) o).radius == radius
|
|
200
|
-
&& ((FastImageBlur) o).scaleType == scaleType
|
|
209
|
+
&& ((FastImageBlur) o).scaleType == scaleType
|
|
210
|
+
&& ((FastImageBlur) o).density == density;
|
|
201
211
|
}
|
|
202
212
|
|
|
203
213
|
@Override
|
|
204
214
|
public int hashCode() {
|
|
205
|
-
return ID.hashCode() * 31 + Float.floatToIntBits(radius) * 31 + (scaleType == null ? 0 : scaleType.ordinal())
|
|
215
|
+
return (ID.hashCode() * 31 + Float.floatToIntBits(radius) * 31 + (scaleType == null ? 0 : scaleType.ordinal())) * 31
|
|
216
|
+
+ Float.floatToIntBits(density);
|
|
206
217
|
}
|
|
207
218
|
|
|
208
219
|
@Override
|
|
209
220
|
public void updateDiskCacheKey(@NonNull MessageDigest messageDigest) {
|
|
210
221
|
messageDigest.update(ID_BYTES);
|
|
211
|
-
|
|
222
|
+
String key = radius + "," + scaleType;
|
|
223
|
+
if (density != 1) key += "," + density;
|
|
224
|
+
messageDigest.update(key.getBytes(Charset.forName("UTF-8")));
|
|
212
225
|
}
|
|
213
226
|
}
|
|
@@ -82,6 +82,8 @@ public class FastImageRequestListener implements RequestListener<Drawable> {
|
|
|
82
82
|
boolean local = !(model instanceof FastImageUrl);
|
|
83
83
|
int[] size = FastImageSourceSize.get(resource, model, local,
|
|
84
84
|
dataSource == DataSource.RESOURCE_DISK_CACHE);
|
|
85
|
+
// Before the view shows it (the target does once this returns).
|
|
86
|
+
view.onImageSize(size);
|
|
85
87
|
if (size != null) {
|
|
86
88
|
sendLoad(view, size);
|
|
87
89
|
return false;
|
|
@@ -92,7 +94,9 @@ public class FastImageRequestListener implements RequestListener<Drawable> {
|
|
|
92
94
|
FastImageSourceSize.readLocal(view.getContext(), source, resource, model, new FastImageSourceSize.Callback() {
|
|
93
95
|
@Override
|
|
94
96
|
public void onSize(int[] size) {
|
|
95
|
-
if (target.getRequest()
|
|
97
|
+
if (target.getRequest() != request) return;
|
|
98
|
+
view.onImageSize(size);
|
|
99
|
+
sendLoad(view, size);
|
|
96
100
|
}
|
|
97
101
|
});
|
|
98
102
|
return false;
|
|
@@ -128,6 +128,8 @@ final class FastImageSourceSize {
|
|
|
128
128
|
private static DownsampleStrategy strategy(@Nullable ImageView.ScaleType scaleType) {
|
|
129
129
|
if (scaleType == null) return DownsampleStrategy.DEFAULT;
|
|
130
130
|
switch (scaleType) {
|
|
131
|
+
case CENTER:
|
|
132
|
+
return FULL_SIZE;
|
|
131
133
|
case CENTER_CROP:
|
|
132
134
|
return DownsampleStrategy.CENTER_OUTSIDE;
|
|
133
135
|
case CENTER_INSIDE:
|
|
@@ -142,6 +144,26 @@ final class FastImageSourceSize {
|
|
|
142
144
|
}
|
|
143
145
|
}
|
|
144
146
|
|
|
147
|
+
// At the image's own size (objectFit none shows it at that size, whatever
|
|
148
|
+
// the view's), or scaled down to at most MAX_PIXELS pixels, which a
|
|
149
|
+
// bitmap can be drawn at (Android won't draw one over 100 MB).
|
|
150
|
+
static final DownsampleStrategy FULL_SIZE = new FullSize();
|
|
151
|
+
|
|
152
|
+
private static final float MAX_PIXELS = 4096f * 4096f;
|
|
153
|
+
|
|
154
|
+
private static final class FullSize extends DownsampleStrategy {
|
|
155
|
+
@Override
|
|
156
|
+
public float getScaleFactor(int sourceWidth, int sourceHeight, int requestedWidth, int requestedHeight) {
|
|
157
|
+
float pixels = (float) sourceWidth * sourceHeight;
|
|
158
|
+
return pixels > MAX_PIXELS ? (float) Math.sqrt(MAX_PIXELS / pixels) : 1;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
@Override
|
|
162
|
+
public SampleSizeRounding getSampleSizeRounding(int sourceWidth, int sourceHeight, int requestedWidth, int requestedHeight) {
|
|
163
|
+
return SampleSizeRounding.QUALITY;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
145
167
|
// The image's size, or null when it has to be read from a local image's
|
|
146
168
|
// header first (see readLocal).
|
|
147
169
|
@Nullable
|
|
@@ -6,6 +6,7 @@ import androidx.annotation.NonNull;
|
|
|
6
6
|
import androidx.annotation.Nullable;
|
|
7
7
|
|
|
8
8
|
import com.bumptech.glide.Registry;
|
|
9
|
+
import com.bumptech.glide.load.Option;
|
|
9
10
|
import com.bumptech.glide.load.Options;
|
|
10
11
|
import com.bumptech.glide.load.ResourceDecoder;
|
|
11
12
|
import com.bumptech.glide.load.engine.Resource;
|
|
@@ -17,6 +18,7 @@ import java.io.IOException;
|
|
|
17
18
|
import java.io.InputStream;
|
|
18
19
|
import java.nio.ByteBuffer;
|
|
19
20
|
import java.nio.charset.StandardCharsets;
|
|
21
|
+
import java.security.MessageDigest;
|
|
20
22
|
import java.util.Locale;
|
|
21
23
|
|
|
22
24
|
// SVG images (static), drawn with AndroidSVG, which FastImage includes
|
|
@@ -36,6 +38,31 @@ final class FastImageSvg {
|
|
|
36
38
|
@Nullable
|
|
37
39
|
private static Boolean available;
|
|
38
40
|
|
|
41
|
+
// objectFit scale-down and none show an image at its own size in dp
|
|
42
|
+
// (FastImageViewWithUrl.updateImageMatrix): an SVG is drawn at that size,
|
|
43
|
+
// the screen's pixels per dp (DENSITY) times its own, rather than at its
|
|
44
|
+
// size in pixels and then enlarged. With scale-down (FITS), no larger than
|
|
45
|
+
// the view. Part of the disk cache key, since the bitmap's size depends on
|
|
46
|
+
// them. Unset (0) for the other fits, which draw it for the view's size.
|
|
47
|
+
static final Option<Float> DENSITY = Option.disk(
|
|
48
|
+
"com.dylanvann.fastimage.FastImageSvg.Density", 0f,
|
|
49
|
+
new Option.CacheKeyUpdater<Float>() {
|
|
50
|
+
@Override
|
|
51
|
+
public void update(@NonNull byte[] keyBytes, @NonNull Float value, @NonNull MessageDigest messageDigest) {
|
|
52
|
+
messageDigest.update(keyBytes);
|
|
53
|
+
messageDigest.update(ByteBuffer.allocate(4).putFloat(value).array());
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
static final Option<Boolean> FITS = Option.disk(
|
|
57
|
+
"com.dylanvann.fastimage.FastImageSvg.Fits", false,
|
|
58
|
+
new Option.CacheKeyUpdater<Boolean>() {
|
|
59
|
+
@Override
|
|
60
|
+
public void update(@NonNull byte[] keyBytes, @NonNull Boolean value, @NonNull MessageDigest messageDigest) {
|
|
61
|
+
messageDigest.update(keyBytes);
|
|
62
|
+
messageDigest.update((byte) (value ? 1 : 0));
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
|
|
39
66
|
private FastImageSvg() {
|
|
40
67
|
}
|
|
41
68
|
|
|
@@ -70,6 +70,16 @@ final class FastImageSvgRenderer {
|
|
|
70
70
|
float scale = strategy != null
|
|
71
71
|
? strategy.getScaleFactor(sourceWidth, sourceHeight, targetWidth, targetHeight)
|
|
72
72
|
: 1f;
|
|
73
|
+
// objectFit scale-down and none: at its own size in dp (see
|
|
74
|
+
// FastImageSvg.DENSITY), and for scale-down no larger than the view.
|
|
75
|
+
Float density = options.get(FastImageSvg.DENSITY);
|
|
76
|
+
if (density != null && density > 0) {
|
|
77
|
+
scale = density;
|
|
78
|
+
if (Boolean.TRUE.equals(options.get(FastImageSvg.FITS))) {
|
|
79
|
+
scale = Math.min(scale, Math.min(
|
|
80
|
+
targetWidth / (float) sourceWidth, targetHeight / (float) sourceHeight));
|
|
81
|
+
}
|
|
82
|
+
}
|
|
73
83
|
if (Float.isNaN(scale) || Float.isInfinite(scale) || scale <= 0) scale = 1f;
|
|
74
84
|
float pixels = sourceWidth * scale * sourceHeight * scale;
|
|
75
85
|
if (pixels > MAX_PIXELS) scale *= (float) Math.sqrt(MAX_PIXELS / pixels);
|
|
@@ -49,6 +49,11 @@ class FastImageViewConverter {
|
|
|
49
49
|
put("cover", ScaleType.CENTER_CROP);
|
|
50
50
|
put("stretch", ScaleType.FIT_XY);
|
|
51
51
|
put("center", ScaleType.CENTER_INSIDE);
|
|
52
|
+
// objectFit="scale-down": as center, laid out in dp (see
|
|
53
|
+
// FastImageViewWithUrl.setResizeMode).
|
|
54
|
+
put("scale-down", ScaleType.CENTER_INSIDE);
|
|
55
|
+
// objectFit="none": the image at its own size, centered.
|
|
56
|
+
put("none", ScaleType.CENTER);
|
|
52
57
|
}};
|
|
53
58
|
|
|
54
59
|
// Whether the source has a uri to load (not null, missing or blank).
|
|
@@ -184,7 +184,7 @@ class FastImageViewManager extends SimpleViewManager<FastImageViewWithUrl> {
|
|
|
184
184
|
boolean repeat = "repeat".equals(resizeMode);
|
|
185
185
|
final FastImageViewWithUrl.ScaleType scaleType =
|
|
186
186
|
repeat ? FastImageViewWithUrl.ScaleType.FIT_XY : FastImageViewConverter.getScaleType(resizeMode);
|
|
187
|
-
view.setResizeMode(scaleType, repeat);
|
|
187
|
+
view.setResizeMode(scaleType, repeat, "center".equals(resizeMode));
|
|
188
188
|
}
|
|
189
189
|
|
|
190
190
|
@Override
|
|
@@ -9,6 +9,7 @@ import android.annotation.SuppressLint;
|
|
|
9
9
|
import android.content.Context;
|
|
10
10
|
import android.graphics.Bitmap;
|
|
11
11
|
import android.graphics.Canvas;
|
|
12
|
+
import android.graphics.Matrix;
|
|
12
13
|
import android.graphics.Paint;
|
|
13
14
|
import android.graphics.PorterDuff;
|
|
14
15
|
import android.graphics.PorterDuffXfermode;
|
|
@@ -60,6 +61,9 @@ class FastImageViewWithUrl extends AppCompatImageView implements ReactPointerEve
|
|
|
60
61
|
public FastImageViewWithUrl(Context context, @Nullable RequestManager requestManager) {
|
|
61
62
|
super(context);
|
|
62
63
|
this.requestManager = requestManager;
|
|
64
|
+
// An image larger than the view (objectFit none) is cropped to it,
|
|
65
|
+
// also when the view doesn't clip its content (overflow visible).
|
|
66
|
+
setCropToPadding(true);
|
|
63
67
|
}
|
|
64
68
|
|
|
65
69
|
// FastImage sends "none" when it has pointerEvents="box-none" (the image
|
|
@@ -609,19 +613,134 @@ class FastImageViewWithUrl extends AppCompatImageView implements ReactPointerEve
|
|
|
609
613
|
// as React Native's Image does. The view fills with it (FIT_XY).
|
|
610
614
|
private boolean mRepeat = false;
|
|
611
615
|
|
|
616
|
+
// The scale type for resizeMode, which Glide decodes the image for and the
|
|
617
|
+
// blur scales with. For scale-down (CENTER_INSIDE) and none (CENTER) the
|
|
618
|
+
// view is MATRIX: it lays the image out itself (see updateImageMatrix).
|
|
619
|
+
// Starts as ImageView's.
|
|
620
|
+
private ScaleType mResizeScaleType = ScaleType.FIT_CENTER;
|
|
621
|
+
|
|
622
|
+
// resizeMode center: CENTER_INSIDE as ImageView draws it, an image smaller
|
|
623
|
+
// than the view at its size in pixels on the screen (scale-down: in dp).
|
|
624
|
+
private boolean mInPixels = false;
|
|
625
|
+
|
|
626
|
+
// The scale type the image on screen is laid out for (see
|
|
627
|
+
// applyResizeMode).
|
|
628
|
+
private ScaleType mLayoutScaleType = ScaleType.FIT_CENTER;
|
|
629
|
+
|
|
612
630
|
// Glide crops or fits the bitmap for the scale type when it loads, so a
|
|
613
631
|
// new resizeMode needs a reload to take effect (#762).
|
|
614
|
-
public void setResizeMode(ScaleType scaleType, boolean repeat) {
|
|
615
|
-
if (scaleType ==
|
|
616
|
-
|
|
632
|
+
public void setResizeMode(ScaleType scaleType, boolean repeat, boolean inPixels) {
|
|
633
|
+
if (scaleType == mResizeScaleType && repeat == mRepeat && inPixels == mInPixels) return;
|
|
634
|
+
mResizeScaleType = scaleType;
|
|
635
|
+
mInPixels = inPixels;
|
|
617
636
|
mRepeat = repeat;
|
|
618
637
|
mNeedsReload = true;
|
|
638
|
+
// A loaded image on screen was cropped or fitted for the old scale
|
|
639
|
+
// type: it keeps its layout until the image for the new one is ready
|
|
640
|
+
// (onImageLoaded), or the load fails. Laid out for the new one, a
|
|
641
|
+
// cover crop would be stretched to the image's own size.
|
|
642
|
+
if (mShownRequest == null) applyResizeMode();
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
// Lays the view out for resizeMode.
|
|
646
|
+
private void applyResizeMode() {
|
|
647
|
+
mLayoutScaleType = mResizeScaleType;
|
|
648
|
+
setScaleType(!mInPixels && (mResizeScaleType == ScaleType.CENTER_INSIDE || mResizeScaleType == ScaleType.CENTER)
|
|
649
|
+
? ScaleType.MATRIX
|
|
650
|
+
: mResizeScaleType);
|
|
651
|
+
updateImageMatrix();
|
|
619
652
|
}
|
|
620
653
|
|
|
621
654
|
// Repeats what the view shows (the loaded image, and defaultSource).
|
|
622
655
|
@Override
|
|
623
656
|
public void setImageDrawable(@Nullable Drawable drawable) {
|
|
624
657
|
super.setImageDrawable(mRepeat ? tiled(drawable) : drawable);
|
|
658
|
+
updateImageMatrix();
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
@Override
|
|
662
|
+
protected boolean setFrame(int l, int t, int r, int b) {
|
|
663
|
+
boolean changed = super.setFrame(l, t, r, b);
|
|
664
|
+
updateImageMatrix();
|
|
665
|
+
return changed;
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
// The loaded image's own size in pixels (as onLoad has it), and its scale
|
|
669
|
+
// (a bundled image's, so it's its size in points; 1 for others, as on
|
|
670
|
+
// iOS), for scale-down and none. 0 until it's known.
|
|
671
|
+
private int mImageWidth = 0;
|
|
672
|
+
private int mImageHeight = 0;
|
|
673
|
+
private double mImageScale = 1;
|
|
674
|
+
// The loading source's scale.
|
|
675
|
+
private double mLoadingScale = 1;
|
|
676
|
+
@Nullable
|
|
677
|
+
private Matrix mImageMatrix;
|
|
678
|
+
|
|
679
|
+
// The loading image's own size, from FastImageRequestListener as it's
|
|
680
|
+
// ready (null until a local image's has been read).
|
|
681
|
+
void onImageSize(@Nullable int[] size) {
|
|
682
|
+
mImageWidth = size != null ? size[0] : 0;
|
|
683
|
+
mImageHeight = size != null ? size[1] : 0;
|
|
684
|
+
mImageScale = mLoadingScale;
|
|
685
|
+
updateImageMatrix();
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
// Pixels on screen per pixel of the loaded image at its own size: an image
|
|
689
|
+
// is its size in pixels in dp (a bundled one in points, its pixels divided
|
|
690
|
+
// by its scale), as on iOS.
|
|
691
|
+
private float imageDensity() {
|
|
692
|
+
return (float) (getResources().getDisplayMetrics().density / mImageScale);
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
// The same for the loading image.
|
|
696
|
+
private float loadingDensity() {
|
|
697
|
+
return (float) (getResources().getDisplayMetrics().density / mLoadingScale);
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
// scale-down: the image at its own size, centered, or scaled down
|
|
701
|
+
// uniformly to fit if that's larger than the view. Glide decodes it no
|
|
702
|
+
// larger than that (CenterInside), and it's drawn at this size. none: the
|
|
703
|
+
// image at its own size, centered, whatever its size (cropped by the view;
|
|
704
|
+
// Glide decodes it at its size). defaultSource (a resource) is already at
|
|
705
|
+
// its size on screen.
|
|
706
|
+
private void updateImageMatrix() {
|
|
707
|
+
if (getScaleType() != ScaleType.MATRIX) return;
|
|
708
|
+
Drawable drawable = getDrawable();
|
|
709
|
+
if (drawable == null) return;
|
|
710
|
+
int drawableWidth = drawable.getIntrinsicWidth();
|
|
711
|
+
int drawableHeight = drawable.getIntrinsicHeight();
|
|
712
|
+
float viewWidth = getWidth() - getPaddingLeft() - getPaddingRight();
|
|
713
|
+
float viewHeight = getHeight() - getPaddingTop() - getPaddingBottom();
|
|
714
|
+
if (mImageMatrix == null) mImageMatrix = new Matrix();
|
|
715
|
+
Matrix matrix = mImageMatrix;
|
|
716
|
+
if (drawableWidth <= 0 || drawableHeight <= 0 || viewWidth <= 0 || viewHeight <= 0) {
|
|
717
|
+
matrix.reset();
|
|
718
|
+
setImageMatrix(matrix);
|
|
719
|
+
return;
|
|
720
|
+
}
|
|
721
|
+
float width;
|
|
722
|
+
float height;
|
|
723
|
+
if (drawable == mDefaultSource) {
|
|
724
|
+
width = drawableWidth;
|
|
725
|
+
height = drawableHeight;
|
|
726
|
+
} else if (mImageWidth > 0 && mImageHeight > 0) {
|
|
727
|
+
width = mImageWidth * imageDensity();
|
|
728
|
+
height = mImageHeight * imageDensity();
|
|
729
|
+
} else {
|
|
730
|
+
// Its size isn't known yet (a local image's is read after it
|
|
731
|
+
// loads): Glide decoded it at its size unless it's larger than
|
|
732
|
+
// the view, which shows it scaled down to fit either way.
|
|
733
|
+
width = drawableWidth * imageDensity();
|
|
734
|
+
height = drawableHeight * imageDensity();
|
|
735
|
+
}
|
|
736
|
+
float fit = mLayoutScaleType == ScaleType.CENTER
|
|
737
|
+
? 1
|
|
738
|
+
: Math.min(1, Math.min(viewWidth / width, viewHeight / height));
|
|
739
|
+
width *= fit;
|
|
740
|
+
height *= fit;
|
|
741
|
+
matrix.setScale(width / drawableWidth, height / drawableHeight);
|
|
742
|
+
matrix.postTranslate(Math.round((viewWidth - width) / 2), Math.round((viewHeight - height) / 2));
|
|
743
|
+
setImageMatrix(matrix);
|
|
625
744
|
}
|
|
626
745
|
|
|
627
746
|
@Nullable
|
|
@@ -668,7 +787,9 @@ class FastImageViewWithUrl extends AppCompatImageView implements ReactPointerEve
|
|
|
668
787
|
// pixelated, and the blur. A blurred animated image shows its first frame,
|
|
669
788
|
// as on iOS.
|
|
670
789
|
private RequestOptions renderingOptions(Object model) {
|
|
671
|
-
FastImageBlur blur = mBlurRadius > 0
|
|
790
|
+
FastImageBlur blur = mBlurRadius > 0
|
|
791
|
+
? new FastImageBlur(mBlurRadius, mRepeat ? null : mResizeScaleType, mInPixels ? 1 : loadingDensity())
|
|
792
|
+
: null;
|
|
672
793
|
RequestOptions options;
|
|
673
794
|
if (mRepeat) {
|
|
674
795
|
// Decoded at its own size, or scaled down to fit the view if it's
|
|
@@ -690,7 +811,15 @@ class FastImageViewWithUrl extends AppCompatImageView implements ReactPointerEve
|
|
|
690
811
|
if (blur != null) options = options.optionalTransform(blur);
|
|
691
812
|
} else {
|
|
692
813
|
options = FastImageSourceSize.scaleTypeOptions(
|
|
693
|
-
|
|
814
|
+
mResizeScaleType, FastImageSourceSize.capture(mResizeScaleType, model), blur);
|
|
815
|
+
boolean ownSize = mResizeScaleType == ScaleType.CENTER_INSIDE || mResizeScaleType == ScaleType.CENTER;
|
|
816
|
+
// scale-down and none show it at its own size in dp: an SVG is
|
|
817
|
+
// drawn at that size, so it's sharp (not with a blur, which hides
|
|
818
|
+
// the difference).
|
|
819
|
+
if (ownSize && !mInPixels && blur == null) {
|
|
820
|
+
options = options.set(FastImageSvg.DENSITY, loadingDensity())
|
|
821
|
+
.set(FastImageSvg.FITS, mResizeScaleType == ScaleType.CENTER_INSIDE);
|
|
822
|
+
}
|
|
694
823
|
}
|
|
695
824
|
return blur != null ? options.dontAnimate() : options;
|
|
696
825
|
}
|
|
@@ -708,12 +837,13 @@ class FastImageViewWithUrl extends AppCompatImageView implements ReactPointerEve
|
|
|
708
837
|
// have).
|
|
709
838
|
private boolean mRestarting = false;
|
|
710
839
|
|
|
711
|
-
// The request for the image the view shows
|
|
712
|
-
//
|
|
713
|
-
//
|
|
714
|
-
//
|
|
715
|
-
//
|
|
716
|
-
//
|
|
840
|
+
// The request for the image the view shows (from when it has loaded until
|
|
841
|
+
// another image or defaultSource replaces it), and the one loading. A new
|
|
842
|
+
// source starts with the shown one as a thumbnail, from the cache only, so
|
|
843
|
+
// the image stays until the new one has loaded instead of flashing blank
|
|
844
|
+
// (#747), also if the source changes again meanwhile. This is Glide's safe
|
|
845
|
+
// way to do that: the previous bitmap can't be kept in the view itself,
|
|
846
|
+
// since Glide reuses it once its request is cleared.
|
|
717
847
|
@Nullable
|
|
718
848
|
private RequestBuilder<Drawable> mShownRequest;
|
|
719
849
|
@Nullable
|
|
@@ -751,6 +881,8 @@ class FastImageViewWithUrl extends AppCompatImageView implements ReactPointerEve
|
|
|
751
881
|
void onImageLoaded() {
|
|
752
882
|
mLoadEnded = true;
|
|
753
883
|
endProgress();
|
|
884
|
+
// Before the view shows it (the target does once this returns).
|
|
885
|
+
applyResizeMode();
|
|
754
886
|
mShownRequest = mLoadingRequest;
|
|
755
887
|
mShownWidth = mLoadingWidth;
|
|
756
888
|
mShownHeight = mLoadingHeight;
|
|
@@ -905,6 +1037,7 @@ class FastImageViewWithUrl extends AppCompatImageView implements ReactPointerEve
|
|
|
905
1037
|
void onImageFailed(boolean hadThumbnail) {
|
|
906
1038
|
mLoadEnded = true;
|
|
907
1039
|
endProgress();
|
|
1040
|
+
applyResizeMode();
|
|
908
1041
|
mShownRequest = null;
|
|
909
1042
|
if (!hadThumbnail) return;
|
|
910
1043
|
final int load = mLoadCount;
|
|
@@ -961,15 +1094,17 @@ class FastImageViewWithUrl extends AppCompatImageView implements ReactPointerEve
|
|
|
961
1094
|
mRestarting = false;
|
|
962
1095
|
mLoadCount++;
|
|
963
1096
|
// A fade in progress carries on, as on iOS: into the next image if it
|
|
964
|
-
// shows at once, and a next image that fades starts from it.
|
|
1097
|
+
// shows at once, and a next image that fades starts from it. The
|
|
1098
|
+
// image on screen stays the shown one while the next loads (as its
|
|
1099
|
+
// thumbnail, or a copy), so a change before then keeps it too.
|
|
965
1100
|
RequestBuilder<Drawable> shownRequest = mShownRequest;
|
|
966
|
-
mShownRequest = null;
|
|
967
1101
|
mLoadingRequest = null;
|
|
968
1102
|
mBaseRequest = null;
|
|
969
1103
|
mModel = null;
|
|
970
1104
|
|
|
971
1105
|
// Nothing to show.
|
|
972
1106
|
if (mSource == null && mDefaultSource == null) {
|
|
1107
|
+
mShownRequest = null;
|
|
973
1108
|
// Cancel existing requests.
|
|
974
1109
|
clearView(requestManager);
|
|
975
1110
|
|
|
@@ -980,6 +1115,9 @@ class FastImageViewWithUrl extends AppCompatImageView implements ReactPointerEve
|
|
|
980
1115
|
return;
|
|
981
1116
|
}
|
|
982
1117
|
|
|
1118
|
+
mLoadingScale = mSource != null && mSource.hasKey("__packager_asset")
|
|
1119
|
+
? Math.max(number(mSource, "scale", 1), 0.01)
|
|
1120
|
+
: 1;
|
|
983
1121
|
//final GlideUrl glideUrl = FastImageViewConverter.getGlideUrl(view.getContext(), mSource);
|
|
984
1122
|
final FastImageSource imageSource = FastImageViewConverter.hasUri(mSource)
|
|
985
1123
|
? FastImageViewConverter.getImageSource(getContext(), mSource)
|
|
@@ -995,6 +1133,7 @@ class FastImageViewWithUrl extends AppCompatImageView implements ReactPointerEve
|
|
|
995
1133
|
FastImageEvents.send(this, REACT_ON_ERROR_EVENT, event);
|
|
996
1134
|
FastImageEvents.sendLoadEnd(this, error);
|
|
997
1135
|
|
|
1136
|
+
mShownRequest = null;
|
|
998
1137
|
// Cancel existing requests.
|
|
999
1138
|
clearView(requestManager);
|
|
1000
1139
|
|
|
@@ -1053,7 +1192,11 @@ class FastImageViewWithUrl extends AppCompatImageView implements ReactPointerEve
|
|
|
1053
1192
|
RequestBuilder<Drawable> builder = base.clone().apply(renderingOptions(model));
|
|
1054
1193
|
RequestBuilder<Drawable> request = builder.clone();
|
|
1055
1194
|
|
|
1056
|
-
|
|
1195
|
+
// Only defaultSource: it replaces the image.
|
|
1196
|
+
if (model == null) {
|
|
1197
|
+
meanwhile = null;
|
|
1198
|
+
mShownRequest = null;
|
|
1199
|
+
}
|
|
1057
1200
|
boolean thumbnail = shownRequest != null && model != null && meanwhile == null;
|
|
1058
1201
|
// After the clone: loading the image that's showing again (at a
|
|
1059
1202
|
// new size, or with another blur) doesn't fade it in again. Over a
|