react-native-nitro-image-pipeline 1.3.2 → 1.4.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 CHANGED
@@ -306,6 +306,20 @@ RenderScript's true Gaussian, downscaling first when sigma exceeds the single-pa
306
306
  ~10.6px and compensating the radius so the result is unchanged. Both clamp at the edges, so blurred
307
307
  images keep their borders instead of fading out.
308
308
 
309
+ ### `setMemoryCacheLimit(bytes)`
310
+
311
+ Caps the in-memory cache of decoded bitmaps at `bytes`, evicting least-recently-used entries
312
+ immediately if the cache is currently larger. Pass `0` to disable in-memory caching entirely — the
313
+ disk cache keeps working. Synchronous; throws on negative or non-finite values.
314
+
315
+ ```ts
316
+ // Keep at most 32 MB of decoded bitmaps in RAM
317
+ NitroImagePipeline.setMemoryCacheLimit(32 * 1024 * 1024);
318
+
319
+ // Or opt out of decoded-bitmap caching altogether
320
+ NitroImagePipeline.setMemoryCacheLimit(0);
321
+ ```
322
+
309
323
  ### `clearCache()`
310
324
 
311
325
  Removes all cached images from memory and disk. Returns `Promise<void>` that resolves once both caches are cleared.
@@ -320,12 +334,23 @@ The pipeline is set up so RAM scales with what you display, not with what you do
320
334
  48 MP photo decompresses to ~190 MB of bitmap no matter how small you display it.
321
335
  - **Prefetching stores bytes, not bitmaps.** `preLoadImage(s)` writes the download to the disk
322
336
  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.
337
+ - **The in-memory cache is capped and tunable** (defaults: 128 MB on iOS, 25% of the app's memory
338
+ class on Android). It holds decoded bitmaps for instant re-display and evicts
339
+ least-recently-used entries — also in response to memory warnings and backgrounding. Memory
340
+ profilers attribute this cache to the app; a plateau at the cap is expected and evictable, not a
341
+ leak. Lower the cap with [`setMemoryCacheLimit`](#setmemorycachelimitbytes), use `cache: 'disk'`
342
+ or `cache: 'none'` on images you won't show again soon, and `clearCache()` to drop everything.
343
+ - **Coming from a setup with no decoded-image cache** (e.g. loading files you downloaded
344
+ yourself)? Steady-state RAM will read higher here *by design*: after screens unmount, the cache
345
+ keeps their bitmaps around for instant re-display. For the old memory profile with the
346
+ pipeline's features intact, pass `cache: 'disk'` on your requests or call
347
+ `setMemoryCacheLimit(0)` once — RAM then holds only the images currently referenced, and
348
+ re-displays decode from the disk cache (cheap, since decodes are subsampled to the target size).
349
+ - **Android + `Image.dispose()`:** only call `dispose()` on images loaded with `cache: 'disk'` /
350
+ `cache: 'none'` (or with the memory cache disabled). With the memory cache on, the returned
351
+ image shares its bitmap with the cache, and disposing recycles a bitmap the cache may serve
352
+ again. Without `dispose()`, images are freed by the JS garbage collector — their bitmap size is
353
+ reported to it, so unreferenced images do get collected under pressure.
329
354
 
330
355
  ## Upgrading from 0.3.x
331
356
 
@@ -157,6 +157,18 @@ class HybridNitroImagePipeline : HybridNitroImagePipelineSpec() {
157
157
  }
158
158
  }
159
159
 
160
+ override fun setMemoryCacheLimit(bytes: Double) {
161
+ require(bytes >= 0 && bytes.isFinite()) {
162
+ "Memory cache limit must be a non-negative, finite number of bytes (got $bytes)"
163
+ }
164
+ imageLoader.memoryCache?.apply {
165
+ maxSize = bytes.toLong()
166
+ // Setting maxSize only affects future inserts; evict down to it now so
167
+ // the call frees memory immediately.
168
+ trimToSize(bytes.toLong())
169
+ }
170
+ }
171
+
160
172
  override fun clearCache(): Promise<Unit> = Promise.async {
161
173
  imageLoader.memoryCache?.clear()
162
174
  // DiskCache.clear() does file I/O; keep it off the JS thread but await
@@ -228,6 +228,22 @@ class HybridNitroImagePipeline: HybridNitroImagePipelineSpec {
228
228
  }
229
229
  }
230
230
 
231
+ func setMemoryCacheLimit(bytes: Double) throws {
232
+ guard bytes >= 0, bytes.isFinite else {
233
+ throw RuntimeError.error(
234
+ withMessage: "Memory cache limit must be a non-negative, finite number of bytes (got \(bytes))"
235
+ )
236
+ }
237
+ guard let cache = pipeline.configuration.imageCache as? ImageCache else { return }
238
+ // Double(Int.max) rounds up to 2^63, so Int(_:) of the clamped value
239
+ // would trap; branch instead of min-ing.
240
+ let limit = bytes >= Double(Int.max) ? Int.max : Int(bytes)
241
+ cache.costLimit = limit
242
+ // Setting the limit only affects future inserts; evict down to it now
243
+ // so the call frees memory immediately.
244
+ cache.trim(toCost: limit)
245
+ }
246
+
231
247
  func clearCache() throws -> Promise<Void> {
232
248
  return Promise.async {
233
249
  self.pipeline.cache.removeAll()
@@ -73,6 +73,16 @@ export interface NitroImagePipeline extends HybridObject<{
73
73
  * {@linkcode Options.blur}.
74
74
  */
75
75
  gaussianBlur(image: Image, radius: number): Promise<Image>;
76
+ /**
77
+ * Caps the in-memory cache of decoded bitmaps at `bytes`, evicting
78
+ * least-recently-used entries immediately if it is currently larger. Pass
79
+ * `0` to disable in-memory caching entirely (the disk cache still works).
80
+ *
81
+ * Defaults: 128 MB on iOS, 25% of the app's memory class on Android. The
82
+ * cache trades RAM for instant re-display; lower it (or use
83
+ * `cache: 'disk'` per request) in memory-constrained apps.
84
+ */
85
+ setMemoryCacheLimit(bytes: number): void;
76
86
  clearCache(): Promise<void>;
77
87
  }
78
88
  //# sourceMappingURL=nitro-image-toolkit.nitro.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"nitro-image-toolkit.nitro.d.ts","sourceRoot":"","sources":["../../../../src/specs/nitro-image-toolkit.nitro.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,0BAA0B,CAAC;AACtD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAE/D,MAAM,MAAM,WAAW,GAAG,QAAQ,GAAG,MAAM,GAAG,MAAM,CAAC;AAErD;;;GAGG;AACH,MAAM,WAAW,WAAW;IAC1B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;GAIG;AACH,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,MAAM,OAAO,GAAG;IACpB;;;;;;;;;;;OAWG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB;;;;;;;;;;;;;OAaG;IACH,YAAY,CAAC,EAAE,MAAM,GAAG,WAAW,CAAC;IACpC;;;;;;;OAOG;IACH,MAAM,CAAC,EAAE,aAAa,CAAC;CACxB,CAAC;AAEF,MAAM,WAAW,kBAAmB,SAAQ,YAAY,CAAC;IACvD,GAAG,EAAE,OAAO,CAAC;IACb,OAAO,EAAE,QAAQ,CAAC;CACnB,CAAC;IACA,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC;IAC1D,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACzC,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7C;;;;OAIG;IACH,YAAY,CAAC,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC;IAG3D,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC7B"}
1
+ {"version":3,"file":"nitro-image-toolkit.nitro.d.ts","sourceRoot":"","sources":["../../../../src/specs/nitro-image-toolkit.nitro.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,0BAA0B,CAAC;AACtD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAE/D,MAAM,MAAM,WAAW,GAAG,QAAQ,GAAG,MAAM,GAAG,MAAM,CAAC;AAErD;;;GAGG;AACH,MAAM,WAAW,WAAW;IAC1B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;GAIG;AACH,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,MAAM,OAAO,GAAG;IACpB;;;;;;;;;;;OAWG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB;;;;;;;;;;;;;OAaG;IACH,YAAY,CAAC,EAAE,MAAM,GAAG,WAAW,CAAC;IACpC;;;;;;;OAOG;IACH,MAAM,CAAC,EAAE,aAAa,CAAC;CACxB,CAAC;AAEF,MAAM,WAAW,kBAAmB,SAAQ,YAAY,CAAC;IACvD,GAAG,EAAE,OAAO,CAAC;IACb,OAAO,EAAE,QAAQ,CAAC;CACnB,CAAC;IACA,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC;IAC1D,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACzC,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7C;;;;OAIG;IACH,YAAY,CAAC,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC;IAG3D;;;;;;;;OAQG;IACH,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAEzC,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC7B"}
@@ -142,6 +142,10 @@ namespace margelo::nitro::nitroimagepipeline {
142
142
  return __promise;
143
143
  }();
144
144
  }
145
+ void JHybridNitroImagePipelineSpec::setMemoryCacheLimit(double bytes) {
146
+ static const auto method = _javaPart->javaClassStatic()->getMethod<void(double /* bytes */)>("setMemoryCacheLimit");
147
+ method(_javaPart, bytes);
148
+ }
145
149
  std::shared_ptr<Promise<void>> JHybridNitroImagePipelineSpec::clearCache() {
146
150
  static const auto method = _javaPart->javaClassStatic()->getMethod<jni::local_ref<JPromise::javaobject>()>("clearCache");
147
151
  auto __result = method(_javaPart);
@@ -58,6 +58,7 @@ namespace margelo::nitro::nitroimagepipeline {
58
58
  std::shared_ptr<Promise<void>> preLoadImage(const std::string& url) override;
59
59
  std::shared_ptr<Promise<void>> preLoadImages(const std::vector<std::string>& urls) override;
60
60
  std::shared_ptr<Promise<std::shared_ptr<margelo::nitro::image::HybridImageSpec>>> gaussianBlur(const std::shared_ptr<margelo::nitro::image::HybridImageSpec>& image, double radius) override;
61
+ void setMemoryCacheLimit(double bytes) override;
61
62
  std::shared_ptr<Promise<void>> clearCache() override;
62
63
 
63
64
  private:
@@ -47,6 +47,10 @@ abstract class HybridNitroImagePipelineSpec: HybridObject() {
47
47
  @Keep
48
48
  abstract fun gaussianBlur(image: com.margelo.nitro.image.HybridImageSpec, radius: Double): Promise<com.margelo.nitro.image.HybridImageSpec>
49
49
 
50
+ @DoNotStrip
51
+ @Keep
52
+ abstract fun setMemoryCacheLimit(bytes: Double): Unit
53
+
50
54
  @DoNotStrip
51
55
  @Keep
52
56
  abstract fun clearCache(): Promise<Unit>
@@ -297,5 +297,14 @@ namespace margelo::nitro::nitroimagepipeline::bridge::swift {
297
297
  inline Result_std__shared_ptr_Promise_void___ create_Result_std__shared_ptr_Promise_void___(const std::exception_ptr& error) noexcept {
298
298
  return Result<std::shared_ptr<Promise<void>>>::withError(error);
299
299
  }
300
+
301
+ // pragma MARK: Result<void>
302
+ using Result_void_ = Result<void>;
303
+ inline Result_void_ create_Result_void_() noexcept {
304
+ return Result<void>::withValue();
305
+ }
306
+ inline Result_void_ create_Result_void_(const std::exception_ptr& error) noexcept {
307
+ return Result<void>::withError(error);
308
+ }
300
309
 
301
310
  } // namespace margelo::nitro::nitroimagepipeline::bridge::swift
@@ -117,6 +117,12 @@ namespace margelo::nitro::nitroimagepipeline {
117
117
  auto __value = std::move(__result.value());
118
118
  return __value;
119
119
  }
120
+ inline void setMemoryCacheLimit(double bytes) override {
121
+ auto __result = _swiftPart.setMemoryCacheLimit(std::forward<decltype(bytes)>(bytes));
122
+ if (__result.hasError()) [[unlikely]] {
123
+ std::rethrow_exception(__result.error());
124
+ }
125
+ }
120
126
  inline std::shared_ptr<Promise<void>> clearCache() override {
121
127
  auto __result = _swiftPart.clearCache();
122
128
  if (__result.hasError()) [[unlikely]] {
@@ -18,6 +18,7 @@ public protocol HybridNitroImagePipelineSpec_protocol: HybridObject {
18
18
  func preLoadImage(url: String) throws -> Promise<Void>
19
19
  func preLoadImages(urls: [String]) throws -> Promise<Void>
20
20
  func gaussianBlur(image: (any HybridImageSpec), radius: Double) throws -> Promise<(any HybridImageSpec)>
21
+ func setMemoryCacheLimit(bytes: Double) throws -> Void
21
22
  func clearCache() throws -> Promise<Void>
22
23
  }
23
24
 
@@ -211,6 +211,17 @@ open class HybridNitroImagePipelineSpec_cxx {
211
211
  }
212
212
  }
213
213
 
214
+ @inline(__always)
215
+ public final func setMemoryCacheLimit(bytes: Double) -> bridge.Result_void_ {
216
+ do {
217
+ try self.__implementation.setMemoryCacheLimit(bytes: bytes)
218
+ return bridge.create_Result_void_()
219
+ } catch (let __error) {
220
+ let __exceptionPtr = __error.toCpp()
221
+ return bridge.create_Result_void_(__exceptionPtr)
222
+ }
223
+ }
224
+
214
225
  @inline(__always)
215
226
  public final func clearCache() -> bridge.Result_std__shared_ptr_Promise_void___ {
216
227
  do {
@@ -18,6 +18,7 @@ namespace margelo::nitro::nitroimagepipeline {
18
18
  prototype.registerHybridMethod("preLoadImage", &HybridNitroImagePipelineSpec::preLoadImage);
19
19
  prototype.registerHybridMethod("preLoadImages", &HybridNitroImagePipelineSpec::preLoadImages);
20
20
  prototype.registerHybridMethod("gaussianBlur", &HybridNitroImagePipelineSpec::gaussianBlur);
21
+ prototype.registerHybridMethod("setMemoryCacheLimit", &HybridNitroImagePipelineSpec::setMemoryCacheLimit);
21
22
  prototype.registerHybridMethod("clearCache", &HybridNitroImagePipelineSpec::clearCache);
22
23
  });
23
24
  }
@@ -61,6 +61,7 @@ namespace margelo::nitro::nitroimagepipeline {
61
61
  virtual std::shared_ptr<Promise<void>> preLoadImage(const std::string& url) = 0;
62
62
  virtual std::shared_ptr<Promise<void>> preLoadImages(const std::vector<std::string>& urls) = 0;
63
63
  virtual std::shared_ptr<Promise<std::shared_ptr<margelo::nitro::image::HybridImageSpec>>> gaussianBlur(const std::shared_ptr<margelo::nitro::image::HybridImageSpec>& image, double radius) = 0;
64
+ virtual void setMemoryCacheLimit(double bytes) = 0;
64
65
  virtual std::shared_ptr<Promise<void>> clearCache() = 0;
65
66
 
66
67
  protected:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-nitro-image-pipeline",
3
- "version": "1.3.2",
3
+ "version": "1.4.0",
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",
@@ -80,5 +80,16 @@ export interface NitroImagePipeline extends HybridObject<{
80
80
  gaussianBlur(image: Image, radius: number): Promise<Image>;
81
81
  // Future: brightness, saturation, tint, etc.
82
82
 
83
+ /**
84
+ * Caps the in-memory cache of decoded bitmaps at `bytes`, evicting
85
+ * least-recently-used entries immediately if it is currently larger. Pass
86
+ * `0` to disable in-memory caching entirely (the disk cache still works).
87
+ *
88
+ * Defaults: 128 MB on iOS, 25% of the app's memory class on Android. The
89
+ * cache trades RAM for instant re-display; lower it (or use
90
+ * `cache: 'disk'` per request) in memory-constrained apps.
91
+ */
92
+ setMemoryCacheLimit(bytes: number): void;
93
+
83
94
  clearCache(): Promise<void>;
84
95
  }