react-native-nitro-image-pipeline 0.1.1 → 0.2.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.
@@ -8,6 +8,7 @@ import coil3.ImageLoader
8
8
  import coil3.disk.DiskCache
9
9
  import coil3.memory.MemoryCache
10
10
  import coil3.network.okhttp.OkHttpNetworkFetcherFactory
11
+ import coil3.request.CachePolicy
11
12
  import coil3.request.ErrorResult
12
13
  import coil3.request.ImageRequest
13
14
  import coil3.request.SuccessResult
@@ -53,55 +54,68 @@ class HybridNitroImagePipeline : HybridNitroImagePipelineSpec() {
53
54
  .build()
54
55
  }
55
56
 
56
- override fun loadImage(url: String, options: Options?): Promise<HybridImageSpec> =
57
- Promise.async {
58
- val blur = options?.blur?.toFloat() ?: 0f
59
- val cornerRadius = options?.cornerRadius?.toFloat() ?: 0f
60
- val transformations = buildList {
61
- if (blur > 0f) add(BlurTransformation(context, blur))
62
- if (cornerRadius > 0f) add(RoundedCornersTransformation(cornerRadius))
63
- }
64
- val memoryCacheKey = buildString {
65
- append(url)
66
- if (blur > 0f) append("_blur$blur")
67
- if (cornerRadius > 0f) append("_corner$cornerRadius")
68
- }
69
- val request =
70
- ImageRequest.Builder(context)
71
- .data(url)
72
- .allowHardware(true)
73
- .memoryCacheKey(memoryCacheKey)
74
- .transformations(transformations)
75
- .build()
76
-
77
- val result = imageLoader.execute(request)
78
- when (result) {
79
- is ErrorResult -> throw result.throwable
80
- is SuccessResult -> {
81
- val bitmap =
82
- when (val img = result.image) {
83
- is BitmapImage -> img.bitmap
84
- is DrawableImage -> img.drawable.toBitmap()
85
- else -> throw Error("Unsupported image type: ${img.javaClass.simpleName}")
57
+ override fun loadImage(url: String, options: Options?): Promise<HybridImageSpec> = Promise.async {
58
+ val blur = options?.blur?.toFloat() ?: 0f
59
+ val cornerRadius = options?.cornerRadius?.toFloat() ?: 0f
60
+ val transformations = buildList {
61
+ if (blur > 0f) add(BlurTransformation(context, blur))
62
+ if (cornerRadius > 0f) add(RoundedCornersTransformation(cornerRadius))
63
+ }
64
+ val memoryCacheKey = buildString {
65
+ append(url)
66
+ if (blur > 0f) append("_blur$blur")
67
+ if (cornerRadius > 0f) append("_corner$cornerRadius")
68
+ }
69
+ val request =
70
+ ImageRequest.Builder(context)
71
+ .data(url)
72
+ .apply {
73
+ when (options?.cache) {
74
+ CacheOption.MEMORY -> {
75
+ memoryCachePolicy(CachePolicy.ENABLED)
76
+ diskCachePolicy(CachePolicy.DISABLED)
86
77
  }
87
- HybridImage(bitmap)
88
- }
89
- }
90
- }
78
+ CacheOption.DISK -> {
79
+ memoryCachePolicy(CachePolicy.DISABLED)
80
+ diskCachePolicy(CachePolicy.ENABLED)
81
+ }
82
+ CacheOption.NONE -> {
83
+ memoryCachePolicy(CachePolicy.DISABLED)
84
+ diskCachePolicy(CachePolicy.DISABLED)
85
+ }
86
+ null -> Unit // Coil defaults: both enabled
87
+ }
88
+ }
89
+ .allowHardware(true)
90
+ .memoryCacheKey(memoryCacheKey)
91
+ .transformations(transformations)
92
+ .build()
91
93
 
92
- override fun preLoadImage(url: String): Promise<Unit> =
93
- Promise.async {
94
- val request = ImageRequest.Builder(context).data(url).build()
95
- imageLoader.execute(request)
94
+ when (val result = imageLoader.execute(request)) {
95
+ is ErrorResult -> throw result.throwable
96
+ is SuccessResult -> {
97
+ val bitmap =
98
+ when (val img = result.image) {
99
+ is BitmapImage -> img.bitmap
100
+ is DrawableImage -> img.drawable.toBitmap()
101
+ else -> throw Error("Unsupported image type: ${img.javaClass.simpleName}")
102
+ }
103
+ HybridImage(bitmap)
96
104
  }
105
+ }
106
+ }
97
107
 
98
- override fun preLoadImages(urls: Array<String>): Promise<Unit> =
99
- Promise.async {
100
- for (url in urls) {
101
- val request = ImageRequest.Builder(context).data(url).build()
102
- imageLoader.execute(request)
103
- }
104
- }
108
+ override fun preLoadImage(url: String): Promise<Unit> = Promise.async {
109
+ val request = ImageRequest.Builder(context).data(url).build()
110
+ imageLoader.execute(request)
111
+ }
112
+
113
+ override fun preLoadImages(urls: Array<String>): Promise<Unit> = Promise.async {
114
+ for (url in urls) {
115
+ val request = ImageRequest.Builder(context).data(url).build()
116
+ imageLoader.execute(request)
117
+ }
118
+ }
105
119
 
106
120
  override fun gaussianBlur(image: HybridImageSpec, radius: Double): Promise<HybridImageSpec> =
107
121
  Promise.async {
@@ -8,12 +8,13 @@ import com.facebook.react.module.model.ReactModuleInfoProvider
8
8
  public class NitroImagePipelinePackage : BaseReactPackage() {
9
9
  override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? = null
10
10
 
11
- override fun getReactModuleInfoProvider(): ReactModuleInfoProvider =
12
- ReactModuleInfoProvider { emptyMap() }
11
+ override fun getReactModuleInfoProvider(): ReactModuleInfoProvider = ReactModuleInfoProvider {
12
+ emptyMap()
13
+ }
13
14
 
14
15
  companion object {
15
16
  init {
16
- NitroImagePipelineOnLoad.initializeNative();
17
+ NitroImagePipelineOnLoad.initializeNative()
17
18
  }
18
19
  }
19
- }
20
+ }
@@ -20,65 +20,71 @@ class BlurTransformation(
20
20
  private val sampling: Float = 1f,
21
21
  ) : Transformation() {
22
22
 
23
- init {
24
- require(radius in 0.01..25.0) { "radius must be in (0, 25]." }
25
- require(sampling >= 1f) { "sampling must be >= 1." }
26
- }
27
-
28
- override val cacheKey = "${BlurTransformation::class.java.name}-$radius-$sampling"
29
-
30
- override suspend fun transform(input: Bitmap, size: Size): Bitmap {
31
- val paint = Paint(Paint.ANTI_ALIAS_FLAG or Paint.FILTER_BITMAP_FLAG)
32
- // Auto-scale so the blurred image is at most 512px on the longest side.
33
- // Blur is a low-pass filter so blurring at lower resolution is identical in quality.
34
- val autoSampling = (maxOf(input.width, input.height) / 512f).coerceAtLeast(sampling)
35
- val scaledWidth = (input.width / autoSampling).toInt().coerceAtLeast(1)
36
- val scaledHeight = (input.height / autoSampling).toInt().coerceAtLeast(1)
37
- val config = input.config ?: Bitmap.Config.ARGB_8888
23
+ init {
24
+ require(radius in 0.01..25.0) { "radius must be in (0, 25]." }
25
+ require(sampling >= 1f) { "sampling must be >= 1." }
26
+ }
38
27
 
39
- val output = Bitmap.createBitmap(scaledWidth, scaledHeight, config)
40
- output.applyCanvas {
41
- scale(1 / autoSampling, 1 / autoSampling)
42
- drawBitmap(input, 0f, 0f, paint)
43
- }
28
+ override val cacheKey = "${BlurTransformation::class.java.name}-$radius-$sampling"
44
29
 
45
- var script: RenderScript? = null
46
- var tmpIn: Allocation? = null
47
- var tmpOut: Allocation? = null
48
- var blur: ScriptIntrinsicBlur? = null
49
- try {
50
- script = RenderScript.create(context)
51
- tmpIn = Allocation.createFromBitmap(script, output, Allocation.MipmapControl.MIPMAP_NONE, Allocation.USAGE_SCRIPT)
52
- tmpOut = Allocation.createTyped(script, tmpIn.type)
53
- blur = ScriptIntrinsicBlur.create(script, Element.U8_4(script))
54
- blur.setRadius(radius)
55
- blur.setInput(tmpIn)
56
- blur.forEach(tmpOut)
57
- tmpOut.copyTo(output)
58
- } finally {
59
- script?.destroy()
60
- tmpIn?.destroy()
61
- tmpOut?.destroy()
62
- blur?.destroy()
63
- }
30
+ override suspend fun transform(input: Bitmap, size: Size): Bitmap {
31
+ val paint = Paint(Paint.ANTI_ALIAS_FLAG or Paint.FILTER_BITMAP_FLAG)
32
+ // Auto-scale so the blurred image is at most 512px on the longest side.
33
+ // Blur is a low-pass filter so blurring at lower resolution is identical in quality.
34
+ val autoSampling = (maxOf(input.width, input.height) / 512f).coerceAtLeast(sampling)
35
+ val scaledWidth = (input.width / autoSampling).toInt().coerceAtLeast(1)
36
+ val scaledHeight = (input.height / autoSampling).toInt().coerceAtLeast(1)
37
+ val config = input.config ?: Bitmap.Config.ARGB_8888
64
38
 
65
- return output.scale(input.width, input.height)
39
+ val output = Bitmap.createBitmap(scaledWidth, scaledHeight, config)
40
+ output.applyCanvas {
41
+ scale(1 / autoSampling, 1 / autoSampling)
42
+ drawBitmap(input, 0f, 0f, paint)
66
43
  }
67
44
 
68
- override fun equals(other: Any?): Boolean {
69
- if (this === other) return true
70
- return other is BlurTransformation &&
71
- context == other.context &&
72
- radius == other.radius &&
73
- sampling == other.sampling
45
+ var script: RenderScript? = null
46
+ var tmpIn: Allocation? = null
47
+ var tmpOut: Allocation? = null
48
+ var blur: ScriptIntrinsicBlur? = null
49
+ try {
50
+ script = RenderScript.create(context)
51
+ tmpIn =
52
+ Allocation.createFromBitmap(
53
+ script,
54
+ output,
55
+ Allocation.MipmapControl.MIPMAP_NONE,
56
+ Allocation.USAGE_SCRIPT,
57
+ )
58
+ tmpOut = Allocation.createTyped(script, tmpIn.type)
59
+ blur = ScriptIntrinsicBlur.create(script, Element.U8_4(script))
60
+ blur.setRadius(radius)
61
+ blur.setInput(tmpIn)
62
+ blur.forEach(tmpOut)
63
+ tmpOut.copyTo(output)
64
+ } finally {
65
+ script?.destroy()
66
+ tmpIn?.destroy()
67
+ tmpOut?.destroy()
68
+ blur?.destroy()
74
69
  }
75
70
 
76
- override fun hashCode(): Int {
77
- var result = context.hashCode()
78
- result = 31 * result + radius.hashCode()
79
- result = 31 * result + sampling.hashCode()
80
- return result
81
- }
71
+ return output.scale(input.width, input.height)
72
+ }
73
+
74
+ override fun equals(other: Any?): Boolean {
75
+ if (this === other) return true
76
+ return other is BlurTransformation &&
77
+ context == other.context &&
78
+ radius == other.radius &&
79
+ sampling == other.sampling
80
+ }
81
+
82
+ override fun hashCode(): Int {
83
+ var result = context.hashCode()
84
+ result = 31 * result + radius.hashCode()
85
+ result = 31 * result + sampling.hashCode()
86
+ return result
87
+ }
82
88
 
83
- override fun toString() = "BlurTransformation(radius=$radius, sampling=$sampling)"
89
+ override fun toString() = "BlurTransformation(radius=$radius, sampling=$sampling)"
84
90
  }
@@ -58,11 +58,21 @@ class HybridNitroImagePipeline: HybridNitroImagePipelineSpec {
58
58
 
59
59
  func loadImage(url: String, options: Options?) throws -> Promise<any HybridImageSpec> {
60
60
  return Promise.async {
61
- let imgRequest = ImageRequest(url: URL(string: url), processors: [
62
- .gaussianBlur(radius: Int(options?.blur ?? 0)),
63
- .roundedCorners(radius: options?.cornerRadius ?? 0)
61
+ let cacheOptions: ImageRequest.Options = switch options?.cache {
62
+ case .memory: [.disableDiskCache]
63
+ case .disk: [.disableMemoryCache]
64
+ case .none?: [.disableDiskCache, .disableMemoryCache]
65
+ default: []
66
+ }
67
+
68
+ let imgRequest = ImageRequest(
69
+ url: URL(string: url),
70
+ processors: [
71
+ .gaussianBlur(radius: Int(options?.blur ?? 0)),
72
+ .roundedCorners(radius: options?.cornerRadius ?? 0)
73
+ ], options: cacheOptions
74
+ )
64
75
 
65
- ])
66
76
  let image = try await ImagePipeline.shared.image(for: imgRequest)
67
77
  return HybridImage(uiImage: image)
68
78
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-nitro-image-pipeline",
3
- "version": "0.1.1",
3
+ "version": "0.2.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",
@@ -62,7 +62,7 @@
62
62
  "@biomejs/biome": "^2.4.10",
63
63
  "@semantic-release/changelog": "^6.0.3",
64
64
  "@semantic-release/git": "^10.0.1",
65
- "@types/jest": "^29.5.12",
65
+ "@types/jest": "^30.0.0",
66
66
  "@types/react": "19.2.0",
67
67
  "conventional-changelog-conventionalcommits": "^9.1.0",
68
68
  "nitrogen": "0.35.2",