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 +107 -27
- package/android/src/main/java/com/margelo/nitro/rnremoveimagebg/HybridImageBackgroundRemover.kt +76 -30
- package/lib/ImageProcessing.web.d.ts +17 -62
- package/lib/ImageProcessing.web.js +74 -232
- package/package.json +13 -14
- package/src/ImageProcessing.web.ts +90 -296
- package/src/__tests__/ImageProcessing.test.ts +132 -114
package/README.md
CHANGED
|
@@ -15,28 +15,8 @@
|
|
|
15
15
|
|
|
16
16
|
## Installation
|
|
17
17
|
|
|
18
|
-
Install from GitHub:
|
|
19
|
-
|
|
20
18
|
```bash
|
|
21
|
-
|
|
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
|
-
|
|
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
|
|
364
|
-
3.
|
|
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
|
|
package/android/src/main/java/com/margelo/nitro/rnremoveimagebg/HybridImageBackgroundRemover.kt
CHANGED
|
@@ -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
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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(
|
|
93
|
+
continuation.resumeWithException(Exception("Application Context is null"))
|
|
94
|
+
return@addOnSuccessListener
|
|
85
95
|
}
|
|
86
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 @
|
|
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
|
-
*
|
|
13
|
+
* Remove background from image
|
|
9
14
|
*/
|
|
10
|
-
export
|
|
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
|
|
22
|
-
|
|
23
|
-
|
|
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
|
-
|
|
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;
|