-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathbackground-remove.module.js
More file actions
158 lines (138 loc) · 5.31 KB
/
Copy pathbackground-remove.module.js
File metadata and controls
158 lines (138 loc) · 5.31 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
// background-remove.module.js
//
// Lightweight, pure-browser background remover.
// Strategy:
// 1. Draw the image to an off-screen canvas.
// 2. Sample the corner pixels to estimate the background color.
// 3. For each pixel, compare to bg color; if similar -> make transparent.
//
// Best for: objects on a fairly uniform background (white/gray/green/etc).
export const BackgroundRemove = {
/**
* Load an image from a File, Blob, or URL string.
* Returns a Promise<HTMLImageElement>.
*/
loadImage(src) {
return new Promise((resolve, reject) => {
const img = new Image();
img.crossOrigin = "anonymous";
if (src instanceof File || src instanceof Blob) {
const reader = new FileReader();
reader.onload = e => {
img.onload = () => resolve(img);
img.onerror = reject;
img.src = e.target.result;
};
reader.onerror = reject;
reader.readAsDataURL(src);
} else if (typeof src === "string") {
img.onload = () => resolve(img);
img.onerror = reject;
img.src = src;
} else {
reject(new Error("Unsupported src type. Use File, Blob, or URL string."));
}
});
},
/**
* Core function: remove background.
*
* @param {HTMLImageElement} img
* @param {Object} [opts]
* @param {number} [opts.sampleSize=10] - how many pixels per corner to sample
* @param {number} [opts.threshold=40] - color-distance threshold; bigger = more aggressive
* @param {boolean} [opts.softEdges=true] - soften alpha near threshold
* @returns {HTMLCanvasElement} a canvas with transparent background
*/
removeBackgroundFromImage(img, opts = {}) {
const {
sampleSize = 10,
threshold = 40,
softEdges = true
} = opts;
// Prepare canvas
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
const w = canvas.width = img.naturalWidth || img.width;
const h = canvas.height = img.naturalHeight || img.height;
ctx.drawImage(img, 0, 0, w, h);
const imageData = ctx.getImageData(0, 0, w, h);
const data = imageData.data;
// 1. Estimate background color by sampling corners
const bgColor = this._estimateBackgroundColor(data, w, h, sampleSize);
// 2. For each pixel, compare to bgColor and adjust alpha
const thrSq = threshold * threshold;
for (let y = 0; y < h; y++) {
for (let x = 0; x < w; x++) {
const idx = (y * w + x) * 4;
const r = data[idx];
const g = data[idx + 1];
const b = data[idx + 2];
const a = data[idx + 3];
// If already fully transparent, skip
if (a === 0) continue;
const distSq =
(r - bgColor.r) * (r - bgColor.r) +
(g - bgColor.g) * (g - bgColor.g) +
(b - bgColor.b) * (b - bgColor.b);
if (distSq < thrSq) {
// background-ish → transparency
if (!softEdges) {
data[idx + 3] = 0;
} else {
// Soft edge: scale alpha depending on how close it is
const dist = Math.sqrt(distSq);
const factor = dist / threshold; // 0..1
data[idx + 3] = Math.round(a * factor);
}
}
}
}
ctx.putImageData(imageData, 0, 0);
return canvas;
},
/**
* Estimate background color by sampling small blocks from the four corners.
* Returns {r,g,b}
*/
_estimateBackgroundColor(data, w, h, sampleSize) {
const samples = [];
const pushBlock = (x0, y0) => {
const xEnd = Math.min(x0 + sampleSize, w);
const yEnd = Math.min(y0 + sampleSize, h);
for (let y = y0; y < yEnd; y++) {
for (let x = x0; x < xEnd; x++) {
const idx = (y * w + x) * 4;
const r = data[idx];
const g = data[idx + 1];
const b = data[idx + 2];
const a = data[idx + 3];
if (a > 0) {
samples.push({ r, g, b });
}
}
}
};
// 4 corners
pushBlock(0, 0); // top-left
pushBlock(w - sampleSize, 0); // top-right
pushBlock(0, h - sampleSize); // bottom-left
pushBlock(w - sampleSize, h - sampleSize); // bottom-right
if (samples.length === 0) {
// fallback: default white-ish
return { r: 255, g: 255, b: 255 };
}
let rSum = 0, gSum = 0, bSum = 0;
for (const s of samples) {
rSum += s.r;
gSum += s.g;
bSum += s.b;
}
const n = samples.length;
return {
r: Math.round(rSum / n),
g: Math.round(gSum / n),
b: Math.round(bSum / n)
};
}
};