rn-remove-image-bg 0.0.22 → 0.0.24
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 +38 -12
- package/lib/ImageProcessing.web.d.ts +18 -25
- package/lib/ImageProcessing.web.js +67 -103
- package/lib/web/core/BackgroundRemover.d.ts +8 -0
- package/lib/web/core/BackgroundRemover.js +33 -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 +32 -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 +18 -17
- package/src/ImageProcessing.web.ts +94 -149
- package/src/web/core/BackgroundRemover.ts +39 -0
- package/src/web/core/CacheManager.ts +76 -0
- package/src/web/core/types.ts +56 -0
- package/src/web/errors/WebErrorAdapter.ts +44 -0
- package/src/web/utils/CompressImage.ts +38 -0
- package/src/web/utils/ThumbhashGenerator.ts +40 -0
- package/src/web/utils/formatConverter.ts +17 -0
- package/src/web/utils/uriHelper.ts +51 -0
package/README.md
CHANGED
|
@@ -324,22 +324,48 @@ try {
|
|
|
324
324
|
|
|
325
325
|
> **First-time Use**: On Android, the ML Kit model downloads automatically on first use. The library waits for the download to complete (up to ~15 seconds with retries) before processing. Subsequent calls are instant.
|
|
326
326
|
|
|
327
|
-
### Web
|
|
327
|
+
### Web (React Native Web)
|
|
328
328
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
329
|
+
This package supports **React Native Web** via the `@imgly/background-removal` library.
|
|
330
|
+
|
|
331
|
+
#### Setup
|
|
332
|
+
|
|
333
|
+
No manual setup is required! The library is installed automatically via npm and works with Metro bundler out of the box.
|
|
334
|
+
|
|
335
|
+
#### Usage
|
|
336
|
+
|
|
337
|
+
Use `removeBgImage` exactly like on native platforms.
|
|
338
|
+
|
|
339
|
+
```typescript
|
|
340
|
+
import { removeBgImage } from 'rn-remove-image-bg';
|
|
341
|
+
|
|
342
|
+
const result = await removeBgImage(uri, {
|
|
343
|
+
maxDimension: 1024,
|
|
344
|
+
onProgress: (p) => console.log(p),
|
|
345
|
+
// Web-specific: Path to custom hosted models (optional)
|
|
346
|
+
// If omitted, models are downloaded from @imgly CDN (~30MB)
|
|
347
|
+
publicPath: '/assets/models/'
|
|
348
|
+
});
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
#### Asset Handling & Offline Support
|
|
352
|
+
|
|
353
|
+
By default, the library downloads AI models (~30MB) from the **Internet (CDN)** on the first run and caches them in the browser (IndexedDB). This keeps your app bundle small (~2MB).
|
|
354
|
+
|
|
355
|
+
**For Offline Support / Self-Hosting:**
|
|
356
|
+
1. Copy the `.wasm` and `.onnx` files from `node_modules/@imgly/background-removal/dist/` to your web `public/models` folder.
|
|
357
|
+
2. Pass `publicPath: '/models/'` (or your chosen path) in the options.
|
|
358
|
+
|
|
359
|
+
#### Compatibility
|
|
333
360
|
|
|
334
|
-
|
|
361
|
+
- **Browsers**: Chrome, Edge, Firefox, Safari (versions with WebGPU or WebGL2 support).
|
|
362
|
+
- **Bundlers**: Works with **Metro** (Expo) out of the box.
|
|
363
|
+
- **CORS**: If loading images from remote URLs, ensure they have `Access-Control-Allow-Origin: *` headers.
|
|
335
364
|
|
|
336
|
-
|
|
337
|
-
- **Performance**: Zero UI blocking.
|
|
338
|
-
- **Output**: Data URL (`data:image/png;base64,...`) or WEBP Blob.
|
|
339
|
-
- **Requirements**:
|
|
340
|
-
- **CORS**: Images loaded from external URLs must be CORS-enabled (`Access-Control-Allow-Origin: *`).
|
|
365
|
+
#### Troubleshooting (Web)
|
|
341
366
|
|
|
342
|
-
|
|
367
|
+
- **"Resource not found"**: If you see 404s for `.wasm` files, ensure you have internet access (for CDN) or have correctly configured `publicPath`.
|
|
368
|
+
- **CORS Errors**: "Tainted canvas" or "SecurityError" means the image server didn't send CORS headers. Proxy the image or configure your S3 bucket/server.
|
|
343
369
|
|
|
344
370
|
---
|
|
345
371
|
|
|
@@ -1,32 +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;
|
|
1
4
|
/**
|
|
2
|
-
*
|
|
3
|
-
* Requires the script to be added manually to index.html.
|
|
5
|
+
* Remove background from image (Web Implementation)
|
|
4
6
|
*/
|
|
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
|
-
}
|
|
12
7
|
export declare function removeBgImage(uri: string, options?: RemoveBgImageOptions): Promise<string>;
|
|
8
|
+
/**
|
|
9
|
+
* Backward compatibility alias
|
|
10
|
+
* @deprecated Use removeBgImage
|
|
11
|
+
*/
|
|
13
12
|
export declare const removeBackground: typeof removeBgImage;
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
height?: number;
|
|
18
|
-
quality?: number;
|
|
19
|
-
format?: 'webp' | 'png' | 'jpeg';
|
|
20
|
-
}
|
|
21
|
-
export interface GenerateThumbhashOptions {
|
|
22
|
-
size?: number;
|
|
23
|
-
}
|
|
13
|
+
/**
|
|
14
|
+
* Compress image (Web Implementation)
|
|
15
|
+
*/
|
|
24
16
|
export declare function compressImage(uri: string, options?: CompressImageOptions): Promise<string>;
|
|
25
|
-
|
|
26
|
-
|
|
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>;
|
|
27
22
|
export declare function getCacheSize(): number;
|
|
28
|
-
export declare function onLowMemory(): Promise<number>;
|
|
29
|
-
export declare function configureCache(_config:
|
|
30
|
-
maxEntries?: number;
|
|
31
|
-
}): void;
|
|
23
|
+
export declare function onLowMemory(_deleteFiles?: boolean): Promise<number>;
|
|
24
|
+
export declare function configureCache(_config: Record<string, unknown>): void;
|
|
32
25
|
export declare function getCacheDirectory(): string;
|
|
@@ -1,116 +1,80 @@
|
|
|
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';
|
|
1
6
|
/**
|
|
2
|
-
*
|
|
3
|
-
* Requires the script to be added manually to index.html.
|
|
7
|
+
* Remove background from image (Web Implementation)
|
|
4
8
|
*/
|
|
5
9
|
export async function removeBgImage(uri, options = {}) {
|
|
6
|
-
const { onProgress, debug = false } = options;
|
|
7
|
-
// Safety check
|
|
8
|
-
if (typeof imglyBackgroundRemoval === 'undefined') {
|
|
9
|
-
throw new Error('[rn-remove-image-bg] Library not found. Please add the following script to your web index.html:\n' +
|
|
10
|
-
'<script src="https://cdn.jsdelivr.net/npm/@imgly/background-removal@1.7.0/dist/imgly-background-removal.min.js"></script>');
|
|
11
|
-
}
|
|
10
|
+
const { onProgress, useCache = true, debug = false } = options;
|
|
12
11
|
if (debug)
|
|
13
|
-
console.log('[
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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);
|
|
25
31
|
}
|
|
26
|
-
};
|
|
27
|
-
const blob = await imglyBackgroundRemoval.removeBackground(uri, config);
|
|
28
|
-
onProgress?.(100);
|
|
29
|
-
// Convert blob to DataURL
|
|
30
|
-
return new Promise((resolve, reject) => {
|
|
31
|
-
const reader = new FileReader();
|
|
32
|
-
reader.onloadend = () => resolve(reader.result);
|
|
33
|
-
reader.onerror = reject;
|
|
34
|
-
reader.readAsDataURL(blob);
|
|
35
32
|
});
|
|
33
|
+
// 3. Convert to Data URL
|
|
34
|
+
const dataUrl = await blobToDataUrl(blob);
|
|
35
|
+
onProgress?.(100);
|
|
36
|
+
// 4. Cache Result
|
|
37
|
+
if (useCache) {
|
|
38
|
+
cacheManager.set(uri, options, dataUrl);
|
|
39
|
+
}
|
|
40
|
+
return dataUrl;
|
|
36
41
|
}
|
|
42
|
+
/**
|
|
43
|
+
* Backward compatibility alias
|
|
44
|
+
* @deprecated Use removeBgImage
|
|
45
|
+
*/
|
|
37
46
|
export const removeBackground = removeBgImage;
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
// Helper to load image
|
|
42
|
-
function loadImage(src) {
|
|
43
|
-
return new Promise((resolve, reject) => {
|
|
44
|
-
const img = new Image();
|
|
45
|
-
img.crossOrigin = 'anonymous';
|
|
46
|
-
img.onload = () => resolve(img);
|
|
47
|
-
img.onerror = reject;
|
|
48
|
-
img.src = src;
|
|
49
|
-
});
|
|
50
|
-
}
|
|
47
|
+
/**
|
|
48
|
+
* Compress image (Web Implementation)
|
|
49
|
+
*/
|
|
51
50
|
export async function compressImage(uri, options = {}) {
|
|
52
|
-
|
|
53
|
-
try {
|
|
54
|
-
const img = await loadImage(uri);
|
|
55
|
-
const scale = Math.min(width / img.width, height / img.height, 1);
|
|
56
|
-
const targetWidth = Math.round(img.width * scale);
|
|
57
|
-
const targetHeight = Math.round(img.height * scale);
|
|
58
|
-
const canvas = document.createElement('canvas');
|
|
59
|
-
canvas.width = targetWidth;
|
|
60
|
-
canvas.height = targetHeight;
|
|
61
|
-
const ctx = canvas.getContext('2d');
|
|
62
|
-
if (!ctx)
|
|
63
|
-
throw new Error('Could not get canvas context');
|
|
64
|
-
ctx.drawImage(img, 0, 0, targetWidth, targetHeight);
|
|
65
|
-
const mimeType = format === 'png' ? 'image/png' : format === 'jpeg' ? 'image/jpeg' : 'image/webp';
|
|
66
|
-
let dataUrl = canvas.toDataURL(mimeType, quality);
|
|
67
|
-
// Reduce quality if over size limit
|
|
68
|
-
let currentQuality = quality;
|
|
69
|
-
const getSize = (url) => ((url.split(',')[1] || '').length * 3) / 4 / 1024;
|
|
70
|
-
while (getSize(dataUrl) > maxSizeKB && currentQuality > 0.5) {
|
|
71
|
-
currentQuality -= 0.1;
|
|
72
|
-
dataUrl = canvas.toDataURL(mimeType, currentQuality);
|
|
73
|
-
}
|
|
74
|
-
return dataUrl;
|
|
75
|
-
}
|
|
76
|
-
catch (error) {
|
|
77
|
-
console.warn('[rmbg] compressImage failed:', error);
|
|
78
|
-
return uri;
|
|
79
|
-
}
|
|
51
|
+
return compressImageWeb(uri, options);
|
|
80
52
|
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
canvas.height = size;
|
|
88
|
-
const ctx = canvas.getContext('2d');
|
|
89
|
-
if (!ctx)
|
|
90
|
-
throw new Error('Could not get canvas context');
|
|
91
|
-
ctx.drawImage(img, 0, 0, size, size);
|
|
92
|
-
const imageData = ctx.getImageData(0, 0, size, size);
|
|
93
|
-
// Load thumbhash from CDN
|
|
94
|
-
// @ts-ignore
|
|
95
|
-
const { rgbaToThumbHash } = await import(/* webpackIgnore: true */ 'https://cdn.jsdelivr.net/npm/thumbhash@0.1/+esm');
|
|
96
|
-
const hash = rgbaToThumbHash(size, size, imageData.data);
|
|
97
|
-
return btoa(String.fromCharCode(...hash));
|
|
98
|
-
}
|
|
99
|
-
catch (error) {
|
|
100
|
-
console.warn('[rmbg] generateThumbhash failed:', error);
|
|
101
|
-
return '';
|
|
102
|
-
}
|
|
53
|
+
/**
|
|
54
|
+
* Generate thumbhash (Web Implementation)
|
|
55
|
+
*/
|
|
56
|
+
export async function generateThumbhash(uri, _options = {}) {
|
|
57
|
+
// Web implementation currently doesn't use options (size hardcoded or auto-scaled)
|
|
58
|
+
return generateThumbhashWeb(uri);
|
|
103
59
|
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
60
|
+
// Cache Management APIs
|
|
61
|
+
export async function clearCache(_deleteFiles = false) {
|
|
62
|
+
cacheManager.clear();
|
|
63
|
+
console.log('[Web] Cache cleared');
|
|
64
|
+
}
|
|
65
|
+
export function getCacheSize() {
|
|
66
|
+
return cacheManager.size();
|
|
67
|
+
}
|
|
68
|
+
export async function onLowMemory(_deleteFiles = true) {
|
|
69
|
+
const size = cacheManager.size();
|
|
70
|
+
cacheManager.clear();
|
|
71
|
+
console.log(`[Web] Cleared ${size} items due to low memory`);
|
|
72
|
+
return size;
|
|
73
|
+
}
|
|
74
|
+
export function configureCache(_config) {
|
|
75
|
+
// Web cache is simple in-memory LRU, config not fully supported yet but stubbed
|
|
76
|
+
console.log('[Web] Cache configuration updated (no-op on web)');
|
|
109
77
|
}
|
|
110
|
-
export function
|
|
111
|
-
|
|
112
|
-
await clearCache();
|
|
113
|
-
return 0;
|
|
78
|
+
export function getCacheDirectory() {
|
|
79
|
+
return ''; // No file system on web
|
|
114
80
|
}
|
|
115
|
-
export function configureCache(_config) { }
|
|
116
|
-
export function getCacheDirectory() { return ''; }
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { RemoveBgImageOptions } from './types';
|
|
2
|
+
export declare const BackgroundRemover: {
|
|
3
|
+
/**
|
|
4
|
+
* Removes background from an image.
|
|
5
|
+
* Returns a Blobl of the processed image (PNG).
|
|
6
|
+
*/
|
|
7
|
+
remove(uri: string, options: RemoveBgImageOptions): Promise<Blob>;
|
|
8
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { removeBackground as imglyRemove } from '@imgly/background-removal';
|
|
2
|
+
import { mapErrorToBackgroundRemovalError } from '../errors/WebErrorAdapter';
|
|
3
|
+
import { normalizeUri } from '../utils/uriHelper';
|
|
4
|
+
export const BackgroundRemover = {
|
|
5
|
+
/**
|
|
6
|
+
* Removes background from an image.
|
|
7
|
+
* Returns a Blobl of the processed image (PNG).
|
|
8
|
+
*/
|
|
9
|
+
async remove(uri, options) {
|
|
10
|
+
try {
|
|
11
|
+
const normalizedUri = await normalizeUri(uri);
|
|
12
|
+
const config = {
|
|
13
|
+
// Pass publicPath if provided (for self-hosted assets)
|
|
14
|
+
publicPath: options.publicPath,
|
|
15
|
+
// Map progress callback
|
|
16
|
+
progress: (_key, current, total) => {
|
|
17
|
+
if (options.onProgress && total > 0) {
|
|
18
|
+
const p = Math.min(100, Math.round((current / total) * 100));
|
|
19
|
+
options.onProgress(p);
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
// Enable debug logging if requested
|
|
23
|
+
debug: options.debug ?? false,
|
|
24
|
+
};
|
|
25
|
+
// Execute removal
|
|
26
|
+
const blob = await imglyRemove(normalizedUri, config);
|
|
27
|
+
return blob;
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
throw mapErrorToBackgroundRemovalError(error);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { RemoveBgImageOptions } from './types';
|
|
2
|
+
export declare class CacheManager {
|
|
3
|
+
private cache;
|
|
4
|
+
private readonly MAX_SIZE;
|
|
5
|
+
constructor();
|
|
6
|
+
/**
|
|
7
|
+
* Generates a unique cache key based on input URI and processing options
|
|
8
|
+
*/
|
|
9
|
+
private generateKey;
|
|
10
|
+
get(uri: string, options: RemoveBgImageOptions): string | null;
|
|
11
|
+
set(uri: string, options: RemoveBgImageOptions, dataUrl: string): void;
|
|
12
|
+
clear(): void;
|
|
13
|
+
size(): number;
|
|
14
|
+
private evictOldest;
|
|
15
|
+
}
|
|
16
|
+
export declare const cacheManager: CacheManager;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
export class CacheManager {
|
|
2
|
+
cache;
|
|
3
|
+
MAX_SIZE = 50; // Limit to 50 items to avoid memory leaks
|
|
4
|
+
constructor() {
|
|
5
|
+
this.cache = new Map();
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Generates a unique cache key based on input URI and processing options
|
|
9
|
+
*/
|
|
10
|
+
generateKey(uri, options) {
|
|
11
|
+
// We include relevant options that affect output
|
|
12
|
+
const { format = 'PNG', quality = 100, maxDimension = 0 } = options;
|
|
13
|
+
return `${uri}|${format}|${quality}|${maxDimension}`;
|
|
14
|
+
}
|
|
15
|
+
get(uri, options) {
|
|
16
|
+
const key = this.generateKey(uri, options);
|
|
17
|
+
const entry = this.cache.get(key);
|
|
18
|
+
if (entry) {
|
|
19
|
+
entry.timestamp = Date.now(); // Update usage timestamp (simple LRU)
|
|
20
|
+
return entry.dataUrl;
|
|
21
|
+
}
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
set(uri, options, dataUrl) {
|
|
25
|
+
const key = this.generateKey(uri, options);
|
|
26
|
+
// Evict if full
|
|
27
|
+
if (this.cache.size >= this.MAX_SIZE) {
|
|
28
|
+
this.evictOldest();
|
|
29
|
+
}
|
|
30
|
+
this.cache.set(key, {
|
|
31
|
+
dataUrl,
|
|
32
|
+
timestamp: Date.now()
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
clear() {
|
|
36
|
+
this.cache.clear();
|
|
37
|
+
}
|
|
38
|
+
size() {
|
|
39
|
+
return this.cache.size;
|
|
40
|
+
}
|
|
41
|
+
evictOldest() {
|
|
42
|
+
// Find oldest entry
|
|
43
|
+
let oldestKey = null;
|
|
44
|
+
let oldestTime = Infinity;
|
|
45
|
+
for (const [key, entry] of this.cache.entries()) {
|
|
46
|
+
if (entry.timestamp < oldestTime) {
|
|
47
|
+
oldestTime = entry.timestamp;
|
|
48
|
+
oldestKey = key;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
if (oldestKey) {
|
|
52
|
+
this.cache.delete(oldestKey);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
// Singleton instance
|
|
57
|
+
export const cacheManager = new CacheManager();
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export type OutputFormat = 'PNG' | 'WEBP';
|
|
2
|
+
export interface RemoveBgImageOptions {
|
|
3
|
+
/**
|
|
4
|
+
* Output format of the processed image.
|
|
5
|
+
* Default: 'PNG'
|
|
6
|
+
*/
|
|
7
|
+
format?: OutputFormat;
|
|
8
|
+
/**
|
|
9
|
+
* Quality of the output image (0-100). Only applies to WEBP/JPEG.
|
|
10
|
+
* Default: 100
|
|
11
|
+
*/
|
|
12
|
+
quality?: number;
|
|
13
|
+
/**
|
|
14
|
+
* Callback to track download and processing progress (0-100).
|
|
15
|
+
*/
|
|
16
|
+
onProgress?: (progress: number) => void;
|
|
17
|
+
/**
|
|
18
|
+
* Enable debug logging.
|
|
19
|
+
* Default: false
|
|
20
|
+
*/
|
|
21
|
+
debug?: boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Maximum dimension for the output image. Use this to resize large images before processing.
|
|
24
|
+
*/
|
|
25
|
+
maxDimension?: number;
|
|
26
|
+
/**
|
|
27
|
+
* Public path to serve the model assets from.
|
|
28
|
+
* If not provided, it will attempt to fetch from @imgly CDN.
|
|
29
|
+
* Important for Metro bundler compatibility if not using CDN.
|
|
30
|
+
*/
|
|
31
|
+
publicPath?: string;
|
|
32
|
+
/**
|
|
33
|
+
* Whether to use the cache for this request.
|
|
34
|
+
* Default: true
|
|
35
|
+
*/
|
|
36
|
+
useCache?: boolean;
|
|
37
|
+
}
|
|
38
|
+
export interface CompressImageOptions {
|
|
39
|
+
maxSizeKB?: number;
|
|
40
|
+
width?: number;
|
|
41
|
+
height?: number;
|
|
42
|
+
quality?: number;
|
|
43
|
+
format?: 'webp' | 'png' | 'jpeg';
|
|
44
|
+
}
|
|
45
|
+
export interface GenerateThumbhashOptions {
|
|
46
|
+
size?: number;
|
|
47
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare class BackgroundRemovalError extends Error {
|
|
2
|
+
code: string;
|
|
3
|
+
constructor(message: string, code?: string);
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Maps library errors to standardized BackgroundRemovalError
|
|
7
|
+
*/
|
|
8
|
+
export declare function mapErrorToBackgroundRemovalError(error: unknown): BackgroundRemovalError;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export class BackgroundRemovalError extends Error {
|
|
2
|
+
code;
|
|
3
|
+
constructor(message, code = 'UNKNOWN_ERROR') {
|
|
4
|
+
super(message);
|
|
5
|
+
this.name = 'BackgroundRemovalError';
|
|
6
|
+
this.code = code;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Maps library errors to standardized BackgroundRemovalError
|
|
11
|
+
*/
|
|
12
|
+
export function mapErrorToBackgroundRemovalError(error) {
|
|
13
|
+
if (error instanceof BackgroundRemovalError)
|
|
14
|
+
return error;
|
|
15
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
16
|
+
// Model loading errors
|
|
17
|
+
if (message.includes('fetch') || message.includes('network') || message.includes('Failed to load resource')) {
|
|
18
|
+
return new BackgroundRemovalError(`Failed to download AI model. Please check your internet connection. (Details: ${message})`, 'MODEL_DOWNLOAD_ERROR');
|
|
19
|
+
}
|
|
20
|
+
// WASM errors
|
|
21
|
+
if (message.includes('wasm') || message.includes('WebAssembly')) {
|
|
22
|
+
return new BackgroundRemovalError(`WebAssembly failed to initialize. Your browser might not support it. (Details: ${message})`, 'WASM_INIT_ERROR');
|
|
23
|
+
}
|
|
24
|
+
// Processing errors
|
|
25
|
+
if (message.includes('memory') || message.includes('allocation')) {
|
|
26
|
+
return new BackgroundRemovalError('Out of memory. Try using a smaller maxDimension or closing other tabs.', 'MEMORY_ERROR');
|
|
27
|
+
}
|
|
28
|
+
return new BackgroundRemovalError(message);
|
|
29
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { loadImage } from './uriHelper';
|
|
2
|
+
export async function compressImage(uri, options) {
|
|
3
|
+
const img = await loadImage(uri);
|
|
4
|
+
let { width, height, quality = 0.8, format = 'jpeg' } = options;
|
|
5
|
+
// Default dimensions to original if not specified
|
|
6
|
+
if (!width)
|
|
7
|
+
width = img.naturalWidth;
|
|
8
|
+
if (!height)
|
|
9
|
+
height = img.naturalHeight;
|
|
10
|
+
// Calculate aspect ratio if one dimension is missing (though simpler to just use natural if both default)
|
|
11
|
+
const ratio = img.naturalWidth / img.naturalHeight;
|
|
12
|
+
if (options.width && !options.height)
|
|
13
|
+
height = Math.round(width / ratio);
|
|
14
|
+
if (options.height && !options.width)
|
|
15
|
+
width = Math.round(height * ratio);
|
|
16
|
+
// Normalize format
|
|
17
|
+
const mimeType = format === 'png' ? 'image/png' : format === 'webp' ? 'image/webp' : 'image/jpeg';
|
|
18
|
+
// Create canvas
|
|
19
|
+
const canvas = document.createElement('canvas');
|
|
20
|
+
canvas.width = width;
|
|
21
|
+
canvas.height = height;
|
|
22
|
+
const ctx = canvas.getContext('2d');
|
|
23
|
+
if (!ctx) {
|
|
24
|
+
throw new Error('Canvas 2D context not available');
|
|
25
|
+
}
|
|
26
|
+
// Draw image
|
|
27
|
+
ctx.drawImage(img, 0, 0, width, height);
|
|
28
|
+
// Export
|
|
29
|
+
// Note: quality (0-1) is ignored for PNG
|
|
30
|
+
const dataUrl = canvas.toDataURL(mimeType, quality);
|
|
31
|
+
return dataUrl;
|
|
32
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function generateThumbhash(uri: string): Promise<string>;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import * as ThumbHash from 'thumbhash';
|
|
2
|
+
import { loadImage } from './uriHelper';
|
|
3
|
+
export async function generateThumbhash(uri) {
|
|
4
|
+
const img = await loadImage(uri);
|
|
5
|
+
// Thumbhash works best with images < 100x100
|
|
6
|
+
const maxSize = 100;
|
|
7
|
+
let width = img.naturalWidth;
|
|
8
|
+
let height = img.naturalHeight;
|
|
9
|
+
const scale = Math.min(maxSize / width, maxSize / height);
|
|
10
|
+
if (scale < 1) {
|
|
11
|
+
width = Math.round(width * scale);
|
|
12
|
+
height = Math.round(height * scale);
|
|
13
|
+
}
|
|
14
|
+
const canvas = document.createElement('canvas');
|
|
15
|
+
canvas.width = width;
|
|
16
|
+
canvas.height = height;
|
|
17
|
+
const ctx = canvas.getContext('2d');
|
|
18
|
+
if (!ctx) {
|
|
19
|
+
throw new Error('Canvas 2D context not available');
|
|
20
|
+
}
|
|
21
|
+
ctx.drawImage(img, 0, 0, width, height);
|
|
22
|
+
// Get RGBA data
|
|
23
|
+
const imageData = ctx.getImageData(0, 0, width, height);
|
|
24
|
+
const rgba = imageData.data;
|
|
25
|
+
// Generate binary hash
|
|
26
|
+
const hash = ThumbHash.rgbaToThumbHash(width, height, rgba);
|
|
27
|
+
// Convert to base64 using browser API
|
|
28
|
+
// hash is Uint8Array, spread into String.fromCharCode is safe for small thumbhashes (~30 bytes)
|
|
29
|
+
const binary = String.fromCharCode(...hash);
|
|
30
|
+
return window.btoa(binary);
|
|
31
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Converts a Blob to a Data URL string (base64)
|
|
3
|
+
*/
|
|
4
|
+
export function blobToDataUrl(blob) {
|
|
5
|
+
return new Promise((resolve, reject) => {
|
|
6
|
+
const reader = new FileReader();
|
|
7
|
+
reader.onload = () => {
|
|
8
|
+
if (typeof reader.result === 'string') {
|
|
9
|
+
resolve(reader.result);
|
|
10
|
+
}
|
|
11
|
+
else {
|
|
12
|
+
reject(new Error('Failed to convert blob to data URL'));
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
reader.onerror = () => reject(reader.error);
|
|
16
|
+
reader.readAsDataURL(blob);
|
|
17
|
+
});
|
|
18
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Helper to normalize React Native URIs for Web consumption.
|
|
3
|
+
* Handles file://, asset://, and data: URIs.
|
|
4
|
+
*/
|
|
5
|
+
export declare function normalizeUri(uri: string): Promise<string>;
|
|
6
|
+
/**
|
|
7
|
+
* Loads an image from a URI into an HTMLImageElement for processing.
|
|
8
|
+
* Handles CORS cross-origin issues.
|
|
9
|
+
*/
|
|
10
|
+
export declare function loadImage(uri: string): Promise<HTMLImageElement>;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Helper to normalize React Native URIs for Web consumption.
|
|
3
|
+
* Handles file://, asset://, and data: URIs.
|
|
4
|
+
*/
|
|
5
|
+
export async function normalizeUri(uri) {
|
|
6
|
+
if (!uri)
|
|
7
|
+
return '';
|
|
8
|
+
// Handle data URIs (return as is)
|
|
9
|
+
if (uri.startsWith('data:')) {
|
|
10
|
+
return uri;
|
|
11
|
+
}
|
|
12
|
+
// Handle http/https (return as is)
|
|
13
|
+
if (uri.startsWith('http')) {
|
|
14
|
+
return uri;
|
|
15
|
+
}
|
|
16
|
+
// Handle Expo asset:// URIs (convert to http relative path if needed, or pass through)
|
|
17
|
+
// In Expo Web, bundled assets are usually served from /assets/
|
|
18
|
+
if (uri.startsWith('asset://')) {
|
|
19
|
+
// NOTE: complex asset:// handling might require expo-asset,
|
|
20
|
+
// but often on web the uri passed is already resolved or a relative path.
|
|
21
|
+
// For now, we return it. If it fails, we might need 'expo-asset' module.
|
|
22
|
+
return uri.replace('asset://', '/assets/');
|
|
23
|
+
}
|
|
24
|
+
// Handle file:// URIs
|
|
25
|
+
// On web, file:// is blocked for security unless it's a blob url created by the app
|
|
26
|
+
if (uri.startsWith('file://')) {
|
|
27
|
+
// If it's a local file picked by user, it might be a blob: reference in disguise or invalid.
|
|
28
|
+
// We strip the protocol for relative paths check.
|
|
29
|
+
return uri;
|
|
30
|
+
}
|
|
31
|
+
// Relative paths
|
|
32
|
+
return uri;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Loads an image from a URI into an HTMLImageElement for processing.
|
|
36
|
+
* Handles CORS cross-origin issues.
|
|
37
|
+
*/
|
|
38
|
+
export function loadImage(uri) {
|
|
39
|
+
return new Promise((resolve, reject) => {
|
|
40
|
+
const img = new Image();
|
|
41
|
+
img.crossOrigin = 'Anonymous'; // Enable CORS
|
|
42
|
+
img.onload = () => resolve(img);
|
|
43
|
+
img.onerror = (e) => reject(new Error(`Failed to load image at ${uri}: ${String(e)}`));
|
|
44
|
+
img.src = uri;
|
|
45
|
+
});
|
|
46
|
+
}
|