react-native-nitro-image-pipeline 0.3.5 → 1.0.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 +58 -4
- package/android/src/main/java/com/margelo/nitro/nitroimagepipeline/HybridNitroImagePipeline.kt +9 -4
- package/android/src/main/java/com/margelo/nitro/nitroimagepipeline/transform/BlurTransformation.kt +50 -24
- package/ios/GaussianBlur.swift +135 -0
- package/ios/GaussianBlurProcessor.swift +41 -0
- package/ios/HybridNitroImagePipeline.swift +3 -18
- package/lib/commonjs/index.js.map +1 -1
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/src/index.d.ts +5 -0
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/specs/nitro-image-toolkit.nitro.d.ts +14 -0
- package/lib/typescript/src/specs/nitro-image-toolkit.nitro.d.ts.map +1 -1
- package/package.json +21 -13
- package/src/index.ts +5 -0
- package/src/specs/nitro-image-toolkit.nitro.ts +18 -2
package/README.md
CHANGED
|
@@ -48,7 +48,7 @@ import { useImage } from 'react-native-nitro-image-pipeline';
|
|
|
48
48
|
function MyComponent() {
|
|
49
49
|
const { image, error } = useImage({
|
|
50
50
|
url: 'https://example.com/photo.jpg',
|
|
51
|
-
blur: 4,
|
|
51
|
+
blur: 4, // Gaussian sigma in source pixels — same result on iOS and Android
|
|
52
52
|
cornerRadius: 12,
|
|
53
53
|
});
|
|
54
54
|
|
|
@@ -67,7 +67,7 @@ import { NitroImagePipeline } from 'react-native-nitro-image-pipeline';
|
|
|
67
67
|
|
|
68
68
|
// Load an image with options
|
|
69
69
|
const image = await NitroImagePipeline.loadImage('https://example.com/photo.jpg', {
|
|
70
|
-
blur: 4,
|
|
70
|
+
blur: 4, // Gaussian sigma in source pixels — see "Blur units"
|
|
71
71
|
cornerRadius: 12,
|
|
72
72
|
cache: 'disk',
|
|
73
73
|
});
|
|
@@ -96,7 +96,7 @@ Loads an image from a URL and returns a `Promise<Image>`.
|
|
|
96
96
|
|
|
97
97
|
| Option | Type | Default | Description |
|
|
98
98
|
|---|---|---|---|
|
|
99
|
-
| `blur` | `number` | `0` | Gaussian blur
|
|
99
|
+
| `blur` | `number` | `0` | Gaussian blur strength applied at load time — see [Blur units](#blur-units) |
|
|
100
100
|
| `cornerRadius` | `number` | `0` | Corner radius in points |
|
|
101
101
|
| `cache` | `'memory' \| 'disk' \| 'none'` | platform default | Caching strategy |
|
|
102
102
|
|
|
@@ -110,12 +110,66 @@ Prefetches multiple images into the cache. Returns `Promise<void>`.
|
|
|
110
110
|
|
|
111
111
|
### `gaussianBlur(image, radius)`
|
|
112
112
|
|
|
113
|
-
Applies a Gaussian blur to an existing `Image` object. Returns `Promise<Image>`.
|
|
113
|
+
Applies a Gaussian blur to an existing `Image` object. Returns `Promise<Image>`. `radius` uses the
|
|
114
|
+
same unit as the `blur` option — see [Blur units](#blur-units).
|
|
115
|
+
|
|
116
|
+
### Blur units
|
|
117
|
+
|
|
118
|
+
`blur` (and `gaussianBlur`'s `radius`) is the **standard deviation (sigma) of the Gaussian, in
|
|
119
|
+
source-image pixels**. The same value on the same source file produces the same result on iOS and
|
|
120
|
+
Android — the platforms are calibrated against each other rather than each exposing its native
|
|
121
|
+
backend's own idea of "radius".
|
|
122
|
+
|
|
123
|
+
```ts
|
|
124
|
+
// ~11px of blur on both platforms, whatever the device
|
|
125
|
+
await NitroImagePipeline.loadImage(url, { blur: 11 });
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Two things follow from the unit being *source* pixels:
|
|
129
|
+
|
|
130
|
+
- Blur is measured against the image's own resolution, not the size it is displayed at. A 4000px
|
|
131
|
+
photo at `blur: 11` looks subtler than a 400px thumbnail at `blur: 11`. To keep a feed visually
|
|
132
|
+
consistent, scale the value with the source width.
|
|
133
|
+
- Coming from React Native's `<Image blurRadius={n} />`? That halves its input internally, so
|
|
134
|
+
`blurRadius={n}` ≈ `blur: n / 2`. RN's value is also density-scaled on Android and not on iOS,
|
|
135
|
+
which is why the two never quite matched there.
|
|
136
|
+
|
|
137
|
+
Values below ~1 are smaller than the smallest kernel either backend can build and are effectively a
|
|
138
|
+
no-op. There is no upper bound.
|
|
139
|
+
|
|
140
|
+
Implementation: iOS runs three Accelerate box-convolution passes sized to hit the requested sigma
|
|
141
|
+
(the standard three-box Gaussian approximation, accurate to a few percent); Android uses
|
|
142
|
+
RenderScript's true Gaussian, downscaling first when sigma exceeds the single-pass ceiling of
|
|
143
|
+
~10.6px and compensating the radius so the result is unchanged. Both clamp at the edges, so blurred
|
|
144
|
+
images keep their borders instead of fading out.
|
|
114
145
|
|
|
115
146
|
### `clearCache()`
|
|
116
147
|
|
|
117
148
|
Removes all cached images from memory and disk. Returns `Promise<void>` that resolves once both caches are cleared.
|
|
118
149
|
|
|
150
|
+
## Upgrading from 0.3.x
|
|
151
|
+
|
|
152
|
+
`blur` and `gaussianBlur(image, radius)` changed meaning in 1.0. They used to hand the number
|
|
153
|
+
straight to each platform's native blur, and the two platforms disagreed about what it meant; now
|
|
154
|
+
both read it as a Gaussian sigma in source-image pixels (see [Blur units](#blur-units)).
|
|
155
|
+
|
|
156
|
+
| | what `blur: n` did in 0.3.x | what it does in 1.0 |
|
|
157
|
+
|---|---|---|
|
|
158
|
+
| iOS | fed `n` to `CIGaussianBlur(inputRadius:)`, measured at `sigma ≈ 1.18 × n` | `sigma = n` |
|
|
159
|
+
| Android | RenderScript on a copy downscaled to 512px, so strength scaled with the source resolution: `sigma ≈ (0.4n + 0.6) × max(w, h) / 512` | `sigma = n`, resolution-independent |
|
|
160
|
+
|
|
161
|
+
To keep the look you had:
|
|
162
|
+
|
|
163
|
+
- **iOS:** multiply your old value by ~1.18 (`blur: 10` → `blur: 12`).
|
|
164
|
+
- **Android:** there is no single factor — the old result depended on the source image's
|
|
165
|
+
resolution. Re-tune against iOS, which the two platforms now agree with.
|
|
166
|
+
|
|
167
|
+
Also changed:
|
|
168
|
+
|
|
169
|
+
- `blur` above 25 used to reject the promise on Android. Sigma is now unbounded.
|
|
170
|
+
- Fractional values used to be truncated to whole numbers on iOS. They are honoured now.
|
|
171
|
+
- Blurred images used to fade out at the borders on iOS. Edges are clamped on both platforms now.
|
|
172
|
+
|
|
119
173
|
## Credits
|
|
120
174
|
|
|
121
175
|
Bootstrapped with [create-nitro-module](https://github.com/patrickkabwe/create-nitro-module).
|
package/android/src/main/java/com/margelo/nitro/nitroimagepipeline/HybridNitroImagePipeline.kt
CHANGED
|
@@ -105,10 +105,15 @@ class HybridNitroImagePipeline : HybridNitroImagePipelineSpec() {
|
|
|
105
105
|
override fun gaussianBlur(image: HybridImageSpec, radius: Double): Promise<HybridImageSpec> =
|
|
106
106
|
Promise.async {
|
|
107
107
|
val hybridImage = image as? HybridImage ?: throw Error("Image is not a HybridImage")
|
|
108
|
-
|
|
109
|
-
val
|
|
110
|
-
|
|
111
|
-
|
|
108
|
+
// `radius` is a Gaussian sigma in source-image pixels — see BlurTransformation.
|
|
109
|
+
val sigma = radius.toFloat()
|
|
110
|
+
if (sigma <= 0f) {
|
|
111
|
+
hybridImage
|
|
112
|
+
} else {
|
|
113
|
+
val blurred =
|
|
114
|
+
BlurTransformation(context, sigma).transform(hybridImage.bitmap, Size.ORIGINAL)
|
|
115
|
+
HybridImage(blurred)
|
|
116
|
+
}
|
|
112
117
|
}
|
|
113
118
|
|
|
114
119
|
override fun clearCache(): Promise<Unit> = Promise.async {
|
package/android/src/main/java/com/margelo/nitro/nitroimagepipeline/transform/BlurTransformation.kt
CHANGED
|
@@ -13,33 +13,57 @@ import androidx.core.graphics.applyCanvas
|
|
|
13
13
|
import androidx.core.graphics.scale
|
|
14
14
|
import coil3.size.Size
|
|
15
15
|
import coil3.transform.Transformation
|
|
16
|
+
import kotlin.math.ceil
|
|
17
|
+
import kotlin.math.max
|
|
16
18
|
|
|
19
|
+
/**
|
|
20
|
+
* Gaussian blur of standard deviation [sigma], measured in *source image pixels*.
|
|
21
|
+
*
|
|
22
|
+
* The unit is the point of this class: the same sigma on the same source file produces the same
|
|
23
|
+
* result here and on iOS. It is deliberately not a "radius" — RenderScript, CoreImage and React
|
|
24
|
+
* Native's `blurRadius` each define radius differently, which is why blurs never matched across
|
|
25
|
+
* platforms.
|
|
26
|
+
*
|
|
27
|
+
* For reference, React Native's `<Image blurRadius={n} />` halves its input before convolving, so
|
|
28
|
+
* `blurRadius={n}` is roughly `sigma = n / 2`.
|
|
29
|
+
*/
|
|
17
30
|
class BlurTransformation(
|
|
18
31
|
private val context: Context,
|
|
19
|
-
private val
|
|
20
|
-
private val sampling: Float = 1f,
|
|
32
|
+
private val sigma: Float,
|
|
21
33
|
) : Transformation() {
|
|
22
34
|
|
|
23
35
|
init {
|
|
24
|
-
require(
|
|
25
|
-
require(sampling >= 1f) { "sampling must be >= 1." }
|
|
36
|
+
require(sigma > 0f && sigma.isFinite()) { "sigma must be > 0." }
|
|
26
37
|
}
|
|
27
38
|
|
|
28
|
-
override val cacheKey = "${BlurTransformation::class.java.name}-$
|
|
39
|
+
override val cacheKey = "${BlurTransformation::class.java.name}-$sigma"
|
|
29
40
|
|
|
30
41
|
override suspend fun transform(input: Bitmap, size: Size): Bitmap {
|
|
42
|
+
// ScriptIntrinsicBlur's `radius` maps to a Gaussian sigma of 0.4 * radius + 0.6, and radius is
|
|
43
|
+
// capped at 25 — so a single pass tops out at sigma ~10.6px. Larger blurs are reached by
|
|
44
|
+
// blurring a downscaled copy: scaling down by `s`, blurring with sigma/s and scaling back up
|
|
45
|
+
// multiplies the effective sigma by `s`. A blur is a low-pass filter, so the detail the
|
|
46
|
+
// downscale drops is detail the blur was going to remove anyway. Downscale only as far as the
|
|
47
|
+
// sigma ceiling forces, which keeps small blurs pixel-exact.
|
|
48
|
+
val sampling = max(1f, sigma / MAX_SIGMA)
|
|
49
|
+
val scaledWidth = ceil(input.width / sampling).toInt().coerceAtLeast(1)
|
|
50
|
+
val scaledHeight = ceil(input.height / sampling).toInt().coerceAtLeast(1)
|
|
51
|
+
// Rounding to whole pixels shifts the scale slightly; derive the sigma from the scale actually
|
|
52
|
+
// applied rather than the requested one.
|
|
53
|
+
val scaleX = scaledWidth.toFloat() / input.width
|
|
54
|
+
val scaleY = scaledHeight.toFloat() / input.height
|
|
55
|
+
val scaledSigma = sigma * max(scaleX, scaleY)
|
|
56
|
+
val radius = ((scaledSigma - SIGMA_INTERCEPT) / SIGMA_SLOPE).coerceIn(MIN_RADIUS, MAX_RADIUS)
|
|
57
|
+
|
|
31
58
|
val paint = Paint(Paint.ANTI_ALIAS_FLAG or Paint.FILTER_BITMAP_FLAG)
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
val scaledWidth = (input.width / autoSampling).toInt().coerceAtLeast(1)
|
|
36
|
-
val scaledHeight = (input.height / autoSampling).toInt().coerceAtLeast(1)
|
|
37
|
-
val softwareInput = if (input.config == Bitmap.Config.HARDWARE) input.copy(Bitmap.Config.ARGB_8888, false) else input
|
|
59
|
+
val softwareInput =
|
|
60
|
+
if (input.config == Bitmap.Config.HARDWARE) input.copy(Bitmap.Config.ARGB_8888, false)
|
|
61
|
+
else input
|
|
38
62
|
val softwareConfig = softwareInput.config ?: Bitmap.Config.ARGB_8888
|
|
39
63
|
|
|
40
64
|
val output = Bitmap.createBitmap(scaledWidth, scaledHeight, softwareConfig)
|
|
41
65
|
output.applyCanvas {
|
|
42
|
-
scale(
|
|
66
|
+
scale(scaleX, scaleY)
|
|
43
67
|
drawBitmap(softwareInput, 0f, 0f, paint)
|
|
44
68
|
}
|
|
45
69
|
|
|
@@ -69,23 +93,25 @@ class BlurTransformation(
|
|
|
69
93
|
blur?.destroy()
|
|
70
94
|
}
|
|
71
95
|
|
|
72
|
-
return
|
|
96
|
+
return if (scaledWidth == input.width && scaledHeight == input.height) output
|
|
97
|
+
else output.scale(input.width, input.height)
|
|
73
98
|
}
|
|
74
99
|
|
|
75
100
|
override fun equals(other: Any?): Boolean {
|
|
76
101
|
if (this === other) return true
|
|
77
|
-
return other is BlurTransformation &&
|
|
78
|
-
context == other.context &&
|
|
79
|
-
radius == other.radius &&
|
|
80
|
-
sampling == other.sampling
|
|
102
|
+
return other is BlurTransformation && context == other.context && sigma == other.sigma
|
|
81
103
|
}
|
|
82
104
|
|
|
83
|
-
override fun hashCode(): Int
|
|
84
|
-
var result = context.hashCode()
|
|
85
|
-
result = 31 * result + radius.hashCode()
|
|
86
|
-
result = 31 * result + sampling.hashCode()
|
|
87
|
-
return result
|
|
88
|
-
}
|
|
105
|
+
override fun hashCode(): Int = 31 * context.hashCode() + sigma.hashCode()
|
|
89
106
|
|
|
90
|
-
override fun toString() = "BlurTransformation(
|
|
107
|
+
override fun toString() = "BlurTransformation(sigma=$sigma)"
|
|
108
|
+
|
|
109
|
+
private companion object {
|
|
110
|
+
// sigma = 0.4 * radius + 0.6, per RenderScript's ScriptIntrinsicBlur.
|
|
111
|
+
const val SIGMA_SLOPE = 0.4f
|
|
112
|
+
const val SIGMA_INTERCEPT = 0.6f
|
|
113
|
+
const val MIN_RADIUS = 0.01f
|
|
114
|
+
const val MAX_RADIUS = 25f
|
|
115
|
+
const val MAX_SIGMA = SIGMA_SLOPE * MAX_RADIUS + SIGMA_INTERCEPT
|
|
116
|
+
}
|
|
91
117
|
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
//
|
|
2
|
+
// GaussianBlur.swift
|
|
3
|
+
// NitroImagePipeline
|
|
4
|
+
//
|
|
5
|
+
// Cross-platform Gaussian blur — the kernel itself.
|
|
6
|
+
//
|
|
7
|
+
// This file deliberately depends on nothing but Accelerate and CoreGraphics
|
|
8
|
+
// so `scripts/verify-blur.swift` can compile and measure it on the host
|
|
9
|
+
// machine. The UIImage/Nuke plumbing lives in GaussianBlurProcessor.swift.
|
|
10
|
+
//
|
|
11
|
+
// `sigma` is the standard deviation of the Gaussian, measured in *source
|
|
12
|
+
// image pixels* — the same sigma applied to the same source file produces
|
|
13
|
+
// the same result on iOS and Android. It is deliberately not a "radius":
|
|
14
|
+
// CIGaussianBlur, RenderScript and React Native's `blurRadius` each define
|
|
15
|
+
// radius differently, which is why blurs never matched across platforms.
|
|
16
|
+
//
|
|
17
|
+
// For reference, React Native's `<Image blurRadius={n} />` halves its input
|
|
18
|
+
// before convolving, so `blurRadius={n}` is roughly `sigma = n / 2`.
|
|
19
|
+
//
|
|
20
|
+
|
|
21
|
+
import Accelerate
|
|
22
|
+
import CoreGraphics
|
|
23
|
+
import Foundation
|
|
24
|
+
|
|
25
|
+
enum GaussianBlur {
|
|
26
|
+
/// Runs the box-blur passes over `cgImage`. Returns `nil` if the pixel
|
|
27
|
+
/// buffers could not be allocated.
|
|
28
|
+
static func convolve(_ cgImage: CGImage, boxes: [UInt32]) -> CGImage? {
|
|
29
|
+
// Normalise to premultiplied ARGB8888 — vImageBoxConvolve_ARGB8888
|
|
30
|
+
// needs four 8-bit channels, and premultiplied alpha is what keeps
|
|
31
|
+
// transparent edges from bleeding dark halos into the blur.
|
|
32
|
+
guard var format = vImage_CGImageFormat(
|
|
33
|
+
bitsPerComponent: 8,
|
|
34
|
+
bitsPerPixel: 32,
|
|
35
|
+
colorSpace: CGColorSpaceCreateDeviceRGB(),
|
|
36
|
+
bitmapInfo: CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedFirst.rawValue),
|
|
37
|
+
renderingIntent: .defaultIntent
|
|
38
|
+
) else { return nil }
|
|
39
|
+
|
|
40
|
+
var source = vImage_Buffer()
|
|
41
|
+
guard vImageBuffer_InitWithCGImage(
|
|
42
|
+
&source, &format, nil, cgImage, vImage_Flags(kvImageNoFlags)
|
|
43
|
+
) == kvImageNoError else { return nil }
|
|
44
|
+
defer { free(source.data) }
|
|
45
|
+
|
|
46
|
+
var scratch = vImage_Buffer()
|
|
47
|
+
guard vImageBuffer_Init(
|
|
48
|
+
&scratch, source.height, source.width, 32, vImage_Flags(kvImageNoFlags)
|
|
49
|
+
) == kvImageNoError else { return nil }
|
|
50
|
+
defer { free(scratch.data) }
|
|
51
|
+
|
|
52
|
+
// kvImageEdgeExtend clamps at the borders instead of sampling
|
|
53
|
+
// transparent black, so the image keeps its edges instead of fading
|
|
54
|
+
// out into the frame (CIGaussianBlur's default, and the reason blurred
|
|
55
|
+
// images used to come back with washed-out borders).
|
|
56
|
+
let flags = vImage_Flags(kvImageEdgeExtend)
|
|
57
|
+
// The passes use two different kernel widths; size the scratch buffer
|
|
58
|
+
// for the widest one so every pass fits in it.
|
|
59
|
+
let widest = boxes.max() ?? 1
|
|
60
|
+
let tempSize = vImageBoxConvolve_ARGB8888(
|
|
61
|
+
&source, &scratch, nil, 0, 0, widest, widest, nil,
|
|
62
|
+
vImage_Flags(kvImageEdgeExtend | kvImageGetTempBufferSize)
|
|
63
|
+
)
|
|
64
|
+
guard tempSize > 0, let temp = malloc(tempSize) else { return nil }
|
|
65
|
+
defer { free(temp) }
|
|
66
|
+
|
|
67
|
+
// Ping-pong between the two buffers; after an odd number of passes the
|
|
68
|
+
// result sits in `input`.
|
|
69
|
+
var input = source
|
|
70
|
+
var output = scratch
|
|
71
|
+
for box in boxes {
|
|
72
|
+
guard vImageBoxConvolve_ARGB8888(
|
|
73
|
+
&input, &output, temp, 0, 0, box, box, nil, flags
|
|
74
|
+
) == kvImageNoError else { return nil }
|
|
75
|
+
swap(&input, &output)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
var error = vImage_Error(kvImageNoError)
|
|
79
|
+
guard let blurred = vImageCreateCGImageFromBuffer(
|
|
80
|
+
&input, &format, nil, nil, vImage_Flags(kvImageNoFlags), &error
|
|
81
|
+
)?.takeRetainedValue(), error == kvImageNoError else { return nil }
|
|
82
|
+
|
|
83
|
+
return blurred
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/// Widths for the three box-blur passes that approximate a Gaussian of
|
|
87
|
+
/// `sigma` (the classic approximation: three boxes land within a few
|
|
88
|
+
/// percent of a true Gaussian, and Accelerate runs them in O(1) per pixel
|
|
89
|
+
/// regardless of how wide they are).
|
|
90
|
+
///
|
|
91
|
+
/// Three boxes of widths w1…w3 produce a standard deviation of
|
|
92
|
+
/// `sqrt((w1² + w2² + w3² - 3) / 12)`. Box widths have to be odd, so the
|
|
93
|
+
/// passes are split between the two odd integers straddling the ideal
|
|
94
|
+
/// width and the split landing closest to `sigma` wins — which keeps the
|
|
95
|
+
/// error under half a pixel at any sigma instead of always undershooting.
|
|
96
|
+
static func boxSizes(forSigma sigma: Double) -> [UInt32] {
|
|
97
|
+
let passes = 3
|
|
98
|
+
var lower = Int(((12 * sigma * sigma / Double(passes)) + 1).squareRoot())
|
|
99
|
+
if lower % 2 == 0 { lower -= 1 }
|
|
100
|
+
// Upper bound keeps `upper` inside UInt32 for absurd sigmas; a kernel
|
|
101
|
+
// that wide is already far larger than any real image.
|
|
102
|
+
lower = min(max(lower, 1), Int(UInt32.max) - 2)
|
|
103
|
+
let upper = lower + 2
|
|
104
|
+
|
|
105
|
+
// Written as a plain loop rather than map/min(by:): the inferred
|
|
106
|
+
// version tripped "type of expression is ambiguous" on Swift 6.2.
|
|
107
|
+
var best: [UInt32] = []
|
|
108
|
+
var bestError: Double = .infinity
|
|
109
|
+
for lowerCount in 0...passes {
|
|
110
|
+
var candidate: [UInt32] = []
|
|
111
|
+
for pass in 0..<passes {
|
|
112
|
+
candidate.append(UInt32(pass < lowerCount ? lower : upper))
|
|
113
|
+
}
|
|
114
|
+
// `.magnitude` rather than `abs()`: Nitro's C++ interop puts
|
|
115
|
+
// std::abs overloads in scope, and Swift 6.2 calls the resulting
|
|
116
|
+
// `abs` ambiguous when this file is compiled as part of the pod.
|
|
117
|
+
let error: Double = (standardDeviation(of: candidate) - sigma).magnitude
|
|
118
|
+
if error < bestError {
|
|
119
|
+
bestError = error
|
|
120
|
+
best = candidate
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return best
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/// The standard deviation three box blurs of the given widths add up to.
|
|
127
|
+
private static func standardDeviation(of boxes: [UInt32]) -> Double {
|
|
128
|
+
var sum: Double = 0
|
|
129
|
+
for box in boxes {
|
|
130
|
+
let width = Double(box)
|
|
131
|
+
sum += width * width - 1
|
|
132
|
+
}
|
|
133
|
+
return (sum / 12).squareRoot()
|
|
134
|
+
}
|
|
135
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
//
|
|
2
|
+
// GaussianBlurProcessor.swift
|
|
3
|
+
// NitroImagePipeline
|
|
4
|
+
//
|
|
5
|
+
// UIImage and Nuke plumbing around the kernel in GaussianBlur.swift.
|
|
6
|
+
//
|
|
7
|
+
|
|
8
|
+
import CoreGraphics
|
|
9
|
+
import Foundation
|
|
10
|
+
import Nuke
|
|
11
|
+
import UIKit
|
|
12
|
+
|
|
13
|
+
extension GaussianBlur {
|
|
14
|
+
/// Applies a Gaussian blur of standard deviation `sigma` (in image pixels).
|
|
15
|
+
///
|
|
16
|
+
/// Returns `image` unchanged when `sigma` is too small to affect a pixel,
|
|
17
|
+
/// and `nil` only when the pixel buffers could not be allocated.
|
|
18
|
+
static func apply(to image: UIImage, sigma: Double) -> UIImage? {
|
|
19
|
+
guard sigma > 0, sigma.isFinite, let cgImage = image.cgImage else { return image }
|
|
20
|
+
|
|
21
|
+
let boxes = boxSizes(forSigma: sigma)
|
|
22
|
+
guard boxes.contains(where: { $0 > 1 }) else { return image }
|
|
23
|
+
guard let blurred = convolve(cgImage, boxes: boxes) else { return nil }
|
|
24
|
+
|
|
25
|
+
return UIImage(cgImage: blurred, scale: image.scale, orientation: image.imageOrientation)
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/// Nuke processor so `loadImage(url:, { blur })` and `gaussianBlur(image:, sigma)`
|
|
30
|
+
/// go through the exact same kernel.
|
|
31
|
+
struct GaussianBlurProcessor: ImageProcessing {
|
|
32
|
+
let sigma: Double
|
|
33
|
+
|
|
34
|
+
var identifier: String { "com.nitroimagepipeline.gaussianBlur?sigma=\(sigma)" }
|
|
35
|
+
|
|
36
|
+
var hashableIdentifier: AnyHashable { identifier }
|
|
37
|
+
|
|
38
|
+
func process(_ image: PlatformImage) -> PlatformImage? {
|
|
39
|
+
GaussianBlur.apply(to: image, sigma: sigma)
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -11,7 +11,6 @@ import NitroImage
|
|
|
11
11
|
import Nuke
|
|
12
12
|
|
|
13
13
|
import UIKit
|
|
14
|
-
import CoreImage
|
|
15
14
|
|
|
16
15
|
private class HybridImage: HybridImageSpec, NativeImage {
|
|
17
16
|
let uiImage: UIImage
|
|
@@ -101,9 +100,6 @@ class HybridNitroImagePipeline: HybridNitroImagePipelineSpec {
|
|
|
101
100
|
|
|
102
101
|
private static let sharedPrefetcher = ImagePrefetcher(pipeline: sharedPipeline)
|
|
103
102
|
|
|
104
|
-
// CIContext is expensive to create; Apple recommends reusing one.
|
|
105
|
-
private static let ciContext = CIContext()
|
|
106
|
-
|
|
107
103
|
private var pipeline: ImagePipeline { Self.sharedPipeline }
|
|
108
104
|
private var prefetcher: ImagePrefetcher { Self.sharedPrefetcher }
|
|
109
105
|
|
|
@@ -122,7 +118,7 @@ class HybridNitroImagePipeline: HybridNitroImagePipelineSpec {
|
|
|
122
118
|
|
|
123
119
|
var processors: [any ImageProcessing] = []
|
|
124
120
|
if let blur = options?.blur, blur > 0 {
|
|
125
|
-
processors.append(
|
|
121
|
+
processors.append(GaussianBlurProcessor(sigma: blur))
|
|
126
122
|
}
|
|
127
123
|
if let cornerRadius = options?.cornerRadius, cornerRadius > 0 {
|
|
128
124
|
processors.append(.roundedCorners(radius: cornerRadius))
|
|
@@ -168,22 +164,11 @@ class HybridNitroImagePipeline: HybridNitroImagePipelineSpec {
|
|
|
168
164
|
throw RuntimeError.error(withMessage: "Unsupported image type")
|
|
169
165
|
}
|
|
170
166
|
|
|
171
|
-
let
|
|
172
|
-
|
|
173
|
-
guard let ciImage = CIImage(image: uiImage) else {
|
|
174
|
-
throw RuntimeError.error(withMessage: "Failed to read image data")
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
let filter = CIFilter(name: "CIGaussianBlur")!
|
|
178
|
-
filter.setValue(ciImage, forKey: kCIInputImageKey)
|
|
179
|
-
filter.setValue(radius, forKey: kCIInputRadiusKey)
|
|
180
|
-
|
|
181
|
-
guard let output = filter.outputImage,
|
|
182
|
-
let cgImage = Self.ciContext.createCGImage(output, from: ciImage.extent) else {
|
|
167
|
+
guard let blurred = GaussianBlur.apply(to: nativeImage.uiImage, sigma: radius) else {
|
|
183
168
|
throw RuntimeError.error(withMessage: "Failed to apply gaussian blur")
|
|
184
169
|
}
|
|
185
170
|
|
|
186
|
-
return HybridImage(uiImage:
|
|
171
|
+
return HybridImage(uiImage: blurred)
|
|
187
172
|
}
|
|
188
173
|
}
|
|
189
174
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["_react","require","_reactNativeNitroModules","NitroImagePipeline","exports","NitroModules","createHybridObject","useImage","url","blur","cornerRadius","cache","image","setImage","useState","undefined","error","loadedUrlRef","useRef","useEffect","cancelled","current","result","loadImage","e","Error"],"sourceRoot":"../../src","sources":["index.ts"],"mappings":";;;;;;;AAAA,IAAAA,MAAA,GAAAC,OAAA;AAEA,IAAAC,wBAAA,GAAAD,OAAA;AAUO,MAAME,kBAAkB,GAAAC,OAAA,CAAAD,kBAAA,GAC7BE,qCAAY,CAACC,kBAAkB,CAAyB,oBAAoB,CAAC;AAmB/E;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAASC,QAAQA,CAAC;EACvBC,GAAG;EACHC,IAAI,GAAG,CAAC;EACRC,YAAY,GAAG,CAAC;EAChBC;
|
|
1
|
+
{"version":3,"names":["_react","require","_reactNativeNitroModules","NitroImagePipeline","exports","NitroModules","createHybridObject","useImage","url","blur","cornerRadius","cache","image","setImage","useState","undefined","error","loadedUrlRef","useRef","useEffect","cancelled","current","result","loadImage","e","Error"],"sourceRoot":"../../src","sources":["index.ts"],"mappings":";;;;;;;AAAA,IAAAA,MAAA,GAAAC,OAAA;AAEA,IAAAC,wBAAA,GAAAD,OAAA;AAUO,MAAME,kBAAkB,GAAAC,OAAA,CAAAD,kBAAA,GAC7BE,qCAAY,CAACC,kBAAkB,CAAyB,oBAAoB,CAAC;AAmB/E;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAASC,QAAQA,CAAC;EACvBC,GAAG;EACHC,IAAI,GAAG,CAAC;EACRC,YAAY,GAAG,CAAC;EAChBC;AAWF,CAAC,EAAU;EACT,MAAM,CAACC,KAAK,EAAEC,QAAQ,CAAC,GAAG,IAAAC,eAAQ,EAAS;IACzCF,KAAK,EAAEG,SAAS;IAChBC,KAAK,EAAED;EACT,CAAC,CAAC;EACF,MAAME,YAAY,GAAG,IAAAC,aAAM,EAACV,GAAG,CAAC;EAEhC,IAAAW,gBAAS,EAAC,MAAM;IACd,IAAIC,SAAS,GAAG,KAAK;IACrB;IACA;IACA;IACA,IAAIH,YAAY,CAACI,OAAO,KAAKb,GAAG,EAAE;MAChCS,YAAY,CAACI,OAAO,GAAGb,GAAG;MAC1BK,QAAQ,CAAC;QAAED,KAAK,EAAEG,SAAS;QAAEC,KAAK,EAAED;MAAU,CAAC,CAAC;IAClD;IAEA,CAAC,YAAY;MACX,IAAI;QACF,MAAMO,MAAM,GAAG,MAAMnB,kBAAkB,CAACoB,SAAS,CAACf,GAAG,EAAE;UACrDC,IAAI;UACJC,YAAY;UACZC;QACF,CAAC,CAAC;QAEF,IAAI,CAACS,SAAS,EAAE;UACdP,QAAQ,CAAC;YAAED,KAAK,EAAEU,MAAM;YAAEN,KAAK,EAAED;UAAU,CAAC,CAAC;QAC/C;MACF,CAAC,CAAC,OAAOS,CAAC,EAAE;QACV,MAAMR,KAAK,GAAGQ,CAAC,YAAYC,KAAK,GAAGD,CAAC,GAAG,IAAIC,KAAK,CAAC,GAAGD,CAAC,EAAE,CAAC;QACxD,IAAI,CAACJ,SAAS,EAAE;UACdP,QAAQ,CAAC;YAAED,KAAK,EAAEG,SAAS;YAAEC,KAAK,EAAEA;UAAM,CAAC,CAAC;QAC9C;MACF;IACF,CAAC,EAAE,CAAC;IAEJ,OAAO,MAAM;MACXI,SAAS,GAAG,IAAI;IAClB,CAAC;EACH,CAAC,EAAE,CAACZ,GAAG,EAAEC,IAAI,EAAEC,YAAY,EAAEC,KAAK,CAAC,CAAC;EAEpC,OAAOC,KAAK;AACd","ignoreList":[]}
|
package/lib/module/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["useEffect","useRef","useState","NitroModules","NitroImagePipeline","createHybridObject","useImage","url","blur","cornerRadius","cache","image","setImage","undefined","error","loadedUrlRef","cancelled","current","result","loadImage","e","Error"],"sourceRoot":"../../src","sources":["index.ts"],"mappings":";;AAAA,SAASA,SAAS,EAAEC,MAAM,EAAEC,QAAQ,QAAQ,OAAO;AAEnD,SAASC,YAAY,QAAQ,4BAA4B;AAUzD,OAAO,MAAMC,kBAAkB,GAC7BD,YAAY,CAACE,kBAAkB,CAAyB,oBAAoB,CAAC;AAmB/E;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,SAASC,QAAQA,CAAC;EACvBC,GAAG;EACHC,IAAI,GAAG,CAAC;EACRC,YAAY,GAAG,CAAC;EAChBC;
|
|
1
|
+
{"version":3,"names":["useEffect","useRef","useState","NitroModules","NitroImagePipeline","createHybridObject","useImage","url","blur","cornerRadius","cache","image","setImage","undefined","error","loadedUrlRef","cancelled","current","result","loadImage","e","Error"],"sourceRoot":"../../src","sources":["index.ts"],"mappings":";;AAAA,SAASA,SAAS,EAAEC,MAAM,EAAEC,QAAQ,QAAQ,OAAO;AAEnD,SAASC,YAAY,QAAQ,4BAA4B;AAUzD,OAAO,MAAMC,kBAAkB,GAC7BD,YAAY,CAACE,kBAAkB,CAAyB,oBAAoB,CAAC;AAmB/E;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,SAASC,QAAQA,CAAC;EACvBC,GAAG;EACHC,IAAI,GAAG,CAAC;EACRC,YAAY,GAAG,CAAC;EAChBC;AAWF,CAAC,EAAU;EACT,MAAM,CAACC,KAAK,EAAEC,QAAQ,CAAC,GAAGV,QAAQ,CAAS;IACzCS,KAAK,EAAEE,SAAS;IAChBC,KAAK,EAAED;EACT,CAAC,CAAC;EACF,MAAME,YAAY,GAAGd,MAAM,CAACM,GAAG,CAAC;EAEhCP,SAAS,CAAC,MAAM;IACd,IAAIgB,SAAS,GAAG,KAAK;IACrB;IACA;IACA;IACA,IAAID,YAAY,CAACE,OAAO,KAAKV,GAAG,EAAE;MAChCQ,YAAY,CAACE,OAAO,GAAGV,GAAG;MAC1BK,QAAQ,CAAC;QAAED,KAAK,EAAEE,SAAS;QAAEC,KAAK,EAAED;MAAU,CAAC,CAAC;IAClD;IAEA,CAAC,YAAY;MACX,IAAI;QACF,MAAMK,MAAM,GAAG,MAAMd,kBAAkB,CAACe,SAAS,CAACZ,GAAG,EAAE;UACrDC,IAAI;UACJC,YAAY;UACZC;QACF,CAAC,CAAC;QAEF,IAAI,CAACM,SAAS,EAAE;UACdJ,QAAQ,CAAC;YAAED,KAAK,EAAEO,MAAM;YAAEJ,KAAK,EAAED;UAAU,CAAC,CAAC;QAC/C;MACF,CAAC,CAAC,OAAOO,CAAC,EAAE;QACV,MAAMN,KAAK,GAAGM,CAAC,YAAYC,KAAK,GAAGD,CAAC,GAAG,IAAIC,KAAK,CAAC,GAAGD,CAAC,EAAE,CAAC;QACxD,IAAI,CAACJ,SAAS,EAAE;UACdJ,QAAQ,CAAC;YAAED,KAAK,EAAEE,SAAS;YAAEC,KAAK,EAAEA;UAAM,CAAC,CAAC;QAC9C;MACF;IACF,CAAC,EAAE,CAAC;IAEJ,OAAO,MAAM;MACXE,SAAS,GAAG,IAAI;IAClB,CAAC;EACH,CAAC,EAAE,CAACT,GAAG,EAAEC,IAAI,EAAEC,YAAY,EAAEC,KAAK,CAAC,CAAC;EAEpC,OAAOC,KAAK;AACd","ignoreList":[]}
|
|
@@ -22,6 +22,11 @@ type Result = {
|
|
|
22
22
|
*/
|
|
23
23
|
export declare function useImage({ url, blur, cornerRadius, cache, }: {
|
|
24
24
|
url: string;
|
|
25
|
+
/**
|
|
26
|
+
* Gaussian blur strength, as the standard deviation (sigma) of the blur in
|
|
27
|
+
* source-image pixels. Matches across iOS and Android; roughly half of
|
|
28
|
+
* React Native's `blurRadius`.
|
|
29
|
+
*/
|
|
25
30
|
blur?: number;
|
|
26
31
|
cornerRadius?: number;
|
|
27
32
|
cache?: CacheOption;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,0BAA0B,CAAC;AAGtD,OAAO,KAAK,EACV,WAAW,EACX,kBAAkB,IAAI,sBAAsB,EAC5C,OAAO,EACR,MAAM,mCAAmC,CAAC;AAE3C,YAAY,EAAE,WAAW,EAAE,OAAO,EAAE,CAAC;AAErC,eAAO,MAAM,kBAAkB,wBACgD,CAAC;AAEhF,KAAK,MAAM,GAEP;IACE,KAAK,EAAE,SAAS,CAAC;IACjB,KAAK,EAAE,SAAS,CAAC;CAClB,GAED;IACE,KAAK,EAAE,KAAK,CAAC;IACb,KAAK,EAAE,SAAS,CAAC;CAClB,GAED;IACE,KAAK,EAAE,SAAS,CAAC;IACjB,KAAK,EAAE,KAAK,CAAC;CACd,CAAC;AAEN;;;;;;;GAOG;AACH,wBAAgB,QAAQ,CAAC,EACvB,GAAG,EACH,IAAQ,EACR,YAAgB,EAChB,KAAK,GACN,EAAE;IACD,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE,WAAW,CAAC;CACrB,GAAG,MAAM,CA0CT"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,0BAA0B,CAAC;AAGtD,OAAO,KAAK,EACV,WAAW,EACX,kBAAkB,IAAI,sBAAsB,EAC5C,OAAO,EACR,MAAM,mCAAmC,CAAC;AAE3C,YAAY,EAAE,WAAW,EAAE,OAAO,EAAE,CAAC;AAErC,eAAO,MAAM,kBAAkB,wBACgD,CAAC;AAEhF,KAAK,MAAM,GAEP;IACE,KAAK,EAAE,SAAS,CAAC;IACjB,KAAK,EAAE,SAAS,CAAC;CAClB,GAED;IACE,KAAK,EAAE,KAAK,CAAC;IACb,KAAK,EAAE,SAAS,CAAC;CAClB,GAED;IACE,KAAK,EAAE,SAAS,CAAC;IACjB,KAAK,EAAE,KAAK,CAAC;CACd,CAAC;AAEN;;;;;;;GAOG;AACH,wBAAgB,QAAQ,CAAC,EACvB,GAAG,EACH,IAAQ,EACR,YAAgB,EAChB,KAAK,GACN,EAAE;IACD,GAAG,EAAE,MAAM,CAAC;IACZ;;;;OAIG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE,WAAW,CAAC;CACrB,GAAG,MAAM,CA0CT"}
|
|
@@ -2,6 +2,15 @@ import type { Image } from 'react-native-nitro-image';
|
|
|
2
2
|
import type { HybridObject } from 'react-native-nitro-modules';
|
|
3
3
|
export type CacheOption = 'memory' | 'disk' | 'none';
|
|
4
4
|
export type Options = {
|
|
5
|
+
/**
|
|
6
|
+
* Gaussian blur strength, given as the standard deviation (sigma) of the
|
|
7
|
+
* blur in **source-image pixels**. The same value on the same source image
|
|
8
|
+
* produces the same result on iOS and Android.
|
|
9
|
+
*
|
|
10
|
+
* React Native's `<Image blurRadius={n} />` is roughly `blur: n / 2`.
|
|
11
|
+
*
|
|
12
|
+
* @default 0 (no blur)
|
|
13
|
+
*/
|
|
5
14
|
blur?: number;
|
|
6
15
|
cache?: CacheOption;
|
|
7
16
|
cornerRadius?: number;
|
|
@@ -13,6 +22,11 @@ export interface NitroImagePipeline extends HybridObject<{
|
|
|
13
22
|
loadImage(url: string, options?: Options): Promise<Image>;
|
|
14
23
|
preLoadImage(url: string): Promise<void>;
|
|
15
24
|
preLoadImages(urls: string[]): Promise<void>;
|
|
25
|
+
/**
|
|
26
|
+
* Blurs an already-loaded image. `radius` is the standard deviation (sigma)
|
|
27
|
+
* of the Gaussian in **source-image pixels**, the same unit as
|
|
28
|
+
* {@linkcode Options.blur}.
|
|
29
|
+
*/
|
|
16
30
|
gaussianBlur(image: Image, radius: number): Promise<Image>;
|
|
17
31
|
clearCache(): Promise<void>;
|
|
18
32
|
}
|
|
@@ -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;AACrD,MAAM,MAAM,OAAO,GAAG;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF,MAAM,WAAW,
|
|
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;AACrD,MAAM,MAAM,OAAO,GAAG;IACpB;;;;;;;;OAQG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-nitro-image-pipeline",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.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",
|
|
@@ -9,12 +9,15 @@
|
|
|
9
9
|
"source": "src/index",
|
|
10
10
|
"scripts": {
|
|
11
11
|
"typecheck": "tsc --noEmit",
|
|
12
|
-
"lint": "
|
|
13
|
-
"
|
|
12
|
+
"lint": "oxlint && oxfmt --check",
|
|
13
|
+
"lint:fix": "oxlint --fix && oxfmt",
|
|
14
|
+
"format": "oxfmt",
|
|
14
15
|
"clean": "git clean -dfX",
|
|
16
|
+
"prepare": "lefthook install || true",
|
|
15
17
|
"release": "semantic-release",
|
|
16
18
|
"build": "bun run typecheck && bob build",
|
|
17
|
-
"codegen": "nitrogen --logLevel=\"debug\" && bun run build"
|
|
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\""
|
|
18
21
|
},
|
|
19
22
|
"keywords": [
|
|
20
23
|
"react-native",
|
|
@@ -59,26 +62,28 @@
|
|
|
59
62
|
"registry": "https://registry.npmjs.org/"
|
|
60
63
|
},
|
|
61
64
|
"devDependencies": {
|
|
62
|
-
"@biomejs/biome": "^2.5.6",
|
|
63
65
|
"@semantic-release/changelog": "^7.0.0",
|
|
64
66
|
"@semantic-release/git": "^11.0.1",
|
|
65
|
-
"@types/bun": "^1.
|
|
67
|
+
"@types/bun": "^1.4.0",
|
|
66
68
|
"@types/jest": "^30.0.0",
|
|
67
69
|
"@types/react": "19.2.18",
|
|
68
|
-
"conventional-changelog-conventionalcommits": "^
|
|
69
|
-
"
|
|
70
|
+
"conventional-changelog-conventionalcommits": "^9.1.0",
|
|
71
|
+
"lefthook": "^2.1.10",
|
|
72
|
+
"nitrogen": "0.37.0",
|
|
73
|
+
"oxfmt": "^0.65.0",
|
|
74
|
+
"oxlint": "^1.80.0",
|
|
70
75
|
"react": "19.2.3",
|
|
71
76
|
"react-native": "0.84.1",
|
|
72
77
|
"react-native-builder-bob": "^0.43.0",
|
|
73
|
-
"react-native-nitro-modules": "0.
|
|
74
|
-
"semantic-release": "^25.0.
|
|
75
|
-
"typescript": "^
|
|
78
|
+
"react-native-nitro-modules": "0.37.0",
|
|
79
|
+
"semantic-release": "^25.0.9",
|
|
80
|
+
"typescript": "^7.0.2"
|
|
76
81
|
},
|
|
77
82
|
"peerDependencies": {
|
|
78
83
|
"react": "*",
|
|
79
84
|
"react-native": "*",
|
|
80
85
|
"react-native-nitro-modules": "*",
|
|
81
|
-
"react-native-nitro-image": "
|
|
86
|
+
"react-native-nitro-image": "0.15.1"
|
|
82
87
|
},
|
|
83
88
|
"overrides": {
|
|
84
89
|
"lodash-es": "4.17.21"
|
|
@@ -96,5 +101,8 @@
|
|
|
96
101
|
}
|
|
97
102
|
]
|
|
98
103
|
]
|
|
99
|
-
}
|
|
104
|
+
},
|
|
105
|
+
"trustedDependencies": [
|
|
106
|
+
"lefthook"
|
|
107
|
+
]
|
|
100
108
|
}
|
package/src/index.ts
CHANGED
|
@@ -45,6 +45,11 @@ export function useImage({
|
|
|
45
45
|
cache,
|
|
46
46
|
}: {
|
|
47
47
|
url: string;
|
|
48
|
+
/**
|
|
49
|
+
* Gaussian blur strength, as the standard deviation (sigma) of the blur in
|
|
50
|
+
* source-image pixels. Matches across iOS and Android; roughly half of
|
|
51
|
+
* React Native's `blurRadius`.
|
|
52
|
+
*/
|
|
48
53
|
blur?: number;
|
|
49
54
|
cornerRadius?: number;
|
|
50
55
|
cache?: CacheOption;
|
|
@@ -3,16 +3,32 @@ import type { HybridObject } from 'react-native-nitro-modules';
|
|
|
3
3
|
|
|
4
4
|
export type CacheOption = 'memory' | 'disk' | 'none';
|
|
5
5
|
export type Options = {
|
|
6
|
+
/**
|
|
7
|
+
* Gaussian blur strength, given as the standard deviation (sigma) of the
|
|
8
|
+
* blur in **source-image pixels**. The same value on the same source image
|
|
9
|
+
* produces the same result on iOS and Android.
|
|
10
|
+
*
|
|
11
|
+
* React Native's `<Image blurRadius={n} />` is roughly `blur: n / 2`.
|
|
12
|
+
*
|
|
13
|
+
* @default 0 (no blur)
|
|
14
|
+
*/
|
|
6
15
|
blur?: number;
|
|
7
16
|
cache?: CacheOption;
|
|
8
17
|
cornerRadius?: number;
|
|
9
18
|
};
|
|
10
19
|
|
|
11
|
-
export interface NitroImagePipeline
|
|
12
|
-
|
|
20
|
+
export interface NitroImagePipeline extends HybridObject<{
|
|
21
|
+
ios: 'swift';
|
|
22
|
+
android: 'kotlin';
|
|
23
|
+
}> {
|
|
13
24
|
loadImage(url: string, options?: Options): Promise<Image>;
|
|
14
25
|
preLoadImage(url: string): Promise<void>;
|
|
15
26
|
preLoadImages(urls: string[]): Promise<void>;
|
|
27
|
+
/**
|
|
28
|
+
* Blurs an already-loaded image. `radius` is the standard deviation (sigma)
|
|
29
|
+
* of the Gaussian in **source-image pixels**, the same unit as
|
|
30
|
+
* {@linkcode Options.blur}.
|
|
31
|
+
*/
|
|
16
32
|
gaussianBlur(image: Image, radius: number): Promise<Image>;
|
|
17
33
|
// Future: brightness, saturation, tint, etc.
|
|
18
34
|
|