react-native-nitro-image-pipeline 1.2.1 → 1.3.2
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 +100 -2
- package/android/src/main/java/com/margelo/nitro/nitroimagepipeline/HybridNitroImagePipeline.kt +17 -10
- package/ios/HybridNitroImagePipeline.swift +43 -4
- package/lib/commonjs/PipelineImage.js +19 -4
- package/lib/commonjs/PipelineImage.js.map +1 -1
- package/lib/commonjs/index.js.map +1 -1
- package/lib/module/PipelineImage.js +19 -4
- package/lib/module/PipelineImage.js.map +1 -1
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/src/PipelineImage.d.ts +17 -1
- package/lib/typescript/src/PipelineImage.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +1 -1
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/package.json +3 -2
- package/src/PipelineImage.tsx +88 -61
- package/src/index.ts +5 -1
package/README.md
CHANGED
|
@@ -71,6 +71,80 @@ so a style that already rounds the view rounds the bitmap too, with no separate
|
|
|
71
71
|
other prop (`resizeMode`, `recyclingKey`, `testID`, …) is passed straight through to
|
|
72
72
|
`NativeNitroImage`.
|
|
73
73
|
|
|
74
|
+
### Animating with `react-native-reanimated`
|
|
75
|
+
|
|
76
|
+
`<PipelineImage>` forwards its `ref` to the underlying `NativeNitroImage` host view, so it can be
|
|
77
|
+
passed straight to `Animated.createAnimatedComponent` — from
|
|
78
|
+
[react-native-reanimated](https://docs.swmansion.com/react-native-reanimated/) or React Native's
|
|
79
|
+
built-in `Animated`:
|
|
80
|
+
|
|
81
|
+
```tsx
|
|
82
|
+
import Animated, { FadeIn, useAnimatedStyle, useSharedValue, withSpring } from 'react-native-reanimated';
|
|
83
|
+
import { PipelineImage } from 'react-native-nitro-image-pipeline';
|
|
84
|
+
|
|
85
|
+
const AnimatedPipelineImage = Animated.createAnimatedComponent(PipelineImage);
|
|
86
|
+
|
|
87
|
+
function Photo({ url }: { url: string }) {
|
|
88
|
+
const pressed = useSharedValue(false);
|
|
89
|
+
const animatedStyle = useAnimatedStyle(() => ({
|
|
90
|
+
transform: [{ scale: withSpring(pressed.value ? 1.1 : 1) }],
|
|
91
|
+
}));
|
|
92
|
+
|
|
93
|
+
return (
|
|
94
|
+
<AnimatedPipelineImage
|
|
95
|
+
url={url}
|
|
96
|
+
entering={FadeIn}
|
|
97
|
+
style={[styles.photo, animatedStyle]}
|
|
98
|
+
onTouchStart={() => (pressed.value = true)}
|
|
99
|
+
onTouchEnd={() => (pressed.value = false)}
|
|
100
|
+
/>
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const styles = StyleSheet.create({ photo: { width: 300, height: 200, borderRadius: 12 } });
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Layout animations (`entering`/`exiting`) work as on any animated component, and wrapping a plain
|
|
108
|
+
`<PipelineImage>` in an `Animated.View` is always an option if you'd rather not create one.
|
|
109
|
+
|
|
110
|
+
Because the pipeline bakes its processing into the bitmap at load time, the props fall into two
|
|
111
|
+
groups — view-layer properties that animate freely, and bitmap properties that don't:
|
|
112
|
+
|
|
113
|
+
- **`transform` and `opacity`** — the ideal case: they run entirely on the UI thread and never
|
|
114
|
+
touch the bitmap. Prefer a `scale` transform over animating `width`/`height`.
|
|
115
|
+
- **`width`/`height`** — the animation itself works (Reanimated drives the native view directly),
|
|
116
|
+
but the bitmap doesn't follow it. With numeric dimensions in `style` (including an animated
|
|
117
|
+
style's initial values) the bitmap is decoded once at that size and stretched by the view while
|
|
118
|
+
it animates; with flex/percent sizing the size comes from `onLayout`, which fires repeatedly
|
|
119
|
+
during the animation and requests a new variant each time. Animate a `scale` transform instead
|
|
120
|
+
and let the layout settle where it will.
|
|
121
|
+
- **`borderRadius`** — by default the component bakes `style`'s `borderRadius` into the bitmap;
|
|
122
|
+
an animated radius updates only the view layer, so the baked rounding wins and stays stale. To
|
|
123
|
+
animate rounding, opt out of baking with `cornerRadius={0}` and round at the view layer instead:
|
|
124
|
+
`overflow: 'hidden'` plus the animated `borderRadius`.
|
|
125
|
+
- **`blur`** — not animatable. It's a load-time bitmap operation behind an async native call, not
|
|
126
|
+
a view property, so a changing `blur` re-runs the pipeline per value — far too slow to drive
|
|
127
|
+
per-frame. To animate blurriness, render the sharp and blurred variants as two stacked
|
|
128
|
+
`<PipelineImage>`s and cross-fade the blurred one's `opacity` (both share the URL cache, so the
|
|
129
|
+
second variant loads from the same fetched source):
|
|
130
|
+
|
|
131
|
+
```tsx
|
|
132
|
+
function BlurFade({ url, blurred }: { url: string; blurred: boolean }) {
|
|
133
|
+
const blurOpacity = useAnimatedStyle(() => ({
|
|
134
|
+
opacity: withTiming(blurred ? 1 : 0),
|
|
135
|
+
}));
|
|
136
|
+
|
|
137
|
+
return (
|
|
138
|
+
<View style={styles.photo}>
|
|
139
|
+
<PipelineImage url={url} style={StyleSheet.absoluteFill} />
|
|
140
|
+
<Animated.View style={[StyleSheet.absoluteFill, blurOpacity]}>
|
|
141
|
+
<PipelineImage url={url} blur={12} style={StyleSheet.absoluteFill} />
|
|
142
|
+
</Animated.View>
|
|
143
|
+
</View>
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
74
148
|
### `useImage` hook
|
|
75
149
|
|
|
76
150
|
The simplest way to load an image in a component:
|
|
@@ -164,6 +238,7 @@ Loads an image from a URL and returns a `Promise<Image>`.
|
|
|
164
238
|
| `onLoad` | `(image: Image) => void` | — | Called when the image finishes loading |
|
|
165
239
|
| `onError` | `(error: Error) => void` | — | Called if loading fails |
|
|
166
240
|
| `onLayout` | `(event: LayoutChangeEvent) => void` | — | Standard `View` layout callback; also drives the deferred resize for non-numeric sizes |
|
|
241
|
+
| `ref` | `Ref<PipelineImageRef>` | — | Forwarded to the underlying `NativeNitroImage` host view — gives access to native-view methods (`measure`, …) and makes the component work with `Animated.createAnimatedComponent` (see [Animating](#animating-with-react-native-reanimated)) |
|
|
167
242
|
| `…NativeNitroImage props` | — | — | Everything else (`resizeMode`, `recyclingKey`, `testID`, …) is passed through to `NativeNitroImage` |
|
|
168
243
|
|
|
169
244
|
### `resizeForStyle(style)` / `resizeForLayout(width, height)`
|
|
@@ -184,11 +259,17 @@ omitted.
|
|
|
184
259
|
|
|
185
260
|
### `preLoadImage(url)`
|
|
186
261
|
|
|
187
|
-
Prefetches a single image into the cache. Returns `Promise<void>`.
|
|
262
|
+
Prefetches a single image into the **disk cache**, without decoding it. Returns `Promise<void>`.
|
|
263
|
+
|
|
264
|
+
Prefetching only pays the network and disk I/O cost up front — no bitmap is decoded or held in
|
|
265
|
+
memory, so prefetching a long list of URLs doesn't balloon RAM. The image is decoded (at the
|
|
266
|
+
`resize` target size, when one is given) the first time `loadImage`/`useImage`/`<PipelineImage>`
|
|
267
|
+
actually displays it.
|
|
188
268
|
|
|
189
269
|
### `preLoadImages(urls)`
|
|
190
270
|
|
|
191
|
-
Prefetches multiple images into the cache
|
|
271
|
+
Prefetches multiple images into the disk cache — same behavior as `preLoadImage`, for a batch.
|
|
272
|
+
Returns `Promise<void>`.
|
|
192
273
|
|
|
193
274
|
### `gaussianBlur(image, radius)`
|
|
194
275
|
|
|
@@ -229,6 +310,23 @@ images keep their borders instead of fading out.
|
|
|
229
310
|
|
|
230
311
|
Removes all cached images from memory and disk. Returns `Promise<void>` that resolves once both caches are cleared.
|
|
231
312
|
|
|
313
|
+
## Memory usage
|
|
314
|
+
|
|
315
|
+
The pipeline is set up so RAM scales with what you display, not with what you download:
|
|
316
|
+
|
|
317
|
+
- **Pass `resize` (or just use `<PipelineImage>`, which derives it from layout).** With a target
|
|
318
|
+
size known, both platforms decode the source *near that size* instead of at full resolution —
|
|
319
|
+
iOS via a downsampled thumbnail decode, Android via Coil's subsampling. Without `resize`, a
|
|
320
|
+
48 MP photo decompresses to ~190 MB of bitmap no matter how small you display it.
|
|
321
|
+
- **Prefetching stores bytes, not bitmaps.** `preLoadImage(s)` writes the download to the disk
|
|
322
|
+
cache and skips decoding entirely.
|
|
323
|
+
- **The in-memory cache is capped** (128 MB on iOS, 25% of the app's memory class on Android),
|
|
324
|
+
holds decoded bitmaps for instant re-display, and evicts least-recently-used entries — also in
|
|
325
|
+
response to memory warnings and backgrounding. Memory profilers attribute this cache to the app;
|
|
326
|
+
a plateau at the cap is expected and evictable, not a leak. Use `cache: 'disk'` or
|
|
327
|
+
`cache: 'none'` on images you know you won't show again soon, and `clearCache()` to drop
|
|
328
|
+
everything.
|
|
329
|
+
|
|
232
330
|
## Upgrading from 0.3.x
|
|
233
331
|
|
|
234
332
|
`blur` and `gaussianBlur(image, radius)` changed meaning in 1.0. They used to hand the number
|
package/android/src/main/java/com/margelo/nitro/nitroimagepipeline/HybridNitroImagePipeline.kt
CHANGED
|
@@ -3,8 +3,11 @@ package com.margelo.nitro.nitroimagepipeline
|
|
|
3
3
|
import android.content.Context
|
|
4
4
|
import androidx.core.graphics.drawable.toBitmap
|
|
5
5
|
import coil3.BitmapImage
|
|
6
|
+
import coil3.ColorImage
|
|
6
7
|
import coil3.DrawableImage
|
|
7
8
|
import coil3.ImageLoader
|
|
9
|
+
import coil3.decode.DecodeResult
|
|
10
|
+
import coil3.decode.Decoder
|
|
8
11
|
import coil3.disk.DiskCache
|
|
9
12
|
import coil3.memory.MemoryCache
|
|
10
13
|
import coil3.network.okhttp.OkHttpNetworkFetcherFactory
|
|
@@ -117,21 +120,25 @@ class HybridNitroImagePipeline : HybridNitroImagePipelineSpec() {
|
|
|
117
120
|
}
|
|
118
121
|
}
|
|
119
122
|
|
|
123
|
+
// Preloading warms the disk cache only: the memory cache is skipped and the
|
|
124
|
+
// decode step is replaced with a no-op (Coil's documented preload pattern),
|
|
125
|
+
// so prefetching N URLs costs network + disk I/O instead of N full-resolution
|
|
126
|
+
// bitmaps on the heap. The image is decoded — subsampled to the requested
|
|
127
|
+
// size — only when a loadImage actually displays it.
|
|
128
|
+
private fun preloadRequest(url: String): ImageRequest =
|
|
129
|
+
ImageRequest.Builder(context)
|
|
130
|
+
.data(url)
|
|
131
|
+
.memoryCachePolicy(CachePolicy.DISABLED)
|
|
132
|
+
.decoderFactory { _, _, _ -> Decoder { DecodeResult(ColorImage(), false) } }
|
|
133
|
+
.build()
|
|
134
|
+
|
|
120
135
|
override fun preLoadImage(url: String): Promise<Unit> = Promise.async {
|
|
121
|
-
|
|
122
|
-
imageLoader.execute(request)
|
|
136
|
+
imageLoader.execute(preloadRequest(url))
|
|
123
137
|
}
|
|
124
138
|
|
|
125
139
|
override fun preLoadImages(urls: Array<String>): Promise<Unit> = Promise.async {
|
|
126
140
|
coroutineScope {
|
|
127
|
-
urls
|
|
128
|
-
.map { url ->
|
|
129
|
-
async {
|
|
130
|
-
val request = ImageRequest.Builder(context).data(url).build()
|
|
131
|
-
imageLoader.execute(request)
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
.awaitAll()
|
|
141
|
+
urls.map { url -> async { imageLoader.execute(preloadRequest(url)) } }.awaitAll()
|
|
135
142
|
}
|
|
136
143
|
Unit
|
|
137
144
|
}
|
|
@@ -95,10 +95,28 @@ class HybridNitroImagePipeline: HybridNitroImagePipelineSpec {
|
|
|
95
95
|
// the original download, so they survive memory eviction and
|
|
96
96
|
// restarts without dropping the original for other variants.
|
|
97
97
|
configuration.dataCachePolicy = .storeAll
|
|
98
|
+
// A private, capped memory cache. The default (ImageCache.shared)
|
|
99
|
+
// sizes itself at 15% of physical RAM — up to 768 MB of decoded
|
|
100
|
+
// bitmaps on modern devices before anything is evicted, which shows
|
|
101
|
+
// up as "high RAM usage" even though it is all evictable cache.
|
|
102
|
+
// 128 MB still holds ~10 full-screen bitmaps or hundreds of list
|
|
103
|
+
// thumbnails, and the LRU cache keeps trimming itself on memory
|
|
104
|
+
// warnings and when the app enters the background.
|
|
105
|
+
configuration.imageCache = ImageCache(
|
|
106
|
+
costLimit: min(ImageCache.defaultCostLimit, 128 * 1024 * 1024)
|
|
107
|
+
)
|
|
98
108
|
return ImagePipeline(configuration: configuration)
|
|
99
109
|
}()
|
|
100
110
|
|
|
101
|
-
|
|
111
|
+
// `.diskCache` stores the downloaded data without decoding it, so
|
|
112
|
+
// prefetching N URLs costs network + disk I/O instead of N decoded
|
|
113
|
+
// full-resolution bitmaps parked in the memory cache (the default
|
|
114
|
+
// `.memoryCache` destination). The image is decoded — at the requested
|
|
115
|
+
// target size — only when a loadImage actually displays it.
|
|
116
|
+
private static let sharedPrefetcher = ImagePrefetcher(
|
|
117
|
+
pipeline: sharedPipeline,
|
|
118
|
+
destination: .diskCache
|
|
119
|
+
)
|
|
102
120
|
|
|
103
121
|
private var pipeline: ImagePipeline { Self.sharedPipeline }
|
|
104
122
|
private var prefetcher: ImagePrefetcher { Self.sharedPrefetcher }
|
|
@@ -133,13 +151,21 @@ class HybridNitroImagePipeline: HybridNitroImagePipelineSpec {
|
|
|
133
151
|
}
|
|
134
152
|
}
|
|
135
153
|
|
|
154
|
+
/// The target size in pixels, or `nil` when no (valid) resize was requested.
|
|
155
|
+
private static func resizeSize(for options: Options?) -> CGSize? {
|
|
156
|
+
guard let resize = options?.resize, resize.width > 0, resize.height > 0 else {
|
|
157
|
+
return nil
|
|
158
|
+
}
|
|
159
|
+
return CGSize(width: resize.width, height: resize.height)
|
|
160
|
+
}
|
|
161
|
+
|
|
136
162
|
private static func processors(for options: Options?) -> [any ImageProcessing] {
|
|
137
163
|
var processors: [any ImageProcessing] = []
|
|
138
164
|
// Resize first: blur sigma and corner radii are defined in pixels
|
|
139
165
|
// of the bitmap they run on, so they must see the final size.
|
|
140
|
-
if let
|
|
166
|
+
if let size = resizeSize(for: options) {
|
|
141
167
|
processors.append(ImageProcessors.Resize(
|
|
142
|
-
size:
|
|
168
|
+
size: size,
|
|
143
169
|
unit: .pixels,
|
|
144
170
|
contentMode: .aspectFill,
|
|
145
171
|
crop: true,
|
|
@@ -161,11 +187,24 @@ class HybridNitroImagePipeline: HybridNitroImagePipelineSpec {
|
|
|
161
187
|
throw RuntimeError.error(withMessage: "Invalid URL: \(url)")
|
|
162
188
|
}
|
|
163
189
|
|
|
164
|
-
|
|
190
|
+
var imgRequest = ImageRequest(
|
|
165
191
|
url: imageUrl,
|
|
166
192
|
processors: Self.processors(for: options),
|
|
167
193
|
options: Self.cacheOptions(for: options?.cache)
|
|
168
194
|
)
|
|
195
|
+
// With a target size known, decode near it (aspect-fill, so the
|
|
196
|
+
// decoded image always covers the target) instead of at full
|
|
197
|
+
// resolution — a 48 MP photo displayed as a 300 pt card would
|
|
198
|
+
// otherwise decompress to ~190 MB before Resize shrinks it.
|
|
199
|
+
// Matches Android, where the request's size() drives subsampling;
|
|
200
|
+
// the exact size and crop still come from the Resize processor.
|
|
201
|
+
if let size = Self.resizeSize(for: options) {
|
|
202
|
+
imgRequest.thumbnail = ImageRequest.ThumbnailOptions(
|
|
203
|
+
size: size,
|
|
204
|
+
unit: .pixels,
|
|
205
|
+
contentMode: .aspectFill
|
|
206
|
+
)
|
|
207
|
+
}
|
|
169
208
|
|
|
170
209
|
let image = try await self.pipeline.image(for: imgRequest)
|
|
171
210
|
return HybridImage(uiImage: image)
|
|
@@ -3,13 +3,19 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", {
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
|
-
exports.PipelineImage =
|
|
6
|
+
exports.PipelineImage = void 0;
|
|
7
7
|
var _react = require("react");
|
|
8
8
|
var _reactNative = require("react-native");
|
|
9
9
|
var _reactNativeNitroImage = require("react-native-nitro-image");
|
|
10
10
|
var _resizeForStyle = require("./resizeForStyle");
|
|
11
11
|
var _useImage = require("./useImage");
|
|
12
12
|
var _jsxRuntime = require("react/jsx-runtime");
|
|
13
|
+
/**
|
|
14
|
+
* The instance `<PipelineImage>` exposes through its `ref` — the underlying
|
|
15
|
+
* `NativeNitroImage` host view, with the usual native-view methods
|
|
16
|
+
* (`measure`, …).
|
|
17
|
+
*/
|
|
18
|
+
|
|
13
19
|
function sameSize(a, b) {
|
|
14
20
|
return a?.width === b?.width && a?.height === b?.height;
|
|
15
21
|
}
|
|
@@ -37,6 +43,10 @@ function scaleRadius(cornerRadius, scale) {
|
|
|
37
43
|
* without flashing. Without an explicit `cornerRadius` prop, `style`'s
|
|
38
44
|
* `borderRadius`-family properties are baked into the bitmap instead — no
|
|
39
45
|
* separate view-layer rounding needed.
|
|
46
|
+
*
|
|
47
|
+
* The `ref` is forwarded to the underlying `NativeNitroImage` host view, so
|
|
48
|
+
* the component works with `Animated.createAnimatedComponent` (Reanimated or
|
|
49
|
+
* React Native's built-in `Animated`).
|
|
40
50
|
* @example
|
|
41
51
|
* ```tsx
|
|
42
52
|
* <PipelineImage
|
|
@@ -46,7 +56,7 @@ function scaleRadius(cornerRadius, scale) {
|
|
|
46
56
|
* />
|
|
47
57
|
* ```
|
|
48
58
|
*/
|
|
49
|
-
function PipelineImage({
|
|
59
|
+
const PipelineImage = exports.PipelineImage = /*#__PURE__*/(0, _react.forwardRef)(function PipelineImage({
|
|
50
60
|
url,
|
|
51
61
|
blur = 0,
|
|
52
62
|
cornerRadius,
|
|
@@ -56,7 +66,7 @@ function PipelineImage({
|
|
|
56
66
|
style,
|
|
57
67
|
onLayout,
|
|
58
68
|
...viewProps
|
|
59
|
-
}) {
|
|
69
|
+
}, ref) {
|
|
60
70
|
const scale = _reactNative.PixelRatio.get();
|
|
61
71
|
const styleSize = (0, _resizeForStyle.resizeForStyle)(style);
|
|
62
72
|
const [layoutSize, setLayoutSize] = (0, _react.useState)(undefined);
|
|
@@ -103,9 +113,14 @@ function PipelineImage({
|
|
|
103
113
|
};
|
|
104
114
|
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNativeNitroImage.NativeNitroImage, {
|
|
105
115
|
...viewProps,
|
|
116
|
+
ref: ref,
|
|
106
117
|
style: style,
|
|
107
118
|
onLayout: handleLayout,
|
|
108
119
|
image: image
|
|
109
120
|
});
|
|
110
|
-
}
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
// Reanimated and DevTools read the display name; the forwardRef wrapper
|
|
124
|
+
// would otherwise report as anonymous.
|
|
125
|
+
PipelineImage.displayName = 'PipelineImage';
|
|
111
126
|
//# sourceMappingURL=PipelineImage.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["_react","require","_reactNative","_reactNativeNitroImage","_resizeForStyle","_useImage","_jsxRuntime","sameSize","a","b","width","height","scaleRadius","cornerRadius","scale","topLeft","topRight","bottomLeft","bottomRight","PipelineImage","url","blur","cache","onLoad","onError","style","onLayout","viewProps","PixelRatio","get","styleSize","resizeForStyle","layoutSize","setLayoutSize","useState","undefined","resize","effectiveCornerRadius","cornerRadiusForStyle","image","error","useImage","enabled","onLoadRef","useRef","onErrorRef","useEffect","current","handleLayout","event","nativeEvent","layout","next","resizeForLayout","prev","jsx","NativeNitroImage"],"sourceRoot":"../../src","sources":["PipelineImage.tsx"],"mappings":";;;;;;AAAA,IAAAA,MAAA,GAAAC,OAAA;
|
|
1
|
+
{"version":3,"names":["_react","require","_reactNative","_reactNativeNitroImage","_resizeForStyle","_useImage","_jsxRuntime","sameSize","a","b","width","height","scaleRadius","cornerRadius","scale","topLeft","topRight","bottomLeft","bottomRight","PipelineImage","exports","forwardRef","url","blur","cache","onLoad","onError","style","onLayout","viewProps","ref","PixelRatio","get","styleSize","resizeForStyle","layoutSize","setLayoutSize","useState","undefined","resize","effectiveCornerRadius","cornerRadiusForStyle","image","error","useImage","enabled","onLoadRef","useRef","onErrorRef","useEffect","current","handleLayout","event","nativeEvent","layout","next","resizeForLayout","prev","jsx","NativeNitroImage","displayName"],"sourceRoot":"../../src","sources":["PipelineImage.tsx"],"mappings":"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","ignoreList":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["_NitroImagePipeline","require","_PipelineImage","_resizeForStyle","_useImage"],"sourceRoot":"../../src","sources":["index.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,IAAAA,mBAAA,GAAAC,OAAA;AACA,IAAAC,cAAA,GAAAD,OAAA;
|
|
1
|
+
{"version":3,"names":["_NitroImagePipeline","require","_PipelineImage","_resizeForStyle","_useImage"],"sourceRoot":"../../src","sources":["index.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,IAAAA,mBAAA,GAAAC,OAAA;AACA,IAAAC,cAAA,GAAAD,OAAA;AAKA,IAAAE,eAAA,GAAAF,OAAA;AAWA,IAAAG,SAAA,GAAAH,OAAA","ignoreList":[]}
|
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
import { useEffect, useRef, useState } from 'react';
|
|
3
|
+
import { forwardRef, useEffect, useRef, useState } from 'react';
|
|
4
4
|
import { PixelRatio } from 'react-native';
|
|
5
5
|
import { NativeNitroImage } from 'react-native-nitro-image';
|
|
6
6
|
import { cornerRadiusForStyle, resizeForLayout, resizeForStyle } from './resizeForStyle';
|
|
7
7
|
import { useImage } from './useImage';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* The instance `<PipelineImage>` exposes through its `ref` — the underlying
|
|
11
|
+
* `NativeNitroImage` host view, with the usual native-view methods
|
|
12
|
+
* (`measure`, …).
|
|
13
|
+
*/
|
|
8
14
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
9
15
|
function sameSize(a, b) {
|
|
10
16
|
return a?.width === b?.width && a?.height === b?.height;
|
|
@@ -33,6 +39,10 @@ function scaleRadius(cornerRadius, scale) {
|
|
|
33
39
|
* without flashing. Without an explicit `cornerRadius` prop, `style`'s
|
|
34
40
|
* `borderRadius`-family properties are baked into the bitmap instead — no
|
|
35
41
|
* separate view-layer rounding needed.
|
|
42
|
+
*
|
|
43
|
+
* The `ref` is forwarded to the underlying `NativeNitroImage` host view, so
|
|
44
|
+
* the component works with `Animated.createAnimatedComponent` (Reanimated or
|
|
45
|
+
* React Native's built-in `Animated`).
|
|
36
46
|
* @example
|
|
37
47
|
* ```tsx
|
|
38
48
|
* <PipelineImage
|
|
@@ -42,7 +52,7 @@ function scaleRadius(cornerRadius, scale) {
|
|
|
42
52
|
* />
|
|
43
53
|
* ```
|
|
44
54
|
*/
|
|
45
|
-
export function PipelineImage({
|
|
55
|
+
export const PipelineImage = /*#__PURE__*/forwardRef(function PipelineImage({
|
|
46
56
|
url,
|
|
47
57
|
blur = 0,
|
|
48
58
|
cornerRadius,
|
|
@@ -52,7 +62,7 @@ export function PipelineImage({
|
|
|
52
62
|
style,
|
|
53
63
|
onLayout,
|
|
54
64
|
...viewProps
|
|
55
|
-
}) {
|
|
65
|
+
}, ref) {
|
|
56
66
|
const scale = PixelRatio.get();
|
|
57
67
|
const styleSize = resizeForStyle(style);
|
|
58
68
|
const [layoutSize, setLayoutSize] = useState(undefined);
|
|
@@ -99,9 +109,14 @@ export function PipelineImage({
|
|
|
99
109
|
};
|
|
100
110
|
return /*#__PURE__*/_jsx(NativeNitroImage, {
|
|
101
111
|
...viewProps,
|
|
112
|
+
ref: ref,
|
|
102
113
|
style: style,
|
|
103
114
|
onLayout: handleLayout,
|
|
104
115
|
image: image
|
|
105
116
|
});
|
|
106
|
-
}
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
// Reanimated and DevTools read the display name; the forwardRef wrapper
|
|
120
|
+
// would otherwise report as anonymous.
|
|
121
|
+
PipelineImage.displayName = 'PipelineImage';
|
|
107
122
|
//# sourceMappingURL=PipelineImage.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["useEffect","useRef","useState","PixelRatio","NativeNitroImage","cornerRadiusForStyle","resizeForLayout","resizeForStyle","useImage","jsx","_jsx","sameSize","a","b","width","height","scaleRadius","cornerRadius","scale","topLeft","topRight","bottomLeft","bottomRight","PipelineImage","url","blur","cache","onLoad","onError","style","onLayout","viewProps","get","styleSize","layoutSize","setLayoutSize","undefined","resize","effectiveCornerRadius","image","error","enabled","onLoadRef","onErrorRef","current","handleLayout","event","nativeEvent","layout","next","prev"],"sourceRoot":"../../src","sources":["PipelineImage.tsx"],"mappings":";;AAAA,
|
|
1
|
+
{"version":3,"names":["forwardRef","useEffect","useRef","useState","PixelRatio","NativeNitroImage","cornerRadiusForStyle","resizeForLayout","resizeForStyle","useImage","jsx","_jsx","sameSize","a","b","width","height","scaleRadius","cornerRadius","scale","topLeft","topRight","bottomLeft","bottomRight","PipelineImage","url","blur","cache","onLoad","onError","style","onLayout","viewProps","ref","get","styleSize","layoutSize","setLayoutSize","undefined","resize","effectiveCornerRadius","image","error","enabled","onLoadRef","onErrorRef","current","handleLayout","event","nativeEvent","layout","next","prev","displayName"],"sourceRoot":"../../src","sources":["PipelineImage.tsx"],"mappings":"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","ignoreList":[]}
|
package/lib/module/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["NitroImagePipeline","PipelineImage","cornerRadiusForStyle","resizeForLayout","resizeForStyle","useImage"],"sourceRoot":"../../src","sources":["index.ts"],"mappings":";;AAAA,SAASA,kBAAkB,QAAQ,sBAAsB;AACzD,
|
|
1
|
+
{"version":3,"names":["NitroImagePipeline","PipelineImage","cornerRadiusForStyle","resizeForLayout","resizeForStyle","useImage"],"sourceRoot":"../../src","sources":["index.ts"],"mappings":";;AAAA,SAASA,kBAAkB,QAAQ,sBAAsB;AACzD,SACEC,aAAa,QAGR,iBAAiB;AACxB,SACEC,oBAAoB,EACpBC,eAAe,EACfC,cAAc,QACT,kBAAkB;AAOzB,SAASC,QAAQ,QAAQ,YAAY","ignoreList":[]}
|
|
@@ -1,8 +1,15 @@
|
|
|
1
|
+
import { type ComponentRef } from 'react';
|
|
1
2
|
import { type HostComponent } from 'react-native';
|
|
2
3
|
import { type Image, NativeNitroImage } from 'react-native-nitro-image';
|
|
3
4
|
import type { CacheOption, CornerRadii } from './specs/nitro-image-toolkit.nitro';
|
|
4
5
|
type ReactProps<T> = T extends HostComponent<infer P> ? P : never;
|
|
5
6
|
type NativeImageProps = ReactProps<typeof NativeNitroImage>;
|
|
7
|
+
/**
|
|
8
|
+
* The instance `<PipelineImage>` exposes through its `ref` — the underlying
|
|
9
|
+
* `NativeNitroImage` host view, with the usual native-view methods
|
|
10
|
+
* (`measure`, …).
|
|
11
|
+
*/
|
|
12
|
+
export type PipelineImageRef = ComponentRef<typeof NativeNitroImage>;
|
|
6
13
|
export interface PipelineImageProps extends Omit<NativeImageProps, 'image'> {
|
|
7
14
|
/** URL of the image to load through the pipeline. */
|
|
8
15
|
url: string;
|
|
@@ -43,6 +50,10 @@ export interface PipelineImageProps extends Omit<NativeImageProps, 'image'> {
|
|
|
43
50
|
* without flashing. Without an explicit `cornerRadius` prop, `style`'s
|
|
44
51
|
* `borderRadius`-family properties are baked into the bitmap instead — no
|
|
45
52
|
* separate view-layer rounding needed.
|
|
53
|
+
*
|
|
54
|
+
* The `ref` is forwarded to the underlying `NativeNitroImage` host view, so
|
|
55
|
+
* the component works with `Animated.createAnimatedComponent` (Reanimated or
|
|
56
|
+
* React Native's built-in `Animated`).
|
|
46
57
|
* @example
|
|
47
58
|
* ```tsx
|
|
48
59
|
* <PipelineImage
|
|
@@ -52,6 +63,11 @@ export interface PipelineImageProps extends Omit<NativeImageProps, 'image'> {
|
|
|
52
63
|
* />
|
|
53
64
|
* ```
|
|
54
65
|
*/
|
|
55
|
-
export declare
|
|
66
|
+
export declare const PipelineImage: import("react").ForwardRefExoticComponent<PipelineImageProps & import("react").RefAttributes<import("react").Component<{
|
|
67
|
+
hybridRef?: import("react-native-nitro-modules").NitroViewWrappedCallback<((ref: import("react-native-nitro-modules").HybridView<import("react-native-nitro-image/lib/typescript/specs/ImageView.nitro").NativeNitroImageViewProps, import("react-native-nitro-image/lib/typescript/specs/ImageView.nitro").NativeNitroImageViewMethods>) => void) | undefined> | undefined;
|
|
68
|
+
image?: Image | import("react-native-nitro-image").ImageLoader;
|
|
69
|
+
resizeMode?: import("react-native-nitro-image/lib/typescript/specs/ImageView.nitro").ResizeMode;
|
|
70
|
+
recyclingKey?: string;
|
|
71
|
+
} & import("react-native").ViewProps, {}, any> & import("react-native").ReactNativeElement>>;
|
|
56
72
|
export {};
|
|
57
73
|
//# sourceMappingURL=PipelineImage.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PipelineImage.d.ts","sourceRoot":"","sources":["../../../src/PipelineImage.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"PipelineImage.d.ts","sourceRoot":"","sources":["../../../src/PipelineImage.tsx"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,YAAY,EAKlB,MAAM,OAAO,CAAC;AACf,OAAO,EACL,KAAK,aAAa,EAGnB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,KAAK,KAAK,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAOxE,OAAO,KAAK,EACV,WAAW,EACX,WAAW,EAEZ,MAAM,mCAAmC,CAAC;AAG3C,KAAK,UAAU,CAAC,CAAC,IAAI,CAAC,SAAS,aAAa,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;AAClE,KAAK,gBAAgB,GAAG,UAAU,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAE5D;;;;GAIG;AACH,MAAM,MAAM,gBAAgB,GAAG,YAAY,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAErE,MAAM,WAAW,kBAAmB,SAAQ,IAAI,CAAC,gBAAgB,EAAE,OAAO,CAAC;IACzE,qDAAqD;IACrD,GAAG,EAAE,MAAM,CAAC;IACZ;;;;;;OAMG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;;;;;;;OASG;IACH,YAAY,CAAC,EAAE,MAAM,GAAG,WAAW,CAAC;IACpC,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,0EAA0E;IAC1E,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IAChC,iCAAiC;IACjC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CAClC;AAqBD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,eAAO,MAAM,aAAa;;;;;4FAqEzB,CAAC"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { NitroImagePipeline } from './NitroImagePipeline';
|
|
2
|
-
export { PipelineImage, type PipelineImageProps } from './PipelineImage';
|
|
2
|
+
export { PipelineImage, type PipelineImageProps, type PipelineImageRef, } from './PipelineImage';
|
|
3
3
|
export { cornerRadiusForStyle, resizeForLayout, resizeForStyle, } from './resizeForStyle';
|
|
4
4
|
export type { CacheOption, CornerRadii, Options, ResizeOptions, } from './specs/nitro-image-toolkit.nitro';
|
|
5
5
|
export { useImage } from './useImage';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EACL,aAAa,EACb,KAAK,kBAAkB,EACvB,KAAK,gBAAgB,GACtB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,oBAAoB,EACpB,eAAe,EACf,cAAc,GACf,MAAM,kBAAkB,CAAC;AAC1B,YAAY,EACV,WAAW,EACX,WAAW,EACX,OAAO,EACP,aAAa,GACd,MAAM,mCAAmC,CAAC;AAC3C,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-nitro-image-pipeline",
|
|
3
|
-
"version": "1.2
|
|
3
|
+
"version": "1.3.2",
|
|
4
4
|
"description": "High-performance image loading, caching, and processing for React Native, built with Nitro Modules",
|
|
5
5
|
"main": "./lib/commonjs/index.js",
|
|
6
6
|
"module": "./lib/module/index.js",
|
|
@@ -86,7 +86,8 @@
|
|
|
86
86
|
"react-native-nitro-image": "0.15.1"
|
|
87
87
|
},
|
|
88
88
|
"overrides": {
|
|
89
|
-
"lodash-es": "4.17.21"
|
|
89
|
+
"lodash-es": "4.17.21",
|
|
90
|
+
"typescript": "$typescript"
|
|
90
91
|
},
|
|
91
92
|
"react-native-builder-bob": {
|
|
92
93
|
"source": "src",
|
package/src/PipelineImage.tsx
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
type ComponentRef,
|
|
3
|
+
forwardRef,
|
|
4
|
+
useEffect,
|
|
5
|
+
useRef,
|
|
6
|
+
useState,
|
|
7
|
+
} from 'react';
|
|
2
8
|
import {
|
|
3
9
|
type HostComponent,
|
|
4
10
|
type LayoutChangeEvent,
|
|
@@ -21,6 +27,13 @@ import { useImage } from './useImage';
|
|
|
21
27
|
type ReactProps<T> = T extends HostComponent<infer P> ? P : never;
|
|
22
28
|
type NativeImageProps = ReactProps<typeof NativeNitroImage>;
|
|
23
29
|
|
|
30
|
+
/**
|
|
31
|
+
* The instance `<PipelineImage>` exposes through its `ref` — the underlying
|
|
32
|
+
* `NativeNitroImage` host view, with the usual native-view methods
|
|
33
|
+
* (`measure`, …).
|
|
34
|
+
*/
|
|
35
|
+
export type PipelineImageRef = ComponentRef<typeof NativeNitroImage>;
|
|
36
|
+
|
|
24
37
|
export interface PipelineImageProps extends Omit<NativeImageProps, 'image'> {
|
|
25
38
|
/** URL of the image to load through the pipeline. */
|
|
26
39
|
url: string;
|
|
@@ -81,6 +94,10 @@ function scaleRadius(
|
|
|
81
94
|
* without flashing. Without an explicit `cornerRadius` prop, `style`'s
|
|
82
95
|
* `borderRadius`-family properties are baked into the bitmap instead — no
|
|
83
96
|
* separate view-layer rounding needed.
|
|
97
|
+
*
|
|
98
|
+
* The `ref` is forwarded to the underlying `NativeNitroImage` host view, so
|
|
99
|
+
* the component works with `Animated.createAnimatedComponent` (Reanimated or
|
|
100
|
+
* React Native's built-in `Animated`).
|
|
84
101
|
* @example
|
|
85
102
|
* ```tsx
|
|
86
103
|
* <PipelineImage
|
|
@@ -90,67 +107,77 @@ function scaleRadius(
|
|
|
90
107
|
* />
|
|
91
108
|
* ```
|
|
92
109
|
*/
|
|
93
|
-
export
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
110
|
+
export const PipelineImage = forwardRef<PipelineImageRef, PipelineImageProps>(
|
|
111
|
+
function PipelineImage(
|
|
112
|
+
{
|
|
113
|
+
url,
|
|
114
|
+
blur = 0,
|
|
115
|
+
cornerRadius,
|
|
116
|
+
cache,
|
|
117
|
+
onLoad,
|
|
118
|
+
onError,
|
|
119
|
+
style,
|
|
120
|
+
onLayout,
|
|
121
|
+
...viewProps
|
|
122
|
+
},
|
|
123
|
+
ref,
|
|
124
|
+
) {
|
|
125
|
+
const scale = PixelRatio.get();
|
|
126
|
+
const styleSize = resizeForStyle(style);
|
|
127
|
+
const [layoutSize, setLayoutSize] = useState<ResizeOptions | undefined>(
|
|
128
|
+
undefined,
|
|
129
|
+
);
|
|
130
|
+
// A numeric style is what the caller declared, so it wins and starts the
|
|
131
|
+
// request a frame earlier; the measured layout is the fallback.
|
|
132
|
+
const resize = styleSize ?? layoutSize;
|
|
133
|
+
// Same precedence: an explicit prop wins over what style implies.
|
|
134
|
+
const effectiveCornerRadius =
|
|
135
|
+
cornerRadius ?? cornerRadiusForStyle(style) ?? 0;
|
|
115
136
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
137
|
+
const { image, error } = useImage({
|
|
138
|
+
url,
|
|
139
|
+
blur: blur * scale,
|
|
140
|
+
cornerRadius: scaleRadius(effectiveCornerRadius, scale),
|
|
141
|
+
cache,
|
|
142
|
+
resize,
|
|
143
|
+
enabled: resize !== undefined,
|
|
144
|
+
});
|
|
124
145
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
146
|
+
// Latest callbacks in refs so inline arrow props don't re-fire the effects.
|
|
147
|
+
const onLoadRef = useRef(onLoad);
|
|
148
|
+
const onErrorRef = useRef(onError);
|
|
149
|
+
useEffect(() => {
|
|
150
|
+
onLoadRef.current = onLoad;
|
|
151
|
+
onErrorRef.current = onError;
|
|
152
|
+
});
|
|
153
|
+
useEffect(() => {
|
|
154
|
+
if (image) onLoadRef.current?.(image);
|
|
155
|
+
}, [image]);
|
|
156
|
+
useEffect(() => {
|
|
157
|
+
if (error) onErrorRef.current?.(error);
|
|
158
|
+
}, [error]);
|
|
138
159
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
160
|
+
const handleLayout = (event: LayoutChangeEvent) => {
|
|
161
|
+
onLayout?.(event);
|
|
162
|
+
const { width, height } = event.nativeEvent.layout;
|
|
163
|
+
const next = resizeForLayout(width, height);
|
|
164
|
+
// Always record it (even when a numeric style is in charge) so a later
|
|
165
|
+
// switch to a non-numeric style has a size to fall back on.
|
|
166
|
+
setLayoutSize((prev) => (sameSize(prev, next) ? prev : next));
|
|
167
|
+
};
|
|
147
168
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
169
|
+
return (
|
|
170
|
+
<NativeNitroImage
|
|
171
|
+
{...viewProps}
|
|
172
|
+
ref={ref}
|
|
173
|
+
style={style}
|
|
174
|
+
onLayout={handleLayout}
|
|
175
|
+
image={image}
|
|
176
|
+
/>
|
|
177
|
+
);
|
|
178
|
+
},
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
// Reanimated and DevTools read the display name; the forwardRef wrapper
|
|
182
|
+
// would otherwise report as anonymous.
|
|
183
|
+
PipelineImage.displayName = 'PipelineImage';
|
package/src/index.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
export { NitroImagePipeline } from './NitroImagePipeline';
|
|
2
|
-
export {
|
|
2
|
+
export {
|
|
3
|
+
PipelineImage,
|
|
4
|
+
type PipelineImageProps,
|
|
5
|
+
type PipelineImageRef,
|
|
6
|
+
} from './PipelineImage';
|
|
3
7
|
export {
|
|
4
8
|
cornerRadiusForStyle,
|
|
5
9
|
resizeForLayout,
|