rn-remove-image-bg 0.0.13 → 0.0.14

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,6 +1,7 @@
1
1
  /**
2
2
  * Web implementation using @huggingface/transformers
3
3
  * Uses BRIAAI RMBG-1.4 model for background removal.
4
+ * Loads from CDN to bypass Metro bundler issues.
4
5
  */
5
6
  export type OutputFormat = 'PNG' | 'WEBP';
6
7
  export interface RemoveBgImageOptions {
@@ -24,8 +25,8 @@ export interface CompressImageOptions {
24
25
  export interface GenerateThumbhashOptions {
25
26
  size?: number;
26
27
  }
27
- export declare function compressImage(uri: string, _options?: CompressImageOptions): Promise<string>;
28
- export declare function generateThumbhash(_uri: string, _options?: GenerateThumbhashOptions): Promise<string>;
28
+ export declare function compressImage(uri: string, options?: CompressImageOptions): Promise<string>;
29
+ export declare function generateThumbhash(imageUri: string, options?: GenerateThumbhashOptions): Promise<string>;
29
30
  export declare function clearCache(): Promise<void>;
30
31
  export declare function getCacheSize(): number;
31
32
  export declare function onLowMemory(): Promise<number>;
@@ -1,10 +1,22 @@
1
1
  /**
2
2
  * Web implementation using @huggingface/transformers
3
3
  * Uses BRIAAI RMBG-1.4 model for background removal.
4
+ * Loads from CDN to bypass Metro bundler issues.
4
5
  */
5
6
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
6
7
  let pipeline = null;
7
8
  let loadPromise = null;
9
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
10
+ let transformersModule = null;
11
+ // Load transformers.js from CDN to avoid Metro bundling issues
12
+ async function loadTransformers() {
13
+ if (transformersModule)
14
+ return transformersModule;
15
+ // Use dynamic import from CDN
16
+ const cdnUrl = 'https://cdn.jsdelivr.net/npm/@huggingface/transformers@3';
17
+ transformersModule = await import(/* webpackIgnore: true */ cdnUrl);
18
+ return transformersModule;
19
+ }
8
20
  async function ensureLoaded(onProgress, debug) {
9
21
  if (pipeline)
10
22
  return pipeline;
@@ -15,7 +27,7 @@ async function ensureLoaded(onProgress, debug) {
15
27
  loadPromise = (async () => {
16
28
  if (debug)
17
29
  console.log('[rmbg] Loading model...');
18
- const { pipeline: createPipeline, env } = await import('@huggingface/transformers');
30
+ const { pipeline: createPipeline, env } = await loadTransformers();
19
31
  env.allowLocalModels = false;
20
32
  env.useBrowserCache = true;
21
33
  onProgress?.(10);
@@ -68,13 +80,68 @@ function toDataUrl(mask, format, quality) {
68
80
  return canvas.toDataURL(format === 'WEBP' ? 'image/webp' : 'image/png', quality / 100);
69
81
  }
70
82
  export const removeBackground = removeBgImage;
71
- export async function compressImage(uri, _options) {
72
- console.warn('[rmbg] compressImage not implemented on web, returning original');
73
- return uri;
83
+ // Helper to load image
84
+ function loadImage(src) {
85
+ return new Promise((resolve, reject) => {
86
+ const img = new Image();
87
+ img.crossOrigin = 'anonymous';
88
+ img.onload = () => resolve(img);
89
+ img.onerror = reject;
90
+ img.src = src;
91
+ });
74
92
  }
75
- export async function generateThumbhash(_uri, _options) {
76
- console.warn('[rmbg] generateThumbhash not implemented on web');
77
- return '';
93
+ export async function compressImage(uri, options = {}) {
94
+ const { maxSizeKB = 250, width = 1024, height = 1024, quality = 0.85, format = 'webp', } = options;
95
+ try {
96
+ const img = await loadImage(uri);
97
+ const scale = Math.min(width / img.width, height / img.height, 1);
98
+ const targetWidth = Math.round(img.width * scale);
99
+ const targetHeight = Math.round(img.height * scale);
100
+ const canvas = document.createElement('canvas');
101
+ canvas.width = targetWidth;
102
+ canvas.height = targetHeight;
103
+ const ctx = canvas.getContext('2d');
104
+ if (!ctx)
105
+ throw new Error('Could not get canvas context');
106
+ ctx.drawImage(img, 0, 0, targetWidth, targetHeight);
107
+ const mimeType = format === 'png' ? 'image/png' : format === 'jpeg' ? 'image/jpeg' : 'image/webp';
108
+ let dataUrl = canvas.toDataURL(mimeType, quality);
109
+ // Reduce quality if over size limit
110
+ let currentQuality = quality;
111
+ const getSize = (url) => ((url.split(',')[1] || '').length * 3) / 4 / 1024;
112
+ while (getSize(dataUrl) > maxSizeKB && currentQuality > 0.5) {
113
+ currentQuality -= 0.1;
114
+ dataUrl = canvas.toDataURL(mimeType, currentQuality);
115
+ }
116
+ return dataUrl;
117
+ }
118
+ catch (error) {
119
+ console.warn('[rmbg] compressImage failed:', error);
120
+ return uri;
121
+ }
122
+ }
123
+ export async function generateThumbhash(imageUri, options = {}) {
124
+ const { size = 32 } = options;
125
+ try {
126
+ const img = await loadImage(imageUri);
127
+ const canvas = document.createElement('canvas');
128
+ canvas.width = size;
129
+ canvas.height = size;
130
+ const ctx = canvas.getContext('2d');
131
+ if (!ctx)
132
+ throw new Error('Could not get canvas context');
133
+ ctx.drawImage(img, 0, 0, size, size);
134
+ const imageData = ctx.getImageData(0, 0, size, size);
135
+ // Load thumbhash from CDN
136
+ // @ts-expect-error CDN import works at runtime
137
+ const { rgbaToThumbHash } = await import(/* webpackIgnore: true */ 'https://cdn.jsdelivr.net/npm/thumbhash@0.1/+esm');
138
+ const hash = rgbaToThumbHash(size, size, imageData.data);
139
+ return btoa(String.fromCharCode(...hash));
140
+ }
141
+ catch (error) {
142
+ console.warn('[rmbg] generateThumbhash failed:', error);
143
+ return '';
144
+ }
78
145
  }
79
146
  export async function clearCache() {
80
147
  pipeline = null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rn-remove-image-bg",
3
- "version": "0.0.13",
3
+ "version": "0.0.14",
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,6 +1,7 @@
1
1
  /**
2
2
  * Web implementation using @huggingface/transformers
3
3
  * Uses BRIAAI RMBG-1.4 model for background removal.
4
+ * Loads from CDN to bypass Metro bundler issues.
4
5
  */
5
6
 
6
7
  export type OutputFormat = 'PNG' | 'WEBP';
@@ -15,6 +16,18 @@ export interface RemoveBgImageOptions {
15
16
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
16
17
  let pipeline: any = null;
17
18
  let loadPromise: Promise<void> | null = null;
19
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
20
+ let transformersModule: any = null;
21
+
22
+ // Load transformers.js from CDN to avoid Metro bundling issues
23
+ async function loadTransformers() {
24
+ if (transformersModule) return transformersModule;
25
+
26
+ // Use dynamic import from CDN
27
+ const cdnUrl = 'https://cdn.jsdelivr.net/npm/@huggingface/transformers@3';
28
+ transformersModule = await import(/* webpackIgnore: true */ cdnUrl);
29
+ return transformersModule;
30
+ }
18
31
 
19
32
  async function ensureLoaded(onProgress?: (p: number) => void, debug?: boolean) {
20
33
  if (pipeline) return pipeline;
@@ -27,7 +40,7 @@ async function ensureLoaded(onProgress?: (p: number) => void, debug?: boolean) {
27
40
  loadPromise = (async () => {
28
41
  if (debug) console.log('[rmbg] Loading model...');
29
42
 
30
- const { pipeline: createPipeline, env } = await import('@huggingface/transformers');
43
+ const { pipeline: createPipeline, env } = await loadTransformers();
31
44
 
32
45
  env.allowLocalModels = false;
33
46
  env.useBrowserCache = true;
@@ -36,7 +49,7 @@ async function ensureLoaded(onProgress?: (p: number) => void, debug?: boolean) {
36
49
 
37
50
  pipeline = await createPipeline('image-segmentation', 'briaai/RMBG-1.4', {
38
51
  dtype: 'q8',
39
- progress_callback: (info) => {
52
+ progress_callback: (info: { progress?: number }) => {
40
53
  if (onProgress && 'progress' in info && typeof info.progress === 'number') {
41
54
  onProgress(Math.min(10 + (info.progress / 100) * 50, 60));
42
55
  }
@@ -99,7 +112,17 @@ function toDataUrl(
99
112
 
100
113
  export const removeBackground = removeBgImage;
101
114
 
102
- // Stub exports for API compatibility with native
115
+ // Helper to load image
116
+ function loadImage(src: string): Promise<HTMLImageElement> {
117
+ return new Promise((resolve, reject) => {
118
+ const img = new Image();
119
+ img.crossOrigin = 'anonymous';
120
+ img.onload = () => resolve(img);
121
+ img.onerror = reject;
122
+ img.src = src;
123
+ });
124
+ }
125
+
103
126
  export interface CompressImageOptions {
104
127
  maxSizeKB?: number;
105
128
  width?: number;
@@ -112,14 +135,76 @@ export interface GenerateThumbhashOptions {
112
135
  size?: number;
113
136
  }
114
137
 
115
- export async function compressImage(uri: string, _options?: CompressImageOptions): Promise<string> {
116
- console.warn('[rmbg] compressImage not implemented on web, returning original');
117
- return uri;
138
+ export async function compressImage(
139
+ uri: string,
140
+ options: CompressImageOptions = {}
141
+ ): Promise<string> {
142
+ const {
143
+ maxSizeKB = 250,
144
+ width = 1024,
145
+ height = 1024,
146
+ quality = 0.85,
147
+ format = 'webp',
148
+ } = options;
149
+
150
+ try {
151
+ const img = await loadImage(uri);
152
+ const scale = Math.min(width / img.width, height / img.height, 1);
153
+ const targetWidth = Math.round(img.width * scale);
154
+ const targetHeight = Math.round(img.height * scale);
155
+
156
+ const canvas = document.createElement('canvas');
157
+ canvas.width = targetWidth;
158
+ canvas.height = targetHeight;
159
+ const ctx = canvas.getContext('2d');
160
+ if (!ctx) throw new Error('Could not get canvas context');
161
+ ctx.drawImage(img, 0, 0, targetWidth, targetHeight);
162
+
163
+ const mimeType = format === 'png' ? 'image/png' : format === 'jpeg' ? 'image/jpeg' : 'image/webp';
164
+ let dataUrl = canvas.toDataURL(mimeType, quality);
165
+
166
+ // Reduce quality if over size limit
167
+ let currentQuality = quality;
168
+ const getSize = (url: string) => ((url.split(',')[1] || '').length * 3) / 4 / 1024;
169
+ while (getSize(dataUrl) > maxSizeKB && currentQuality > 0.5) {
170
+ currentQuality -= 0.1;
171
+ dataUrl = canvas.toDataURL(mimeType, currentQuality);
172
+ }
173
+
174
+ return dataUrl;
175
+ } catch (error) {
176
+ console.warn('[rmbg] compressImage failed:', error);
177
+ return uri;
178
+ }
118
179
  }
119
180
 
120
- export async function generateThumbhash(_uri: string, _options?: GenerateThumbhashOptions): Promise<string> {
121
- console.warn('[rmbg] generateThumbhash not implemented on web');
122
- return '';
181
+ export async function generateThumbhash(
182
+ imageUri: string,
183
+ options: GenerateThumbhashOptions = {}
184
+ ): Promise<string> {
185
+ const { size = 32 } = options;
186
+
187
+ try {
188
+ const img = await loadImage(imageUri);
189
+
190
+ const canvas = document.createElement('canvas');
191
+ canvas.width = size;
192
+ canvas.height = size;
193
+ const ctx = canvas.getContext('2d');
194
+ if (!ctx) throw new Error('Could not get canvas context');
195
+ ctx.drawImage(img, 0, 0, size, size);
196
+
197
+ const imageData = ctx.getImageData(0, 0, size, size);
198
+
199
+ // Load thumbhash from CDN
200
+ // @ts-expect-error CDN import works at runtime
201
+ const { rgbaToThumbHash } = await import(/* webpackIgnore: true */ 'https://cdn.jsdelivr.net/npm/thumbhash@0.1/+esm');
202
+ const hash = rgbaToThumbHash(size, size, imageData.data);
203
+ return btoa(String.fromCharCode(...hash));
204
+ } catch (error) {
205
+ console.warn('[rmbg] generateThumbhash failed:', error);
206
+ return '';
207
+ }
123
208
  }
124
209
 
125
210
  export async function clearCache(): Promise<void> {