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.
@@ -1,7 +1,7 @@
1
1
  /**
2
- * Web implementation using Inline Web Worker & WebGPU.
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 @huggingface/transformers from CDN to bypass Metro bundler issues.
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 & WebGPU.
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 @huggingface/transformers from CDN to bypass Metro bundler issues.
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 pipeline = null;
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 (!pipeline) {
26
- // Dynamic import from CDN
27
- const transformers = await import('https://cdn.jsdelivr.net/npm/@huggingface/transformers@3.3.0/+esm');
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
- // Configure environment
31
- env.allowLocalModels = false;
32
- env.useBrowserCache = true;
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
- });
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, format, quality } = payload;
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
- // Handle Mask
73
- // mask.data is usually 1-channel or 3-channel
74
- const mask = result.mask;
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
- ctx.putImageData(imageData, 0, 0);
108
-
109
- // 3. Convert to Blob/DataURL
110
- const blob = await offscreen.convertToBlob({
111
- type: format === 'WEBP' ? 'image/webp' : 'image/png',
112
- quality: quality / 100
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
- // Map 0-100 progress
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 model
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 { format = 'PNG', quality = 100, onProgress } = options;
177
- onProgress?.(1); // Start
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
- // The worker handles the heavy calculation
180
- const result = await sendToWorker('removeBg', { uri, format, quality }, (p) => {
181
- // Transformers.js progress is model downloading mainly
182
- // We can map it: 0-90% download/load, 90-100% inference
183
- onProgress?.(p * 0.9);
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,6 +1,6 @@
1
1
  {
2
2
  "name": "rn-remove-image-bg",
3
- "version": "0.0.16",
3
+ "version": "0.0.19",
4
4
  "description": "rn-remove-image-bg",
5
5
  "homepage": "https://github.com/a-eid/rn-remove-image-bg",
6
6
  "main": "lib/index",
@@ -1,7 +1,7 @@
1
1
  /**
2
- * Web implementation using Inline Web Worker & WebGPU.
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 @huggingface/transformers from CDN to bypass Metro bundler issues.
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 pipeline = null;
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 (!pipeline) {
36
- // Dynamic import from CDN
37
- const transformers = await import('https://cdn.jsdelivr.net/npm/@huggingface/transformers@3.3.0/+esm');
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
- // Configure environment
41
- env.allowLocalModels = false;
42
- env.useBrowserCache = true;
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
- });
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, format, quality } = payload;
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
- // Simple resizing logic if dimensions differ (Nearest Neighbor)
88
- // (Preprocessing usually resizes input, so output mask matches input size typically?
89
- // Actually RMBG-1.4 output is fixed size 1024x1024 usually, need resize)
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
- ctx.putImageData(imageData, 0, 0);
118
-
119
- // 3. Convert to Blob/DataURL
120
- const blob = await offscreen.convertToBlob({
121
- type: format === 'WEBP' ? 'image/webp' : 'image/png',
122
- quality: quality / 100
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
- // Map 0-100 progress
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 model
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 { format = 'PNG', quality = 100, onProgress } = options;
130
+ const { onProgress, debug = false } = options;
131
+
132
+ if (debug) console.log('[rmbg] Starting...');
133
+ onProgress?.(1);
196
134
 
197
- onProgress?.(1); // Start
135
+ // Ensure worker is ready
198
136
  await ensureInit();
199
137
 
200
- // The worker handles the heavy calculation
201
- const result = await sendToWorker('removeBg', { uri, format, quality }, (p) => {
202
- // Transformers.js progress is model downloading mainly
203
- // We can map it: 0-90% download/load, 90-100% inference
204
- onProgress?.(p * 0.9);
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;