rm background
Browse files
src/lib/components/MonsterGenerator/MonsterGenerator.svelte
CHANGED
@@ -329,7 +329,8 @@ Write your response within \`\`\`json\`\`\``;
|
|
329 |
// Process the image to make white background transparent
|
330 |
console.log('Processing image for transparency...');
|
331 |
try {
|
332 |
-
const
|
|
|
333 |
state.monsterImage = {
|
334 |
imageUrl: url,
|
335 |
imageData: transparentBase64,
|
|
|
329 |
// Process the image to make white background transparent
|
330 |
console.log('Processing image for transparency...');
|
331 |
try {
|
332 |
+
const largeWhiteSegmentThreshold = 0.1; // 10% of image area
|
333 |
+
const transparentBase64 = await makeWhiteTransparent(url, largeWhiteSegmentThreshold);
|
334 |
state.monsterImage = {
|
335 |
imageUrl: url,
|
336 |
imageData: transparentBase64,
|
src/lib/utils/imageProcessing.ts
CHANGED
@@ -1,8 +1,9 @@
|
|
1 |
/**
|
2 |
* Converts an image URL to a base64 data URL with white background made transparent
|
3 |
* Uses flood-fill from edges to only remove background white, preserving internal white
|
|
|
4 |
*/
|
5 |
-
export async function makeWhiteTransparent(imageUrl: string): Promise<string> {
|
6 |
return new Promise((resolve, reject) => {
|
7 |
const img = new Image();
|
8 |
img.crossOrigin = 'anonymous';
|
@@ -114,13 +115,77 @@ export async function makeWhiteTransparent(imageUrl: string): Promise<string> {
|
|
114 |
}
|
115 |
}
|
116 |
|
117 |
-
// Apply transparency based on mask
|
118 |
for (let i = 0; i < mask.length; i++) {
|
119 |
if (mask[i]) {
|
120 |
data[i * 4 + 3] = 0; // Set alpha to 0
|
121 |
}
|
122 |
}
|
123 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
124 |
// Put the modified image data back
|
125 |
ctx.putImageData(imageData, 0, 0);
|
126 |
|
|
|
1 |
/**
|
2 |
* Converts an image URL to a base64 data URL with white background made transparent
|
3 |
* Uses flood-fill from edges to only remove background white, preserving internal white
|
4 |
+
* Also removes large white segments that exceed the threshold percentage
|
5 |
*/
|
6 |
+
export async function makeWhiteTransparent(imageUrl: string, largeSegmentThreshold: number = 0.1): Promise<string> {
|
7 |
return new Promise((resolve, reject) => {
|
8 |
const img = new Image();
|
9 |
img.crossOrigin = 'anonymous';
|
|
|
115 |
}
|
116 |
}
|
117 |
|
118 |
+
// Apply transparency based on edge flood fill mask
|
119 |
for (let i = 0; i < mask.length; i++) {
|
120 |
if (mask[i]) {
|
121 |
data[i * 4 + 3] = 0; // Set alpha to 0
|
122 |
}
|
123 |
}
|
124 |
|
125 |
+
// Now detect and remove large white segments
|
126 |
+
const totalPixels = width * height;
|
127 |
+
const segmentMask = new Uint8Array(width * height);
|
128 |
+
const visited = new Uint8Array(width * height);
|
129 |
+
|
130 |
+
// Find all white segments using flood fill
|
131 |
+
for (let y = 0; y < height; y++) {
|
132 |
+
for (let x = 0; x < width; x++) {
|
133 |
+
const idx = y * width + x;
|
134 |
+
|
135 |
+
// Skip if already transparent, visited, or not white
|
136 |
+
if (mask[idx] || visited[idx] || !isWhite(idx)) continue;
|
137 |
+
|
138 |
+
// Start flood fill for this white segment
|
139 |
+
const segmentPixels: number[] = [];
|
140 |
+
const segmentQueue: number[] = [idx];
|
141 |
+
visited[idx] = 1;
|
142 |
+
|
143 |
+
while (segmentQueue.length > 0) {
|
144 |
+
const currentIdx = segmentQueue.pop()!;
|
145 |
+
segmentPixels.push(currentIdx);
|
146 |
+
|
147 |
+
const cx = currentIdx % width;
|
148 |
+
const cy = Math.floor(currentIdx / width);
|
149 |
+
|
150 |
+
// Check 4 neighbors
|
151 |
+
const neighbors = [
|
152 |
+
{ dx: -1, dy: 0 }, { dx: 1, dy: 0 },
|
153 |
+
{ dx: 0, dy: -1 }, { dx: 0, dy: 1 }
|
154 |
+
];
|
155 |
+
|
156 |
+
for (const { dx, dy } of neighbors) {
|
157 |
+
const nx = cx + dx;
|
158 |
+
const ny = cy + dy;
|
159 |
+
|
160 |
+
if (nx >= 0 && nx < width && ny >= 0 && ny < height) {
|
161 |
+
const nIdx = ny * width + nx;
|
162 |
+
|
163 |
+
if (!visited[nIdx] && !mask[nIdx] && isWhite(nIdx) && colorSimilar(currentIdx, nIdx)) {
|
164 |
+
visited[nIdx] = 1;
|
165 |
+
segmentQueue.push(nIdx);
|
166 |
+
}
|
167 |
+
}
|
168 |
+
}
|
169 |
+
}
|
170 |
+
|
171 |
+
// Check if this segment is larger than threshold
|
172 |
+
const segmentSize = segmentPixels.length / totalPixels;
|
173 |
+
if (segmentSize > largeSegmentThreshold) {
|
174 |
+
// Mark all pixels in this segment for removal
|
175 |
+
for (const pixelIdx of segmentPixels) {
|
176 |
+
segmentMask[pixelIdx] = 1;
|
177 |
+
}
|
178 |
+
}
|
179 |
+
}
|
180 |
+
}
|
181 |
+
|
182 |
+
// Apply transparency to large segments
|
183 |
+
for (let i = 0; i < segmentMask.length; i++) {
|
184 |
+
if (segmentMask[i]) {
|
185 |
+
data[i * 4 + 3] = 0; // Set alpha to 0
|
186 |
+
}
|
187 |
+
}
|
188 |
+
|
189 |
// Put the modified image data back
|
190 |
ctx.putImageData(imageData, 0, 0);
|
191 |
|