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 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. Returns `Promise<void>`.
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
@@ -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
- val request = ImageRequest.Builder(context).data(url).build()
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
- private static let sharedPrefetcher = ImagePrefetcher(pipeline: sharedPipeline)
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 resize = options?.resize, resize.width > 0, resize.height > 0 {
166
+ if let size = resizeSize(for: options) {
141
167
  processors.append(ImageProcessors.Resize(
142
- size: CGSize(width: resize.width, height: resize.height),
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
- let imgRequest = ImageRequest(
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 = 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":"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","ignoreList":[]}
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;AACA,IAAAE,eAAA,GAAAF,OAAA;AAWA,IAAAG,SAAA,GAAAH,OAAA","ignoreList":[]}
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":"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","ignoreList":[]}
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":";;AAAA,SAEEA,UAAU,EACVC,SAAS,EACTC,MAAM,EACNC,QAAQ,QACH,OAAO;AACd,SAGEC,UAAU,QACL,cAAc;AACrB,SAAqBC,gBAAgB,QAAQ,0BAA0B;AAEvE,SACEC,oBAAoB,EACpBC,eAAe,EACfC,cAAc,QACT,kBAAkB;AAMzB,SAASC,QAAQ,QAAQ,YAAY;;AAKrC;AACA;AACA;AACA;AACA;AAJA,SAAAC,GAAA,IAAAC,IAAA;AAoCA,SAASC,QAAQA,CAACC,CAAiB,EAAEC,CAAiB,EAAW;EAC/D,OAAOD,CAAC,EAAEE,KAAK,KAAKD,CAAC,EAAEC,KAAK,IAAIF,CAAC,EAAEG,MAAM,KAAKF,CAAC,EAAEE,MAAM;AACzD;AAEA,SAASC,WAAWA,CAClBC,YAAkC,EAClCC,KAAa,EACS;EACtB,IAAI,OAAOD,YAAY,KAAK,QAAQ,EAAE;IACpC,OAAOA,YAAY,GAAGC,KAAK;EAC7B;EACA,OAAO;IACLC,OAAO,EAAE,CAACF,YAAY,CAACE,OAAO,IAAI,CAAC,IAAID,KAAK;IAC5CE,QAAQ,EAAE,CAACH,YAAY,CAACG,QAAQ,IAAI,CAAC,IAAIF,KAAK;IAC9CG,UAAU,EAAE,CAACJ,YAAY,CAACI,UAAU,IAAI,CAAC,IAAIH,KAAK;IAClDI,WAAW,EAAE,CAACL,YAAY,CAACK,WAAW,IAAI,CAAC,IAAIJ;EACjD,CAAC;AACH;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,MAAMK,aAAa,gBAAGxB,UAAU,CACrC,SAASwB,aAAaA,CACpB;EACEC,GAAG;EACHC,IAAI,GAAG,CAAC;EACRR,YAAY;EACZS,KAAK;EACLC,MAAM;EACNC,OAAO;EACPC,KAAK;EACLC,QAAQ;EACR,GAAGC;AACL,CAAC,EACDC,GAAG,EACH;EACA,MAAMd,KAAK,GAAGf,UAAU,CAAC8B,GAAG,CAAC,CAAC;EAC9B,MAAMC,SAAS,GAAG3B,cAAc,CAACsB,KAAK,CAAC;EACvC,MAAM,CAACM,UAAU,EAAEC,aAAa,CAAC,GAAGlC,QAAQ,CAC1CmC,SACF,CAAC;EACD;EACA;EACA,MAAMC,MAAM,GAAGJ,SAAS,IAAIC,UAAU;EACtC;EACA,MAAMI,qBAAqB,GACzBtB,YAAY,IAAIZ,oBAAoB,CAACwB,KAAK,CAAC,IAAI,CAAC;EAElD,MAAM;IAAEW,KAAK;IAAEC;EAAM,CAAC,GAAGjC,QAAQ,CAAC;IAChCgB,GAAG;IACHC,IAAI,EAAEA,IAAI,GAAGP,KAAK;IAClBD,YAAY,EAAED,WAAW,CAACuB,qBAAqB,EAAErB,KAAK,CAAC;IACvDQ,KAAK;IACLY,MAAM;IACNI,OAAO,EAAEJ,MAAM,KAAKD;EACtB,CAAC,CAAC;;EAEF;EACA,MAAMM,SAAS,GAAG1C,MAAM,CAAC0B,MAAM,CAAC;EAChC,MAAMiB,UAAU,GAAG3C,MAAM,CAAC2B,OAAO,CAAC;EAClC5B,SAAS,CAAC,MAAM;IACd2C,SAAS,CAACE,OAAO,GAAGlB,MAAM;IAC1BiB,UAAU,CAACC,OAAO,GAAGjB,OAAO;EAC9B,CAAC,CAAC;EACF5B,SAAS,CAAC,MAAM;IACd,IAAIwC,KAAK,EAAEG,SAAS,CAACE,OAAO,GAAGL,KAAK,CAAC;EACvC,CAAC,EAAE,CAACA,KAAK,CAAC,CAAC;EACXxC,SAAS,CAAC,MAAM;IACd,IAAIyC,KAAK,EAAEG,UAAU,CAACC,OAAO,GAAGJ,KAAK,CAAC;EACxC,CAAC,EAAE,CAACA,KAAK,CAAC,CAAC;EAEX,MAAMK,YAAY,GAAIC,KAAwB,IAAK;IACjDjB,QAAQ,GAAGiB,KAAK,CAAC;IACjB,MAAM;MAAEjC,KAAK;MAAEC;IAAO,CAAC,GAAGgC,KAAK,CAACC,WAAW,CAACC,MAAM;IAClD,MAAMC,IAAI,GAAG5C,eAAe,CAACQ,KAAK,EAAEC,MAAM,CAAC;IAC3C;IACA;IACAqB,aAAa,CAAEe,IAAI,IAAMxC,QAAQ,CAACwC,IAAI,EAAED,IAAI,CAAC,GAAGC,IAAI,GAAGD,IAAK,CAAC;EAC/D,CAAC;EAED,oBACExC,IAAA,CAACN,gBAAgB;IAAA,GACX2B,SAAS;IACbC,GAAG,EAAEA,GAAI;IACTH,KAAK,EAAEA,KAAM;IACbC,QAAQ,EAAEgB,YAAa;IACvBN,KAAK,EAAEA;EAAM,CACd,CAAC;AAEN,CACF,CAAC;;AAED;AACA;AACAjB,aAAa,CAAC6B,WAAW,GAAG,eAAe","ignoreList":[]}
@@ -1 +1 @@
1
- {"version":3,"names":["NitroImagePipeline","PipelineImage","cornerRadiusForStyle","resizeForLayout","resizeForStyle","useImage"],"sourceRoot":"../../src","sources":["index.ts"],"mappings":";;AAAA,SAASA,kBAAkB,QAAQ,sBAAsB;AACzD,SAASC,aAAa,QAAiC,iBAAiB;AACxE,SACEC,oBAAoB,EACpBC,eAAe,EACfC,cAAc,QACT,kBAAkB;AAOzB,SAASC,QAAQ,QAAQ,YAAY","ignoreList":[]}
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 function PipelineImage({ url, blur, cornerRadius, cache, onLoad, onError, style, onLayout, ...viewProps }: PipelineImageProps): import("react").JSX.Element;
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":"AACA,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,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;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,aAAa,CAAC,EAC5B,GAAG,EACH,IAAQ,EACR,YAAY,EACZ,KAAK,EACL,MAAM,EACN,OAAO,EACP,KAAK,EACL,QAAQ,EACR,GAAG,SAAS,EACb,EAAE,kBAAkB,+BAqDpB"}
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,EAAE,aAAa,EAAE,KAAK,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AACzE,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"}
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.1",
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",
@@ -1,4 +1,10 @@
1
- import { useEffect, useRef, useState } from 'react';
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 function PipelineImage({
94
- url,
95
- blur = 0,
96
- cornerRadius,
97
- cache,
98
- onLoad,
99
- onError,
100
- style,
101
- onLayout,
102
- ...viewProps
103
- }: PipelineImageProps) {
104
- const scale = PixelRatio.get();
105
- const styleSize = resizeForStyle(style);
106
- const [layoutSize, setLayoutSize] = useState<ResizeOptions | undefined>(
107
- undefined,
108
- );
109
- // A numeric style is what the caller declared, so it wins and starts the
110
- // request a frame earlier; the measured layout is the fallback.
111
- const resize = styleSize ?? layoutSize;
112
- // Same precedence: an explicit prop wins over what style implies.
113
- const effectiveCornerRadius =
114
- cornerRadius ?? cornerRadiusForStyle(style) ?? 0;
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
- const { image, error } = useImage({
117
- url,
118
- blur: blur * scale,
119
- cornerRadius: scaleRadius(effectiveCornerRadius, scale),
120
- cache,
121
- resize,
122
- enabled: resize !== undefined,
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
- // Latest callbacks in refs so inline arrow props don't re-fire the effects.
126
- const onLoadRef = useRef(onLoad);
127
- const onErrorRef = useRef(onError);
128
- useEffect(() => {
129
- onLoadRef.current = onLoad;
130
- onErrorRef.current = onError;
131
- });
132
- useEffect(() => {
133
- if (image) onLoadRef.current?.(image);
134
- }, [image]);
135
- useEffect(() => {
136
- if (error) onErrorRef.current?.(error);
137
- }, [error]);
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
- const handleLayout = (event: LayoutChangeEvent) => {
140
- onLayout?.(event);
141
- const { width, height } = event.nativeEvent.layout;
142
- const next = resizeForLayout(width, height);
143
- // Always record it (even when a numeric style is in charge) so a later
144
- // switch to a non-numeric style has a size to fall back on.
145
- setLayoutSize((prev) => (sameSize(prev, next) ? prev : next));
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
- return (
149
- <NativeNitroImage
150
- {...viewProps}
151
- style={style}
152
- onLayout={handleLayout}
153
- image={image}
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 { PipelineImage, type PipelineImageProps } from './PipelineImage';
2
+ export {
3
+ PipelineImage,
4
+ type PipelineImageProps,
5
+ type PipelineImageRef,
6
+ } from './PipelineImage';
3
7
  export {
4
8
  cornerRadiusForStyle,
5
9
  resizeForLayout,