rn-remove-image-bg 0.0.31 → 0.0.32
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/lib/ImageProcessing.d.ts +167 -0
- package/lib/ImageProcessing.js +323 -0
- package/lib/ImageProcessing.web.d.ts +25 -0
- package/lib/ImageProcessing.web.js +89 -0
- package/lib/cache.d.ts +72 -0
- package/lib/cache.js +228 -0
- package/lib/errors.d.ts +20 -0
- package/lib/errors.js +64 -0
- package/lib/index.d.ts +6 -0
- package/lib/index.js +9 -0
- package/lib/specs/Example.nitro.d.ts +0 -0
- package/lib/specs/Example.nitro.js +2 -0
- package/lib/specs/ImageBackgroundRemover.nitro.d.ts +41 -0
- package/lib/specs/ImageBackgroundRemover.nitro.js +1 -0
- package/lib/web/core/BackgroundRemover.d.ts +31 -0
- package/lib/web/core/BackgroundRemover.js +64 -0
- package/lib/web/core/CacheManager.d.ts +16 -0
- package/lib/web/core/CacheManager.js +57 -0
- package/lib/web/core/types.d.ts +47 -0
- package/lib/web/core/types.js +1 -0
- package/lib/web/errors/WebErrorAdapter.d.ts +8 -0
- package/lib/web/errors/WebErrorAdapter.js +29 -0
- package/lib/web/utils/CompressImage.d.ts +2 -0
- package/lib/web/utils/CompressImage.js +34 -0
- package/lib/web/utils/ThumbhashGenerator.d.ts +1 -0
- package/lib/web/utils/ThumbhashGenerator.js +31 -0
- package/lib/web/utils/formatConverter.d.ts +4 -0
- package/lib/web/utils/formatConverter.js +18 -0
- package/lib/web/utils/uriHelper.d.ts +10 -0
- package/lib/web/utils/uriHelper.js +46 -0
- package/package.json +1 -1
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import * as ImageManipulator from 'expo-image-manipulator';
|
|
2
|
+
import type { OutputFormat, NativeRemoveBackgroundOptions } from './specs/ImageBackgroundRemover.nitro';
|
|
3
|
+
export type { OutputFormat, NativeRemoveBackgroundOptions };
|
|
4
|
+
export interface CompressImageOptions {
|
|
5
|
+
/**
|
|
6
|
+
* Maximum file size in KB (default: 250)
|
|
7
|
+
*/
|
|
8
|
+
maxSizeKB?: number;
|
|
9
|
+
/**
|
|
10
|
+
* Initial image width (default: 1024)
|
|
11
|
+
*/
|
|
12
|
+
width?: number;
|
|
13
|
+
/**
|
|
14
|
+
* Initial image height (default: 1024)
|
|
15
|
+
*/
|
|
16
|
+
height?: number;
|
|
17
|
+
/**
|
|
18
|
+
* Initial compression quality (0-1, default: 0.85)
|
|
19
|
+
*/
|
|
20
|
+
quality?: number;
|
|
21
|
+
/**
|
|
22
|
+
* Image format (default: WEBP)
|
|
23
|
+
*/
|
|
24
|
+
format?: ImageManipulator.SaveFormat;
|
|
25
|
+
}
|
|
26
|
+
export interface GenerateThumbhashOptions {
|
|
27
|
+
/**
|
|
28
|
+
* Thumbhash size (default: 32)
|
|
29
|
+
*/
|
|
30
|
+
size?: number;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Options for background removal
|
|
34
|
+
*/
|
|
35
|
+
export interface RemoveBgImageOptions {
|
|
36
|
+
/**
|
|
37
|
+
* Maximum dimension (width or height) for processing
|
|
38
|
+
* Larger images will be downsampled for better performance
|
|
39
|
+
* @default 2048
|
|
40
|
+
*/
|
|
41
|
+
maxDimension?: number;
|
|
42
|
+
/**
|
|
43
|
+
* Output image format
|
|
44
|
+
* - PNG: Lossless, larger file size, best for transparency
|
|
45
|
+
* - WEBP: Smaller file size, good quality
|
|
46
|
+
* @default 'PNG'
|
|
47
|
+
*/
|
|
48
|
+
format?: OutputFormat;
|
|
49
|
+
/**
|
|
50
|
+
* Quality for WEBP format (0-100)
|
|
51
|
+
* Ignored when format is PNG
|
|
52
|
+
* @default 100
|
|
53
|
+
*/
|
|
54
|
+
quality?: number;
|
|
55
|
+
/**
|
|
56
|
+
* Progress callback (0-100)
|
|
57
|
+
* Note: Progress is approximate and may not be linear
|
|
58
|
+
*/
|
|
59
|
+
onProgress?: (progress: number) => void;
|
|
60
|
+
/**
|
|
61
|
+
* Use cached result if available
|
|
62
|
+
* @default true
|
|
63
|
+
*/
|
|
64
|
+
useCache?: boolean;
|
|
65
|
+
/**
|
|
66
|
+
* Enable debug logging
|
|
67
|
+
* @default false
|
|
68
|
+
*/
|
|
69
|
+
debug?: boolean;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Compress image to WebP format with configurable options
|
|
73
|
+
*/
|
|
74
|
+
export declare function compressImage(uri: string, options?: CompressImageOptions): Promise<string>;
|
|
75
|
+
/**
|
|
76
|
+
* Generate thumbhash from image URI (Native/Mobile)
|
|
77
|
+
*/
|
|
78
|
+
export declare function generateThumbhash(imageUri: string, options?: GenerateThumbhashOptions): Promise<string>;
|
|
79
|
+
/**
|
|
80
|
+
* Remove background from image using native ML models
|
|
81
|
+
*
|
|
82
|
+
* @param uri - File path or file:// URI to the source image
|
|
83
|
+
* @param options - Processing options
|
|
84
|
+
* @returns Promise resolving to a URI suitable for use with `<Image>` component.
|
|
85
|
+
* - **iOS/Android**: File path (`file:///path/to/cache/bg_removed_xxx.png`)
|
|
86
|
+
* - **Web**: Data URL (`data:image/png;base64,...`)
|
|
87
|
+
*
|
|
88
|
+
* @throws {BackgroundRemovalError} When image cannot be processed
|
|
89
|
+
*
|
|
90
|
+
* @example
|
|
91
|
+
* ```typescript
|
|
92
|
+
* const result = await removeBgImage('file:///path/to/photo.jpg')
|
|
93
|
+
* // Use directly in Image component
|
|
94
|
+
* <Image source={{ uri: result }} />
|
|
95
|
+
* ```
|
|
96
|
+
*
|
|
97
|
+
* @example
|
|
98
|
+
* ```typescript
|
|
99
|
+
* // With options
|
|
100
|
+
* const result = await removeBgImage('file:///path/to/photo.jpg', {
|
|
101
|
+
* maxDimension: 1024,
|
|
102
|
+
* format: 'WEBP',
|
|
103
|
+
* quality: 90,
|
|
104
|
+
* onProgress: (p) => console.log(`Progress: ${p}%`)
|
|
105
|
+
* })
|
|
106
|
+
* ```
|
|
107
|
+
*/
|
|
108
|
+
export declare function removeBgImage(uri: string, options?: RemoveBgImageOptions): Promise<string>;
|
|
109
|
+
/**
|
|
110
|
+
* Backward compatibility alias for removeBgImage
|
|
111
|
+
* @deprecated Use removeBgImage instead
|
|
112
|
+
*/
|
|
113
|
+
export declare const removeBackground: typeof removeBgImage;
|
|
114
|
+
/**
|
|
115
|
+
* Clear the background removal cache
|
|
116
|
+
* @param deleteFiles - Also delete cached files from disk (default: false)
|
|
117
|
+
*/
|
|
118
|
+
export declare function clearCache(deleteFiles?: boolean): Promise<void>;
|
|
119
|
+
/**
|
|
120
|
+
* Get the current cache size
|
|
121
|
+
*/
|
|
122
|
+
export declare function getCacheSize(): number;
|
|
123
|
+
/**
|
|
124
|
+
* Handle low memory conditions by clearing the cache
|
|
125
|
+
* Call this when your app receives memory warnings
|
|
126
|
+
*
|
|
127
|
+
* @param deleteFiles - Also delete cached files from disk (default: true)
|
|
128
|
+
* @returns Number of entries that were cleared
|
|
129
|
+
*
|
|
130
|
+
* @example
|
|
131
|
+
* ```typescript
|
|
132
|
+
* import { AppState } from 'react-native'
|
|
133
|
+
* import { onLowMemory } from 'rn-remove-image-bg'
|
|
134
|
+
*
|
|
135
|
+
* // In your app initialization
|
|
136
|
+
* AppState.addEventListener('memoryWarning', () => {
|
|
137
|
+
* onLowMemory()
|
|
138
|
+
* })
|
|
139
|
+
* ```
|
|
140
|
+
*/
|
|
141
|
+
export declare function onLowMemory(deleteFiles?: boolean): Promise<number>;
|
|
142
|
+
/**
|
|
143
|
+
* Configure the background removal cache
|
|
144
|
+
* Call this early in your app lifecycle to customize cache behavior
|
|
145
|
+
*
|
|
146
|
+
* @example
|
|
147
|
+
* ```typescript
|
|
148
|
+
* import { configureCache } from 'rn-remove-image-bg'
|
|
149
|
+
*
|
|
150
|
+
* configureCache({
|
|
151
|
+
* maxEntries: 100,
|
|
152
|
+
* maxAgeMinutes: 60,
|
|
153
|
+
* persistToDisk: true
|
|
154
|
+
* })
|
|
155
|
+
* ```
|
|
156
|
+
*/
|
|
157
|
+
export declare function configureCache(config: {
|
|
158
|
+
maxEntries?: number;
|
|
159
|
+
maxAgeMinutes?: number;
|
|
160
|
+
persistToDisk?: boolean;
|
|
161
|
+
cacheDirectory?: string;
|
|
162
|
+
}): void;
|
|
163
|
+
/**
|
|
164
|
+
* Get the cache directory path
|
|
165
|
+
* Useful for debugging or manual cache management
|
|
166
|
+
*/
|
|
167
|
+
export declare function getCacheDirectory(): string;
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
import { Image } from 'react-native';
|
|
2
|
+
import * as ImageManipulator from 'expo-image-manipulator';
|
|
3
|
+
import * as FileSystem from 'expo-file-system/legacy';
|
|
4
|
+
import { Buffer } from 'buffer';
|
|
5
|
+
import { rgbaToThumbHash } from 'thumbhash';
|
|
6
|
+
import { NitroModules } from 'react-native-nitro-modules';
|
|
7
|
+
import { BackgroundRemovalError, wrapNativeError } from './errors';
|
|
8
|
+
import { bgRemovalCache } from './cache';
|
|
9
|
+
let nativeRemover;
|
|
10
|
+
function getNativeRemover() {
|
|
11
|
+
if (!nativeRemover) {
|
|
12
|
+
nativeRemover = NitroModules.createHybridObject('ImageBackgroundRemover');
|
|
13
|
+
}
|
|
14
|
+
return nativeRemover;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Validate image path format
|
|
18
|
+
*/
|
|
19
|
+
function validateImagePath(uri) {
|
|
20
|
+
if (!uri || uri.trim().length === 0) {
|
|
21
|
+
throw new BackgroundRemovalError('Image path cannot be empty', 'INVALID_PATH');
|
|
22
|
+
}
|
|
23
|
+
// Must be a file path or file:// URI
|
|
24
|
+
const isValidPath = uri.startsWith('file://') ||
|
|
25
|
+
uri.startsWith('/') ||
|
|
26
|
+
uri.match(/^[a-zA-Z]:\\/);
|
|
27
|
+
if (!isValidPath) {
|
|
28
|
+
throw new BackgroundRemovalError(`Invalid file path format: ${uri}. Expected file:// URI or absolute path.`, 'INVALID_PATH');
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Validate options
|
|
33
|
+
*/
|
|
34
|
+
function validateOptions(options) {
|
|
35
|
+
if (options.maxDimension !== undefined) {
|
|
36
|
+
if (options.maxDimension < 100 || options.maxDimension > 8192) {
|
|
37
|
+
throw new BackgroundRemovalError('maxDimension must be between 100 and 8192', 'INVALID_OPTIONS');
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
if (options.quality !== undefined) {
|
|
41
|
+
if (options.quality < 0 || options.quality > 100) {
|
|
42
|
+
throw new BackgroundRemovalError('quality must be between 0 and 100', 'INVALID_OPTIONS');
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
if (options.format !== undefined &&
|
|
46
|
+
!['PNG', 'WEBP'].includes(options.format)) {
|
|
47
|
+
throw new BackgroundRemovalError('format must be either "PNG" or "WEBP"', 'INVALID_OPTIONS');
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/** Default options for background removal */
|
|
51
|
+
const DEFAULT_OPTIONS = {
|
|
52
|
+
maxDimension: 2048,
|
|
53
|
+
format: 'PNG',
|
|
54
|
+
quality: 100,
|
|
55
|
+
useCache: true,
|
|
56
|
+
debug: false,
|
|
57
|
+
};
|
|
58
|
+
/**
|
|
59
|
+
* Compress image to WebP format with configurable options
|
|
60
|
+
*/
|
|
61
|
+
export async function compressImage(uri, options = {}) {
|
|
62
|
+
const { maxSizeKB = 250, width = 1024, height = 1024, quality = 0.85, format = ImageManipulator.SaveFormat.WEBP, } = options;
|
|
63
|
+
const startTime = Date.now();
|
|
64
|
+
const maxSize = maxSizeKB * 1024;
|
|
65
|
+
// Get original file size and dimensions
|
|
66
|
+
const originalInfo = await FileSystem.getInfoAsync(uri);
|
|
67
|
+
const originalSize = 'size' in originalInfo ? originalInfo.size : 0;
|
|
68
|
+
const { width: originalWidth, height: originalHeight } = await new Promise((resolve, reject) => {
|
|
69
|
+
Image.getSize(uri, (w, h) => resolve({ width: w, height: h }), reject);
|
|
70
|
+
});
|
|
71
|
+
// Calculate target dimensions maintaining aspect ratio
|
|
72
|
+
// We want the image to fit WITHIN the bounding box defined by width x height
|
|
73
|
+
const scale = Math.min(width / originalWidth, height / originalHeight);
|
|
74
|
+
// If image is smaller than target box, we can keep original size (scale = 1) if we don't want to upscale.
|
|
75
|
+
// Generally "compress" implies making smaller or equal.
|
|
76
|
+
// If the image is larger, scale < 1. If smaller, scale >= 1.
|
|
77
|
+
// Let's cap scale at 1 to prevent upscaling unless explicitly desired (usually not for compression).
|
|
78
|
+
const finalScale = Math.min(scale, 1);
|
|
79
|
+
const resizeWidth = Math.round(originalWidth * finalScale);
|
|
80
|
+
const resizeHeight = Math.round(originalHeight * finalScale);
|
|
81
|
+
// Start with calculated dimensions and quality
|
|
82
|
+
let result = await ImageManipulator.manipulateAsync(uri, [{ resize: { width: resizeWidth, height: resizeHeight } }], {
|
|
83
|
+
compress: quality,
|
|
84
|
+
format,
|
|
85
|
+
});
|
|
86
|
+
let fileInfo = await FileSystem.getInfoAsync(result.uri);
|
|
87
|
+
// If still too large, reduce quality
|
|
88
|
+
if ('size' in fileInfo && fileInfo.size > maxSize) {
|
|
89
|
+
let currentQuality = quality * 0.9;
|
|
90
|
+
while (currentQuality > 0.5 &&
|
|
91
|
+
'size' in fileInfo &&
|
|
92
|
+
fileInfo.size > maxSize) {
|
|
93
|
+
result = await ImageManipulator.manipulateAsync(uri, [{ resize: { width: resizeWidth, height: resizeHeight } }], {
|
|
94
|
+
compress: currentQuality,
|
|
95
|
+
format,
|
|
96
|
+
});
|
|
97
|
+
fileInfo = await FileSystem.getInfoAsync(result.uri);
|
|
98
|
+
if ('size' in fileInfo && fileInfo.size <= maxSize)
|
|
99
|
+
break;
|
|
100
|
+
currentQuality -= 0.05;
|
|
101
|
+
}
|
|
102
|
+
// If still too large, reduce dimensions
|
|
103
|
+
if ('size' in fileInfo && fileInfo.size > maxSize) {
|
|
104
|
+
const smallerWidth = Math.floor(resizeWidth * 0.75);
|
|
105
|
+
const smallerHeight = Math.floor(resizeHeight * 0.75);
|
|
106
|
+
result = await ImageManipulator.manipulateAsync(uri, [{ resize: { width: smallerWidth, height: smallerHeight } }], {
|
|
107
|
+
compress: 0.75,
|
|
108
|
+
format,
|
|
109
|
+
});
|
|
110
|
+
fileInfo = await FileSystem.getInfoAsync(result.uri);
|
|
111
|
+
// Final quality reduction if needed
|
|
112
|
+
if ('size' in fileInfo && fileInfo.size > maxSize) {
|
|
113
|
+
let finalQuality = 0.7;
|
|
114
|
+
while (finalQuality > 0.5 &&
|
|
115
|
+
'size' in fileInfo &&
|
|
116
|
+
fileInfo.size > maxSize) {
|
|
117
|
+
result = await ImageManipulator.manipulateAsync(uri, [{ resize: { width: smallerWidth, height: smallerHeight } }], {
|
|
118
|
+
compress: finalQuality,
|
|
119
|
+
format,
|
|
120
|
+
});
|
|
121
|
+
fileInfo = await FileSystem.getInfoAsync(result.uri);
|
|
122
|
+
if ('size' in fileInfo && fileInfo.size <= maxSize)
|
|
123
|
+
break;
|
|
124
|
+
finalQuality -= 0.05;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
const finalSize = 'size' in fileInfo ? fileInfo.size : 0;
|
|
130
|
+
const duration = Date.now() - startTime;
|
|
131
|
+
console.log(`[Native] Image Compression:`, {
|
|
132
|
+
originalSize: `${(originalSize / 1024).toFixed(2)} KB`,
|
|
133
|
+
compressedSize: `${(finalSize / 1024).toFixed(2)} KB`,
|
|
134
|
+
reduction: `${(((originalSize - finalSize) / originalSize) * 100).toFixed(1)}%`,
|
|
135
|
+
duration: `${duration}ms`,
|
|
136
|
+
dimensions: `${resizeWidth}x${resizeHeight}`,
|
|
137
|
+
});
|
|
138
|
+
return result.uri;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Generate thumbhash from image URI (Native/Mobile)
|
|
142
|
+
*/
|
|
143
|
+
export async function generateThumbhash(imageUri, options = {}) {
|
|
144
|
+
const { size = 32 } = options;
|
|
145
|
+
// 1. Create tiny PNG
|
|
146
|
+
const tiny = await ImageManipulator.manipulateAsync(imageUri, [{ resize: { width: size, height: size } }], { format: ImageManipulator.SaveFormat.PNG });
|
|
147
|
+
// 2. Read as base64
|
|
148
|
+
const base64 = await FileSystem.readAsStringAsync(tiny.uri, {
|
|
149
|
+
encoding: 'base64',
|
|
150
|
+
});
|
|
151
|
+
// 3. Decode PNG and generate thumbhash
|
|
152
|
+
const UPNG = require('upng-js');
|
|
153
|
+
const buffer = Buffer.from(base64, 'base64');
|
|
154
|
+
const img = UPNG.decode(buffer);
|
|
155
|
+
const rgba = UPNG.toRGBA8(img)[0];
|
|
156
|
+
const hash = rgbaToThumbHash(size, size, new Uint8Array(rgba));
|
|
157
|
+
// 4. Convert to base64
|
|
158
|
+
return Buffer.from(hash).toString('base64');
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Remove background from image using native ML models
|
|
162
|
+
*
|
|
163
|
+
* @param uri - File path or file:// URI to the source image
|
|
164
|
+
* @param options - Processing options
|
|
165
|
+
* @returns Promise resolving to a URI suitable for use with `<Image>` component.
|
|
166
|
+
* - **iOS/Android**: File path (`file:///path/to/cache/bg_removed_xxx.png`)
|
|
167
|
+
* - **Web**: Data URL (`data:image/png;base64,...`)
|
|
168
|
+
*
|
|
169
|
+
* @throws {BackgroundRemovalError} When image cannot be processed
|
|
170
|
+
*
|
|
171
|
+
* @example
|
|
172
|
+
* ```typescript
|
|
173
|
+
* const result = await removeBgImage('file:///path/to/photo.jpg')
|
|
174
|
+
* // Use directly in Image component
|
|
175
|
+
* <Image source={{ uri: result }} />
|
|
176
|
+
* ```
|
|
177
|
+
*
|
|
178
|
+
* @example
|
|
179
|
+
* ```typescript
|
|
180
|
+
* // With options
|
|
181
|
+
* const result = await removeBgImage('file:///path/to/photo.jpg', {
|
|
182
|
+
* maxDimension: 1024,
|
|
183
|
+
* format: 'WEBP',
|
|
184
|
+
* quality: 90,
|
|
185
|
+
* onProgress: (p) => console.log(`Progress: ${p}%`)
|
|
186
|
+
* })
|
|
187
|
+
* ```
|
|
188
|
+
*/
|
|
189
|
+
export async function removeBgImage(uri, options = {}) {
|
|
190
|
+
const startTime = Date.now();
|
|
191
|
+
const opts = { ...DEFAULT_OPTIONS, ...options };
|
|
192
|
+
const { onProgress, debug } = opts;
|
|
193
|
+
// Validate inputs
|
|
194
|
+
validateImagePath(uri);
|
|
195
|
+
validateOptions(options);
|
|
196
|
+
if (debug) {
|
|
197
|
+
console.log('[rn-remove-image-bg] Starting background removal:', uri);
|
|
198
|
+
console.log('[rn-remove-image-bg] Options:', opts);
|
|
199
|
+
}
|
|
200
|
+
// Report initial progress
|
|
201
|
+
onProgress?.(5);
|
|
202
|
+
// Check cache if enabled
|
|
203
|
+
if (opts.useCache) {
|
|
204
|
+
const optionsHash = bgRemovalCache.hashOptions({
|
|
205
|
+
maxDimension: opts.maxDimension,
|
|
206
|
+
format: opts.format,
|
|
207
|
+
quality: opts.quality,
|
|
208
|
+
});
|
|
209
|
+
const cached = await bgRemovalCache.get(uri, optionsHash);
|
|
210
|
+
if (cached) {
|
|
211
|
+
if (debug) {
|
|
212
|
+
console.log('[rn-remove-image-bg] Cache hit:', cached);
|
|
213
|
+
}
|
|
214
|
+
onProgress?.(100);
|
|
215
|
+
return cached.startsWith('file://') ? cached : `file://${cached}`;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
onProgress?.(10);
|
|
219
|
+
try {
|
|
220
|
+
// Prepare native options
|
|
221
|
+
const nativeOptions = {
|
|
222
|
+
maxDimension: opts.maxDimension,
|
|
223
|
+
format: opts.format,
|
|
224
|
+
quality: opts.quality,
|
|
225
|
+
};
|
|
226
|
+
onProgress?.(20);
|
|
227
|
+
// Call native implementation
|
|
228
|
+
const result = await getNativeRemover().removeBackground(uri, nativeOptions);
|
|
229
|
+
onProgress?.(90);
|
|
230
|
+
// Normalize result path
|
|
231
|
+
const resultPath = result.startsWith('file://')
|
|
232
|
+
? result
|
|
233
|
+
: `file://${result}`;
|
|
234
|
+
// Cache the result
|
|
235
|
+
if (opts.useCache) {
|
|
236
|
+
const optionsHash = bgRemovalCache.hashOptions({
|
|
237
|
+
maxDimension: opts.maxDimension,
|
|
238
|
+
format: opts.format,
|
|
239
|
+
quality: opts.quality,
|
|
240
|
+
});
|
|
241
|
+
bgRemovalCache.set(uri, optionsHash, resultPath);
|
|
242
|
+
}
|
|
243
|
+
if (debug) {
|
|
244
|
+
console.log('[rn-remove-image-bg] Completed in', Date.now() - startTime, 'ms');
|
|
245
|
+
console.log('[rn-remove-image-bg] Result:', resultPath);
|
|
246
|
+
}
|
|
247
|
+
onProgress?.(100);
|
|
248
|
+
return resultPath;
|
|
249
|
+
}
|
|
250
|
+
catch (error) {
|
|
251
|
+
if (debug) {
|
|
252
|
+
console.error('[rn-remove-image-bg] Failed:', error);
|
|
253
|
+
}
|
|
254
|
+
throw wrapNativeError(error);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Backward compatibility alias for removeBgImage
|
|
259
|
+
* @deprecated Use removeBgImage instead
|
|
260
|
+
*/
|
|
261
|
+
export const removeBackground = removeBgImage;
|
|
262
|
+
/**
|
|
263
|
+
* Clear the background removal cache
|
|
264
|
+
* @param deleteFiles - Also delete cached files from disk (default: false)
|
|
265
|
+
*/
|
|
266
|
+
export async function clearCache(deleteFiles = false) {
|
|
267
|
+
await bgRemovalCache.clear(deleteFiles);
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Get the current cache size
|
|
271
|
+
*/
|
|
272
|
+
export function getCacheSize() {
|
|
273
|
+
return bgRemovalCache.size;
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Handle low memory conditions by clearing the cache
|
|
277
|
+
* Call this when your app receives memory warnings
|
|
278
|
+
*
|
|
279
|
+
* @param deleteFiles - Also delete cached files from disk (default: true)
|
|
280
|
+
* @returns Number of entries that were cleared
|
|
281
|
+
*
|
|
282
|
+
* @example
|
|
283
|
+
* ```typescript
|
|
284
|
+
* import { AppState } from 'react-native'
|
|
285
|
+
* import { onLowMemory } from 'rn-remove-image-bg'
|
|
286
|
+
*
|
|
287
|
+
* // In your app initialization
|
|
288
|
+
* AppState.addEventListener('memoryWarning', () => {
|
|
289
|
+
* onLowMemory()
|
|
290
|
+
* })
|
|
291
|
+
* ```
|
|
292
|
+
*/
|
|
293
|
+
export async function onLowMemory(deleteFiles = true) {
|
|
294
|
+
const size = bgRemovalCache.size;
|
|
295
|
+
await bgRemovalCache.clear(deleteFiles);
|
|
296
|
+
console.log(`[rn-remove-image-bg] Cleared ${size} cache entries due to memory pressure`);
|
|
297
|
+
return size;
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* Configure the background removal cache
|
|
301
|
+
* Call this early in your app lifecycle to customize cache behavior
|
|
302
|
+
*
|
|
303
|
+
* @example
|
|
304
|
+
* ```typescript
|
|
305
|
+
* import { configureCache } from 'rn-remove-image-bg'
|
|
306
|
+
*
|
|
307
|
+
* configureCache({
|
|
308
|
+
* maxEntries: 100,
|
|
309
|
+
* maxAgeMinutes: 60,
|
|
310
|
+
* persistToDisk: true
|
|
311
|
+
* })
|
|
312
|
+
* ```
|
|
313
|
+
*/
|
|
314
|
+
export function configureCache(config) {
|
|
315
|
+
bgRemovalCache.configure(config);
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* Get the cache directory path
|
|
319
|
+
* Useful for debugging or manual cache management
|
|
320
|
+
*/
|
|
321
|
+
export function getCacheDirectory() {
|
|
322
|
+
return bgRemovalCache.getCacheDirectory();
|
|
323
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { RemoveBgImageOptions, CompressImageOptions, GenerateThumbhashOptions, OutputFormat } from './web/core/types';
|
|
2
|
+
export type { RemoveBgImageOptions, CompressImageOptions, GenerateThumbhashOptions, OutputFormat };
|
|
3
|
+
export type NativeRemoveBackgroundOptions = RemoveBgImageOptions;
|
|
4
|
+
/**
|
|
5
|
+
* Remove background from image (Web Implementation)
|
|
6
|
+
*/
|
|
7
|
+
export declare function removeBgImage(uri: string, options?: RemoveBgImageOptions): Promise<string>;
|
|
8
|
+
/**
|
|
9
|
+
* Backward compatibility alias
|
|
10
|
+
* @deprecated Use removeBgImage
|
|
11
|
+
*/
|
|
12
|
+
export declare const removeBackground: typeof removeBgImage;
|
|
13
|
+
/**
|
|
14
|
+
* Compress image (Web Implementation)
|
|
15
|
+
*/
|
|
16
|
+
export declare function compressImage(uri: string, options?: CompressImageOptions): Promise<string>;
|
|
17
|
+
/**
|
|
18
|
+
* Generate thumbhash (Web Implementation)
|
|
19
|
+
*/
|
|
20
|
+
export declare function generateThumbhash(uri: string, _options?: GenerateThumbhashOptions): Promise<string>;
|
|
21
|
+
export declare function clearCache(_deleteFiles?: boolean): Promise<void>;
|
|
22
|
+
export declare function getCacheSize(): number;
|
|
23
|
+
export declare function onLowMemory(_deleteFiles?: boolean): Promise<number>;
|
|
24
|
+
export declare function configureCache(_config: Record<string, unknown>): void;
|
|
25
|
+
export declare function getCacheDirectory(): string;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { BackgroundRemover } from './web/core/BackgroundRemover';
|
|
2
|
+
import { cacheManager } from './web/core/CacheManager';
|
|
3
|
+
import { compressImage as compressImageWeb } from './web/utils/CompressImage';
|
|
4
|
+
import { generateThumbhash as generateThumbhashWeb } from './web/utils/ThumbhashGenerator';
|
|
5
|
+
import { blobToDataUrl } from './web/utils/formatConverter';
|
|
6
|
+
/**
|
|
7
|
+
* Remove background from image (Web Implementation)
|
|
8
|
+
*/
|
|
9
|
+
export async function removeBgImage(uri, options = {}) {
|
|
10
|
+
const { onProgress, useCache = true, debug = false } = options;
|
|
11
|
+
if (debug)
|
|
12
|
+
console.log('[Web] removeBgImage called with:', uri, options);
|
|
13
|
+
// 1. Check Cache
|
|
14
|
+
if (useCache) {
|
|
15
|
+
const cached = cacheManager.get(uri, options);
|
|
16
|
+
if (cached) {
|
|
17
|
+
if (debug)
|
|
18
|
+
console.log('[Web] Cache hit');
|
|
19
|
+
onProgress?.(100);
|
|
20
|
+
return cached;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
// 2. Process
|
|
24
|
+
onProgress?.(10); // Start
|
|
25
|
+
const blob = await BackgroundRemover.remove(uri, {
|
|
26
|
+
...options,
|
|
27
|
+
onProgress: (p) => {
|
|
28
|
+
// Map progress to 10-90 range to leave room for start/end
|
|
29
|
+
const mapped = 10 + Math.round((p * 0.8));
|
|
30
|
+
onProgress?.(mapped);
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
if (debug) {
|
|
34
|
+
console.log('[Web] Blob received:', {
|
|
35
|
+
type: blob.type,
|
|
36
|
+
size: blob.size,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
// 3. Convert to Data URL
|
|
40
|
+
const dataUrl = await blobToDataUrl(blob);
|
|
41
|
+
if (debug) {
|
|
42
|
+
console.log('[Web] DataURL prefix:', dataUrl.substring(0, 50));
|
|
43
|
+
}
|
|
44
|
+
onProgress?.(100);
|
|
45
|
+
// 4. Cache Result
|
|
46
|
+
if (useCache) {
|
|
47
|
+
cacheManager.set(uri, options, dataUrl);
|
|
48
|
+
}
|
|
49
|
+
return dataUrl;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Backward compatibility alias
|
|
53
|
+
* @deprecated Use removeBgImage
|
|
54
|
+
*/
|
|
55
|
+
export const removeBackground = removeBgImage;
|
|
56
|
+
/**
|
|
57
|
+
* Compress image (Web Implementation)
|
|
58
|
+
*/
|
|
59
|
+
export async function compressImage(uri, options = {}) {
|
|
60
|
+
return compressImageWeb(uri, options);
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Generate thumbhash (Web Implementation)
|
|
64
|
+
*/
|
|
65
|
+
export async function generateThumbhash(uri, _options = {}) {
|
|
66
|
+
// Web implementation currently doesn't use options (size hardcoded or auto-scaled)
|
|
67
|
+
return generateThumbhashWeb(uri);
|
|
68
|
+
}
|
|
69
|
+
// Cache Management APIs
|
|
70
|
+
export async function clearCache(_deleteFiles = false) {
|
|
71
|
+
cacheManager.clear();
|
|
72
|
+
console.log('[Web] Cache cleared');
|
|
73
|
+
}
|
|
74
|
+
export function getCacheSize() {
|
|
75
|
+
return cacheManager.size();
|
|
76
|
+
}
|
|
77
|
+
export async function onLowMemory(_deleteFiles = true) {
|
|
78
|
+
const size = cacheManager.size();
|
|
79
|
+
cacheManager.clear();
|
|
80
|
+
console.log(`[Web] Cleared ${size} items due to low memory`);
|
|
81
|
+
return size;
|
|
82
|
+
}
|
|
83
|
+
export function configureCache(_config) {
|
|
84
|
+
// Web cache is simple in-memory LRU, config not fully supported yet but stubbed
|
|
85
|
+
console.log('[Web] Cache configuration updated (no-op on web)');
|
|
86
|
+
}
|
|
87
|
+
export function getCacheDirectory() {
|
|
88
|
+
return ''; // No file system on web
|
|
89
|
+
}
|
package/lib/cache.d.ts
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration for the background removal cache
|
|
3
|
+
*/
|
|
4
|
+
export interface CacheConfig {
|
|
5
|
+
/** Maximum number of entries in memory (default: 50) */
|
|
6
|
+
maxEntries?: number;
|
|
7
|
+
/** Maximum age of cache entries in minutes (default: 30) */
|
|
8
|
+
maxAgeMinutes?: number;
|
|
9
|
+
/** Enable disk persistence (default: false) */
|
|
10
|
+
persistToDisk?: boolean;
|
|
11
|
+
/** Custom cache directory (default: FileSystem.cacheDirectory + 'bg-removal/') */
|
|
12
|
+
cacheDirectory?: string;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* LRU cache for background removal results with optional disk persistence
|
|
16
|
+
*/
|
|
17
|
+
declare class BackgroundRemovalCache {
|
|
18
|
+
private cache;
|
|
19
|
+
private _maxEntries;
|
|
20
|
+
private _maxAgeMs;
|
|
21
|
+
private persistToDisk;
|
|
22
|
+
private cacheDirectory;
|
|
23
|
+
private initialized;
|
|
24
|
+
constructor(config?: CacheConfig);
|
|
25
|
+
/**
|
|
26
|
+
* Configure cache settings
|
|
27
|
+
*/
|
|
28
|
+
configure(config: CacheConfig): void;
|
|
29
|
+
/**
|
|
30
|
+
* Initialize disk cache (load manifest if exists)
|
|
31
|
+
*/
|
|
32
|
+
initialize(): Promise<void>;
|
|
33
|
+
/**
|
|
34
|
+
* Save cache manifest to disk
|
|
35
|
+
*/
|
|
36
|
+
private saveManifest;
|
|
37
|
+
/**
|
|
38
|
+
* Generate a cache key from path and options
|
|
39
|
+
*/
|
|
40
|
+
private generateKey;
|
|
41
|
+
/**
|
|
42
|
+
* Hash options object to string for cache key
|
|
43
|
+
*/
|
|
44
|
+
hashOptions(options: Record<string, unknown>): string;
|
|
45
|
+
/**
|
|
46
|
+
* Get cached result if valid
|
|
47
|
+
*/
|
|
48
|
+
get(path: string, optionsHash: string): Promise<string | null>;
|
|
49
|
+
/**
|
|
50
|
+
* Store result in cache
|
|
51
|
+
*/
|
|
52
|
+
set(path: string, optionsHash: string, resultPath: string): void;
|
|
53
|
+
/**
|
|
54
|
+
* Clear all cached entries
|
|
55
|
+
* @param deleteFiles - Also delete cached files from disk (default: false)
|
|
56
|
+
*/
|
|
57
|
+
clear(deleteFiles?: boolean): Promise<void>;
|
|
58
|
+
/**
|
|
59
|
+
* Get current cache size
|
|
60
|
+
*/
|
|
61
|
+
get size(): number;
|
|
62
|
+
/**
|
|
63
|
+
* Remove expired entries
|
|
64
|
+
*/
|
|
65
|
+
prune(): number;
|
|
66
|
+
/**
|
|
67
|
+
* Get cache directory path
|
|
68
|
+
*/
|
|
69
|
+
getCacheDirectory(): string;
|
|
70
|
+
}
|
|
71
|
+
export declare const bgRemovalCache: BackgroundRemovalCache;
|
|
72
|
+
export {};
|