rn-remove-image-bg 0.0.16 → 0.0.18
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.web.d.ts +2 -2
- package/lib/ImageProcessing.web.js +40 -102
- package/package.json +1 -1
- package/src/ImageProcessing.web.ts +41 -102
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Web implementation using Inline Web Worker
|
|
2
|
+
* Web implementation using @imgly/background-removal via Inline Web Worker.
|
|
3
3
|
* Moves all heavy processing to a background thread to prevent UI freezing.
|
|
4
|
-
* Loads
|
|
4
|
+
* Loads library from CDN to bypass Metro bundler issues.
|
|
5
5
|
*/
|
|
6
6
|
export type OutputFormat = 'PNG' | 'WEBP';
|
|
7
7
|
export interface RemoveBgImageOptions {
|
|
@@ -1,117 +1,52 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Web implementation using Inline Web Worker
|
|
2
|
+
* Web implementation using @imgly/background-removal via Inline Web Worker.
|
|
3
3
|
* Moves all heavy processing to a background thread to prevent UI freezing.
|
|
4
|
-
* Loads
|
|
4
|
+
* Loads library from CDN to bypass Metro bundler issues.
|
|
5
5
|
*/
|
|
6
6
|
// ==========================================
|
|
7
7
|
// INLINE WORKER CODE (Run in background)
|
|
8
8
|
// ==========================================
|
|
9
9
|
const WORKER_CODE = `
|
|
10
|
-
let
|
|
11
|
-
let env = null;
|
|
12
|
-
|
|
13
|
-
// Helper to load image bitmap
|
|
14
|
-
async function loadImageBitmapFromUrl(url) {
|
|
15
|
-
const response = await fetch(url);
|
|
16
|
-
const blob = await response.blob();
|
|
17
|
-
return await createImageBitmap(blob);
|
|
18
|
-
}
|
|
10
|
+
let removeBackground = null;
|
|
19
11
|
|
|
20
12
|
self.onmessage = async (e) => {
|
|
21
13
|
const { id, type, payload } = e.data;
|
|
22
14
|
|
|
23
15
|
try {
|
|
24
16
|
if (type === 'init') {
|
|
25
|
-
if (!
|
|
17
|
+
if (!removeBackground) {
|
|
26
18
|
// Dynamic import from CDN
|
|
27
|
-
|
|
28
|
-
|
|
19
|
+
// Using v1.3.0 which is stable
|
|
20
|
+
const module = await import('https://cdn.jsdelivr.net/npm/@imgly/background-removal@1.3.0/+esm');
|
|
21
|
+
removeBackground = module.removeBackground;
|
|
29
22
|
|
|
30
|
-
//
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
// Try WebGPU, fallback to WASM
|
|
34
|
-
// env.backends.onnx.wasm.numThreads = 1; // Limit threads if needed
|
|
35
|
-
|
|
36
|
-
pipeline = await transformers.pipeline('image-segmentation', 'briaai/RMBG-1.4', {
|
|
37
|
-
device: 'webgpu', // Attempt WebGPU first
|
|
38
|
-
dtype: 'q8', // Quantized for speed
|
|
39
|
-
progress_callback: (info) => {
|
|
40
|
-
self.postMessage({ id, type: 'progress', payload: info });
|
|
41
|
-
}
|
|
42
|
-
});
|
|
23
|
+
// Preload/Init logic could go here if exposed, but imgly inits on first run usually
|
|
24
|
+
// Unless we want to preload the wasm.
|
|
25
|
+
// self.postMessage({ id, type: 'progress', payload: { progress: 0.1 } });
|
|
43
26
|
}
|
|
44
27
|
self.postMessage({ id, type: 'success', payload: true });
|
|
45
28
|
return;
|
|
46
29
|
}
|
|
47
30
|
|
|
48
31
|
if (type === 'removeBg') {
|
|
49
|
-
const { uri,
|
|
50
|
-
|
|
51
|
-
if (!pipeline) throw new Error('Pipeline not initialized');
|
|
52
|
-
|
|
53
|
-
// 1. Run Inference
|
|
54
|
-
const results = await pipeline(uri);
|
|
55
|
-
const result = Array.isArray(results) ? results[0] : results;
|
|
56
|
-
if (!result || !result.mask) throw new Error('No mask generated');
|
|
57
|
-
|
|
58
|
-
// 2. Apply Mask (Pixel Manipulation)
|
|
59
|
-
// We use OffscreenCanvas if available, or just pixel math
|
|
60
|
-
// Since we are in a worker, we can't use DOM Image, but we can use ImageBitmap
|
|
61
|
-
|
|
62
|
-
const originalBitmap = await loadImageBitmapFromUrl(uri);
|
|
63
|
-
const { width, height } = originalBitmap;
|
|
64
|
-
|
|
65
|
-
const offscreen = new OffscreenCanvas(width, height);
|
|
66
|
-
const ctx = offscreen.getContext('2d');
|
|
67
|
-
ctx.drawImage(originalBitmap, 0, 0);
|
|
68
|
-
|
|
69
|
-
const imageData = ctx.getImageData(0, 0, width, height);
|
|
70
|
-
const pixelData = imageData.data;
|
|
32
|
+
const { uri, config } = payload;
|
|
71
33
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
const maskData = mask.data;
|
|
76
|
-
|
|
77
|
-
// Simple resizing logic if dimensions differ (Nearest Neighbor)
|
|
78
|
-
// (Preprocessing usually resizes input, so output mask matches input size typically?
|
|
79
|
-
// Actually RMBG-1.4 output is fixed size 1024x1024 usually, need resize)
|
|
80
|
-
|
|
81
|
-
const maskW = mask.width;
|
|
82
|
-
const maskH = mask.height;
|
|
83
|
-
|
|
84
|
-
for (let i = 0; i < pixelData.length; i += 4) {
|
|
85
|
-
const pixelIndex = i / 4;
|
|
86
|
-
const x = pixelIndex % width;
|
|
87
|
-
const y = Math.floor(pixelIndex / width);
|
|
88
|
-
|
|
89
|
-
// Map to mask coordinates
|
|
90
|
-
const mx = Math.floor(x * (maskW / width));
|
|
91
|
-
const my = Math.floor(y * (maskH / height));
|
|
92
|
-
const maskIdx = (my * maskW + mx);
|
|
93
|
-
|
|
94
|
-
// Get Alpha
|
|
95
|
-
let alpha = 255;
|
|
96
|
-
if (maskData.length === maskW * maskH) {
|
|
97
|
-
alpha = maskData[maskIdx];
|
|
98
|
-
} else {
|
|
99
|
-
alpha = maskData[maskIdx * mask.channels]; // Assuming channels property or stride
|
|
100
|
-
// Fallback if channels undefined:
|
|
101
|
-
if (!mask.channels) alpha = maskData[maskIdx * 3]; // RGB assumption
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
pixelData[i + 3] = alpha;
|
|
34
|
+
if (!removeBackground) {
|
|
35
|
+
const module = await import('https://cdn.jsdelivr.net/npm/@imgly/background-removal@1.3.0/+esm');
|
|
36
|
+
removeBackground = module.removeBackground;
|
|
105
37
|
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
38
|
+
|
|
39
|
+
// Run Inference
|
|
40
|
+
// imgly config: { progress: (key, current, total) => ... }
|
|
41
|
+
const blob = await removeBackground(uri, {
|
|
42
|
+
progress: (key, current, total) => {
|
|
43
|
+
// Map progress roughly
|
|
44
|
+
const p = current / total;
|
|
45
|
+
self.postMessage({ id, type: 'progress', payload: { progress: p } });
|
|
46
|
+
},
|
|
47
|
+
...config
|
|
113
48
|
});
|
|
114
|
-
|
|
49
|
+
|
|
115
50
|
// Convert blob to DataURL for return
|
|
116
51
|
const reader = new FileReader();
|
|
117
52
|
reader.onloadend = () => {
|
|
@@ -120,7 +55,7 @@ const WORKER_CODE = `
|
|
|
120
55
|
reader.readAsDataURL(blob);
|
|
121
56
|
}
|
|
122
57
|
} catch (err) {
|
|
123
|
-
self.postMessage({ id, type: 'error', payload: err.message });
|
|
58
|
+
self.postMessage({ id, type: 'error', payload: err.message || JSON.stringify(err) });
|
|
124
59
|
}
|
|
125
60
|
};
|
|
126
61
|
`;
|
|
@@ -141,8 +76,7 @@ function getWorker() {
|
|
|
141
76
|
return;
|
|
142
77
|
if (type === 'progress') {
|
|
143
78
|
if (deferred.onProgress && payload.progress) {
|
|
144
|
-
|
|
145
|
-
deferred.onProgress(payload.progress);
|
|
79
|
+
deferred.onProgress(payload.progress * 100);
|
|
146
80
|
}
|
|
147
81
|
}
|
|
148
82
|
else if (type === 'success') {
|
|
@@ -164,7 +98,7 @@ function sendToWorker(type, payload, onProgress) {
|
|
|
164
98
|
getWorker().postMessage({ id, type, payload });
|
|
165
99
|
});
|
|
166
100
|
}
|
|
167
|
-
// Initialize
|
|
101
|
+
// Initialize
|
|
168
102
|
let initPromise = null;
|
|
169
103
|
async function ensureInit() {
|
|
170
104
|
if (!initPromise) {
|
|
@@ -173,15 +107,19 @@ async function ensureInit() {
|
|
|
173
107
|
return initPromise;
|
|
174
108
|
}
|
|
175
109
|
export async function removeBgImage(uri, options = {}) {
|
|
176
|
-
const {
|
|
177
|
-
|
|
110
|
+
const { onProgress, debug = false } = options;
|
|
111
|
+
if (debug)
|
|
112
|
+
console.log('[rmbg] Starting...');
|
|
113
|
+
onProgress?.(1);
|
|
114
|
+
// Ensure worker is ready
|
|
178
115
|
await ensureInit();
|
|
179
|
-
//
|
|
180
|
-
const
|
|
181
|
-
|
|
182
|
-
// We can
|
|
183
|
-
|
|
184
|
-
}
|
|
116
|
+
// Config for imgly
|
|
117
|
+
const config = {
|
|
118
|
+
debug: debug,
|
|
119
|
+
// We can pass other options supported by imgly if needed
|
|
120
|
+
// device: 'gpu' is auto-detected usually
|
|
121
|
+
};
|
|
122
|
+
const result = await sendToWorker('removeBg', { uri, config }, onProgress);
|
|
185
123
|
onProgress?.(100);
|
|
186
124
|
return result;
|
|
187
125
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Web implementation using Inline Web Worker
|
|
2
|
+
* Web implementation using @imgly/background-removal via Inline Web Worker.
|
|
3
3
|
* Moves all heavy processing to a background thread to prevent UI freezing.
|
|
4
|
-
* Loads
|
|
4
|
+
* Loads library from CDN to bypass Metro bundler issues.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
export type OutputFormat = 'PNG' | 'WEBP';
|
|
@@ -17,111 +17,46 @@ export interface RemoveBgImageOptions {
|
|
|
17
17
|
// INLINE WORKER CODE (Run in background)
|
|
18
18
|
// ==========================================
|
|
19
19
|
const WORKER_CODE = `
|
|
20
|
-
let
|
|
21
|
-
let env = null;
|
|
22
|
-
|
|
23
|
-
// Helper to load image bitmap
|
|
24
|
-
async function loadImageBitmapFromUrl(url) {
|
|
25
|
-
const response = await fetch(url);
|
|
26
|
-
const blob = await response.blob();
|
|
27
|
-
return await createImageBitmap(blob);
|
|
28
|
-
}
|
|
20
|
+
let removeBackground = null;
|
|
29
21
|
|
|
30
22
|
self.onmessage = async (e) => {
|
|
31
23
|
const { id, type, payload } = e.data;
|
|
32
24
|
|
|
33
25
|
try {
|
|
34
26
|
if (type === 'init') {
|
|
35
|
-
if (!
|
|
27
|
+
if (!removeBackground) {
|
|
36
28
|
// Dynamic import from CDN
|
|
37
|
-
|
|
38
|
-
|
|
29
|
+
// Using v1.3.0 which is stable
|
|
30
|
+
const module = await import('https://cdn.jsdelivr.net/npm/@imgly/background-removal@1.3.0/+esm');
|
|
31
|
+
removeBackground = module.removeBackground;
|
|
39
32
|
|
|
40
|
-
//
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
// Try WebGPU, fallback to WASM
|
|
44
|
-
// env.backends.onnx.wasm.numThreads = 1; // Limit threads if needed
|
|
45
|
-
|
|
46
|
-
pipeline = await transformers.pipeline('image-segmentation', 'briaai/RMBG-1.4', {
|
|
47
|
-
device: 'webgpu', // Attempt WebGPU first
|
|
48
|
-
dtype: 'q8', // Quantized for speed
|
|
49
|
-
progress_callback: (info) => {
|
|
50
|
-
self.postMessage({ id, type: 'progress', payload: info });
|
|
51
|
-
}
|
|
52
|
-
});
|
|
33
|
+
// Preload/Init logic could go here if exposed, but imgly inits on first run usually
|
|
34
|
+
// Unless we want to preload the wasm.
|
|
35
|
+
// self.postMessage({ id, type: 'progress', payload: { progress: 0.1 } });
|
|
53
36
|
}
|
|
54
37
|
self.postMessage({ id, type: 'success', payload: true });
|
|
55
38
|
return;
|
|
56
39
|
}
|
|
57
40
|
|
|
58
41
|
if (type === 'removeBg') {
|
|
59
|
-
const { uri,
|
|
60
|
-
|
|
61
|
-
if (!pipeline) throw new Error('Pipeline not initialized');
|
|
62
|
-
|
|
63
|
-
// 1. Run Inference
|
|
64
|
-
const results = await pipeline(uri);
|
|
65
|
-
const result = Array.isArray(results) ? results[0] : results;
|
|
66
|
-
if (!result || !result.mask) throw new Error('No mask generated');
|
|
67
|
-
|
|
68
|
-
// 2. Apply Mask (Pixel Manipulation)
|
|
69
|
-
// We use OffscreenCanvas if available, or just pixel math
|
|
70
|
-
// Since we are in a worker, we can't use DOM Image, but we can use ImageBitmap
|
|
71
|
-
|
|
72
|
-
const originalBitmap = await loadImageBitmapFromUrl(uri);
|
|
73
|
-
const { width, height } = originalBitmap;
|
|
74
|
-
|
|
75
|
-
const offscreen = new OffscreenCanvas(width, height);
|
|
76
|
-
const ctx = offscreen.getContext('2d');
|
|
77
|
-
ctx.drawImage(originalBitmap, 0, 0);
|
|
78
|
-
|
|
79
|
-
const imageData = ctx.getImageData(0, 0, width, height);
|
|
80
|
-
const pixelData = imageData.data;
|
|
81
|
-
|
|
82
|
-
// Handle Mask
|
|
83
|
-
// mask.data is usually 1-channel or 3-channel
|
|
84
|
-
const mask = result.mask;
|
|
85
|
-
const maskData = mask.data;
|
|
42
|
+
const { uri, config } = payload;
|
|
86
43
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
const maskW = mask.width;
|
|
92
|
-
const maskH = mask.height;
|
|
93
|
-
|
|
94
|
-
for (let i = 0; i < pixelData.length; i += 4) {
|
|
95
|
-
const pixelIndex = i / 4;
|
|
96
|
-
const x = pixelIndex % width;
|
|
97
|
-
const y = Math.floor(pixelIndex / width);
|
|
98
|
-
|
|
99
|
-
// Map to mask coordinates
|
|
100
|
-
const mx = Math.floor(x * (maskW / width));
|
|
101
|
-
const my = Math.floor(y * (maskH / height));
|
|
102
|
-
const maskIdx = (my * maskW + mx);
|
|
103
|
-
|
|
104
|
-
// Get Alpha
|
|
105
|
-
let alpha = 255;
|
|
106
|
-
if (maskData.length === maskW * maskH) {
|
|
107
|
-
alpha = maskData[maskIdx];
|
|
108
|
-
} else {
|
|
109
|
-
alpha = maskData[maskIdx * mask.channels]; // Assuming channels property or stride
|
|
110
|
-
// Fallback if channels undefined:
|
|
111
|
-
if (!mask.channels) alpha = maskData[maskIdx * 3]; // RGB assumption
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
pixelData[i + 3] = alpha;
|
|
44
|
+
if (!removeBackground) {
|
|
45
|
+
const module = await import('https://cdn.jsdelivr.net/npm/@imgly/background-removal@1.3.0/+esm');
|
|
46
|
+
removeBackground = module.removeBackground;
|
|
115
47
|
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
48
|
+
|
|
49
|
+
// Run Inference
|
|
50
|
+
// imgly config: { progress: (key, current, total) => ... }
|
|
51
|
+
const blob = await removeBackground(uri, {
|
|
52
|
+
progress: (key, current, total) => {
|
|
53
|
+
// Map progress roughly
|
|
54
|
+
const p = current / total;
|
|
55
|
+
self.postMessage({ id, type: 'progress', payload: { progress: p } });
|
|
56
|
+
},
|
|
57
|
+
...config
|
|
123
58
|
});
|
|
124
|
-
|
|
59
|
+
|
|
125
60
|
// Convert blob to DataURL for return
|
|
126
61
|
const reader = new FileReader();
|
|
127
62
|
reader.onloadend = () => {
|
|
@@ -130,7 +65,7 @@ const WORKER_CODE = `
|
|
|
130
65
|
reader.readAsDataURL(blob);
|
|
131
66
|
}
|
|
132
67
|
} catch (err) {
|
|
133
|
-
self.postMessage({ id, type: 'error', payload: err.message });
|
|
68
|
+
self.postMessage({ id, type: 'error', payload: err.message || JSON.stringify(err) });
|
|
134
69
|
}
|
|
135
70
|
};
|
|
136
71
|
`;
|
|
@@ -156,8 +91,7 @@ function getWorker() {
|
|
|
156
91
|
|
|
157
92
|
if (type === 'progress') {
|
|
158
93
|
if (deferred.onProgress && payload.progress) {
|
|
159
|
-
|
|
160
|
-
deferred.onProgress(payload.progress);
|
|
94
|
+
deferred.onProgress(payload.progress * 100);
|
|
161
95
|
}
|
|
162
96
|
} else if (type === 'success') {
|
|
163
97
|
deferred.resolve(payload);
|
|
@@ -179,7 +113,7 @@ function sendToWorker(type: string, payload: any, onProgress?: (p: number) => vo
|
|
|
179
113
|
});
|
|
180
114
|
}
|
|
181
115
|
|
|
182
|
-
// Initialize
|
|
116
|
+
// Initialize
|
|
183
117
|
let initPromise: Promise<void> | null = null;
|
|
184
118
|
async function ensureInit() {
|
|
185
119
|
if (!initPromise) {
|
|
@@ -192,17 +126,22 @@ export async function removeBgImage(
|
|
|
192
126
|
uri: string,
|
|
193
127
|
options: RemoveBgImageOptions = {}
|
|
194
128
|
): Promise<string> {
|
|
195
|
-
const {
|
|
129
|
+
const { onProgress, debug = false } = options;
|
|
130
|
+
|
|
131
|
+
if (debug) console.log('[rmbg] Starting...');
|
|
132
|
+
onProgress?.(1);
|
|
196
133
|
|
|
197
|
-
|
|
134
|
+
// Ensure worker is ready
|
|
198
135
|
await ensureInit();
|
|
199
136
|
|
|
200
|
-
//
|
|
201
|
-
const
|
|
202
|
-
|
|
203
|
-
// We can
|
|
204
|
-
|
|
205
|
-
}
|
|
137
|
+
// Config for imgly
|
|
138
|
+
const config = {
|
|
139
|
+
debug: debug,
|
|
140
|
+
// We can pass other options supported by imgly if needed
|
|
141
|
+
// device: 'gpu' is auto-detected usually
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
const result = await sendToWorker('removeBg', { uri, config }, onProgress);
|
|
206
145
|
|
|
207
146
|
onProgress?.(100);
|
|
208
147
|
return result as string;
|