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,
|
|
28
|
-
export declare function generateThumbhash(
|
|
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
|
|
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
|
-
|
|
72
|
-
|
|
73
|
-
return
|
|
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
|
|
76
|
-
|
|
77
|
-
|
|
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,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
|
|
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
|
-
//
|
|
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(
|
|
116
|
-
|
|
117
|
-
|
|
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(
|
|
121
|
-
|
|
122
|
-
|
|
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> {
|