rn-remove-image-bg 0.0.16 → 0.0.19
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 +42 -103
- package/package.json +1 -1
- package/src/ImageProcessing.web.ts +43 -103
|
@@ -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,53 @@
|
|
|
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 (!
|
|
26
|
-
//
|
|
27
|
-
|
|
28
|
-
env = transformers.env;
|
|
17
|
+
if (typeof self.imglyBackgroundRemoval === 'undefined' || !removeBackground) {
|
|
18
|
+
// Use importScripts (Classic Worker) - most robust for Blob URLs
|
|
19
|
+
importScripts('https://cdn.jsdelivr.net/npm/@imgly/background-removal@1.7.0/dist/imgly-background-removal.min.js');
|
|
29
20
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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
|
-
});
|
|
21
|
+
if (!self.imglyBackgroundRemoval) {
|
|
22
|
+
throw new Error('imglyBackgroundRemoval not loaded via importScripts');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
removeBackground = self.imglyBackgroundRemoval.removeBackground;
|
|
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
|
+
importScripts('https://cdn.jsdelivr.net/npm/@imgly/background-removal@1.7.0/dist/imgly-background-removal.min.js');
|
|
36
|
+
removeBackground = self.imglyBackgroundRemoval.removeBackground;
|
|
105
37
|
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
//
|
|
110
|
-
const blob = await
|
|
111
|
-
|
|
112
|
-
|
|
38
|
+
|
|
39
|
+
// Run Inference
|
|
40
|
+
// CRITICAL: Must point publicPath to CDN so it finds WASM/Models
|
|
41
|
+
// otherwise it tries to load from blob: URL
|
|
42
|
+
const blob = await removeBackground(uri, {
|
|
43
|
+
publicPath: 'https://cdn.jsdelivr.net/npm/@imgly/background-removal@1.7.0/dist/',
|
|
44
|
+
progress: (key, current, total) => {
|
|
45
|
+
const p = current / total;
|
|
46
|
+
self.postMessage({ id, type: 'progress', payload: { progress: p } });
|
|
47
|
+
},
|
|
48
|
+
...config
|
|
113
49
|
});
|
|
114
|
-
|
|
50
|
+
|
|
115
51
|
// Convert blob to DataURL for return
|
|
116
52
|
const reader = new FileReader();
|
|
117
53
|
reader.onloadend = () => {
|
|
@@ -120,7 +56,7 @@ const WORKER_CODE = `
|
|
|
120
56
|
reader.readAsDataURL(blob);
|
|
121
57
|
}
|
|
122
58
|
} catch (err) {
|
|
123
|
-
self.postMessage({ id, type: 'error', payload: err.message });
|
|
59
|
+
self.postMessage({ id, type: 'error', payload: err.message || JSON.stringify(err) });
|
|
124
60
|
}
|
|
125
61
|
};
|
|
126
62
|
`;
|
|
@@ -141,8 +77,7 @@ function getWorker() {
|
|
|
141
77
|
return;
|
|
142
78
|
if (type === 'progress') {
|
|
143
79
|
if (deferred.onProgress && payload.progress) {
|
|
144
|
-
|
|
145
|
-
deferred.onProgress(payload.progress);
|
|
80
|
+
deferred.onProgress(payload.progress * 100);
|
|
146
81
|
}
|
|
147
82
|
}
|
|
148
83
|
else if (type === 'success') {
|
|
@@ -164,7 +99,7 @@ function sendToWorker(type, payload, onProgress) {
|
|
|
164
99
|
getWorker().postMessage({ id, type, payload });
|
|
165
100
|
});
|
|
166
101
|
}
|
|
167
|
-
// Initialize
|
|
102
|
+
// Initialize
|
|
168
103
|
let initPromise = null;
|
|
169
104
|
async function ensureInit() {
|
|
170
105
|
if (!initPromise) {
|
|
@@ -173,15 +108,19 @@ async function ensureInit() {
|
|
|
173
108
|
return initPromise;
|
|
174
109
|
}
|
|
175
110
|
export async function removeBgImage(uri, options = {}) {
|
|
176
|
-
const {
|
|
177
|
-
|
|
111
|
+
const { onProgress, debug = false } = options;
|
|
112
|
+
if (debug)
|
|
113
|
+
console.log('[rmbg] Starting...');
|
|
114
|
+
onProgress?.(1);
|
|
115
|
+
// Ensure worker is ready
|
|
178
116
|
await ensureInit();
|
|
179
|
-
//
|
|
180
|
-
const
|
|
181
|
-
|
|
182
|
-
// We can
|
|
183
|
-
|
|
184
|
-
}
|
|
117
|
+
// Config for imgly
|
|
118
|
+
const config = {
|
|
119
|
+
debug: debug,
|
|
120
|
+
// We can pass other options supported by imgly if needed
|
|
121
|
+
// device: 'gpu' is auto-detected usually
|
|
122
|
+
};
|
|
123
|
+
const result = await sendToWorker('removeBg', { uri, config }, onProgress);
|
|
185
124
|
onProgress?.(100);
|
|
186
125
|
return result;
|
|
187
126
|
}
|
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,47 @@ 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 (!
|
|
36
|
-
//
|
|
37
|
-
|
|
38
|
-
env = transformers.env;
|
|
27
|
+
if (typeof self.imglyBackgroundRemoval === 'undefined' || !removeBackground) {
|
|
28
|
+
// Use importScripts (Classic Worker) - most robust for Blob URLs
|
|
29
|
+
importScripts('https://cdn.jsdelivr.net/npm/@imgly/background-removal@1.7.0/dist/imgly-background-removal.min.js');
|
|
39
30
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
-
});
|
|
31
|
+
if (!self.imglyBackgroundRemoval) {
|
|
32
|
+
throw new Error('imglyBackgroundRemoval not loaded via importScripts');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
removeBackground = self.imglyBackgroundRemoval.removeBackground;
|
|
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
|
+
importScripts('https://cdn.jsdelivr.net/npm/@imgly/background-removal@1.7.0/dist/imgly-background-removal.min.js');
|
|
46
|
+
removeBackground = self.imglyBackgroundRemoval.removeBackground;
|
|
115
47
|
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
//
|
|
120
|
-
const blob = await
|
|
121
|
-
|
|
122
|
-
|
|
48
|
+
|
|
49
|
+
// Run Inference
|
|
50
|
+
// CRITICAL: Must point publicPath to CDN so it finds WASM/Models
|
|
51
|
+
// otherwise it tries to load from blob: URL
|
|
52
|
+
const blob = await removeBackground(uri, {
|
|
53
|
+
publicPath: 'https://cdn.jsdelivr.net/npm/@imgly/background-removal@1.7.0/dist/',
|
|
54
|
+
progress: (key, current, total) => {
|
|
55
|
+
const p = current / total;
|
|
56
|
+
self.postMessage({ id, type: 'progress', payload: { progress: p } });
|
|
57
|
+
},
|
|
58
|
+
...config
|
|
123
59
|
});
|
|
124
|
-
|
|
60
|
+
|
|
125
61
|
// Convert blob to DataURL for return
|
|
126
62
|
const reader = new FileReader();
|
|
127
63
|
reader.onloadend = () => {
|
|
@@ -130,7 +66,7 @@ const WORKER_CODE = `
|
|
|
130
66
|
reader.readAsDataURL(blob);
|
|
131
67
|
}
|
|
132
68
|
} catch (err) {
|
|
133
|
-
self.postMessage({ id, type: 'error', payload: err.message });
|
|
69
|
+
self.postMessage({ id, type: 'error', payload: err.message || JSON.stringify(err) });
|
|
134
70
|
}
|
|
135
71
|
};
|
|
136
72
|
`;
|
|
@@ -156,8 +92,7 @@ function getWorker() {
|
|
|
156
92
|
|
|
157
93
|
if (type === 'progress') {
|
|
158
94
|
if (deferred.onProgress && payload.progress) {
|
|
159
|
-
|
|
160
|
-
deferred.onProgress(payload.progress);
|
|
95
|
+
deferred.onProgress(payload.progress * 100);
|
|
161
96
|
}
|
|
162
97
|
} else if (type === 'success') {
|
|
163
98
|
deferred.resolve(payload);
|
|
@@ -179,7 +114,7 @@ function sendToWorker(type: string, payload: any, onProgress?: (p: number) => vo
|
|
|
179
114
|
});
|
|
180
115
|
}
|
|
181
116
|
|
|
182
|
-
// Initialize
|
|
117
|
+
// Initialize
|
|
183
118
|
let initPromise: Promise<void> | null = null;
|
|
184
119
|
async function ensureInit() {
|
|
185
120
|
if (!initPromise) {
|
|
@@ -192,17 +127,22 @@ export async function removeBgImage(
|
|
|
192
127
|
uri: string,
|
|
193
128
|
options: RemoveBgImageOptions = {}
|
|
194
129
|
): Promise<string> {
|
|
195
|
-
const {
|
|
130
|
+
const { onProgress, debug = false } = options;
|
|
131
|
+
|
|
132
|
+
if (debug) console.log('[rmbg] Starting...');
|
|
133
|
+
onProgress?.(1);
|
|
196
134
|
|
|
197
|
-
|
|
135
|
+
// Ensure worker is ready
|
|
198
136
|
await ensureInit();
|
|
199
137
|
|
|
200
|
-
//
|
|
201
|
-
const
|
|
202
|
-
|
|
203
|
-
// We can
|
|
204
|
-
|
|
205
|
-
}
|
|
138
|
+
// Config for imgly
|
|
139
|
+
const config = {
|
|
140
|
+
debug: debug,
|
|
141
|
+
// We can pass other options supported by imgly if needed
|
|
142
|
+
// device: 'gpu' is auto-detected usually
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
const result = await sendToWorker('removeBg', { uri, config }, onProgress);
|
|
206
146
|
|
|
207
147
|
onProgress?.(100);
|
|
208
148
|
return result as string;
|