rn-remove-image-bg 0.0.11 → 0.0.13

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 CHANGED
@@ -15,28 +15,8 @@
15
15
 
16
16
  ## Installation
17
17
 
18
- Install from GitHub:
19
-
20
18
  ```bash
21
- # Using npm
22
- npm install github:a-eid/rn-remove-image-bg react-native-nitro-modules
23
-
24
- # Using yarn
25
- yarn add github:a-eid/rn-remove-image-bg react-native-nitro-modules
26
-
27
- # Using pnpm
28
- pnpm add github:a-eid/rn-remove-image-bg react-native-nitro-modules
29
- ```
30
-
31
- Or add to your `package.json`:
32
-
33
- ```json
34
- {
35
- "dependencies": {
36
- "rn-remove-image-bg": "github:a-eid/rn-remove-image-bg",
37
- "react-native-nitro-modules": "0.31.10"
38
- }
39
- }
19
+ npm install rn-remove-image-bg react-native-nitro-modules
40
20
  ```
41
21
 
42
22
  ### Peer Dependencies
@@ -59,18 +39,86 @@ cd ios && pod install
59
39
 
60
40
  No additional setup required. The ML Kit model (~10MB) downloads automatically on first use.
61
41
 
42
+ > **Important:** Android requires Google Play Services. The first call may take 10-15 seconds while the model downloads.
43
+
62
44
  ---
63
45
 
64
46
  ## Quick Start
65
47
 
48
+ ### Basic Usage
49
+
66
50
  ```typescript
67
51
  import { removeBgImage } from 'rn-remove-image-bg'
68
52
 
69
- // Remove background from an image
70
53
  const resultUri = await removeBgImage('file:///path/to/photo.jpg')
71
- console.log(resultUri) // file:///path/to/cache/bg_removed_xxx.png
54
+ // Returns: file:///path/to/cache/bg_removed_xxx.png (native)
55
+ // Returns: data:image/png;base64,... (web)
56
+ ```
57
+
58
+ ### With React Query (Recommended)
59
+
60
+ ```typescript
61
+ import { useMutation } from '@tanstack/react-query'
62
+ import { removeBgImage } from 'rn-remove-image-bg'
63
+ import { Alert } from 'react-native'
64
+
65
+ function useRemoveBackground() {
66
+ return useMutation({
67
+ mutationFn: async (imageUri: string) => {
68
+ return await removeBgImage(imageUri, {
69
+ maxDimension: 1024, // Faster processing
70
+ format: 'PNG', // Best for transparency
71
+ useCache: true, // Cache results
72
+ })
73
+ },
74
+ onError: (error) => {
75
+ Alert.alert('Error', error.message)
76
+ },
77
+ })
78
+ }
79
+
80
+ // In your component:
81
+ function ImageEditor() {
82
+ const removeBackground = useRemoveBackground()
83
+
84
+ const handleRemoveBackground = () => {
85
+ removeBackground.mutate(selectedImageUri, {
86
+ onSuccess: (resultUri) => {
87
+ setProcessedImage(resultUri)
88
+ },
89
+ })
90
+ }
91
+
92
+ return (
93
+ <Button
94
+ onPress={handleRemoveBackground}
95
+ disabled={removeBackground.isPending}
96
+ title={removeBackground.isPending ? 'Processing...' : 'Remove Background'}
97
+ />
98
+ )
99
+ }
72
100
  ```
73
101
 
102
+ ### With Expo Image Picker
103
+
104
+ ```typescript
105
+ import * as ImagePicker from 'expo-image-picker'
106
+ import { removeBgImage } from 'rn-remove-image-bg'
107
+
108
+ async function pickAndProcessImage() {
109
+ // Pick image
110
+ const result = await ImagePicker.launchImageLibraryAsync({
111
+ mediaTypes: ImagePicker.MediaTypeOptions.Images,
112
+ quality: 1,
113
+ })
114
+
115
+ if (result.canceled) return null
116
+
117
+ // Remove background
118
+ const processedUri = await removeBgImage(result.assets[0].uri)
119
+ return processedUri
120
+ }
121
+
74
122
  ---
75
123
 
76
124
  ## API Reference
@@ -270,10 +318,12 @@ try {
270
318
  ### Android
271
319
 
272
320
  - **Technology**: ML Kit Subject Segmentation (beta)
273
- - **Model**: Downloads ~10MB on first use
321
+ - **Model**: Downloads ~10MB on first use (handled automatically)
274
322
  - **Output**: PNG or WEBP (lossy/lossless based on quality)
275
323
  - **Requires**: Google Play Services
276
324
 
325
+ > **First-time Use**: On Android, the ML Kit model downloads automatically on first use. The library waits for the download to complete (up to ~15 seconds with retries) before processing. Subsequent calls are instant.
326
+
277
327
  ### Web
278
328
 
279
329
  - **Technology**: @imgly/background-removal (WebAssembly + ONNX)
@@ -359,9 +409,39 @@ If you don't need web support, you can tree-shake the `@imgly/background-removal
359
409
 
360
410
  ### Android Model Not Loading?
361
411
 
362
- 1. Ensure device has Google Play Services
363
- 2. Check internet connection for first download
364
- 3. Clear app cache and retry
412
+ 1. Ensure device has Google Play Services installed
413
+ 2. Check internet connection (model downloads on first use)
414
+ 3. Wait 10-15 seconds - the library automatically retries during download
415
+ 4. Clear app cache and retry: `adb shell pm clear com.yourapp`
416
+
417
+ ### Web Not Working?
418
+
419
+ 1. Check browser console for errors
420
+ 2. Ensure your bundler supports WebAssembly
421
+ 3. The first call downloads a ~35MB WASM model
422
+ 4. CORS may block model download - check network tab
423
+
424
+ ### Native Module Not Found?
425
+
426
+ ```bash
427
+ # iOS
428
+ cd ios && pod install && cd ..
429
+ npx expo run:ios --device
430
+
431
+ # Android
432
+ npx expo run:android --device
433
+
434
+ # For Expo managed workflow
435
+ npx expo prebuild --clean
436
+ ```
437
+
438
+ ### TypeScript Errors?
439
+
440
+ Ensure peer dependencies match:
441
+ ```bash
442
+ npx expo install expo-file-system expo-image-manipulator
443
+ npm install react-native-nitro-modules@0.31.10
444
+ ```
365
445
 
366
446
  ---
367
447
 
@@ -3,6 +3,9 @@ package com.margelo.nitro.rnremoveimagebg
3
3
  import android.graphics.Bitmap
4
4
  import android.graphics.BitmapFactory
5
5
  import android.os.Build
6
+ import android.os.Handler
7
+ import android.os.Looper
8
+ import android.util.Log
6
9
  import com.google.mlkit.vision.common.InputImage
7
10
  import com.google.mlkit.vision.segmentation.subject.SubjectSegmentation
8
11
  import com.google.mlkit.vision.segmentation.subject.SubjectSegmenterOptions
@@ -14,6 +17,7 @@ import com.margelo.nitro.NitroModules
14
17
  import java.io.File
15
18
  import java.io.FileOutputStream
16
19
  import java.util.UUID
20
+ import kotlin.coroutines.Continuation
17
21
  import kotlin.coroutines.resume
18
22
  import kotlin.coroutines.resumeWithException
19
23
  import kotlin.coroutines.suspendCoroutine
@@ -25,6 +29,12 @@ class HybridImageBackgroundRemover : HybridImageBackgroundRemoverSpec() {
25
29
  override val memorySize: Long
26
30
  get() = 0L
27
31
 
32
+ companion object {
33
+ private const val TAG = "RNRemoveImageBg"
34
+ private const val MAX_RETRIES = 10
35
+ private const val BASE_DELAY_MS = 1500L
36
+ }
37
+
28
38
  private val segmenter by lazy {
29
39
  val options = SubjectSegmenterOptions.Builder()
30
40
  .enableForegroundBitmap()
@@ -55,44 +65,80 @@ class HybridImageBackgroundRemover : HybridImageBackgroundRemoverSpec() {
55
65
  val inputImage = InputImage.fromBitmap(bitmap, 0)
56
66
 
57
67
  return suspendCoroutine { continuation ->
58
- segmenter.process(inputImage)
59
- .addOnSuccessListener { result ->
60
- val foregroundBitmap = result.foregroundBitmap
61
- if (foregroundBitmap != null) {
62
- try {
63
- val context = NitroModules.applicationContext
64
- if (context == null) {
65
- bitmap.recycle()
66
- foregroundBitmap.recycle()
67
- continuation.resumeWithException(Exception("Application Context is null"))
68
- return@addOnSuccessListener
69
- }
70
-
71
- val outputPath = saveImage(
72
- foregroundBitmap,
73
- context.cacheDir,
74
- options.format,
75
- options.quality.toInt()
76
- )
77
-
78
- bitmap.recycle()
79
- foregroundBitmap.recycle()
80
- continuation.resume(outputPath)
81
- } catch (e: Exception) {
68
+ processWithRetry(inputImage, bitmap, options, continuation, retryCount = 0)
69
+ }
70
+ }
71
+
72
+ /**
73
+ * Process image with retry logic for ML Kit model download.
74
+ * ML Kit Subject Segmentation downloads ~10MB model on first use.
75
+ * This method waits and retries if the model is still downloading.
76
+ */
77
+ private fun processWithRetry(
78
+ inputImage: InputImage,
79
+ bitmap: Bitmap,
80
+ options: NativeRemoveBackgroundOptions,
81
+ continuation: Continuation<String>,
82
+ retryCount: Int
83
+ ) {
84
+ segmenter.process(inputImage)
85
+ .addOnSuccessListener { result ->
86
+ val foregroundBitmap = result.foregroundBitmap
87
+ if (foregroundBitmap != null) {
88
+ try {
89
+ val context = NitroModules.applicationContext
90
+ if (context == null) {
82
91
  bitmap.recycle()
83
92
  foregroundBitmap.recycle()
84
- continuation.resumeWithException(e)
93
+ continuation.resumeWithException(Exception("Application Context is null"))
94
+ return@addOnSuccessListener
85
95
  }
86
- } else {
96
+
97
+ val outputPath = saveImage(
98
+ foregroundBitmap,
99
+ context.cacheDir,
100
+ options.format,
101
+ options.quality.toInt()
102
+ )
103
+
104
+ bitmap.recycle()
105
+ foregroundBitmap.recycle()
106
+ continuation.resume(outputPath)
107
+ } catch (e: Exception) {
87
108
  bitmap.recycle()
88
- continuation.resumeWithException(Exception("Could not generate foreground bitmap"))
109
+ foregroundBitmap.recycle()
110
+ continuation.resumeWithException(e)
89
111
  }
112
+ } else {
113
+ bitmap.recycle()
114
+ continuation.resumeWithException(Exception("Could not generate foreground bitmap"))
90
115
  }
91
- .addOnFailureListener { e ->
116
+ }
117
+ .addOnFailureListener { e ->
118
+ // Check if error is due to model still downloading
119
+ val isModelDownloading = e.message?.contains("Waiting for", ignoreCase = true) == true ||
120
+ e.message?.contains("downloading", ignoreCase = true) == true ||
121
+ e.message?.contains("not yet available", ignoreCase = true) == true
122
+
123
+ if (isModelDownloading && retryCount < MAX_RETRIES) {
124
+ // Calculate delay with exponential backoff (1.5s, 3s, 4.5s, ...)
125
+ val delayMs = BASE_DELAY_MS * (retryCount + 1)
126
+ Log.d(TAG, "ML Kit model downloading, retry ${retryCount + 1}/$MAX_RETRIES in ${delayMs}ms")
127
+
128
+ Handler(Looper.getMainLooper()).postDelayed({
129
+ processWithRetry(inputImage, bitmap, options, continuation, retryCount + 1)
130
+ }, delayMs)
131
+ } else {
92
132
  bitmap.recycle()
93
- continuation.resumeWithException(e)
133
+ if (isModelDownloading) {
134
+ continuation.resumeWithException(
135
+ Exception("ML Kit model download timed out. Please check your internet connection and try again.")
136
+ )
137
+ } else {
138
+ continuation.resumeWithException(e)
139
+ }
94
140
  }
95
- }
141
+ }
96
142
  }
97
143
 
98
144
  /**
@@ -1,13 +1,19 @@
1
1
  /**
2
- * Web implementation using @imgly/background-removal
3
- *
4
- * Provides real background removal on web using WebAssembly and ML models.
5
- * Falls back to no-op if the library fails to load.
2
+ * Web implementation using @huggingface/transformers
3
+ * Uses BRIAAI RMBG-1.4 model for background removal.
6
4
  */
5
+ export type OutputFormat = 'PNG' | 'WEBP';
6
+ export interface RemoveBgImageOptions {
7
+ format?: OutputFormat;
8
+ quality?: number;
9
+ onProgress?: (progress: number) => void;
10
+ debug?: boolean;
11
+ }
7
12
  /**
8
- * Output format for processed images
13
+ * Remove background from image
9
14
  */
10
- export type OutputFormat = 'PNG' | 'WEBP';
15
+ export declare function removeBgImage(uri: string, options?: RemoveBgImageOptions): Promise<string>;
16
+ export declare const removeBackground: typeof removeBgImage;
11
17
  export interface CompressImageOptions {
12
18
  maxSizeKB?: number;
13
19
  width?: number;
@@ -18,63 +24,12 @@ export interface CompressImageOptions {
18
24
  export interface GenerateThumbhashOptions {
19
25
  size?: number;
20
26
  }
21
- export interface RemoveBgImageOptions {
22
- maxDimension?: number;
23
- format?: OutputFormat;
24
- quality?: number;
25
- onProgress?: (progress: number) => void;
26
- useCache?: boolean;
27
- debug?: boolean;
28
- }
29
- /**
30
- * Compress image on web using canvas
31
- * @returns Compressed image as data URL
32
- */
33
- export declare function compressImage(uri: string, options?: CompressImageOptions): Promise<string>;
34
- /**
35
- * Generate thumbhash on web using canvas
36
- * @returns Base64 thumbhash string
37
- */
38
- export declare function generateThumbhash(imageUri: string, options?: GenerateThumbhashOptions): Promise<string>;
39
- /**
40
- * Remove background from image on web using @imgly/background-removal
41
- * @returns Data URL of processed image with transparent background
42
- */
43
- export declare function removeBgImage(uri: string, options?: RemoveBgImageOptions): Promise<string>;
44
- /**
45
- * Backward compatibility alias
46
- * @deprecated Use removeBgImage instead
47
- */
48
- export declare const removeBackground: typeof removeBgImage;
49
- /**
50
- * Clear the web background removal cache
51
- * @param _deleteFiles - Ignored on web (no disk cache)
52
- */
53
- export declare function clearCache(_deleteFiles?: boolean): Promise<void>;
54
- /**
55
- * Get the current cache size
56
- */
27
+ export declare function compressImage(uri: string, _options?: CompressImageOptions): Promise<string>;
28
+ export declare function generateThumbhash(_uri: string, _options?: GenerateThumbhashOptions): Promise<string>;
29
+ export declare function clearCache(): Promise<void>;
57
30
  export declare function getCacheSize(): number;
58
- /**
59
- * Handle low memory conditions by clearing the cache
60
- * On web, this simply clears the in-memory cache
61
- *
62
- * @param _deleteFiles - Ignored on web (no disk cache)
63
- * @returns Number of entries that were cleared
64
- */
65
- export declare function onLowMemory(_deleteFiles?: boolean): Promise<number>;
66
- /**
67
- * Configure the background removal cache
68
- * On web, maxEntries limits cache size. Disk persistence options are no-ops.
69
- */
70
- export declare function configureCache(config: {
31
+ export declare function onLowMemory(): Promise<number>;
32
+ export declare function configureCache(_config: {
71
33
  maxEntries?: number;
72
- maxAgeMinutes?: number;
73
- persistToDisk?: boolean;
74
- cacheDirectory?: string;
75
34
  }): void;
76
- /**
77
- * Get the cache directory path
78
- * On web, returns empty string as there is no disk cache
79
- */
80
35
  export declare function getCacheDirectory(): string;