rn-remove-image-bg 0.0.12 → 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 +104 -26
- 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 -297
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
|
|
@@ -361,9 +409,39 @@ If you don't need web support, you can tree-shake the `@imgly/background-removal
|
|
|
361
409
|
|
|
362
410
|
### Android Model Not Loading?
|
|
363
411
|
|
|
364
|
-
1. Ensure device has Google Play Services
|
|
365
|
-
2. Check internet connection
|
|
366
|
-
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
|
+
```
|
|
367
445
|
|
|
368
446
|
---
|
|
369
447
|
|
|
@@ -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;
|
|
@@ -1,249 +1,91 @@
|
|
|
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
|
*/
|
|
7
|
-
//
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
*/
|
|
17
|
-
function setCacheEntry(key, value) {
|
|
18
|
-
// If key exists, delete it first (to update LRU order)
|
|
19
|
-
if (webCache.has(key)) {
|
|
20
|
-
webCache.delete(key);
|
|
21
|
-
}
|
|
22
|
-
// Evict oldest entries if at capacity
|
|
23
|
-
while (webCache.size >= webCacheConfig.maxEntries) {
|
|
24
|
-
const oldestKey = webCache.keys().next().value;
|
|
25
|
-
if (oldestKey) {
|
|
26
|
-
webCache.delete(oldestKey);
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
webCache.set(key, value);
|
|
30
|
-
}
|
|
31
|
-
/**
|
|
32
|
-
* Get entry from cache and update LRU order
|
|
33
|
-
*/
|
|
34
|
-
function getCacheEntry(key) {
|
|
35
|
-
const value = webCache.get(key);
|
|
36
|
-
if (value !== undefined) {
|
|
37
|
-
// Move to end (most recently used)
|
|
38
|
-
webCache.delete(key);
|
|
39
|
-
webCache.set(key, value);
|
|
40
|
-
}
|
|
41
|
-
return value;
|
|
42
|
-
}
|
|
43
|
-
/**
|
|
44
|
-
* Compress image on web using canvas
|
|
45
|
-
* @returns Compressed image as data URL
|
|
46
|
-
*/
|
|
47
|
-
export async function compressImage(uri, options = {}) {
|
|
48
|
-
const { maxSizeKB = 250, width = 1024, height = 1024, quality = 0.85, format = 'webp', } = options;
|
|
49
|
-
try {
|
|
50
|
-
// Load image
|
|
51
|
-
const img = await loadImage(uri);
|
|
52
|
-
// Calculate target dimensions maintaining aspect ratio
|
|
53
|
-
const scale = Math.min(width / img.width, height / img.height, 1);
|
|
54
|
-
const targetWidth = Math.round(img.width * scale);
|
|
55
|
-
const targetHeight = Math.round(img.height * scale);
|
|
56
|
-
// Create canvas and draw resized image
|
|
57
|
-
const canvas = document.createElement('canvas');
|
|
58
|
-
canvas.width = targetWidth;
|
|
59
|
-
canvas.height = targetHeight;
|
|
60
|
-
const ctx = canvas.getContext('2d');
|
|
61
|
-
if (!ctx)
|
|
62
|
-
throw new Error('Could not get canvas context');
|
|
63
|
-
ctx.drawImage(img, 0, 0, targetWidth, targetHeight);
|
|
64
|
-
// Convert to data URL with compression
|
|
65
|
-
const mimeType = format === 'png'
|
|
66
|
-
? 'image/png'
|
|
67
|
-
: format === 'jpeg'
|
|
68
|
-
? 'image/jpeg'
|
|
69
|
-
: 'image/webp';
|
|
70
|
-
let dataUrl = canvas.toDataURL(mimeType, quality);
|
|
71
|
-
// If still too large, reduce quality iteratively
|
|
72
|
-
let currentQuality = quality;
|
|
73
|
-
while (getDataUrlSizeKB(dataUrl) > maxSizeKB && currentQuality > 0.5) {
|
|
74
|
-
currentQuality -= 0.1;
|
|
75
|
-
dataUrl = canvas.toDataURL(mimeType, currentQuality);
|
|
76
|
-
}
|
|
77
|
-
return dataUrl;
|
|
78
|
-
}
|
|
79
|
-
catch (error) {
|
|
80
|
-
console.warn('[rn-remove-image-bg] compressImage failed on web, returning original:', error);
|
|
81
|
-
return uri;
|
|
5
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
6
|
+
let pipeline = null;
|
|
7
|
+
let loadPromise = null;
|
|
8
|
+
async function ensureLoaded(onProgress, debug) {
|
|
9
|
+
if (pipeline)
|
|
10
|
+
return pipeline;
|
|
11
|
+
if (loadPromise) {
|
|
12
|
+
await loadPromise;
|
|
13
|
+
return pipeline;
|
|
82
14
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
const canvas = document.createElement('canvas');
|
|
96
|
-
canvas.width = size;
|
|
97
|
-
canvas.height = size;
|
|
98
|
-
const ctx = canvas.getContext('2d');
|
|
99
|
-
if (!ctx)
|
|
100
|
-
throw new Error('Could not get canvas context');
|
|
101
|
-
ctx.drawImage(img, 0, 0, size, size);
|
|
102
|
-
// Get RGBA data
|
|
103
|
-
const imageData = ctx.getImageData(0, 0, size, size);
|
|
104
|
-
const hash = rgbaToThumbHash(size, size, imageData.data);
|
|
105
|
-
// Convert to base64
|
|
106
|
-
return btoa(String.fromCharCode(...hash));
|
|
107
|
-
}
|
|
108
|
-
catch (error) {
|
|
109
|
-
console.warn('[rn-remove-image-bg] generateThumbhash failed on web:', error);
|
|
110
|
-
return '';
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
/**
|
|
114
|
-
* Remove background from image on web using @imgly/background-removal
|
|
115
|
-
* @returns Data URL of processed image with transparent background
|
|
116
|
-
*/
|
|
117
|
-
export async function removeBgImage(uri, options = {}) {
|
|
118
|
-
const { format = 'PNG', quality = 100, onProgress, useCache = true, debug = false, } = options;
|
|
119
|
-
// Check cache
|
|
120
|
-
const cacheKey = `${uri}::${format}::${quality}`;
|
|
121
|
-
if (useCache) {
|
|
122
|
-
const cachedResult = getCacheEntry(cacheKey);
|
|
123
|
-
if (cachedResult) {
|
|
124
|
-
if (debug) {
|
|
125
|
-
console.log('[rn-remove-image-bg] Web cache hit');
|
|
126
|
-
}
|
|
127
|
-
onProgress?.(100);
|
|
128
|
-
return cachedResult;
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
if (debug) {
|
|
132
|
-
console.log('[rn-remove-image-bg] Starting web background removal:', uri);
|
|
133
|
-
}
|
|
134
|
-
onProgress?.(5);
|
|
135
|
-
try {
|
|
136
|
-
// Dynamically import the library to prevent Metro from parsing onnxruntime-web at build time
|
|
137
|
-
const { removeBackground: imglyRemoveBackground } = await import('@imgly/background-removal');
|
|
138
|
-
// Call @imgly/background-removal
|
|
139
|
-
const blob = await imglyRemoveBackground(uri, {
|
|
140
|
-
progress: (key, current, total) => {
|
|
141
|
-
if (onProgress && total > 0) {
|
|
142
|
-
// Map progress to 10-90 range
|
|
143
|
-
const progress = Math.round(10 + (current / total) * 80);
|
|
144
|
-
onProgress(Math.min(progress, 90));
|
|
15
|
+
loadPromise = (async () => {
|
|
16
|
+
if (debug)
|
|
17
|
+
console.log('[rmbg] Loading model...');
|
|
18
|
+
const { pipeline: createPipeline, env } = await import('@huggingface/transformers');
|
|
19
|
+
env.allowLocalModels = false;
|
|
20
|
+
env.useBrowserCache = true;
|
|
21
|
+
onProgress?.(10);
|
|
22
|
+
pipeline = await createPipeline('image-segmentation', 'briaai/RMBG-1.4', {
|
|
23
|
+
dtype: 'q8',
|
|
24
|
+
progress_callback: (info) => {
|
|
25
|
+
if (onProgress && 'progress' in info && typeof info.progress === 'number') {
|
|
26
|
+
onProgress(Math.min(10 + (info.progress / 100) * 50, 60));
|
|
145
27
|
}
|
|
146
|
-
if (debug) {
|
|
147
|
-
console.log(`[rn-remove-image-bg] ${key}: ${current}/${total}`);
|
|
148
|
-
}
|
|
149
|
-
},
|
|
150
|
-
output: {
|
|
151
|
-
format: format === 'WEBP' ? 'image/webp' : 'image/png',
|
|
152
|
-
quality: quality / 100,
|
|
153
28
|
},
|
|
154
29
|
});
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
setCacheEntry(cacheKey, dataUrl);
|
|
161
|
-
}
|
|
162
|
-
if (debug) {
|
|
163
|
-
console.log('[rn-remove-image-bg] Web background removal complete');
|
|
164
|
-
}
|
|
165
|
-
onProgress?.(100);
|
|
166
|
-
return dataUrl;
|
|
167
|
-
}
|
|
168
|
-
catch (error) {
|
|
169
|
-
console.error('[rn-remove-image-bg] Web background removal failed:', error);
|
|
170
|
-
// Throw error instead of silent failure
|
|
171
|
-
const message = error instanceof Error ? error.message : 'Web background removal failed';
|
|
172
|
-
throw new Error(`Background removal failed: ${message}`);
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
/**
|
|
176
|
-
* Backward compatibility alias
|
|
177
|
-
* @deprecated Use removeBgImage instead
|
|
178
|
-
*/
|
|
179
|
-
export const removeBackground = removeBgImage;
|
|
180
|
-
/**
|
|
181
|
-
* Clear the web background removal cache
|
|
182
|
-
* @param _deleteFiles - Ignored on web (no disk cache)
|
|
183
|
-
*/
|
|
184
|
-
export async function clearCache(_deleteFiles = false) {
|
|
185
|
-
webCache.clear();
|
|
30
|
+
if (debug)
|
|
31
|
+
console.log('[rmbg] Model ready');
|
|
32
|
+
})();
|
|
33
|
+
await loadPromise;
|
|
34
|
+
return pipeline;
|
|
186
35
|
}
|
|
187
36
|
/**
|
|
188
|
-
*
|
|
37
|
+
* Remove background from image
|
|
189
38
|
*/
|
|
190
|
-
export function
|
|
191
|
-
|
|
39
|
+
export async function removeBgImage(uri, options = {}) {
|
|
40
|
+
const { format = 'PNG', quality = 100, onProgress, debug = false } = options;
|
|
41
|
+
if (debug)
|
|
42
|
+
console.log('[rmbg] Processing:', uri);
|
|
43
|
+
onProgress?.(5);
|
|
44
|
+
const segmenter = await ensureLoaded(onProgress, debug);
|
|
45
|
+
onProgress?.(60);
|
|
46
|
+
const results = await segmenter(uri);
|
|
47
|
+
onProgress?.(90);
|
|
48
|
+
const result = Array.isArray(results) ? results[0] : results;
|
|
49
|
+
if (!result?.mask)
|
|
50
|
+
throw new Error('No mask returned');
|
|
51
|
+
// Convert to data URL
|
|
52
|
+
const dataUrl = typeof result.mask === 'string'
|
|
53
|
+
? result.mask
|
|
54
|
+
: toDataUrl(result.mask, format, quality);
|
|
55
|
+
if (debug)
|
|
56
|
+
console.log('[rmbg] Done');
|
|
57
|
+
onProgress?.(100);
|
|
58
|
+
return dataUrl;
|
|
192
59
|
}
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
webCache.clear();
|
|
203
|
-
console.log(`[rn-remove-image-bg] Cleared ${size} web cache entries due to memory pressure`);
|
|
204
|
-
return size;
|
|
60
|
+
function toDataUrl(mask, format, quality) {
|
|
61
|
+
const canvas = document.createElement('canvas');
|
|
62
|
+
canvas.width = mask.width;
|
|
63
|
+
canvas.height = mask.height;
|
|
64
|
+
const ctx = canvas.getContext('2d');
|
|
65
|
+
const imageData = ctx.createImageData(mask.width, mask.height);
|
|
66
|
+
imageData.data.set(mask.data);
|
|
67
|
+
ctx.putImageData(imageData, 0, 0);
|
|
68
|
+
return canvas.toDataURL(format === 'WEBP' ? 'image/webp' : 'image/png', quality / 100);
|
|
205
69
|
}
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
export function configureCache(config) {
|
|
211
|
-
if (config.maxEntries !== undefined && config.maxEntries > 0) {
|
|
212
|
-
webCacheConfig.maxEntries = config.maxEntries;
|
|
213
|
-
}
|
|
214
|
-
if (config.maxAgeMinutes !== undefined && config.maxAgeMinutes > 0) {
|
|
215
|
-
webCacheConfig.maxAgeMinutes = config.maxAgeMinutes;
|
|
216
|
-
}
|
|
217
|
-
// persistToDisk and cacheDirectory are no-ops on web
|
|
70
|
+
export const removeBackground = removeBgImage;
|
|
71
|
+
export async function compressImage(uri, _options) {
|
|
72
|
+
console.warn('[rmbg] compressImage not implemented on web, returning original');
|
|
73
|
+
return uri;
|
|
218
74
|
}
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
* On web, returns empty string as there is no disk cache
|
|
222
|
-
*/
|
|
223
|
-
export function getCacheDirectory() {
|
|
75
|
+
export async function generateThumbhash(_uri, _options) {
|
|
76
|
+
console.warn('[rmbg] generateThumbhash not implemented on web');
|
|
224
77
|
return '';
|
|
225
78
|
}
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
const img = new Image();
|
|
230
|
-
img.crossOrigin = 'anonymous';
|
|
231
|
-
img.onload = () => resolve(img);
|
|
232
|
-
img.onerror = reject;
|
|
233
|
-
img.src = src;
|
|
234
|
-
});
|
|
79
|
+
export async function clearCache() {
|
|
80
|
+
pipeline = null;
|
|
81
|
+
loadPromise = null;
|
|
235
82
|
}
|
|
236
|
-
function
|
|
237
|
-
return
|
|
238
|
-
const reader = new FileReader();
|
|
239
|
-
reader.onloadend = () => resolve(reader.result);
|
|
240
|
-
reader.onerror = reject;
|
|
241
|
-
reader.readAsDataURL(blob);
|
|
242
|
-
});
|
|
83
|
+
export function getCacheSize() {
|
|
84
|
+
return 0;
|
|
243
85
|
}
|
|
244
|
-
function
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
// Base64 encodes 3 bytes as 4 characters
|
|
248
|
-
return (base64.length * 3) / 4 / 1024;
|
|
86
|
+
export async function onLowMemory() {
|
|
87
|
+
await clearCache();
|
|
88
|
+
return 0;
|
|
249
89
|
}
|
|
90
|
+
export function configureCache(_config) { }
|
|
91
|
+
export function getCacheDirectory() { return ''; }
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rn-remove-image-bg",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.13",
|
|
4
4
|
"description": "rn-remove-image-bg",
|
|
5
5
|
"homepage": "https://github.com/a-eid/rn-remove-image-bg",
|
|
6
6
|
"main": "lib/index",
|
|
@@ -35,17 +35,6 @@
|
|
|
35
35
|
"*.podspec",
|
|
36
36
|
"README.md"
|
|
37
37
|
],
|
|
38
|
-
"scripts": {
|
|
39
|
-
"postinstall": "tsc || exit 0;",
|
|
40
|
-
"typecheck": "tsc --noEmit",
|
|
41
|
-
"clean": "rm -rf android/build node_modules/**/android/build lib",
|
|
42
|
-
"lint": "eslint \"**/*.{js,ts,tsx}\" --fix",
|
|
43
|
-
"lint-ci": "eslint \"**/*.{js,ts,tsx}\" -f @jamesacarr/github-actions",
|
|
44
|
-
"typescript": "tsc",
|
|
45
|
-
"specs": "tsc --noEmit false && nitrogen --logLevel=\"debug\"",
|
|
46
|
-
"prepare": "npm run typescript",
|
|
47
|
-
"test": "vitest run"
|
|
48
|
-
},
|
|
49
38
|
"author": "Ahmed Eid <a.eid@yandex.com> (https://github.com/a-eid)",
|
|
50
39
|
"license": "MIT",
|
|
51
40
|
"devDependencies": {
|
|
@@ -96,9 +85,19 @@
|
|
|
96
85
|
"lib/"
|
|
97
86
|
],
|
|
98
87
|
"dependencies": {
|
|
99
|
-
"@
|
|
88
|
+
"@huggingface/transformers": "^3.8.1",
|
|
100
89
|
"buffer": "^6.0.3",
|
|
101
90
|
"thumbhash": "^0.1.1",
|
|
102
91
|
"upng-js": "^2.1.0"
|
|
92
|
+
},
|
|
93
|
+
"scripts": {
|
|
94
|
+
"postinstall": "tsc || exit 0;",
|
|
95
|
+
"typecheck": "tsc --noEmit",
|
|
96
|
+
"clean": "rm -rf android/build node_modules/**/android/build lib",
|
|
97
|
+
"lint": "eslint \"**/*.{js,ts,tsx}\" --fix",
|
|
98
|
+
"lint-ci": "eslint \"**/*.{js,ts,tsx}\" -f @jamesacarr/github-actions",
|
|
99
|
+
"typescript": "tsc",
|
|
100
|
+
"specs": "tsc --noEmit false && nitrogen --logLevel=\"debug\"",
|
|
101
|
+
"test": "vitest run"
|
|
103
102
|
}
|
|
104
|
-
}
|
|
103
|
+
}
|
|
@@ -1,347 +1,140 @@
|
|
|
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
|
*/
|
|
7
5
|
|
|
8
|
-
// @imgly/background-removal is dynamically imported at runtime in removeBgImage()
|
|
9
|
-
// to prevent Metro from parsing onnxruntime-web at build time
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Output format for processed images
|
|
13
|
-
*/
|
|
14
6
|
export type OutputFormat = 'PNG' | 'WEBP';
|
|
15
7
|
|
|
16
|
-
export interface CompressImageOptions {
|
|
17
|
-
maxSizeKB?: number;
|
|
18
|
-
width?: number;
|
|
19
|
-
height?: number;
|
|
20
|
-
quality?: number;
|
|
21
|
-
format?: 'webp' | 'png' | 'jpeg';
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export interface GenerateThumbhashOptions {
|
|
25
|
-
size?: number;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
8
|
export interface RemoveBgImageOptions {
|
|
29
|
-
maxDimension?: number;
|
|
30
9
|
format?: OutputFormat;
|
|
31
10
|
quality?: number;
|
|
32
11
|
onProgress?: (progress: number) => void;
|
|
33
|
-
useCache?: boolean;
|
|
34
12
|
debug?: boolean;
|
|
35
13
|
}
|
|
36
|
-
// Web cache configuration
|
|
37
|
-
const webCacheConfig = {
|
|
38
|
-
maxEntries: 50,
|
|
39
|
-
maxAgeMinutes: 30,
|
|
40
|
-
};
|
|
41
14
|
|
|
42
|
-
//
|
|
43
|
-
|
|
15
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
16
|
+
let pipeline: any = null;
|
|
17
|
+
let loadPromise: Promise<void> | null = null;
|
|
44
18
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
webCache.delete(key);
|
|
19
|
+
async function ensureLoaded(onProgress?: (p: number) => void, debug?: boolean) {
|
|
20
|
+
if (pipeline) return pipeline;
|
|
21
|
+
|
|
22
|
+
if (loadPromise) {
|
|
23
|
+
await loadPromise;
|
|
24
|
+
return pipeline;
|
|
52
25
|
}
|
|
53
26
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
}
|
|
75
|
-
return value;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Compress image on web using canvas
|
|
80
|
-
* @returns Compressed image as data URL
|
|
81
|
-
*/
|
|
82
|
-
export async function compressImage(
|
|
83
|
-
uri: string,
|
|
84
|
-
options: CompressImageOptions = {}
|
|
85
|
-
): Promise<string> {
|
|
86
|
-
const {
|
|
87
|
-
maxSizeKB = 250,
|
|
88
|
-
width = 1024,
|
|
89
|
-
height = 1024,
|
|
90
|
-
quality = 0.85,
|
|
91
|
-
format = 'webp',
|
|
92
|
-
} = options;
|
|
93
|
-
|
|
94
|
-
try {
|
|
95
|
-
// Load image
|
|
96
|
-
const img = await loadImage(uri);
|
|
97
|
-
|
|
98
|
-
// Calculate target dimensions maintaining aspect ratio
|
|
99
|
-
const scale = Math.min(width / img.width, height / img.height, 1);
|
|
100
|
-
const targetWidth = Math.round(img.width * scale);
|
|
101
|
-
const targetHeight = Math.round(img.height * scale);
|
|
102
|
-
|
|
103
|
-
// Create canvas and draw resized image
|
|
104
|
-
const canvas = document.createElement('canvas');
|
|
105
|
-
canvas.width = targetWidth;
|
|
106
|
-
canvas.height = targetHeight;
|
|
107
|
-
const ctx = canvas.getContext('2d');
|
|
108
|
-
if (!ctx) throw new Error('Could not get canvas context');
|
|
109
|
-
ctx.drawImage(img, 0, 0, targetWidth, targetHeight);
|
|
110
|
-
|
|
111
|
-
// Convert to data URL with compression
|
|
112
|
-
const mimeType =
|
|
113
|
-
format === 'png'
|
|
114
|
-
? 'image/png'
|
|
115
|
-
: format === 'jpeg'
|
|
116
|
-
? 'image/jpeg'
|
|
117
|
-
: 'image/webp';
|
|
118
|
-
let dataUrl = canvas.toDataURL(mimeType, quality);
|
|
119
|
-
|
|
120
|
-
// If still too large, reduce quality iteratively
|
|
121
|
-
let currentQuality = quality;
|
|
122
|
-
while (getDataUrlSizeKB(dataUrl) > maxSizeKB && currentQuality > 0.5) {
|
|
123
|
-
currentQuality -= 0.1;
|
|
124
|
-
dataUrl = canvas.toDataURL(mimeType, currentQuality);
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
return dataUrl;
|
|
128
|
-
} catch (error) {
|
|
129
|
-
console.warn(
|
|
130
|
-
'[rn-remove-image-bg] compressImage failed on web, returning original:',
|
|
131
|
-
error
|
|
132
|
-
);
|
|
133
|
-
return uri;
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
/**
|
|
138
|
-
* Generate thumbhash on web using canvas
|
|
139
|
-
* @returns Base64 thumbhash string
|
|
140
|
-
*/
|
|
141
|
-
export async function generateThumbhash(
|
|
142
|
-
imageUri: string,
|
|
143
|
-
options: GenerateThumbhashOptions = {}
|
|
144
|
-
): Promise<string> {
|
|
145
|
-
const { size = 32 } = options;
|
|
146
|
-
|
|
147
|
-
try {
|
|
148
|
-
// Dynamically import thumbhash to avoid bundling issues
|
|
149
|
-
const { rgbaToThumbHash } = await import('thumbhash');
|
|
150
|
-
|
|
151
|
-
// Load and resize image
|
|
152
|
-
const img = await loadImage(imageUri);
|
|
153
|
-
const canvas = document.createElement('canvas');
|
|
154
|
-
canvas.width = size;
|
|
155
|
-
canvas.height = size;
|
|
156
|
-
const ctx = canvas.getContext('2d');
|
|
157
|
-
if (!ctx) throw new Error('Could not get canvas context');
|
|
158
|
-
ctx.drawImage(img, 0, 0, size, size);
|
|
159
|
-
|
|
160
|
-
// Get RGBA data
|
|
161
|
-
const imageData = ctx.getImageData(0, 0, size, size);
|
|
162
|
-
const hash = rgbaToThumbHash(size, size, imageData.data);
|
|
27
|
+
loadPromise = (async () => {
|
|
28
|
+
if (debug) console.log('[rmbg] Loading model...');
|
|
29
|
+
|
|
30
|
+
const { pipeline: createPipeline, env } = await import('@huggingface/transformers');
|
|
31
|
+
|
|
32
|
+
env.allowLocalModels = false;
|
|
33
|
+
env.useBrowserCache = true;
|
|
34
|
+
|
|
35
|
+
onProgress?.(10);
|
|
36
|
+
|
|
37
|
+
pipeline = await createPipeline('image-segmentation', 'briaai/RMBG-1.4', {
|
|
38
|
+
dtype: 'q8',
|
|
39
|
+
progress_callback: (info) => {
|
|
40
|
+
if (onProgress && 'progress' in info && typeof info.progress === 'number') {
|
|
41
|
+
onProgress(Math.min(10 + (info.progress / 100) * 50, 60));
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
if (debug) console.log('[rmbg] Model ready');
|
|
47
|
+
})();
|
|
163
48
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
} catch (error) {
|
|
167
|
-
console.warn(
|
|
168
|
-
'[rn-remove-image-bg] generateThumbhash failed on web:',
|
|
169
|
-
error
|
|
170
|
-
);
|
|
171
|
-
return '';
|
|
172
|
-
}
|
|
49
|
+
await loadPromise;
|
|
50
|
+
return pipeline;
|
|
173
51
|
}
|
|
174
52
|
|
|
175
53
|
/**
|
|
176
|
-
* Remove background from image
|
|
177
|
-
* @returns Data URL of processed image with transparent background
|
|
54
|
+
* Remove background from image
|
|
178
55
|
*/
|
|
179
56
|
export async function removeBgImage(
|
|
180
57
|
uri: string,
|
|
181
58
|
options: RemoveBgImageOptions = {}
|
|
182
59
|
): Promise<string> {
|
|
183
|
-
const {
|
|
184
|
-
format = 'PNG',
|
|
185
|
-
quality = 100,
|
|
186
|
-
onProgress,
|
|
187
|
-
useCache = true,
|
|
188
|
-
debug = false,
|
|
189
|
-
} = options;
|
|
190
|
-
|
|
191
|
-
// Check cache
|
|
192
|
-
const cacheKey = `${uri}::${format}::${quality}`;
|
|
193
|
-
if (useCache) {
|
|
194
|
-
const cachedResult = getCacheEntry(cacheKey);
|
|
195
|
-
if (cachedResult) {
|
|
196
|
-
if (debug) {
|
|
197
|
-
console.log('[rn-remove-image-bg] Web cache hit');
|
|
198
|
-
}
|
|
199
|
-
onProgress?.(100);
|
|
200
|
-
return cachedResult;
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
if (debug) {
|
|
205
|
-
console.log('[rn-remove-image-bg] Starting web background removal:', uri);
|
|
206
|
-
}
|
|
60
|
+
const { format = 'PNG', quality = 100, onProgress, debug = false } = options;
|
|
207
61
|
|
|
62
|
+
if (debug) console.log('[rmbg] Processing:', uri);
|
|
208
63
|
onProgress?.(5);
|
|
209
64
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
const { removeBackground: imglyRemoveBackground } =
|
|
213
|
-
await import('@imgly/background-removal');
|
|
214
|
-
|
|
215
|
-
// Call @imgly/background-removal
|
|
216
|
-
const blob = await imglyRemoveBackground(uri, {
|
|
217
|
-
progress: (key: string, current: number, total: number) => {
|
|
218
|
-
if (onProgress && total > 0) {
|
|
219
|
-
// Map progress to 10-90 range
|
|
220
|
-
const progress = Math.round(10 + (current / total) * 80);
|
|
221
|
-
onProgress(Math.min(progress, 90));
|
|
222
|
-
}
|
|
223
|
-
if (debug) {
|
|
224
|
-
console.log(`[rn-remove-image-bg] ${key}: ${current}/${total}`);
|
|
225
|
-
}
|
|
226
|
-
},
|
|
227
|
-
output: {
|
|
228
|
-
format: format === 'WEBP' ? 'image/webp' : 'image/png',
|
|
229
|
-
quality: quality / 100,
|
|
230
|
-
},
|
|
231
|
-
});
|
|
65
|
+
const segmenter = await ensureLoaded(onProgress, debug);
|
|
66
|
+
onProgress?.(60);
|
|
232
67
|
|
|
233
|
-
|
|
68
|
+
const results = await segmenter(uri);
|
|
69
|
+
onProgress?.(90);
|
|
234
70
|
|
|
235
|
-
|
|
236
|
-
|
|
71
|
+
const result = Array.isArray(results) ? results[0] : results;
|
|
72
|
+
if (!result?.mask) throw new Error('No mask returned');
|
|
237
73
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
74
|
+
// Convert to data URL
|
|
75
|
+
const dataUrl = typeof result.mask === 'string'
|
|
76
|
+
? result.mask
|
|
77
|
+
: toDataUrl(result.mask, format, quality);
|
|
242
78
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
79
|
+
if (debug) console.log('[rmbg] Done');
|
|
80
|
+
onProgress?.(100);
|
|
81
|
+
|
|
82
|
+
return dataUrl;
|
|
83
|
+
}
|
|
246
84
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
85
|
+
function toDataUrl(
|
|
86
|
+
mask: { width: number; height: number; data: Uint8Array | Uint8ClampedArray },
|
|
87
|
+
format: OutputFormat,
|
|
88
|
+
quality: number
|
|
89
|
+
): string {
|
|
90
|
+
const canvas = document.createElement('canvas');
|
|
91
|
+
canvas.width = mask.width;
|
|
92
|
+
canvas.height = mask.height;
|
|
93
|
+
const ctx = canvas.getContext('2d')!;
|
|
94
|
+
const imageData = ctx.createImageData(mask.width, mask.height);
|
|
95
|
+
imageData.data.set(mask.data);
|
|
96
|
+
ctx.putImageData(imageData, 0, 0);
|
|
97
|
+
return canvas.toDataURL(format === 'WEBP' ? 'image/webp' : 'image/png', quality / 100);
|
|
255
98
|
}
|
|
256
99
|
|
|
257
|
-
/**
|
|
258
|
-
* Backward compatibility alias
|
|
259
|
-
* @deprecated Use removeBgImage instead
|
|
260
|
-
*/
|
|
261
100
|
export const removeBackground = removeBgImage;
|
|
262
101
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
/**
|
|
272
|
-
* Get the current cache size
|
|
273
|
-
*/
|
|
274
|
-
export function getCacheSize(): number {
|
|
275
|
-
return webCache.size;
|
|
102
|
+
// Stub exports for API compatibility with native
|
|
103
|
+
export interface CompressImageOptions {
|
|
104
|
+
maxSizeKB?: number;
|
|
105
|
+
width?: number;
|
|
106
|
+
height?: number;
|
|
107
|
+
quality?: number;
|
|
108
|
+
format?: 'webp' | 'png' | 'jpeg';
|
|
276
109
|
}
|
|
277
110
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
* On web, this simply clears the in-memory cache
|
|
281
|
-
*
|
|
282
|
-
* @param _deleteFiles - Ignored on web (no disk cache)
|
|
283
|
-
* @returns Number of entries that were cleared
|
|
284
|
-
*/
|
|
285
|
-
export async function onLowMemory(_deleteFiles = true): Promise<number> {
|
|
286
|
-
const size = webCache.size;
|
|
287
|
-
webCache.clear();
|
|
288
|
-
console.log(
|
|
289
|
-
`[rn-remove-image-bg] Cleared ${size} web cache entries due to memory pressure`
|
|
290
|
-
);
|
|
291
|
-
return size;
|
|
111
|
+
export interface GenerateThumbhashOptions {
|
|
112
|
+
size?: number;
|
|
292
113
|
}
|
|
293
114
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
*/
|
|
298
|
-
export function configureCache(config: {
|
|
299
|
-
maxEntries?: number;
|
|
300
|
-
maxAgeMinutes?: number;
|
|
301
|
-
persistToDisk?: boolean;
|
|
302
|
-
cacheDirectory?: string;
|
|
303
|
-
}): void {
|
|
304
|
-
if (config.maxEntries !== undefined && config.maxEntries > 0) {
|
|
305
|
-
webCacheConfig.maxEntries = config.maxEntries;
|
|
306
|
-
}
|
|
307
|
-
if (config.maxAgeMinutes !== undefined && config.maxAgeMinutes > 0) {
|
|
308
|
-
webCacheConfig.maxAgeMinutes = config.maxAgeMinutes;
|
|
309
|
-
}
|
|
310
|
-
// persistToDisk and cacheDirectory are no-ops on web
|
|
115
|
+
export async function compressImage(uri: string, _options?: CompressImageOptions): Promise<string> {
|
|
116
|
+
console.warn('[rmbg] compressImage not implemented on web, returning original');
|
|
117
|
+
return uri;
|
|
311
118
|
}
|
|
312
119
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
* On web, returns empty string as there is no disk cache
|
|
316
|
-
*/
|
|
317
|
-
export function getCacheDirectory(): string {
|
|
120
|
+
export async function generateThumbhash(_uri: string, _options?: GenerateThumbhashOptions): Promise<string> {
|
|
121
|
+
console.warn('[rmbg] generateThumbhash not implemented on web');
|
|
318
122
|
return '';
|
|
319
123
|
}
|
|
320
124
|
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
return new Promise((resolve, reject) => {
|
|
325
|
-
const img = new Image();
|
|
326
|
-
img.crossOrigin = 'anonymous';
|
|
327
|
-
img.onload = () => resolve(img);
|
|
328
|
-
img.onerror = reject;
|
|
329
|
-
img.src = src;
|
|
330
|
-
});
|
|
125
|
+
export async function clearCache(): Promise<void> {
|
|
126
|
+
pipeline = null;
|
|
127
|
+
loadPromise = null;
|
|
331
128
|
}
|
|
332
129
|
|
|
333
|
-
function
|
|
334
|
-
return
|
|
335
|
-
const reader = new FileReader();
|
|
336
|
-
reader.onloadend = () => resolve(reader.result as string);
|
|
337
|
-
reader.onerror = reject;
|
|
338
|
-
reader.readAsDataURL(blob);
|
|
339
|
-
});
|
|
130
|
+
export function getCacheSize(): number {
|
|
131
|
+
return 0;
|
|
340
132
|
}
|
|
341
133
|
|
|
342
|
-
function
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
// Base64 encodes 3 bytes as 4 characters
|
|
346
|
-
return (base64.length * 3) / 4 / 1024;
|
|
134
|
+
export async function onLowMemory(): Promise<number> {
|
|
135
|
+
await clearCache();
|
|
136
|
+
return 0;
|
|
347
137
|
}
|
|
138
|
+
|
|
139
|
+
export function configureCache(_config: { maxEntries?: number }): void {}
|
|
140
|
+
export function getCacheDirectory(): string { return ''; }
|