rn-remove-image-bg 0.0.10
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/NitroRnRemoveImageBg.podspec +33 -0
- package/README.md +386 -0
- package/android/CMakeLists.txt +28 -0
- package/android/build.gradle +142 -0
- package/android/fix-prefab.gradle +51 -0
- package/android/gradle.properties +5 -0
- package/android/src/main/AndroidManifest.xml +3 -0
- package/android/src/main/cpp/cpp-adapter.cpp +6 -0
- package/android/src/main/java/com/margelo/nitro/rnremoveimagebg/HybridImageBackgroundRemover.kt +189 -0
- package/android/src/main/java/com/margelo/nitro/rnremoveimagebg/NitroRnRemoveImageBgPackage.kt +31 -0
- package/app.plugin.js +12 -0
- package/ios/Bridge.h +8 -0
- package/ios/HybridImageBackgroundRemover.swift +224 -0
- package/ios/NitroRnRemoveImageBgOnLoad.mm +22 -0
- package/lib/ImageProcessing.d.ts +167 -0
- package/lib/ImageProcessing.js +323 -0
- package/lib/ImageProcessing.web.d.ts +80 -0
- package/lib/ImageProcessing.web.js +248 -0
- package/lib/__tests__/cache.test.d.ts +1 -0
- package/lib/__tests__/cache.test.js +87 -0
- package/lib/__tests__/errors.test.d.ts +1 -0
- package/lib/__tests__/errors.test.js +82 -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/nitro.json +17 -0
- package/nitrogen/generated/.gitattributes +1 -0
- package/nitrogen/generated/android/NitroRnRemoveImageBg+autolinking.cmake +81 -0
- package/nitrogen/generated/android/NitroRnRemoveImageBg+autolinking.gradle +27 -0
- package/nitrogen/generated/android/NitroRnRemoveImageBgOnLoad.cpp +44 -0
- package/nitrogen/generated/android/NitroRnRemoveImageBgOnLoad.hpp +25 -0
- package/nitrogen/generated/android/c++/JHybridImageBackgroundRemoverSpec.cpp +72 -0
- package/nitrogen/generated/android/c++/JHybridImageBackgroundRemoverSpec.hpp +65 -0
- package/nitrogen/generated/android/c++/JNativeRemoveBackgroundOptions.hpp +66 -0
- package/nitrogen/generated/android/c++/JOutputFormat.hpp +59 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/rnremoveimagebg/HybridImageBackgroundRemoverSpec.kt +58 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/rnremoveimagebg/NativeRemoveBackgroundOptions.kt +44 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/rnremoveimagebg/NitroRnRemoveImageBgOnLoad.kt +35 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/rnremoveimagebg/OutputFormat.kt +21 -0
- package/nitrogen/generated/ios/NitroRnRemoveImageBg+autolinking.rb +60 -0
- package/nitrogen/generated/ios/NitroRnRemoveImageBg-Swift-Cxx-Bridge.cpp +49 -0
- package/nitrogen/generated/ios/NitroRnRemoveImageBg-Swift-Cxx-Bridge.hpp +111 -0
- package/nitrogen/generated/ios/NitroRnRemoveImageBg-Swift-Cxx-Umbrella.hpp +51 -0
- package/nitrogen/generated/ios/c++/HybridImageBackgroundRemoverSpecSwift.cpp +11 -0
- package/nitrogen/generated/ios/c++/HybridImageBackgroundRemoverSpecSwift.hpp +82 -0
- package/nitrogen/generated/ios/swift/Func_void_std__exception_ptr.swift +47 -0
- package/nitrogen/generated/ios/swift/Func_void_std__string.swift +47 -0
- package/nitrogen/generated/ios/swift/HybridImageBackgroundRemoverSpec.swift +56 -0
- package/nitrogen/generated/ios/swift/HybridImageBackgroundRemoverSpec_cxx.swift +138 -0
- package/nitrogen/generated/ios/swift/NativeRemoveBackgroundOptions.swift +58 -0
- package/nitrogen/generated/ios/swift/OutputFormat.swift +40 -0
- package/nitrogen/generated/shared/c++/HybridImageBackgroundRemoverSpec.cpp +21 -0
- package/nitrogen/generated/shared/c++/HybridImageBackgroundRemoverSpec.hpp +65 -0
- package/nitrogen/generated/shared/c++/NativeRemoveBackgroundOptions.hpp +84 -0
- package/nitrogen/generated/shared/c++/OutputFormat.hpp +76 -0
- package/package.json +104 -0
- package/react-native.config.js +16 -0
- package/src/ImageProcessing.ts +532 -0
- package/src/ImageProcessing.web.ts +342 -0
- package/src/__tests__/ImageProcessing.test.ts +278 -0
- package/src/__tests__/cache.test.ts +110 -0
- package/src/__tests__/errors.test.ts +117 -0
- package/src/cache.ts +305 -0
- package/src/errors.ts +93 -0
- package/src/index.ts +49 -0
- package/src/specs/Example.nitro.ts +1 -0
- package/src/specs/ImageBackgroundRemover.nitro.ts +49 -0
|
@@ -0,0 +1,532 @@
|
|
|
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 type {
|
|
8
|
+
ImageBackgroundRemover,
|
|
9
|
+
OutputFormat,
|
|
10
|
+
NativeRemoveBackgroundOptions,
|
|
11
|
+
} from './specs/ImageBackgroundRemover.nitro';
|
|
12
|
+
import { BackgroundRemovalError, wrapNativeError } from './errors';
|
|
13
|
+
import { bgRemovalCache } from './cache';
|
|
14
|
+
|
|
15
|
+
// Re-export types
|
|
16
|
+
export type { OutputFormat, NativeRemoveBackgroundOptions };
|
|
17
|
+
|
|
18
|
+
let nativeRemover: ImageBackgroundRemover | undefined;
|
|
19
|
+
|
|
20
|
+
function getNativeRemover(): ImageBackgroundRemover {
|
|
21
|
+
if (!nativeRemover) {
|
|
22
|
+
nativeRemover = NitroModules.createHybridObject<ImageBackgroundRemover>(
|
|
23
|
+
'ImageBackgroundRemover'
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
return nativeRemover;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Validate image path format
|
|
31
|
+
*/
|
|
32
|
+
function validateImagePath(uri: string): void {
|
|
33
|
+
if (!uri || uri.trim().length === 0) {
|
|
34
|
+
throw new BackgroundRemovalError(
|
|
35
|
+
'Image path cannot be empty',
|
|
36
|
+
'INVALID_PATH'
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Must be a file path or file:// URI
|
|
41
|
+
const isValidPath =
|
|
42
|
+
uri.startsWith('file://') ||
|
|
43
|
+
uri.startsWith('/') ||
|
|
44
|
+
uri.match(/^[a-zA-Z]:\\/);
|
|
45
|
+
if (!isValidPath) {
|
|
46
|
+
throw new BackgroundRemovalError(
|
|
47
|
+
`Invalid file path format: ${uri}. Expected file:// URI or absolute path.`,
|
|
48
|
+
'INVALID_PATH'
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Validate options
|
|
55
|
+
*/
|
|
56
|
+
function validateOptions(options: Partial<RemoveBgImageOptions>): void {
|
|
57
|
+
if (options.maxDimension !== undefined) {
|
|
58
|
+
if (options.maxDimension < 100 || options.maxDimension > 8192) {
|
|
59
|
+
throw new BackgroundRemovalError(
|
|
60
|
+
'maxDimension must be between 100 and 8192',
|
|
61
|
+
'INVALID_OPTIONS'
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (options.quality !== undefined) {
|
|
67
|
+
if (options.quality < 0 || options.quality > 100) {
|
|
68
|
+
throw new BackgroundRemovalError(
|
|
69
|
+
'quality must be between 0 and 100',
|
|
70
|
+
'INVALID_OPTIONS'
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (
|
|
76
|
+
options.format !== undefined &&
|
|
77
|
+
!['PNG', 'WEBP'].includes(options.format)
|
|
78
|
+
) {
|
|
79
|
+
throw new BackgroundRemovalError(
|
|
80
|
+
'format must be either "PNG" or "WEBP"',
|
|
81
|
+
'INVALID_OPTIONS'
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export interface CompressImageOptions {
|
|
87
|
+
/**
|
|
88
|
+
* Maximum file size in KB (default: 250)
|
|
89
|
+
*/
|
|
90
|
+
maxSizeKB?: number;
|
|
91
|
+
/**
|
|
92
|
+
* Initial image width (default: 1024)
|
|
93
|
+
*/
|
|
94
|
+
width?: number;
|
|
95
|
+
/**
|
|
96
|
+
* Initial image height (default: 1024)
|
|
97
|
+
*/
|
|
98
|
+
height?: number;
|
|
99
|
+
/**
|
|
100
|
+
* Initial compression quality (0-1, default: 0.85)
|
|
101
|
+
*/
|
|
102
|
+
quality?: number;
|
|
103
|
+
/**
|
|
104
|
+
* Image format (default: WEBP)
|
|
105
|
+
*/
|
|
106
|
+
format?: ImageManipulator.SaveFormat;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export interface GenerateThumbhashOptions {
|
|
110
|
+
/**
|
|
111
|
+
* Thumbhash size (default: 32)
|
|
112
|
+
*/
|
|
113
|
+
size?: number;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Options for background removal
|
|
118
|
+
*/
|
|
119
|
+
export interface RemoveBgImageOptions {
|
|
120
|
+
/**
|
|
121
|
+
* Maximum dimension (width or height) for processing
|
|
122
|
+
* Larger images will be downsampled for better performance
|
|
123
|
+
* @default 2048
|
|
124
|
+
*/
|
|
125
|
+
maxDimension?: number;
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Output image format
|
|
129
|
+
* - PNG: Lossless, larger file size, best for transparency
|
|
130
|
+
* - WEBP: Smaller file size, good quality
|
|
131
|
+
* @default 'PNG'
|
|
132
|
+
*/
|
|
133
|
+
format?: OutputFormat;
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Quality for WEBP format (0-100)
|
|
137
|
+
* Ignored when format is PNG
|
|
138
|
+
* @default 100
|
|
139
|
+
*/
|
|
140
|
+
quality?: number;
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Progress callback (0-100)
|
|
144
|
+
* Note: Progress is approximate and may not be linear
|
|
145
|
+
*/
|
|
146
|
+
onProgress?: (progress: number) => void;
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Use cached result if available
|
|
150
|
+
* @default true
|
|
151
|
+
*/
|
|
152
|
+
useCache?: boolean;
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Enable debug logging
|
|
156
|
+
* @default false
|
|
157
|
+
*/
|
|
158
|
+
debug?: boolean;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** Default options for background removal */
|
|
162
|
+
const DEFAULT_OPTIONS: Required<Omit<RemoveBgImageOptions, 'onProgress'>> = {
|
|
163
|
+
maxDimension: 2048,
|
|
164
|
+
format: 'PNG',
|
|
165
|
+
quality: 100,
|
|
166
|
+
useCache: true,
|
|
167
|
+
debug: false,
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Compress image to WebP format with configurable options
|
|
172
|
+
*/
|
|
173
|
+
export async function compressImage(
|
|
174
|
+
uri: string,
|
|
175
|
+
options: CompressImageOptions = {}
|
|
176
|
+
) {
|
|
177
|
+
const {
|
|
178
|
+
maxSizeKB = 250,
|
|
179
|
+
width = 1024,
|
|
180
|
+
height = 1024,
|
|
181
|
+
quality = 0.85,
|
|
182
|
+
format = ImageManipulator.SaveFormat.WEBP,
|
|
183
|
+
} = options;
|
|
184
|
+
|
|
185
|
+
const startTime = Date.now();
|
|
186
|
+
const maxSize = maxSizeKB * 1024;
|
|
187
|
+
|
|
188
|
+
// Get original file size and dimensions
|
|
189
|
+
const originalInfo = await FileSystem.getInfoAsync(uri);
|
|
190
|
+
const originalSize = 'size' in originalInfo ? originalInfo.size : 0;
|
|
191
|
+
|
|
192
|
+
const { width: originalWidth, height: originalHeight } = await new Promise<{
|
|
193
|
+
width: number;
|
|
194
|
+
height: number;
|
|
195
|
+
}>((resolve, reject) => {
|
|
196
|
+
Image.getSize(uri, (w, h) => resolve({ width: w, height: h }), reject);
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
// Calculate target dimensions maintaining aspect ratio
|
|
200
|
+
// We want the image to fit WITHIN the bounding box defined by width x height
|
|
201
|
+
const scale = Math.min(width / originalWidth, height / originalHeight);
|
|
202
|
+
|
|
203
|
+
// If image is smaller than target box, we can keep original size (scale = 1) if we don't want to upscale.
|
|
204
|
+
// Generally "compress" implies making smaller or equal.
|
|
205
|
+
// If the image is larger, scale < 1. If smaller, scale >= 1.
|
|
206
|
+
// Let's cap scale at 1 to prevent upscaling unless explicitly desired (usually not for compression).
|
|
207
|
+
const finalScale = Math.min(scale, 1);
|
|
208
|
+
|
|
209
|
+
const resizeWidth = Math.round(originalWidth * finalScale);
|
|
210
|
+
const resizeHeight = Math.round(originalHeight * finalScale);
|
|
211
|
+
|
|
212
|
+
// Start with calculated dimensions and quality
|
|
213
|
+
let result = await ImageManipulator.manipulateAsync(
|
|
214
|
+
uri,
|
|
215
|
+
[{ resize: { width: resizeWidth, height: resizeHeight } }],
|
|
216
|
+
{
|
|
217
|
+
compress: quality,
|
|
218
|
+
format,
|
|
219
|
+
}
|
|
220
|
+
);
|
|
221
|
+
|
|
222
|
+
let fileInfo = await FileSystem.getInfoAsync(result.uri);
|
|
223
|
+
|
|
224
|
+
// If still too large, reduce quality
|
|
225
|
+
if ('size' in fileInfo && fileInfo.size > maxSize) {
|
|
226
|
+
let currentQuality = quality * 0.9;
|
|
227
|
+
|
|
228
|
+
while (
|
|
229
|
+
currentQuality > 0.5 &&
|
|
230
|
+
'size' in fileInfo &&
|
|
231
|
+
fileInfo.size > maxSize
|
|
232
|
+
) {
|
|
233
|
+
result = await ImageManipulator.manipulateAsync(
|
|
234
|
+
uri,
|
|
235
|
+
[{ resize: { width: resizeWidth, height: resizeHeight } }],
|
|
236
|
+
{
|
|
237
|
+
compress: currentQuality,
|
|
238
|
+
format,
|
|
239
|
+
}
|
|
240
|
+
);
|
|
241
|
+
|
|
242
|
+
fileInfo = await FileSystem.getInfoAsync(result.uri);
|
|
243
|
+
if ('size' in fileInfo && fileInfo.size <= maxSize) break;
|
|
244
|
+
|
|
245
|
+
currentQuality -= 0.05;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// If still too large, reduce dimensions
|
|
249
|
+
if ('size' in fileInfo && fileInfo.size > maxSize) {
|
|
250
|
+
const smallerWidth = Math.floor(resizeWidth * 0.75);
|
|
251
|
+
const smallerHeight = Math.floor(resizeHeight * 0.75);
|
|
252
|
+
|
|
253
|
+
result = await ImageManipulator.manipulateAsync(
|
|
254
|
+
uri,
|
|
255
|
+
[{ resize: { width: smallerWidth, height: smallerHeight } }],
|
|
256
|
+
{
|
|
257
|
+
compress: 0.75,
|
|
258
|
+
format,
|
|
259
|
+
}
|
|
260
|
+
);
|
|
261
|
+
fileInfo = await FileSystem.getInfoAsync(result.uri);
|
|
262
|
+
|
|
263
|
+
// Final quality reduction if needed
|
|
264
|
+
if ('size' in fileInfo && fileInfo.size > maxSize) {
|
|
265
|
+
let finalQuality = 0.7;
|
|
266
|
+
while (
|
|
267
|
+
finalQuality > 0.5 &&
|
|
268
|
+
'size' in fileInfo &&
|
|
269
|
+
fileInfo.size > maxSize
|
|
270
|
+
) {
|
|
271
|
+
result = await ImageManipulator.manipulateAsync(
|
|
272
|
+
uri,
|
|
273
|
+
[{ resize: { width: smallerWidth, height: smallerHeight } }],
|
|
274
|
+
{
|
|
275
|
+
compress: finalQuality,
|
|
276
|
+
format,
|
|
277
|
+
}
|
|
278
|
+
);
|
|
279
|
+
fileInfo = await FileSystem.getInfoAsync(result.uri);
|
|
280
|
+
if ('size' in fileInfo && fileInfo.size <= maxSize) break;
|
|
281
|
+
finalQuality -= 0.05;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const finalSize = 'size' in fileInfo ? fileInfo.size : 0;
|
|
288
|
+
const duration = Date.now() - startTime;
|
|
289
|
+
|
|
290
|
+
console.log(`[Native] Image Compression:`, {
|
|
291
|
+
originalSize: `${(originalSize / 1024).toFixed(2)} KB`,
|
|
292
|
+
compressedSize: `${(finalSize / 1024).toFixed(2)} KB`,
|
|
293
|
+
reduction: `${(((originalSize - finalSize) / originalSize) * 100).toFixed(1)}%`,
|
|
294
|
+
duration: `${duration}ms`,
|
|
295
|
+
dimensions: `${resizeWidth}x${resizeHeight}`,
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
return result.uri;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Generate thumbhash from image URI (Native/Mobile)
|
|
303
|
+
*/
|
|
304
|
+
export async function generateThumbhash(
|
|
305
|
+
imageUri: string,
|
|
306
|
+
options: GenerateThumbhashOptions = {}
|
|
307
|
+
) {
|
|
308
|
+
const { size = 32 } = options;
|
|
309
|
+
|
|
310
|
+
// 1. Create tiny PNG
|
|
311
|
+
const tiny = await ImageManipulator.manipulateAsync(
|
|
312
|
+
imageUri,
|
|
313
|
+
[{ resize: { width: size, height: size } }],
|
|
314
|
+
{ format: ImageManipulator.SaveFormat.PNG }
|
|
315
|
+
);
|
|
316
|
+
|
|
317
|
+
// 2. Read as base64
|
|
318
|
+
const base64 = await FileSystem.readAsStringAsync(tiny.uri, {
|
|
319
|
+
encoding: 'base64',
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
// 3. Decode PNG and generate thumbhash
|
|
323
|
+
const UPNG = require('upng-js');
|
|
324
|
+
const buffer = Buffer.from(base64, 'base64');
|
|
325
|
+
const img = UPNG.decode(buffer);
|
|
326
|
+
const rgba = UPNG.toRGBA8(img)[0];
|
|
327
|
+
const hash = rgbaToThumbHash(size, size, new Uint8Array(rgba));
|
|
328
|
+
|
|
329
|
+
// 4. Convert to base64
|
|
330
|
+
return Buffer.from(hash).toString('base64');
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Remove background from image using native ML models
|
|
335
|
+
*
|
|
336
|
+
* @param uri - File path or file:// URI to the source image
|
|
337
|
+
* @param options - Processing options
|
|
338
|
+
* @returns Promise resolving to a URI suitable for use with `<Image>` component.
|
|
339
|
+
* - **iOS/Android**: File path (`file:///path/to/cache/bg_removed_xxx.png`)
|
|
340
|
+
* - **Web**: Data URL (`data:image/png;base64,...`)
|
|
341
|
+
*
|
|
342
|
+
* @throws {BackgroundRemovalError} When image cannot be processed
|
|
343
|
+
*
|
|
344
|
+
* @example
|
|
345
|
+
* ```typescript
|
|
346
|
+
* const result = await removeBgImage('file:///path/to/photo.jpg')
|
|
347
|
+
* // Use directly in Image component
|
|
348
|
+
* <Image source={{ uri: result }} />
|
|
349
|
+
* ```
|
|
350
|
+
*
|
|
351
|
+
* @example
|
|
352
|
+
* ```typescript
|
|
353
|
+
* // With options
|
|
354
|
+
* const result = await removeBgImage('file:///path/to/photo.jpg', {
|
|
355
|
+
* maxDimension: 1024,
|
|
356
|
+
* format: 'WEBP',
|
|
357
|
+
* quality: 90,
|
|
358
|
+
* onProgress: (p) => console.log(`Progress: ${p}%`)
|
|
359
|
+
* })
|
|
360
|
+
* ```
|
|
361
|
+
*/
|
|
362
|
+
export async function removeBgImage(
|
|
363
|
+
uri: string,
|
|
364
|
+
options: RemoveBgImageOptions = {}
|
|
365
|
+
): Promise<string> {
|
|
366
|
+
const startTime = Date.now();
|
|
367
|
+
const opts = { ...DEFAULT_OPTIONS, ...options };
|
|
368
|
+
const { onProgress, debug } = opts;
|
|
369
|
+
|
|
370
|
+
// Validate inputs
|
|
371
|
+
validateImagePath(uri);
|
|
372
|
+
validateOptions(options);
|
|
373
|
+
|
|
374
|
+
if (debug) {
|
|
375
|
+
console.log('[rn-remove-image-bg] Starting background removal:', uri);
|
|
376
|
+
console.log('[rn-remove-image-bg] Options:', opts);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// Report initial progress
|
|
380
|
+
onProgress?.(5);
|
|
381
|
+
|
|
382
|
+
// Check cache if enabled
|
|
383
|
+
if (opts.useCache) {
|
|
384
|
+
const optionsHash = bgRemovalCache.hashOptions({
|
|
385
|
+
maxDimension: opts.maxDimension,
|
|
386
|
+
format: opts.format,
|
|
387
|
+
quality: opts.quality,
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
const cached = await bgRemovalCache.get(uri, optionsHash);
|
|
391
|
+
if (cached) {
|
|
392
|
+
if (debug) {
|
|
393
|
+
console.log('[rn-remove-image-bg] Cache hit:', cached);
|
|
394
|
+
}
|
|
395
|
+
onProgress?.(100);
|
|
396
|
+
return cached.startsWith('file://') ? cached : `file://${cached}`;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
onProgress?.(10);
|
|
401
|
+
|
|
402
|
+
try {
|
|
403
|
+
// Prepare native options
|
|
404
|
+
const nativeOptions: NativeRemoveBackgroundOptions = {
|
|
405
|
+
maxDimension: opts.maxDimension,
|
|
406
|
+
format: opts.format,
|
|
407
|
+
quality: opts.quality,
|
|
408
|
+
};
|
|
409
|
+
|
|
410
|
+
onProgress?.(20);
|
|
411
|
+
|
|
412
|
+
// Call native implementation
|
|
413
|
+
const result = await getNativeRemover().removeBackground(
|
|
414
|
+
uri,
|
|
415
|
+
nativeOptions
|
|
416
|
+
);
|
|
417
|
+
|
|
418
|
+
onProgress?.(90);
|
|
419
|
+
|
|
420
|
+
// Normalize result path
|
|
421
|
+
const resultPath = result.startsWith('file://')
|
|
422
|
+
? result
|
|
423
|
+
: `file://${result}`;
|
|
424
|
+
|
|
425
|
+
// Cache the result
|
|
426
|
+
if (opts.useCache) {
|
|
427
|
+
const optionsHash = bgRemovalCache.hashOptions({
|
|
428
|
+
maxDimension: opts.maxDimension,
|
|
429
|
+
format: opts.format,
|
|
430
|
+
quality: opts.quality,
|
|
431
|
+
});
|
|
432
|
+
bgRemovalCache.set(uri, optionsHash, resultPath);
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
if (debug) {
|
|
436
|
+
console.log(
|
|
437
|
+
'[rn-remove-image-bg] Completed in',
|
|
438
|
+
Date.now() - startTime,
|
|
439
|
+
'ms'
|
|
440
|
+
);
|
|
441
|
+
console.log('[rn-remove-image-bg] Result:', resultPath);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
onProgress?.(100);
|
|
445
|
+
return resultPath;
|
|
446
|
+
} catch (error) {
|
|
447
|
+
if (debug) {
|
|
448
|
+
console.error('[rn-remove-image-bg] Failed:', error);
|
|
449
|
+
}
|
|
450
|
+
throw wrapNativeError(error);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
/**
|
|
455
|
+
* Backward compatibility alias for removeBgImage
|
|
456
|
+
* @deprecated Use removeBgImage instead
|
|
457
|
+
*/
|
|
458
|
+
export const removeBackground = removeBgImage;
|
|
459
|
+
|
|
460
|
+
/**
|
|
461
|
+
* Clear the background removal cache
|
|
462
|
+
* @param deleteFiles - Also delete cached files from disk (default: false)
|
|
463
|
+
*/
|
|
464
|
+
export async function clearCache(deleteFiles = false): Promise<void> {
|
|
465
|
+
await bgRemovalCache.clear(deleteFiles);
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
/**
|
|
469
|
+
* Get the current cache size
|
|
470
|
+
*/
|
|
471
|
+
export function getCacheSize(): number {
|
|
472
|
+
return bgRemovalCache.size;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
/**
|
|
476
|
+
* Handle low memory conditions by clearing the cache
|
|
477
|
+
* Call this when your app receives memory warnings
|
|
478
|
+
*
|
|
479
|
+
* @param deleteFiles - Also delete cached files from disk (default: true)
|
|
480
|
+
* @returns Number of entries that were cleared
|
|
481
|
+
*
|
|
482
|
+
* @example
|
|
483
|
+
* ```typescript
|
|
484
|
+
* import { AppState } from 'react-native'
|
|
485
|
+
* import { onLowMemory } from 'rn-remove-image-bg'
|
|
486
|
+
*
|
|
487
|
+
* // In your app initialization
|
|
488
|
+
* AppState.addEventListener('memoryWarning', () => {
|
|
489
|
+
* onLowMemory()
|
|
490
|
+
* })
|
|
491
|
+
* ```
|
|
492
|
+
*/
|
|
493
|
+
export async function onLowMemory(deleteFiles = true): Promise<number> {
|
|
494
|
+
const size = bgRemovalCache.size;
|
|
495
|
+
await bgRemovalCache.clear(deleteFiles);
|
|
496
|
+
console.log(
|
|
497
|
+
`[rn-remove-image-bg] Cleared ${size} cache entries due to memory pressure`
|
|
498
|
+
);
|
|
499
|
+
return size;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
/**
|
|
503
|
+
* Configure the background removal cache
|
|
504
|
+
* Call this early in your app lifecycle to customize cache behavior
|
|
505
|
+
*
|
|
506
|
+
* @example
|
|
507
|
+
* ```typescript
|
|
508
|
+
* import { configureCache } from 'rn-remove-image-bg'
|
|
509
|
+
*
|
|
510
|
+
* configureCache({
|
|
511
|
+
* maxEntries: 100,
|
|
512
|
+
* maxAgeMinutes: 60,
|
|
513
|
+
* persistToDisk: true
|
|
514
|
+
* })
|
|
515
|
+
* ```
|
|
516
|
+
*/
|
|
517
|
+
export function configureCache(config: {
|
|
518
|
+
maxEntries?: number;
|
|
519
|
+
maxAgeMinutes?: number;
|
|
520
|
+
persistToDisk?: boolean;
|
|
521
|
+
cacheDirectory?: string;
|
|
522
|
+
}): void {
|
|
523
|
+
bgRemovalCache.configure(config);
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
/**
|
|
527
|
+
* Get the cache directory path
|
|
528
|
+
* Useful for debugging or manual cache management
|
|
529
|
+
*/
|
|
530
|
+
export function getCacheDirectory(): string {
|
|
531
|
+
return bgRemovalCache.getCacheDirectory();
|
|
532
|
+
}
|