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.
Files changed (74) hide show
  1. package/NitroRnRemoveImageBg.podspec +33 -0
  2. package/README.md +386 -0
  3. package/android/CMakeLists.txt +28 -0
  4. package/android/build.gradle +142 -0
  5. package/android/fix-prefab.gradle +51 -0
  6. package/android/gradle.properties +5 -0
  7. package/android/src/main/AndroidManifest.xml +3 -0
  8. package/android/src/main/cpp/cpp-adapter.cpp +6 -0
  9. package/android/src/main/java/com/margelo/nitro/rnremoveimagebg/HybridImageBackgroundRemover.kt +189 -0
  10. package/android/src/main/java/com/margelo/nitro/rnremoveimagebg/NitroRnRemoveImageBgPackage.kt +31 -0
  11. package/app.plugin.js +12 -0
  12. package/ios/Bridge.h +8 -0
  13. package/ios/HybridImageBackgroundRemover.swift +224 -0
  14. package/ios/NitroRnRemoveImageBgOnLoad.mm +22 -0
  15. package/lib/ImageProcessing.d.ts +167 -0
  16. package/lib/ImageProcessing.js +323 -0
  17. package/lib/ImageProcessing.web.d.ts +80 -0
  18. package/lib/ImageProcessing.web.js +248 -0
  19. package/lib/__tests__/cache.test.d.ts +1 -0
  20. package/lib/__tests__/cache.test.js +87 -0
  21. package/lib/__tests__/errors.test.d.ts +1 -0
  22. package/lib/__tests__/errors.test.js +82 -0
  23. package/lib/cache.d.ts +72 -0
  24. package/lib/cache.js +228 -0
  25. package/lib/errors.d.ts +20 -0
  26. package/lib/errors.js +64 -0
  27. package/lib/index.d.ts +6 -0
  28. package/lib/index.js +9 -0
  29. package/lib/specs/Example.nitro.d.ts +0 -0
  30. package/lib/specs/Example.nitro.js +2 -0
  31. package/lib/specs/ImageBackgroundRemover.nitro.d.ts +41 -0
  32. package/lib/specs/ImageBackgroundRemover.nitro.js +1 -0
  33. package/nitro.json +17 -0
  34. package/nitrogen/generated/.gitattributes +1 -0
  35. package/nitrogen/generated/android/NitroRnRemoveImageBg+autolinking.cmake +81 -0
  36. package/nitrogen/generated/android/NitroRnRemoveImageBg+autolinking.gradle +27 -0
  37. package/nitrogen/generated/android/NitroRnRemoveImageBgOnLoad.cpp +44 -0
  38. package/nitrogen/generated/android/NitroRnRemoveImageBgOnLoad.hpp +25 -0
  39. package/nitrogen/generated/android/c++/JHybridImageBackgroundRemoverSpec.cpp +72 -0
  40. package/nitrogen/generated/android/c++/JHybridImageBackgroundRemoverSpec.hpp +65 -0
  41. package/nitrogen/generated/android/c++/JNativeRemoveBackgroundOptions.hpp +66 -0
  42. package/nitrogen/generated/android/c++/JOutputFormat.hpp +59 -0
  43. package/nitrogen/generated/android/kotlin/com/margelo/nitro/rnremoveimagebg/HybridImageBackgroundRemoverSpec.kt +58 -0
  44. package/nitrogen/generated/android/kotlin/com/margelo/nitro/rnremoveimagebg/NativeRemoveBackgroundOptions.kt +44 -0
  45. package/nitrogen/generated/android/kotlin/com/margelo/nitro/rnremoveimagebg/NitroRnRemoveImageBgOnLoad.kt +35 -0
  46. package/nitrogen/generated/android/kotlin/com/margelo/nitro/rnremoveimagebg/OutputFormat.kt +21 -0
  47. package/nitrogen/generated/ios/NitroRnRemoveImageBg+autolinking.rb +60 -0
  48. package/nitrogen/generated/ios/NitroRnRemoveImageBg-Swift-Cxx-Bridge.cpp +49 -0
  49. package/nitrogen/generated/ios/NitroRnRemoveImageBg-Swift-Cxx-Bridge.hpp +111 -0
  50. package/nitrogen/generated/ios/NitroRnRemoveImageBg-Swift-Cxx-Umbrella.hpp +51 -0
  51. package/nitrogen/generated/ios/c++/HybridImageBackgroundRemoverSpecSwift.cpp +11 -0
  52. package/nitrogen/generated/ios/c++/HybridImageBackgroundRemoverSpecSwift.hpp +82 -0
  53. package/nitrogen/generated/ios/swift/Func_void_std__exception_ptr.swift +47 -0
  54. package/nitrogen/generated/ios/swift/Func_void_std__string.swift +47 -0
  55. package/nitrogen/generated/ios/swift/HybridImageBackgroundRemoverSpec.swift +56 -0
  56. package/nitrogen/generated/ios/swift/HybridImageBackgroundRemoverSpec_cxx.swift +138 -0
  57. package/nitrogen/generated/ios/swift/NativeRemoveBackgroundOptions.swift +58 -0
  58. package/nitrogen/generated/ios/swift/OutputFormat.swift +40 -0
  59. package/nitrogen/generated/shared/c++/HybridImageBackgroundRemoverSpec.cpp +21 -0
  60. package/nitrogen/generated/shared/c++/HybridImageBackgroundRemoverSpec.hpp +65 -0
  61. package/nitrogen/generated/shared/c++/NativeRemoveBackgroundOptions.hpp +84 -0
  62. package/nitrogen/generated/shared/c++/OutputFormat.hpp +76 -0
  63. package/package.json +104 -0
  64. package/react-native.config.js +16 -0
  65. package/src/ImageProcessing.ts +532 -0
  66. package/src/ImageProcessing.web.ts +342 -0
  67. package/src/__tests__/ImageProcessing.test.ts +278 -0
  68. package/src/__tests__/cache.test.ts +110 -0
  69. package/src/__tests__/errors.test.ts +117 -0
  70. package/src/cache.ts +305 -0
  71. package/src/errors.ts +93 -0
  72. package/src/index.ts +49 -0
  73. package/src/specs/Example.nitro.ts +1 -0
  74. package/src/specs/ImageBackgroundRemover.nitro.ts +49 -0
@@ -0,0 +1,110 @@
1
+ import { describe, it, expect, beforeEach, vi } from 'vitest';
2
+
3
+ // Mock expo-file-system before importing cache
4
+ vi.mock('expo-file-system/legacy', () => ({
5
+ cacheDirectory: '/mock/cache/',
6
+ getInfoAsync: vi.fn().mockResolvedValue({ exists: false }),
7
+ makeDirectoryAsync: vi.fn().mockResolvedValue(undefined),
8
+ readAsStringAsync: vi.fn().mockResolvedValue('{}'),
9
+ writeAsStringAsync: vi.fn().mockResolvedValue(undefined),
10
+ deleteAsync: vi.fn().mockResolvedValue(undefined),
11
+ }));
12
+
13
+ // Import after mocking
14
+ import { bgRemovalCache } from '../cache';
15
+
16
+ describe('BackgroundRemovalCache', () => {
17
+ beforeEach(async () => {
18
+ // Clear cache before each test
19
+ await bgRemovalCache.clear();
20
+ });
21
+
22
+ describe('hashOptions', () => {
23
+ it('should hash options to JSON string', () => {
24
+ const hash = bgRemovalCache.hashOptions({
25
+ format: 'PNG',
26
+ maxDimension: 1024,
27
+ quality: 100,
28
+ });
29
+
30
+ expect(hash).toBe('{"format":"PNG","maxDimension":1024,"quality":100}');
31
+ });
32
+
33
+ it('should produce consistent hash regardless of key order', () => {
34
+ const hash1 = bgRemovalCache.hashOptions({ a: 1, b: 2 });
35
+ const hash2 = bgRemovalCache.hashOptions({ b: 2, a: 1 });
36
+
37
+ expect(hash1).toBe(hash2);
38
+ });
39
+
40
+ it('should exclude functions from hash', () => {
41
+ const hash = bgRemovalCache.hashOptions({
42
+ value: 1,
43
+ callback: () => {},
44
+ });
45
+
46
+ expect(hash).toBe('{"value":1}');
47
+ });
48
+ });
49
+
50
+ describe('size', () => {
51
+ it('should return 0 for empty cache', () => {
52
+ expect(bgRemovalCache.size).toBe(0);
53
+ });
54
+ });
55
+
56
+ describe('set and get', () => {
57
+ it('should store and retrieve cached results', async () => {
58
+ const path = 'file:///test/image.jpg';
59
+ const optionsHash = bgRemovalCache.hashOptions({ format: 'PNG' });
60
+ const resultPath = 'file:///cache/result.png';
61
+
62
+ bgRemovalCache.set(path, optionsHash, resultPath);
63
+
64
+ expect(bgRemovalCache.size).toBe(1);
65
+ });
66
+ });
67
+
68
+ describe('clear', () => {
69
+ it('should clear all cache entries', async () => {
70
+ bgRemovalCache.set('path1', 'hash1', 'result1');
71
+ bgRemovalCache.set('path2', 'hash2', 'result2');
72
+
73
+ expect(bgRemovalCache.size).toBe(2);
74
+
75
+ await bgRemovalCache.clear();
76
+
77
+ expect(bgRemovalCache.size).toBe(0);
78
+ });
79
+ });
80
+
81
+ describe('getCacheDirectory', () => {
82
+ it('should return the cache directory path', () => {
83
+ const dir = bgRemovalCache.getCacheDirectory();
84
+ expect(dir).toContain('bg-removal');
85
+ });
86
+ });
87
+
88
+ describe('configure', () => {
89
+ it('should allow updating cache configuration', () => {
90
+ bgRemovalCache.configure({
91
+ maxEntries: 100,
92
+ maxAgeMinutes: 60,
93
+ persistToDisk: true,
94
+ });
95
+
96
+ // Configuration applied - no error thrown
97
+ expect(true).toBe(true);
98
+ });
99
+ });
100
+
101
+ describe('prune', () => {
102
+ it('should return 0 when no entries are expired', () => {
103
+ bgRemovalCache.set('path', 'hash', 'result');
104
+
105
+ const removed = bgRemovalCache.prune();
106
+
107
+ expect(removed).toBe(0);
108
+ });
109
+ });
110
+ });
@@ -0,0 +1,117 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { BackgroundRemovalError, wrapNativeError } from '../errors';
3
+
4
+ describe('BackgroundRemovalError', () => {
5
+ describe('constructor', () => {
6
+ it('should create an error with message and code', () => {
7
+ const error = new BackgroundRemovalError('Test message', 'INVALID_PATH');
8
+
9
+ expect(error.message).toBe('Test message');
10
+ expect(error.code).toBe('INVALID_PATH');
11
+ expect(error.name).toBe('BackgroundRemovalError');
12
+ expect(error.originalError).toBeUndefined();
13
+ });
14
+
15
+ it('should preserve original error', () => {
16
+ const originalError = new Error('Original error');
17
+ const error = new BackgroundRemovalError(
18
+ 'Wrapped',
19
+ 'UNKNOWN',
20
+ originalError
21
+ );
22
+
23
+ expect(error.originalError).toBe(originalError);
24
+ });
25
+ });
26
+
27
+ describe('toUserMessage', () => {
28
+ it('should return user-friendly message for INVALID_PATH', () => {
29
+ const error = new BackgroundRemovalError('test', 'INVALID_PATH');
30
+ expect(error.toUserMessage()).toBe('The image path provided is invalid.');
31
+ });
32
+
33
+ it('should return user-friendly message for FILE_NOT_FOUND', () => {
34
+ const error = new BackgroundRemovalError('test', 'FILE_NOT_FOUND');
35
+ expect(error.toUserMessage()).toBe('The image file could not be found.');
36
+ });
37
+
38
+ it('should return user-friendly message for DECODE_FAILED', () => {
39
+ const error = new BackgroundRemovalError('test', 'DECODE_FAILED');
40
+ expect(error.toUserMessage()).toBe(
41
+ 'The image could not be read. Please ensure it is a valid image file.'
42
+ );
43
+ });
44
+
45
+ it('should return user-friendly message for ML_PROCESSING_FAILED', () => {
46
+ const error = new BackgroundRemovalError('test', 'ML_PROCESSING_FAILED');
47
+ expect(error.toUserMessage()).toBe(
48
+ 'Background removal failed. Please try with a different image.'
49
+ );
50
+ });
51
+
52
+ it('should return user-friendly message for SAVE_FAILED', () => {
53
+ const error = new BackgroundRemovalError('test', 'SAVE_FAILED');
54
+ expect(error.toUserMessage()).toBe('Could not save the processed image.');
55
+ });
56
+
57
+ it('should return user-friendly message for INVALID_OPTIONS', () => {
58
+ const error = new BackgroundRemovalError('test', 'INVALID_OPTIONS');
59
+ expect(error.toUserMessage()).toBe(
60
+ 'Invalid options provided for background removal.'
61
+ );
62
+ });
63
+
64
+ it('should return default message for UNKNOWN', () => {
65
+ const error = new BackgroundRemovalError('test', 'UNKNOWN');
66
+ expect(error.toUserMessage()).toBe(
67
+ 'An unexpected error occurred during background removal.'
68
+ );
69
+ });
70
+ });
71
+ });
72
+
73
+ describe('wrapNativeError', () => {
74
+ it('should return same error if already BackgroundRemovalError', () => {
75
+ const error = new BackgroundRemovalError('test', 'INVALID_PATH');
76
+ const wrapped = wrapNativeError(error);
77
+
78
+ expect(wrapped).toBe(error);
79
+ });
80
+
81
+ it('should wrap string error', () => {
82
+ const wrapped = wrapNativeError('Something went wrong');
83
+
84
+ expect(wrapped).toBeInstanceOf(BackgroundRemovalError);
85
+ expect(wrapped.message).toBe('Something went wrong');
86
+ expect(wrapped.code).toBe('UNKNOWN');
87
+ });
88
+
89
+ it('should detect FILE_NOT_FOUND from message', () => {
90
+ const error = new Error('File does not exist at path');
91
+ const wrapped = wrapNativeError(error);
92
+
93
+ expect(wrapped.code).toBe('FILE_NOT_FOUND');
94
+ expect(wrapped.originalError).toBe(error);
95
+ });
96
+
97
+ it('should detect DECODE_FAILED from message', () => {
98
+ const error = new Error('Could not decode image');
99
+ const wrapped = wrapNativeError(error);
100
+
101
+ expect(wrapped.code).toBe('DECODE_FAILED');
102
+ });
103
+
104
+ it('should detect ML_PROCESSING_FAILED from message', () => {
105
+ const error = new Error('Failed to generate mask');
106
+ const wrapped = wrapNativeError(error);
107
+
108
+ expect(wrapped.code).toBe('ML_PROCESSING_FAILED');
109
+ });
110
+
111
+ it('should detect SAVE_FAILED from message', () => {
112
+ const error = new Error('Could not save output file');
113
+ const wrapped = wrapNativeError(error);
114
+
115
+ expect(wrapped.code).toBe('SAVE_FAILED');
116
+ });
117
+ });
package/src/cache.ts ADDED
@@ -0,0 +1,305 @@
1
+ import * as FileSystem from 'expo-file-system/legacy';
2
+
3
+ /**
4
+ * Cache entry for processed images
5
+ */
6
+ interface CacheEntry {
7
+ /** Original file path */
8
+ originalPath: string;
9
+ /** Result file path */
10
+ resultPath: string;
11
+ /** Timestamp when cached */
12
+ timestamp: number;
13
+ /** Options hash to differentiate processing configs */
14
+ optionsHash: string;
15
+ }
16
+
17
+ /**
18
+ * Disk cache manifest structure
19
+ */
20
+ interface CacheManifest {
21
+ version: number;
22
+ entries: Record<string, CacheEntry>;
23
+ }
24
+
25
+ /**
26
+ * Configuration for the background removal cache
27
+ */
28
+ export interface CacheConfig {
29
+ /** Maximum number of entries in memory (default: 50) */
30
+ maxEntries?: number;
31
+ /** Maximum age of cache entries in minutes (default: 30) */
32
+ maxAgeMinutes?: number;
33
+ /** Enable disk persistence (default: false) */
34
+ persistToDisk?: boolean;
35
+ /** Custom cache directory (default: FileSystem.cacheDirectory + 'bg-removal/') */
36
+ cacheDirectory?: string;
37
+ }
38
+
39
+ const MANIFEST_FILENAME = 'cache-manifest.json';
40
+ const CACHE_VERSION = 1;
41
+
42
+ /**
43
+ * LRU cache for background removal results with optional disk persistence
44
+ */
45
+ class BackgroundRemovalCache {
46
+ private cache = new Map<string, CacheEntry>();
47
+ private _maxEntries: number;
48
+ private _maxAgeMs: number;
49
+ private persistToDisk: boolean;
50
+ private cacheDirectory: string;
51
+ private initialized = false;
52
+
53
+ constructor(config: CacheConfig = {}) {
54
+ this._maxEntries = config.maxEntries ?? 50;
55
+ this._maxAgeMs = (config.maxAgeMinutes ?? 30) * 60 * 1000;
56
+ this.persistToDisk = config.persistToDisk ?? false;
57
+ this.cacheDirectory =
58
+ config.cacheDirectory ?? `${FileSystem.cacheDirectory}bg-removal/`;
59
+ }
60
+
61
+ /**
62
+ * Configure cache settings
63
+ */
64
+ configure(config: CacheConfig): void {
65
+ if (config.maxEntries !== undefined) {
66
+ this._maxEntries = config.maxEntries;
67
+ }
68
+ if (config.maxAgeMinutes !== undefined) {
69
+ this._maxAgeMs = config.maxAgeMinutes * 60 * 1000;
70
+ }
71
+ if (config.persistToDisk !== undefined) {
72
+ this.persistToDisk = config.persistToDisk;
73
+ }
74
+ if (config.cacheDirectory !== undefined) {
75
+ this.cacheDirectory = config.cacheDirectory;
76
+ }
77
+ }
78
+
79
+ /**
80
+ * Initialize disk cache (load manifest if exists)
81
+ */
82
+ async initialize(): Promise<void> {
83
+ if (this.initialized || !this.persistToDisk) return;
84
+
85
+ try {
86
+ // Ensure cache directory exists
87
+ const dirInfo = await FileSystem.getInfoAsync(this.cacheDirectory);
88
+ if (!dirInfo.exists) {
89
+ await FileSystem.makeDirectoryAsync(this.cacheDirectory, {
90
+ intermediates: true,
91
+ });
92
+ }
93
+
94
+ // Load manifest if exists
95
+ const manifestPath = `${this.cacheDirectory}${MANIFEST_FILENAME}`;
96
+ const manifestInfo = await FileSystem.getInfoAsync(manifestPath);
97
+
98
+ if (manifestInfo.exists) {
99
+ const manifestJson = await FileSystem.readAsStringAsync(manifestPath);
100
+ const manifest: CacheManifest = JSON.parse(manifestJson);
101
+
102
+ if (manifest.version === CACHE_VERSION) {
103
+ // Validate and load entries
104
+ for (const [key, entry] of Object.entries(manifest.entries)) {
105
+ const fileInfo = await FileSystem.getInfoAsync(entry.resultPath);
106
+ if (fileInfo.exists) {
107
+ this.cache.set(key, entry);
108
+ }
109
+ }
110
+ }
111
+ }
112
+ } catch (error) {
113
+ console.warn(
114
+ '[rn-remove-image-bg] Failed to load cache manifest:',
115
+ error
116
+ );
117
+ }
118
+
119
+ this.initialized = true;
120
+ }
121
+
122
+ /**
123
+ * Save cache manifest to disk
124
+ */
125
+ private async saveManifest(): Promise<void> {
126
+ if (!this.persistToDisk) return;
127
+
128
+ try {
129
+ const manifest: CacheManifest = {
130
+ version: CACHE_VERSION,
131
+ entries: Object.fromEntries(this.cache.entries()),
132
+ };
133
+
134
+ const manifestPath = `${this.cacheDirectory}${MANIFEST_FILENAME}`;
135
+ await FileSystem.writeAsStringAsync(
136
+ manifestPath,
137
+ JSON.stringify(manifest, null, 2)
138
+ );
139
+ } catch (error) {
140
+ console.warn(
141
+ '[rn-remove-image-bg] Failed to save cache manifest:',
142
+ error
143
+ );
144
+ }
145
+ }
146
+
147
+ /**
148
+ * Generate a cache key from path and options
149
+ */
150
+ private generateKey(path: string, optionsHash: string): string {
151
+ return `${path}::${optionsHash}`;
152
+ }
153
+
154
+ /**
155
+ * Hash options object to string for cache key
156
+ */
157
+ hashOptions(options: Record<string, unknown>): string {
158
+ const sorted = Object.keys(options)
159
+ .sort()
160
+ .reduce(
161
+ (acc, key) => {
162
+ const value = options[key];
163
+ // Exclude functions from hash
164
+ if (typeof value !== 'function') {
165
+ acc[key] = value;
166
+ }
167
+ return acc;
168
+ },
169
+ {} as Record<string, unknown>
170
+ );
171
+ return JSON.stringify(sorted);
172
+ }
173
+
174
+ /**
175
+ * Get cached result if valid
176
+ */
177
+ async get(path: string, optionsHash: string): Promise<string | null> {
178
+ await this.initialize();
179
+
180
+ const key = this.generateKey(path, optionsHash);
181
+ const entry = this.cache.get(key);
182
+
183
+ if (!entry) {
184
+ return null;
185
+ }
186
+
187
+ // Check if expired
188
+ if (Date.now() - entry.timestamp > this._maxAgeMs) {
189
+ this.cache.delete(key);
190
+ this.saveManifest();
191
+ return null;
192
+ }
193
+
194
+ // Verify result file still exists
195
+ try {
196
+ const info = await FileSystem.getInfoAsync(entry.resultPath);
197
+ if (!info.exists) {
198
+ this.cache.delete(key);
199
+ this.saveManifest();
200
+ return null;
201
+ }
202
+ } catch {
203
+ this.cache.delete(key);
204
+ this.saveManifest();
205
+ return null;
206
+ }
207
+
208
+ // Move to end (most recently used)
209
+ this.cache.delete(key);
210
+ this.cache.set(key, entry);
211
+
212
+ return entry.resultPath;
213
+ }
214
+
215
+ /**
216
+ * Store result in cache
217
+ */
218
+ set(path: string, optionsHash: string, resultPath: string): void {
219
+ const key = this.generateKey(path, optionsHash);
220
+
221
+ // Evict oldest entries if at capacity
222
+ while (this.cache.size >= this._maxEntries) {
223
+ const oldestKey = this.cache.keys().next().value;
224
+ if (oldestKey) {
225
+ this.cache.delete(oldestKey);
226
+ }
227
+ }
228
+
229
+ this.cache.set(key, {
230
+ originalPath: path,
231
+ resultPath,
232
+ timestamp: Date.now(),
233
+ optionsHash,
234
+ });
235
+
236
+ this.saveManifest();
237
+ }
238
+
239
+ /**
240
+ * Clear all cached entries
241
+ * @param deleteFiles - Also delete cached files from disk (default: false)
242
+ */
243
+ async clear(deleteFiles = false): Promise<void> {
244
+ if (deleteFiles && this.persistToDisk) {
245
+ try {
246
+ // Delete all cached result files
247
+ for (const entry of this.cache.values()) {
248
+ try {
249
+ await FileSystem.deleteAsync(entry.resultPath, {
250
+ idempotent: true,
251
+ });
252
+ } catch {
253
+ // Ignore individual file deletion errors
254
+ }
255
+ }
256
+
257
+ // Delete manifest
258
+ const manifestPath = `${this.cacheDirectory}${MANIFEST_FILENAME}`;
259
+ await FileSystem.deleteAsync(manifestPath, { idempotent: true });
260
+ } catch (error) {
261
+ console.warn('[rn-remove-image-bg] Error clearing cache files:', error);
262
+ }
263
+ }
264
+
265
+ this.cache.clear();
266
+ }
267
+
268
+ /**
269
+ * Get current cache size
270
+ */
271
+ get size(): number {
272
+ return this.cache.size;
273
+ }
274
+
275
+ /**
276
+ * Remove expired entries
277
+ */
278
+ prune(): number {
279
+ const now = Date.now();
280
+ let removed = 0;
281
+
282
+ for (const [key, entry] of this.cache.entries()) {
283
+ if (now - entry.timestamp > this._maxAgeMs) {
284
+ this.cache.delete(key);
285
+ removed++;
286
+ }
287
+ }
288
+
289
+ if (removed > 0) {
290
+ this.saveManifest();
291
+ }
292
+
293
+ return removed;
294
+ }
295
+
296
+ /**
297
+ * Get cache directory path
298
+ */
299
+ getCacheDirectory(): string {
300
+ return this.cacheDirectory;
301
+ }
302
+ }
303
+
304
+ // Export singleton instance
305
+ export const bgRemovalCache = new BackgroundRemovalCache();
package/src/errors.ts ADDED
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Error codes for background removal operations
3
+ */
4
+ export type BackgroundRemovalErrorCode =
5
+ | 'INVALID_PATH'
6
+ | 'FILE_NOT_FOUND'
7
+ | 'DECODE_FAILED'
8
+ | 'ML_PROCESSING_FAILED'
9
+ | 'SAVE_FAILED'
10
+ | 'INVALID_OPTIONS'
11
+ | 'UNKNOWN';
12
+
13
+ /**
14
+ * Custom error class for background removal operations
15
+ */
16
+ export class BackgroundRemovalError extends Error {
17
+ readonly code: BackgroundRemovalErrorCode;
18
+ readonly originalError?: Error;
19
+
20
+ constructor(
21
+ message: string,
22
+ code: BackgroundRemovalErrorCode,
23
+ originalError?: Error
24
+ ) {
25
+ super(message);
26
+ this.name = 'BackgroundRemovalError';
27
+ this.code = code;
28
+ this.originalError = originalError;
29
+
30
+ // Maintain proper stack trace in V8 environments
31
+ if ('captureStackTrace' in Error) {
32
+ (Error as any).captureStackTrace(this, BackgroundRemovalError);
33
+ }
34
+ }
35
+
36
+ /**
37
+ * Create a user-friendly error message
38
+ */
39
+ toUserMessage(): string {
40
+ switch (this.code) {
41
+ case 'INVALID_PATH':
42
+ return 'The image path provided is invalid.';
43
+ case 'FILE_NOT_FOUND':
44
+ return 'The image file could not be found.';
45
+ case 'DECODE_FAILED':
46
+ return 'The image could not be read. Please ensure it is a valid image file.';
47
+ case 'ML_PROCESSING_FAILED':
48
+ return 'Background removal failed. Please try with a different image.';
49
+ case 'SAVE_FAILED':
50
+ return 'Could not save the processed image.';
51
+ case 'INVALID_OPTIONS':
52
+ return 'Invalid options provided for background removal.';
53
+ default:
54
+ return 'An unexpected error occurred during background removal.';
55
+ }
56
+ }
57
+ }
58
+
59
+ /**
60
+ * Helper to wrap native errors with proper typing
61
+ */
62
+ export function wrapNativeError(error: unknown): BackgroundRemovalError {
63
+ if (error instanceof BackgroundRemovalError) {
64
+ return error;
65
+ }
66
+
67
+ const message = error instanceof Error ? error.message : String(error);
68
+ const originalError = error instanceof Error ? error : undefined;
69
+
70
+ // Try to determine error code from message
71
+ if (message.includes('does not exist') || message.includes('not found')) {
72
+ return new BackgroundRemovalError(message, 'FILE_NOT_FOUND', originalError);
73
+ }
74
+ if (message.includes('decode') || message.includes('load image')) {
75
+ return new BackgroundRemovalError(message, 'DECODE_FAILED', originalError);
76
+ }
77
+ if (
78
+ message.includes('mask') ||
79
+ message.includes('segment') ||
80
+ message.includes('ML')
81
+ ) {
82
+ return new BackgroundRemovalError(
83
+ message,
84
+ 'ML_PROCESSING_FAILED',
85
+ originalError
86
+ );
87
+ }
88
+ if (message.includes('save') || message.includes('write')) {
89
+ return new BackgroundRemovalError(message, 'SAVE_FAILED', originalError);
90
+ }
91
+
92
+ return new BackgroundRemovalError(message, 'UNKNOWN', originalError);
93
+ }
package/src/index.ts ADDED
@@ -0,0 +1,49 @@
1
+ import {
2
+ compressImage,
3
+ generateThumbhash,
4
+ removeBgImage,
5
+ removeBackground,
6
+ clearCache,
7
+ getCacheSize,
8
+ onLowMemory,
9
+ configureCache,
10
+ getCacheDirectory,
11
+ } from './ImageProcessing';
12
+ import type {
13
+ CompressImageOptions,
14
+ GenerateThumbhashOptions,
15
+ RemoveBgImageOptions,
16
+ OutputFormat,
17
+ } from './ImageProcessing';
18
+ import {
19
+ BackgroundRemovalError,
20
+ type BackgroundRemovalErrorCode,
21
+ } from './errors';
22
+ import type { CacheConfig } from './cache';
23
+
24
+ export {
25
+ // Functions
26
+ compressImage,
27
+ generateThumbhash,
28
+ removeBgImage,
29
+ removeBackground,
30
+ clearCache,
31
+ getCacheSize,
32
+ // Memory management
33
+ onLowMemory,
34
+ configureCache,
35
+ getCacheDirectory,
36
+ // Errors
37
+ BackgroundRemovalError,
38
+ };
39
+
40
+ export type {
41
+ // Options types
42
+ CompressImageOptions,
43
+ GenerateThumbhashOptions,
44
+ RemoveBgImageOptions,
45
+ OutputFormat,
46
+ BackgroundRemovalErrorCode,
47
+ // Cache types
48
+ CacheConfig,
49
+ };
@@ -0,0 +1 @@
1
+ // TODO: Export specs that extend HybridObject<...> here