react-native-nitro-image-pipeline 1.2.0 → 1.3.1
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 +147 -5
- package/android/src/main/java/com/margelo/nitro/nitroimagepipeline/HybridNitroImagePipeline.kt +17 -15
- package/android/src/main/java/com/margelo/nitro/nitroimagepipeline/transform/ResizeTransformation.kt +1 -2
- package/ios/HybridNitroImagePipeline.swift +54 -38
- package/ios/RoundedCornersProcessor.swift +1 -1
- package/lib/commonjs/NitroImagePipeline.js +9 -0
- package/lib/commonjs/NitroImagePipeline.js.map +1 -0
- package/lib/commonjs/PipelineImage.js +126 -0
- package/lib/commonjs/PipelineImage.js.map +1 -0
- package/lib/commonjs/index.js +40 -88
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/resizeForStyle.js +68 -0
- package/lib/commonjs/resizeForStyle.js.map +1 -0
- package/lib/commonjs/useImage.js +95 -0
- package/lib/commonjs/useImage.js.map +1 -0
- package/lib/module/NitroImagePipeline.js +5 -0
- package/lib/module/NitroImagePipeline.js.map +1 -0
- package/lib/module/PipelineImage.js +122 -0
- package/lib/module/PipelineImage.js.map +1 -0
- package/lib/module/index.js +4 -86
- package/lib/module/index.js.map +1 -1
- package/lib/module/resizeForStyle.js +62 -0
- package/lib/module/resizeForStyle.js.map +1 -0
- package/lib/module/useImage.js +91 -0
- package/lib/module/useImage.js.map +1 -0
- package/lib/typescript/src/NitroImagePipeline.d.ts +3 -0
- package/lib/typescript/src/NitroImagePipeline.d.ts.map +1 -0
- package/lib/typescript/src/PipelineImage.d.ts +73 -0
- package/lib/typescript/src/PipelineImage.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +5 -45
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/resizeForStyle.d.ts +30 -0
- package/lib/typescript/src/resizeForStyle.d.ts.map +1 -0
- package/lib/typescript/src/useImage.d.ts +53 -0
- package/lib/typescript/src/useImage.d.ts.map +1 -0
- package/package.json +3 -2
- package/src/NitroImagePipeline.ts +6 -0
- package/src/PipelineImage.tsx +183 -0
- package/src/index.ts +13 -139
- package/src/resizeForStyle.ts +96 -0
- package/src/useImage.ts +149 -0
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import {
|
|
2
|
+
PixelRatio,
|
|
3
|
+
type StyleProp,
|
|
4
|
+
StyleSheet,
|
|
5
|
+
type ViewStyle,
|
|
6
|
+
} from 'react-native';
|
|
7
|
+
|
|
8
|
+
import type {
|
|
9
|
+
CornerRadii,
|
|
10
|
+
ResizeOptions,
|
|
11
|
+
} from './specs/nitro-image-toolkit.nitro';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Converts a layout size in points (dp) to the pipeline's `resize` option in
|
|
15
|
+
* whole bitmap pixels using `PixelRatio.getPixelSizeForLayoutSize`. Returns
|
|
16
|
+
* `undefined` unless both values are positive numbers.
|
|
17
|
+
*/
|
|
18
|
+
export function resizeForLayout(
|
|
19
|
+
width: unknown,
|
|
20
|
+
height: unknown,
|
|
21
|
+
): ResizeOptions | undefined {
|
|
22
|
+
if (typeof width !== 'number' || typeof height !== 'number') {
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
|
25
|
+
const pixelWidth = PixelRatio.getPixelSizeForLayoutSize(width);
|
|
26
|
+
const pixelHeight = PixelRatio.getPixelSizeForLayoutSize(height);
|
|
27
|
+
return pixelWidth > 0 && pixelHeight > 0
|
|
28
|
+
? { width: pixelWidth, height: pixelHeight }
|
|
29
|
+
: undefined;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Derives the pipeline's `resize` option from a view style whose `width` and
|
|
34
|
+
* `height` are numeric points, so the bitmap matches the display size on
|
|
35
|
+
* every screen density:
|
|
36
|
+
* ```ts
|
|
37
|
+
* useImage({ url, resize: resizeForStyle(styles.image) });
|
|
38
|
+
* ```
|
|
39
|
+
* Arrays and registered styles are flattened. Returns `undefined` when either
|
|
40
|
+
* dimension is missing or not a number (`'50%'`, `'auto'`, flex-driven) —
|
|
41
|
+
* use `<PipelineImage>` for those, which measures the view instead.
|
|
42
|
+
*/
|
|
43
|
+
export function resizeForStyle(
|
|
44
|
+
style: StyleProp<ViewStyle>,
|
|
45
|
+
): ResizeOptions | undefined {
|
|
46
|
+
const flat = StyleSheet.flatten(style);
|
|
47
|
+
return resizeForLayout(flat?.width, flat?.height);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Derives a `cornerRadius` option from a view style's `borderRadius` /
|
|
52
|
+
* `borderTopLeftRadius` / `borderTopRightRadius` / `borderBottomLeftRadius` /
|
|
53
|
+
* `borderBottomRightRadius`, in points. Per-corner properties override
|
|
54
|
+
* `borderRadius` for that corner; a corner with neither set stays square.
|
|
55
|
+
* Non-numeric values (percentages, animated values) are ignored, like
|
|
56
|
+
* {@linkcode resizeForStyle}. Returns `undefined` when none are set.
|
|
57
|
+
*/
|
|
58
|
+
export function cornerRadiusForStyle(
|
|
59
|
+
style: StyleProp<ViewStyle>,
|
|
60
|
+
): number | CornerRadii | undefined {
|
|
61
|
+
const flat = StyleSheet.flatten(style);
|
|
62
|
+
const base =
|
|
63
|
+
typeof flat?.borderRadius === 'number' ? flat.borderRadius : undefined;
|
|
64
|
+
const topLeft =
|
|
65
|
+
typeof flat?.borderTopLeftRadius === 'number'
|
|
66
|
+
? flat.borderTopLeftRadius
|
|
67
|
+
: undefined;
|
|
68
|
+
const topRight =
|
|
69
|
+
typeof flat?.borderTopRightRadius === 'number'
|
|
70
|
+
? flat.borderTopRightRadius
|
|
71
|
+
: undefined;
|
|
72
|
+
const bottomLeft =
|
|
73
|
+
typeof flat?.borderBottomLeftRadius === 'number'
|
|
74
|
+
? flat.borderBottomLeftRadius
|
|
75
|
+
: undefined;
|
|
76
|
+
const bottomRight =
|
|
77
|
+
typeof flat?.borderBottomRightRadius === 'number'
|
|
78
|
+
? flat.borderBottomRightRadius
|
|
79
|
+
: undefined;
|
|
80
|
+
|
|
81
|
+
if (
|
|
82
|
+
topLeft === undefined &&
|
|
83
|
+
topRight === undefined &&
|
|
84
|
+
bottomLeft === undefined &&
|
|
85
|
+
bottomRight === undefined
|
|
86
|
+
) {
|
|
87
|
+
return base;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
topLeft: topLeft ?? base,
|
|
92
|
+
topRight: topRight ?? base,
|
|
93
|
+
bottomLeft: bottomLeft ?? base,
|
|
94
|
+
bottomRight: bottomRight ?? base,
|
|
95
|
+
};
|
|
96
|
+
}
|
package/src/useImage.ts
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { useEffect, useRef, useState } from 'react';
|
|
2
|
+
import type { Image } from 'react-native-nitro-image';
|
|
3
|
+
|
|
4
|
+
import { NitroImagePipeline } from './NitroImagePipeline';
|
|
5
|
+
import type {
|
|
6
|
+
CacheOption,
|
|
7
|
+
CornerRadii,
|
|
8
|
+
ResizeOptions,
|
|
9
|
+
} from './specs/nitro-image-toolkit.nitro';
|
|
10
|
+
|
|
11
|
+
type Result =
|
|
12
|
+
// Loading State
|
|
13
|
+
| {
|
|
14
|
+
image: undefined;
|
|
15
|
+
error: undefined;
|
|
16
|
+
}
|
|
17
|
+
// Loaded state
|
|
18
|
+
| {
|
|
19
|
+
image: Image;
|
|
20
|
+
error: undefined;
|
|
21
|
+
}
|
|
22
|
+
// Error state
|
|
23
|
+
| {
|
|
24
|
+
image: undefined;
|
|
25
|
+
error: Error;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* A hook to asynchronously load an image from the
|
|
30
|
+
* given {@linkcode AsyncImageSource} into memory.
|
|
31
|
+
* @example
|
|
32
|
+
* ```ts
|
|
33
|
+
* const { image, error } = useImage({ filePath: '/tmp/image.jpg' })
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
export function useImage({
|
|
37
|
+
url,
|
|
38
|
+
blur = 0,
|
|
39
|
+
cornerRadius = 0,
|
|
40
|
+
resize,
|
|
41
|
+
cache,
|
|
42
|
+
enabled = true,
|
|
43
|
+
}: {
|
|
44
|
+
url: string;
|
|
45
|
+
/**
|
|
46
|
+
* Gaussian blur strength, as the standard deviation (sigma) of the blur in
|
|
47
|
+
* source-image pixels (of the resized bitmap when `resize` is set). Matches
|
|
48
|
+
* across iOS and Android; roughly half of React Native's `blurRadius`.
|
|
49
|
+
*/
|
|
50
|
+
blur?: number;
|
|
51
|
+
/**
|
|
52
|
+
* Corner radius in pixels of the loaded bitmap. Pass a single number for
|
|
53
|
+
* uniform rounding, or per-corner radii (inline object literals are fine —
|
|
54
|
+
* the hook compares the radii by value, not identity). Pair with `resize`
|
|
55
|
+
* so the radii apply at the size you display instead of the source size.
|
|
56
|
+
*/
|
|
57
|
+
cornerRadius?: number | CornerRadii;
|
|
58
|
+
/**
|
|
59
|
+
* Resize the bitmap to exactly this size in pixels (aspect-fill,
|
|
60
|
+
* center-crop) before blur/rounding. Typically your display size in points
|
|
61
|
+
* multiplied by `PixelRatio.get()`. Inline object literals are fine.
|
|
62
|
+
*/
|
|
63
|
+
resize?: ResizeOptions;
|
|
64
|
+
cache?: CacheOption;
|
|
65
|
+
/**
|
|
66
|
+
* When `false`, no request is made and the result stays in the loading
|
|
67
|
+
* state (or keeps the current image if `url` is unchanged) until it becomes
|
|
68
|
+
* `true`. Use it to defer loading until inputs such as a layout size are
|
|
69
|
+
* known.
|
|
70
|
+
* @default true
|
|
71
|
+
*/
|
|
72
|
+
enabled?: boolean;
|
|
73
|
+
}): Result {
|
|
74
|
+
const [image, setImage] = useState<Result>({
|
|
75
|
+
image: undefined,
|
|
76
|
+
error: undefined,
|
|
77
|
+
});
|
|
78
|
+
const loadedUrlRef = useRef(url);
|
|
79
|
+
|
|
80
|
+
// Split the option into primitives so an inline `{ topLeft: 24, ... }`
|
|
81
|
+
// literal (new identity every render) doesn't re-trigger the effect.
|
|
82
|
+
const isUniformRadius = typeof cornerRadius === 'number';
|
|
83
|
+
const uniformRadius = isUniformRadius ? cornerRadius : 0;
|
|
84
|
+
const {
|
|
85
|
+
topLeft = 0,
|
|
86
|
+
topRight = 0,
|
|
87
|
+
bottomLeft = 0,
|
|
88
|
+
bottomRight = 0,
|
|
89
|
+
} = isUniformRadius ? {} : cornerRadius;
|
|
90
|
+
const resizeWidth = resize?.width ?? 0;
|
|
91
|
+
const resizeHeight = resize?.height ?? 0;
|
|
92
|
+
|
|
93
|
+
useEffect(() => {
|
|
94
|
+
let cancelled = false;
|
|
95
|
+
// Only reset to the loading state when the URL changes; for same-URL
|
|
96
|
+
// param tweaks (blur/cornerRadius/cache) keep showing the current image
|
|
97
|
+
// until the new variant resolves, to avoid flashing empty.
|
|
98
|
+
if (loadedUrlRef.current !== url) {
|
|
99
|
+
loadedUrlRef.current = url;
|
|
100
|
+
setImage({ image: undefined, error: undefined });
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (enabled) {
|
|
104
|
+
(async () => {
|
|
105
|
+
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
|
+
});
|
|
117
|
+
|
|
118
|
+
if (!cancelled) {
|
|
119
|
+
setImage({ image: result, error: undefined });
|
|
120
|
+
}
|
|
121
|
+
} catch (e) {
|
|
122
|
+
const error = e instanceof Error ? e : new Error(`${e}`);
|
|
123
|
+
if (!cancelled) {
|
|
124
|
+
setImage({ image: undefined, error: error });
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
})();
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return () => {
|
|
131
|
+
cancelled = true;
|
|
132
|
+
};
|
|
133
|
+
}, [
|
|
134
|
+
url,
|
|
135
|
+
blur,
|
|
136
|
+
isUniformRadius,
|
|
137
|
+
uniformRadius,
|
|
138
|
+
topLeft,
|
|
139
|
+
topRight,
|
|
140
|
+
bottomLeft,
|
|
141
|
+
bottomRight,
|
|
142
|
+
resizeWidth,
|
|
143
|
+
resizeHeight,
|
|
144
|
+
cache,
|
|
145
|
+
enabled,
|
|
146
|
+
]);
|
|
147
|
+
|
|
148
|
+
return image;
|
|
149
|
+
}
|