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.
@@ -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,52 @@
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) {
17
+ if (!removeBackground) {
26
18
  // Dynamic import from CDN
27
- const transformers = await import('https://cdn.jsdelivr.net/npm/@huggingface/transformers@3.3.0/+esm');
28
- env = transformers.env;
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
- // 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
- });
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, 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
+ const module = await import('https://cdn.jsdelivr.net/npm/@imgly/background-removal@1.3.0/+esm');
36
+ removeBackground = module.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
+ // 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
- // Map 0-100 progress
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 model
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 { format = 'PNG', quality = 100, onProgress } = options;
177
- onProgress?.(1); // Start
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
- // 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
- });
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,6 +1,6 @@
1
1
  {
2
2
  "name": "rn-remove-image-bg",
3
- "version": "0.0.16",
3
+ "version": "0.0.18",
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,46 @@ 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) {
27
+ if (!removeBackground) {
36
28
  // Dynamic import from CDN
37
- const transformers = await import('https://cdn.jsdelivr.net/npm/@huggingface/transformers@3.3.0/+esm');
38
- env = transformers.env;
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
- // 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
- });
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, 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
+ const module = await import('https://cdn.jsdelivr.net/npm/@imgly/background-removal@1.3.0/+esm');
46
+ removeBackground = module.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
+ // 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
- // Map 0-100 progress
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 model
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 { format = 'PNG', quality = 100, onProgress } = options;
129
+ const { onProgress, debug = false } = options;
130
+
131
+ if (debug) console.log('[rmbg] Starting...');
132
+ onProgress?.(1);
196
133
 
197
- onProgress?.(1); // Start
134
+ // Ensure worker is ready
198
135
  await ensureInit();
199
136
 
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
- });
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;