react-native-nitro-image-pipeline 1.4.0 → 1.6.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 (72) hide show
  1. package/README.md +124 -8
  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 +167 -75
  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 +109 -41
  12. package/ios/PipelineImageLoader.swift +195 -0
  13. package/ios/RoundedCornersProcessor.swift +5 -0
  14. package/lib/commonjs/NativePipelineImage.js +72 -0
  15. package/lib/commonjs/NativePipelineImage.js.map +1 -0
  16. package/lib/commonjs/PipelineImage.js.map +1 -1
  17. package/lib/commonjs/index.js +27 -0
  18. package/lib/commonjs/index.js.map +1 -1
  19. package/lib/commonjs/resolveImageSource.js +89 -0
  20. package/lib/commonjs/resolveImageSource.js.map +1 -0
  21. package/lib/commonjs/useImage.js +8 -4
  22. package/lib/commonjs/useImage.js.map +1 -1
  23. package/lib/commonjs/usePipelineImageLoader.js +74 -0
  24. package/lib/commonjs/usePipelineImageLoader.js.map +1 -0
  25. package/lib/module/NativePipelineImage.js +68 -0
  26. package/lib/module/NativePipelineImage.js.map +1 -0
  27. package/lib/module/PipelineImage.js.map +1 -1
  28. package/lib/module/index.js +3 -0
  29. package/lib/module/index.js.map +1 -1
  30. package/lib/module/resolveImageSource.js +83 -0
  31. package/lib/module/resolveImageSource.js.map +1 -0
  32. package/lib/module/useImage.js +8 -4
  33. package/lib/module/useImage.js.map +1 -1
  34. package/lib/module/usePipelineImageLoader.js +70 -0
  35. package/lib/module/usePipelineImageLoader.js.map +1 -0
  36. package/lib/typescript/src/NativePipelineImage.d.ts +71 -0
  37. package/lib/typescript/src/NativePipelineImage.d.ts.map +1 -0
  38. package/lib/typescript/src/PipelineImage.d.ts +7 -2
  39. package/lib/typescript/src/PipelineImage.d.ts.map +1 -1
  40. package/lib/typescript/src/index.d.ts +4 -1
  41. package/lib/typescript/src/index.d.ts.map +1 -1
  42. package/lib/typescript/src/resolveImageSource.d.ts +56 -0
  43. package/lib/typescript/src/resolveImageSource.d.ts.map +1 -0
  44. package/lib/typescript/src/specs/nitro-image-toolkit.nitro.d.ts +56 -1
  45. package/lib/typescript/src/specs/nitro-image-toolkit.nitro.d.ts.map +1 -1
  46. package/lib/typescript/src/useImage.d.ts +10 -4
  47. package/lib/typescript/src/useImage.d.ts.map +1 -1
  48. package/lib/typescript/src/usePipelineImageLoader.d.ts +17 -0
  49. package/lib/typescript/src/usePipelineImageLoader.d.ts.map +1 -0
  50. package/nitrogen/generated/android/c++/JHybridNitroImagePipelineSpec.cpp +13 -0
  51. package/nitrogen/generated/android/c++/JHybridNitroImagePipelineSpec.hpp +1 -0
  52. package/nitrogen/generated/android/c++/JViewOptions.hpp +77 -0
  53. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroimagepipeline/HybridNitroImagePipelineSpec.kt +5 -0
  54. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroimagepipeline/ViewOptions.kt +66 -0
  55. package/nitrogen/generated/ios/NitroImagePipeline-Swift-Cxx-Bridge.cpp +10 -0
  56. package/nitrogen/generated/ios/NitroImagePipeline-Swift-Cxx-Bridge.hpp +44 -0
  57. package/nitrogen/generated/ios/NitroImagePipeline-Swift-Cxx-Umbrella.hpp +8 -0
  58. package/nitrogen/generated/ios/c++/HybridNitroImagePipelineSpecSwift.hpp +14 -0
  59. package/nitrogen/generated/ios/swift/HybridNitroImagePipelineSpec.swift +1 -0
  60. package/nitrogen/generated/ios/swift/HybridNitroImagePipelineSpec_cxx.swift +15 -0
  61. package/nitrogen/generated/ios/swift/ViewOptions.swift +101 -0
  62. package/nitrogen/generated/shared/c++/HybridNitroImagePipelineSpec.cpp +1 -0
  63. package/nitrogen/generated/shared/c++/HybridNitroImagePipelineSpec.hpp +7 -0
  64. package/nitrogen/generated/shared/c++/ViewOptions.hpp +104 -0
  65. package/package.json +4 -3
  66. package/src/NativePipelineImage.tsx +108 -0
  67. package/src/PipelineImage.tsx +7 -2
  68. package/src/index.ts +12 -0
  69. package/src/resolveImageSource.ts +83 -0
  70. package/src/specs/nitro-image-toolkit.nitro.ts +57 -1
  71. package/src/useImage.ts +26 -15
  72. package/src/usePipelineImageLoader.ts +90 -0
@@ -0,0 +1,83 @@
1
+ import { Image as RNImage } from 'react-native';
2
+
3
+ /**
4
+ * What the pipeline can load: a URL string, or the id a `require()`d image
5
+ * asset evaluates to.
6
+ *
7
+ * As a string, `url` is passed to the native loader as-is. Accepted forms:
8
+ *
9
+ * - `https://` / `http://` — fetched over the network and cached on disk.
10
+ * - `file://` URLs and plain absolute paths (`/var/…/photo.jpg`) — read from
11
+ * the file system. The result is cached in memory only; there is nothing
12
+ * to gain from copying a local file into the disk cache.
13
+ * - Android also accepts `content://` URIs and, from a release build's
14
+ * resources, a bare drawable name — which is what `require()` resolves to
15
+ * there. iOS resolves `require()` to a `file://` URL into the app bundle.
16
+ *
17
+ * A `require('./photo.png')` is resolved with `Image.resolveAssetSource`, so
18
+ * it works the same in debug (streamed from Metro) and release (bundled).
19
+ */
20
+ export type ImageSource = string | number;
21
+
22
+ /**
23
+ * Turns an {@linkcode ImageSource} into the URL string the native pipeline
24
+ * loads: strings pass through unchanged, a `require()`d asset is resolved via
25
+ * `Image.resolveAssetSource` (the scale-matched variant, like `<Image>`).
26
+ *
27
+ * The components and hooks call this for you; use it when calling
28
+ * `NitroImagePipeline.loadImage`/`preLoadImage` directly with a `require()`.
29
+ * @throws If `source` is a number that is not a registered asset.
30
+ * @example
31
+ * ```ts
32
+ * const image = await NitroImagePipeline.loadImage(
33
+ * resolveImageUrl(require('./photo.png')),
34
+ * { blur: 4 },
35
+ * );
36
+ * ```
37
+ */
38
+ export function resolveImageUrl(source: ImageSource): string {
39
+ if (typeof source === 'string') {
40
+ return source;
41
+ }
42
+ const resolved = RNImage.resolveAssetSource(source);
43
+ if (resolved == null) {
44
+ throw new Error(`Not a registered image asset: require() id ${source}`);
45
+ }
46
+ return resolved.uri;
47
+ }
48
+
49
+ /**
50
+ * A URL no loader can resolve, standing in for a `require()` id that is not
51
+ * a registered asset. Its scheme has no fetcher on either platform, so the
52
+ * request fails at load time — the same way a missing file does — instead of
53
+ * throwing from a component's render.
54
+ */
55
+ export const UNREGISTERED_ASSET_URL = 'unregistered-asset://';
56
+
57
+ /**
58
+ * {@linkcode resolveImageUrl} for code that runs during render: an
59
+ * unregistered `require()` id yields {@linkcode UNREGISTERED_ASSET_URL} (and
60
+ * a warning in development) rather than throwing.
61
+ */
62
+ export function resolveImageUrlOrFallback(source: ImageSource): string {
63
+ try {
64
+ return resolveImageUrl(source);
65
+ } catch (error) {
66
+ if (__DEV__) {
67
+ console.warn(
68
+ `[react-native-nitro-image-pipeline] ${(error as Error).message}`,
69
+ );
70
+ }
71
+ return UNREGISTERED_ASSET_URL;
72
+ }
73
+ }
74
+
75
+ /**
76
+ * A stable, per-source `recyclingKey` for `<NativeNitroImage>`: the URL
77
+ * itself for strings, and a tag derived from the asset id for a `require()`
78
+ * (resolving it would cost an asset-registry lookup per render for nothing
79
+ * more than a distinct key).
80
+ */
81
+ export function recyclingKeyFor(source: ImageSource): string {
82
+ return typeof source === 'string' ? source : `asset:${source}`;
83
+ }
@@ -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,67 @@ 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
  }> {
103
+ /**
104
+ * Loads `url` through the pipeline. Besides `http(s)://`, `url` may be a
105
+ * `file://` URL or a plain absolute path (read from the file system, cached
106
+ * in memory only), a `data:` URL, or — on Android — a `content://` URI or
107
+ * the bare resource name a `require()` resolves to in a release build. Use
108
+ * `resolveImageUrl(require('./photo.png'))` to pass a bundled asset.
109
+ */
72
110
  loadImage(url: string, options?: Options): Promise<Image>;
111
+ /**
112
+ * Creates an {@linkcode ImageLoader} for `url` that a `<NativeNitroImage>`
113
+ * view drives entirely natively: the request starts when the view attaches
114
+ * to the window — at the view's own laid-out size, with no JS round trips —
115
+ * and is cancelled (and the bitmap released) when it detaches, so
116
+ * off-screen list cells stop costing memory. Loads go through the same
117
+ * pipeline and caches as {@linkcode loadImage}/{@linkcode preLoadImage}.
118
+ *
119
+ * `options` are in **points** (see {@linkcode ViewOptions}); the loader
120
+ * applies the screen scale natively. If the view has no size yet when it
121
+ * attaches, the load waits for its first layout.
122
+ */
123
+ createImageLoader(url: string, options?: ViewOptions): ImageLoader;
124
+ /**
125
+ * Warms the disk cache with the download for `url`, without decoding it.
126
+ * Local sources (`file://`, paths, resources) have no download to warm it
127
+ * with and are a no-op.
128
+ */
73
129
  preLoadImage(url: string): Promise<void>;
74
130
  preLoadImages(urls: string[]): Promise<void>;
75
131
  /**
package/src/useImage.ts CHANGED
@@ -2,6 +2,7 @@ import { useEffect, useRef, useState } from 'react';
2
2
  import type { Image } from 'react-native-nitro-image';
3
3
 
4
4
  import { NitroImagePipeline } from './NitroImagePipeline';
5
+ import { type ImageSource, resolveImageUrl } from './resolveImageSource';
5
6
  import type {
6
7
  CacheOption,
7
8
  CornerRadii,
@@ -26,11 +27,12 @@ type Result =
26
27
  };
27
28
 
28
29
  /**
29
- * A hook to asynchronously load an image from the
30
- * given {@linkcode AsyncImageSource} into memory.
30
+ * A hook to asynchronously load an image from `url` through the pipeline
31
+ * into memory.
31
32
  * @example
32
33
  * ```ts
33
- * const { image, error } = useImage({ filePath: '/tmp/image.jpg' })
34
+ * const { image, error } = useImage({ url: 'https://example.com/photo.jpg' });
35
+ * const { image: logo } = useImage({ url: require('./logo.png') });
34
36
  * ```
35
37
  */
36
38
  export function useImage({
@@ -41,7 +43,11 @@ export function useImage({
41
43
  cache,
42
44
  enabled = true,
43
45
  }: {
44
- url: string;
46
+ /**
47
+ * The image to load: a URL string (`https://`, `file://`, an absolute path)
48
+ * or a `require()`d asset — see {@linkcode ImageSource}.
49
+ */
50
+ url: ImageSource;
45
51
  /**
46
52
  * Gaussian blur strength, as the standard deviation (sigma) of the blur in
47
53
  * source-image pixels (of the resized bitmap when `resize` is set). Matches
@@ -103,17 +109,22 @@ export function useImage({
103
109
  if (enabled) {
104
110
  (async () => {
105
111
  try {
106
- const result = await NitroImagePipeline.loadImage(url, {
107
- blur,
108
- cornerRadius: isUniformRadius
109
- ? uniformRadius
110
- : { topLeft, topRight, bottomLeft, bottomRight },
111
- resize:
112
- resizeWidth > 0 && resizeHeight > 0
113
- ? { width: resizeWidth, height: resizeHeight }
114
- : undefined,
115
- cache,
116
- });
112
+ // Resolved inside the try so an unregistered `require()` id
113
+ // surfaces as the error state instead of throwing from the effect.
114
+ const result = await NitroImagePipeline.loadImage(
115
+ resolveImageUrl(url),
116
+ {
117
+ blur,
118
+ cornerRadius: isUniformRadius
119
+ ? uniformRadius
120
+ : { topLeft, topRight, bottomLeft, bottomRight },
121
+ resize:
122
+ resizeWidth > 0 && resizeHeight > 0
123
+ ? { width: resizeWidth, height: resizeHeight }
124
+ : undefined,
125
+ cache,
126
+ },
127
+ );
117
128
 
118
129
  if (!cancelled) {
119
130
  setImage({ image: result, error: undefined });
@@ -0,0 +1,90 @@
1
+ import { useMemo } from 'react';
2
+ import type { ImageLoader } from 'react-native-nitro-image';
3
+
4
+ import { NitroImagePipeline } from './NitroImagePipeline';
5
+ import {
6
+ type ImageSource,
7
+ resolveImageUrlOrFallback,
8
+ } from './resolveImageSource';
9
+ import type {
10
+ CornerRadii,
11
+ ViewOptions,
12
+ } from './specs/nitro-image-toolkit.nitro';
13
+
14
+ /**
15
+ * Creates (and memoizes) an {@linkcode ImageLoader} for `source` — a URL
16
+ * string or a `require()`d asset, see {@linkcode ImageSource} — to pass to
17
+ * `<NativeNitroImage image={...} />`. The view drives it entirely natively:
18
+ * the request starts when the view attaches — at the view's laid-out size,
19
+ * with no JS round trips — and is cancelled when it detaches. See
20
+ * {@linkcode NitroImagePipeline.createImageLoader}.
21
+ *
22
+ * `blur`/`cornerRadius` are in **points** (unlike `useImage`, where they are
23
+ * bitmap pixels); the screen scale is applied natively. Inline object
24
+ * literals are fine — options are compared by value, not identity.
25
+ */
26
+ export function usePipelineImageLoader(
27
+ source: ImageSource,
28
+ options?: ViewOptions,
29
+ ): ImageLoader {
30
+ // Split the options into primitives (like useImage does) so an inline
31
+ // literal — a new identity every render — doesn't recreate the loader;
32
+ // recreating it would re-trigger the native load.
33
+ const blur = options?.blur;
34
+ const cache = options?.cache;
35
+ const cornerRadius = options?.cornerRadius;
36
+ const isUniformRadius = typeof cornerRadius === 'number';
37
+ const uniformRadius = isUniformRadius ? cornerRadius : 0;
38
+ const hasCornerObject = !isUniformRadius && cornerRadius !== undefined;
39
+ const {
40
+ topLeft = 0,
41
+ topRight = 0,
42
+ bottomLeft = 0,
43
+ bottomRight = 0,
44
+ } = isUniformRadius || cornerRadius === undefined ? {} : cornerRadius;
45
+ const resizeWidth = options?.resize?.width;
46
+ const resizeHeight = options?.resize?.height;
47
+
48
+ return useMemo(() => {
49
+ // Runs during render, so an unregistered `require()` id must not throw:
50
+ // it becomes a URL the native loader fails on at load time instead.
51
+ const url = resolveImageUrlOrFallback(source);
52
+ const cornerRadiusOption: number | CornerRadii | undefined = isUniformRadius
53
+ ? uniformRadius
54
+ : hasCornerObject
55
+ ? { topLeft, topRight, bottomLeft, bottomRight }
56
+ : undefined;
57
+ const stableOptions: ViewOptions = {
58
+ blur,
59
+ cache,
60
+ cornerRadius: cornerRadiusOption,
61
+ resize:
62
+ resizeWidth !== undefined && resizeHeight !== undefined
63
+ ? { width: resizeWidth, height: resizeHeight }
64
+ : undefined,
65
+ };
66
+ const loader = NitroImagePipeline.createImageLoader(url, stableOptions);
67
+ // `NativeNitroImage` needs a way to tell two loader instances apart when
68
+ // diffing its `image` prop; tag the loader with what it will load (the
69
+ // same convention react-native-nitro-image's own loaders use).
70
+ Object.defineProperty(loader, '__source', {
71
+ enumerable: true,
72
+ configurable: true,
73
+ value: { url, options: stableOptions },
74
+ });
75
+ return loader;
76
+ }, [
77
+ source,
78
+ blur,
79
+ cache,
80
+ isUniformRadius,
81
+ uniformRadius,
82
+ hasCornerObject,
83
+ topLeft,
84
+ topRight,
85
+ bottomLeft,
86
+ bottomRight,
87
+ resizeWidth,
88
+ resizeHeight,
89
+ ]);
90
+ }