react-native-nitro-image-pipeline 1.4.0 → 1.5.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.
Files changed (53) hide show
  1. package/README.md +72 -5
  2. package/android/CMakeLists.txt +4 -0
  3. package/android/src/main/cpp/GaussianBlur.cpp +165 -0
  4. package/android/src/main/cpp/GaussianBlur.hpp +52 -0
  5. package/android/src/main/cpp/GaussianBlurJni.cpp +50 -0
  6. package/android/src/main/java/com/margelo/nitro/nitroimagepipeline/HybridNitroImagePipeline.kt +141 -74
  7. package/android/src/main/java/com/margelo/nitro/nitroimagepipeline/PipelineImageLoader.kt +177 -0
  8. package/android/src/main/java/com/margelo/nitro/nitroimagepipeline/transform/BlurTransformation.kt +36 -81
  9. package/android/src/main/java/com/margelo/nitro/nitroimagepipeline/transform/HardwareBitmapTransformation.kt +45 -0
  10. package/ios/GaussianBlur.swift +48 -9
  11. package/ios/HybridNitroImagePipeline.swift +63 -33
  12. package/ios/PipelineImageLoader.swift +195 -0
  13. package/ios/RoundedCornersProcessor.swift +5 -0
  14. package/lib/commonjs/NativePipelineImage.js +71 -0
  15. package/lib/commonjs/NativePipelineImage.js.map +1 -0
  16. package/lib/commonjs/index.js +14 -0
  17. package/lib/commonjs/index.js.map +1 -1
  18. package/lib/commonjs/usePipelineImageLoader.js +69 -0
  19. package/lib/commonjs/usePipelineImageLoader.js.map +1 -0
  20. package/lib/module/NativePipelineImage.js +67 -0
  21. package/lib/module/NativePipelineImage.js.map +1 -0
  22. package/lib/module/index.js +2 -0
  23. package/lib/module/index.js.map +1 -1
  24. package/lib/module/usePipelineImageLoader.js +65 -0
  25. package/lib/module/usePipelineImageLoader.js.map +1 -0
  26. package/lib/typescript/src/NativePipelineImage.d.ts +66 -0
  27. package/lib/typescript/src/NativePipelineImage.d.ts.map +1 -0
  28. package/lib/typescript/src/index.d.ts +3 -1
  29. package/lib/typescript/src/index.d.ts.map +1 -1
  30. package/lib/typescript/src/specs/nitro-image-toolkit.nitro.d.ts +44 -1
  31. package/lib/typescript/src/specs/nitro-image-toolkit.nitro.d.ts.map +1 -1
  32. package/lib/typescript/src/usePipelineImageLoader.d.ts +15 -0
  33. package/lib/typescript/src/usePipelineImageLoader.d.ts.map +1 -0
  34. package/nitrogen/generated/android/c++/JHybridNitroImagePipelineSpec.cpp +13 -0
  35. package/nitrogen/generated/android/c++/JHybridNitroImagePipelineSpec.hpp +1 -0
  36. package/nitrogen/generated/android/c++/JViewOptions.hpp +77 -0
  37. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroimagepipeline/HybridNitroImagePipelineSpec.kt +5 -0
  38. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroimagepipeline/ViewOptions.kt +66 -0
  39. package/nitrogen/generated/ios/NitroImagePipeline-Swift-Cxx-Bridge.cpp +10 -0
  40. package/nitrogen/generated/ios/NitroImagePipeline-Swift-Cxx-Bridge.hpp +44 -0
  41. package/nitrogen/generated/ios/NitroImagePipeline-Swift-Cxx-Umbrella.hpp +8 -0
  42. package/nitrogen/generated/ios/c++/HybridNitroImagePipelineSpecSwift.hpp +14 -0
  43. package/nitrogen/generated/ios/swift/HybridNitroImagePipelineSpec.swift +1 -0
  44. package/nitrogen/generated/ios/swift/HybridNitroImagePipelineSpec_cxx.swift +15 -0
  45. package/nitrogen/generated/ios/swift/ViewOptions.swift +101 -0
  46. package/nitrogen/generated/shared/c++/HybridNitroImagePipelineSpec.cpp +1 -0
  47. package/nitrogen/generated/shared/c++/HybridNitroImagePipelineSpec.hpp +7 -0
  48. package/nitrogen/generated/shared/c++/ViewOptions.hpp +104 -0
  49. package/package.json +4 -3
  50. package/src/NativePipelineImage.tsx +103 -0
  51. package/src/index.ts +7 -0
  52. package/src/specs/nitro-image-toolkit.nitro.ts +45 -1
  53. package/src/usePipelineImageLoader.ts +82 -0
@@ -0,0 +1,104 @@
1
+ ///
2
+ /// ViewOptions.hpp
3
+ /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
4
+ /// https://github.com/mrousavy/nitro
5
+ /// Copyright © Marc Rousavy @ Margelo
6
+ ///
7
+
8
+ #pragma once
9
+
10
+ #if __has_include(<NitroModules/JSIConverter.hpp>)
11
+ #include <NitroModules/JSIConverter.hpp>
12
+ #else
13
+ #error NitroModules cannot be found! Are you sure you installed NitroModules properly?
14
+ #endif
15
+ #if __has_include(<NitroModules/NitroDefines.hpp>)
16
+ #include <NitroModules/NitroDefines.hpp>
17
+ #else
18
+ #error NitroModules cannot be found! Are you sure you installed NitroModules properly?
19
+ #endif
20
+ #if __has_include(<NitroModules/JSIHelpers.hpp>)
21
+ #include <NitroModules/JSIHelpers.hpp>
22
+ #else
23
+ #error NitroModules cannot be found! Are you sure you installed NitroModules properly?
24
+ #endif
25
+ #if __has_include(<NitroModules/PropNameIDCache.hpp>)
26
+ #include <NitroModules/PropNameIDCache.hpp>
27
+ #else
28
+ #error NitroModules cannot be found! Are you sure you installed NitroModules properly?
29
+ #endif
30
+
31
+ // Forward declaration of `CacheOption` to properly resolve imports.
32
+ namespace margelo::nitro::nitroimagepipeline { enum class CacheOption; }
33
+ // Forward declaration of `CornerRadii` to properly resolve imports.
34
+ namespace margelo::nitro::nitroimagepipeline { struct CornerRadii; }
35
+ // Forward declaration of `ResizeOptions` to properly resolve imports.
36
+ namespace margelo::nitro::nitroimagepipeline { struct ResizeOptions; }
37
+
38
+ #include <optional>
39
+ #include "CacheOption.hpp"
40
+ #include "CornerRadii.hpp"
41
+ #include <variant>
42
+ #include "ResizeOptions.hpp"
43
+
44
+ namespace margelo::nitro::nitroimagepipeline {
45
+
46
+ /**
47
+ * A struct which can be represented as a JavaScript object (ViewOptions).
48
+ */
49
+ struct ViewOptions final {
50
+ public:
51
+ std::optional<double> blur SWIFT_PRIVATE;
52
+ std::optional<CacheOption> cache SWIFT_PRIVATE;
53
+ std::optional<std::variant<double, CornerRadii>> cornerRadius SWIFT_PRIVATE;
54
+ std::optional<ResizeOptions> resize SWIFT_PRIVATE;
55
+
56
+ public:
57
+ ViewOptions() = default;
58
+ explicit ViewOptions(std::optional<double> blur, std::optional<CacheOption> cache, std::optional<std::variant<double, CornerRadii>> cornerRadius, std::optional<ResizeOptions> resize): blur(blur), cache(cache), cornerRadius(cornerRadius), resize(resize) {}
59
+
60
+ public:
61
+ friend bool operator==(const ViewOptions& lhs, const ViewOptions& rhs) = default;
62
+ };
63
+
64
+ } // namespace margelo::nitro::nitroimagepipeline
65
+
66
+ namespace margelo::nitro {
67
+
68
+ // C++ ViewOptions <> JS ViewOptions (object)
69
+ template <>
70
+ struct JSIConverter<margelo::nitro::nitroimagepipeline::ViewOptions> final {
71
+ static inline margelo::nitro::nitroimagepipeline::ViewOptions fromJSI(jsi::Runtime& runtime, const jsi::Value& arg) {
72
+ jsi::Object obj = arg.asObject(runtime);
73
+ return margelo::nitro::nitroimagepipeline::ViewOptions(
74
+ JSIConverter<std::optional<double>>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "blur"))),
75
+ JSIConverter<std::optional<margelo::nitro::nitroimagepipeline::CacheOption>>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "cache"))),
76
+ JSIConverter<std::optional<std::variant<double, margelo::nitro::nitroimagepipeline::CornerRadii>>>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "cornerRadius"))),
77
+ JSIConverter<std::optional<margelo::nitro::nitroimagepipeline::ResizeOptions>>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "resize")))
78
+ );
79
+ }
80
+ static inline jsi::Value toJSI(jsi::Runtime& runtime, const margelo::nitro::nitroimagepipeline::ViewOptions& arg) {
81
+ jsi::Object obj(runtime);
82
+ obj.setProperty(runtime, PropNameIDCache::get(runtime, "blur"), JSIConverter<std::optional<double>>::toJSI(runtime, arg.blur));
83
+ obj.setProperty(runtime, PropNameIDCache::get(runtime, "cache"), JSIConverter<std::optional<margelo::nitro::nitroimagepipeline::CacheOption>>::toJSI(runtime, arg.cache));
84
+ obj.setProperty(runtime, PropNameIDCache::get(runtime, "cornerRadius"), JSIConverter<std::optional<std::variant<double, margelo::nitro::nitroimagepipeline::CornerRadii>>>::toJSI(runtime, arg.cornerRadius));
85
+ obj.setProperty(runtime, PropNameIDCache::get(runtime, "resize"), JSIConverter<std::optional<margelo::nitro::nitroimagepipeline::ResizeOptions>>::toJSI(runtime, arg.resize));
86
+ return obj;
87
+ }
88
+ static inline bool canConvert(jsi::Runtime& runtime, const jsi::Value& value) {
89
+ if (!value.isObject()) {
90
+ return false;
91
+ }
92
+ jsi::Object obj = value.getObject(runtime);
93
+ if (!nitro::isPlainObject(runtime, obj)) {
94
+ return false;
95
+ }
96
+ if (!JSIConverter<std::optional<double>>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "blur")))) return false;
97
+ if (!JSIConverter<std::optional<margelo::nitro::nitroimagepipeline::CacheOption>>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "cache")))) return false;
98
+ if (!JSIConverter<std::optional<std::variant<double, margelo::nitro::nitroimagepipeline::CornerRadii>>>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "cornerRadius")))) return false;
99
+ if (!JSIConverter<std::optional<margelo::nitro::nitroimagepipeline::ResizeOptions>>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "resize")))) return false;
100
+ return true;
101
+ }
102
+ };
103
+
104
+ } // namespace margelo::nitro
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-nitro-image-pipeline",
3
- "version": "1.4.0",
3
+ "version": "1.5.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",
@@ -17,7 +17,8 @@
17
17
  "release": "semantic-release",
18
18
  "build": "bun run typecheck && bob build",
19
19
  "codegen": "nitrogen --logLevel=\"debug\" && bun run build",
20
- "verify:blur": "swiftc -O -o \"${TMPDIR:-/tmp}/nitro-verify-blur\" scripts/verify-blur.swift ios/GaussianBlur.swift && \"${TMPDIR:-/tmp}/nitro-verify-blur\""
20
+ "fixtures": "bun scripts/fixture-server.ts",
21
+ "verify:blur": "sh scripts/verify-blur.sh"
21
22
  },
22
23
  "keywords": [
23
24
  "react-native",
@@ -83,7 +84,7 @@
83
84
  "react": "*",
84
85
  "react-native": "*",
85
86
  "react-native-nitro-modules": "*",
86
- "react-native-nitro-image": "0.15.1"
87
+ "react-native-nitro-image": ">=0.15.1 <0.16.0"
87
88
  },
88
89
  "overrides": {
89
90
  "lodash-es": "4.17.21",
@@ -0,0 +1,103 @@
1
+ import { type ComponentRef, forwardRef } from 'react';
2
+ import type { HostComponent } from 'react-native';
3
+ import { NativeNitroImage } from 'react-native-nitro-image';
4
+
5
+ import { cornerRadiusForStyle } from './resizeForStyle';
6
+ import type {
7
+ CacheOption,
8
+ CornerRadii,
9
+ ResizeOptions,
10
+ } from './specs/nitro-image-toolkit.nitro';
11
+ import { usePipelineImageLoader } from './usePipelineImageLoader';
12
+
13
+ type ReactProps<T> = T extends HostComponent<infer P> ? P : never;
14
+ type NativeImageProps = ReactProps<typeof NativeNitroImage>;
15
+
16
+ /**
17
+ * The instance `<NativePipelineImage>` exposes through its `ref` — the
18
+ * underlying `NativeNitroImage` host view, with the usual native-view
19
+ * methods (`measure`, …).
20
+ */
21
+ export type NativePipelineImageRef = ComponentRef<typeof NativeNitroImage>;
22
+
23
+ export interface NativePipelineImageProps extends Omit<
24
+ NativeImageProps,
25
+ 'image'
26
+ > {
27
+ /** URL of the image to load through the pipeline. */
28
+ url: string;
29
+ /**
30
+ * Gaussian blur sigma in **points** (density-independent), like
31
+ * `PipelineImage`. Applied natively with the screen scale.
32
+ * @default 0
33
+ */
34
+ blur?: number;
35
+ /**
36
+ * Corner radius in **points** — a single number or per-corner radii.
37
+ * When omitted, it's derived from `style`'s `borderRadius`-family
38
+ * properties instead — set this prop to override that.
39
+ * @default undefined (derived from `style`, or square corners if unset there)
40
+ */
41
+ cornerRadius?: number | CornerRadii;
42
+ cache?: CacheOption;
43
+ /**
44
+ * Target bitmap size in **pixels**, overriding the size the native side
45
+ * measures from the view. Rarely needed.
46
+ * @default undefined (measure the view natively)
47
+ */
48
+ resize?: ResizeOptions;
49
+ }
50
+
51
+ /**
52
+ * The fully native-driven variant of `PipelineImage`: after the first render
53
+ * there is **zero JS work per image**. The native view starts the request
54
+ * when it attaches to the window — at its own laid-out size, so nothing
55
+ * waits for an `onLayout` round trip — and cancels it (releasing the bitmap)
56
+ * when it detaches, which makes off-screen list cells free. Loads go through
57
+ * the same pipeline and caches as `useImage`/`preLoadImage`.
58
+ *
59
+ * Compared to `PipelineImage`:
60
+ * - No `onLoad`/`onError` callbacks — the loaded `Image` never crosses into
61
+ * JS. Use `PipelineImage` (or `useImage`) when you need them.
62
+ * - The bitmap is loaded once at the size the view first has; if the view is
63
+ * resized later, the bitmap scales with it instead of reloading.
64
+ * @example
65
+ * ```tsx
66
+ * <NativePipelineImage
67
+ * url="https://example.com/photo.jpg"
68
+ * style={{ width: 300, height: 200, borderRadius: 24 }}
69
+ * blur={4}
70
+ * />
71
+ * ```
72
+ */
73
+ export const NativePipelineImage = forwardRef<
74
+ NativePipelineImageRef,
75
+ NativePipelineImageProps
76
+ >(function NativePipelineImage(
77
+ { url, blur, cornerRadius, cache, resize, style, ...viewProps },
78
+ ref,
79
+ ) {
80
+ // Same precedence as PipelineImage: an explicit prop wins over style.
81
+ const effectiveCornerRadius = cornerRadius ?? cornerRadiusForStyle(style);
82
+ const loader = usePipelineImageLoader(url, {
83
+ blur,
84
+ cornerRadius: effectiveCornerRadius,
85
+ cache,
86
+ resize,
87
+ });
88
+
89
+ return (
90
+ <NativeNitroImage
91
+ // Before the spread so a caller-provided recyclingKey wins.
92
+ recyclingKey={url}
93
+ {...viewProps}
94
+ ref={ref}
95
+ style={style}
96
+ image={loader}
97
+ />
98
+ );
99
+ });
100
+
101
+ // Reanimated and DevTools read the display name; the forwardRef wrapper
102
+ // would otherwise report as anonymous.
103
+ NativePipelineImage.displayName = 'NativePipelineImage';
package/src/index.ts CHANGED
@@ -1,3 +1,8 @@
1
+ export {
2
+ NativePipelineImage,
3
+ type NativePipelineImageProps,
4
+ type NativePipelineImageRef,
5
+ } from './NativePipelineImage';
1
6
  export { NitroImagePipeline } from './NitroImagePipeline';
2
7
  export {
3
8
  PipelineImage,
@@ -14,5 +19,7 @@ export type {
14
19
  CornerRadii,
15
20
  Options,
16
21
  ResizeOptions,
22
+ ViewOptions,
17
23
  } from './specs/nitro-image-toolkit.nitro';
18
24
  export { useImage } from './useImage';
25
+ export { usePipelineImageLoader } from './usePipelineImageLoader';
@@ -1,4 +1,4 @@
1
- import type { Image } from 'react-native-nitro-image';
1
+ import type { Image, ImageLoader } from 'react-native-nitro-image';
2
2
  import type { HybridObject } from 'react-native-nitro-modules';
3
3
 
4
4
  export type CacheOption = 'memory' | 'disk' | 'none';
@@ -65,11 +65,55 @@ export type Options = {
65
65
  resize?: ResizeOptions;
66
66
  };
67
67
 
68
+ /**
69
+ * Options for {@linkcode NitroImagePipeline.createImageLoader}. Unlike
70
+ * {@linkcode Options} (used by `loadImage`, where values are bitmap pixels),
71
+ * `blur` and `cornerRadius` here are in **points** (density-independent):
72
+ * the loader runs inside a view and converts to pixels natively with the
73
+ * screen scale. The one exception is {@linkcode resize}, which stays an
74
+ * explicit **pixel** size like everywhere else in the library.
75
+ */
76
+ export type ViewOptions = {
77
+ /**
78
+ * Gaussian blur sigma in **points**. Multiplied by the screen scale
79
+ * natively, so the same value looks the same on every device.
80
+ * @default 0 (no blur)
81
+ */
82
+ blur?: number;
83
+ cache?: CacheOption;
84
+ /**
85
+ * Corner radius in **points**, uniform or per-corner. Converted to pixels
86
+ * with the screen scale and baked into the bitmap at the display size.
87
+ * @default 0 (square corners)
88
+ */
89
+ cornerRadius?: number | CornerRadii;
90
+ /**
91
+ * Target bitmap size in **pixels**, overriding the size measured from the
92
+ * view. Rarely needed — without it the loader resizes to the view's
93
+ * laid-out size × screen scale, which is what you want in a UI.
94
+ * @default undefined (measure the view)
95
+ */
96
+ resize?: ResizeOptions;
97
+ };
98
+
68
99
  export interface NitroImagePipeline extends HybridObject<{
69
100
  ios: 'swift';
70
101
  android: 'kotlin';
71
102
  }> {
72
103
  loadImage(url: string, options?: Options): Promise<Image>;
104
+ /**
105
+ * Creates an {@linkcode ImageLoader} for `url` that a `<NativeNitroImage>`
106
+ * view drives entirely natively: the request starts when the view attaches
107
+ * to the window — at the view's own laid-out size, with no JS round trips —
108
+ * and is cancelled (and the bitmap released) when it detaches, so
109
+ * off-screen list cells stop costing memory. Loads go through the same
110
+ * pipeline and caches as {@linkcode loadImage}/{@linkcode preLoadImage}.
111
+ *
112
+ * `options` are in **points** (see {@linkcode ViewOptions}); the loader
113
+ * applies the screen scale natively. If the view has no size yet when it
114
+ * attaches, the load waits for its first layout.
115
+ */
116
+ createImageLoader(url: string, options?: ViewOptions): ImageLoader;
73
117
  preLoadImage(url: string): Promise<void>;
74
118
  preLoadImages(urls: string[]): Promise<void>;
75
119
  /**
@@ -0,0 +1,82 @@
1
+ import { useMemo } from 'react';
2
+ import type { ImageLoader } from 'react-native-nitro-image';
3
+
4
+ import { NitroImagePipeline } from './NitroImagePipeline';
5
+ import type {
6
+ CornerRadii,
7
+ ViewOptions,
8
+ } from './specs/nitro-image-toolkit.nitro';
9
+
10
+ /**
11
+ * Creates (and memoizes) an {@linkcode ImageLoader} for `url` to pass to
12
+ * `<NativeNitroImage image={...} />`. The view drives it entirely natively:
13
+ * the request starts when the view attaches — at the view's laid-out size,
14
+ * with no JS round trips — and is cancelled when it detaches. See
15
+ * {@linkcode NitroImagePipeline.createImageLoader}.
16
+ *
17
+ * `blur`/`cornerRadius` are in **points** (unlike `useImage`, where they are
18
+ * bitmap pixels); the screen scale is applied natively. Inline object
19
+ * literals are fine — options are compared by value, not identity.
20
+ */
21
+ export function usePipelineImageLoader(
22
+ url: string,
23
+ options?: ViewOptions,
24
+ ): ImageLoader {
25
+ // Split the options into primitives (like useImage does) so an inline
26
+ // literal — a new identity every render — doesn't recreate the loader;
27
+ // recreating it would re-trigger the native load.
28
+ const blur = options?.blur;
29
+ const cache = options?.cache;
30
+ const cornerRadius = options?.cornerRadius;
31
+ const isUniformRadius = typeof cornerRadius === 'number';
32
+ const uniformRadius = isUniformRadius ? cornerRadius : 0;
33
+ const hasCornerObject = !isUniformRadius && cornerRadius !== undefined;
34
+ const {
35
+ topLeft = 0,
36
+ topRight = 0,
37
+ bottomLeft = 0,
38
+ bottomRight = 0,
39
+ } = isUniformRadius || cornerRadius === undefined ? {} : cornerRadius;
40
+ const resizeWidth = options?.resize?.width;
41
+ const resizeHeight = options?.resize?.height;
42
+
43
+ return useMemo(() => {
44
+ const cornerRadiusOption: number | CornerRadii | undefined = isUniformRadius
45
+ ? uniformRadius
46
+ : hasCornerObject
47
+ ? { topLeft, topRight, bottomLeft, bottomRight }
48
+ : undefined;
49
+ const stableOptions: ViewOptions = {
50
+ blur,
51
+ cache,
52
+ cornerRadius: cornerRadiusOption,
53
+ resize:
54
+ resizeWidth !== undefined && resizeHeight !== undefined
55
+ ? { width: resizeWidth, height: resizeHeight }
56
+ : undefined,
57
+ };
58
+ const loader = NitroImagePipeline.createImageLoader(url, stableOptions);
59
+ // `NativeNitroImage` needs a way to tell two loader instances apart when
60
+ // diffing its `image` prop; tag the loader with what it will load (the
61
+ // same convention react-native-nitro-image's own loaders use).
62
+ Object.defineProperty(loader, '__source', {
63
+ enumerable: true,
64
+ configurable: true,
65
+ value: { url, options: stableOptions },
66
+ });
67
+ return loader;
68
+ }, [
69
+ url,
70
+ blur,
71
+ cache,
72
+ isUniformRadius,
73
+ uniformRadius,
74
+ hasCornerObject,
75
+ topLeft,
76
+ topRight,
77
+ bottomLeft,
78
+ bottomRight,
79
+ resizeWidth,
80
+ resizeHeight,
81
+ ]);
82
+ }