closr
Browse files
src/lib/components/MonsterGenerator/MonsterGenerator.svelte
CHANGED
|
@@ -28,7 +28,10 @@ Create a Pokémon-style monster that transforms the object into an imaginative c
|
|
| 28 |
|
| 29 |
Guidelines:
|
| 30 |
- Take the object's key visual elements (colors, shapes, materials) and incorporate them into a creature design
|
| 31 |
-
- Add
|
|
|
|
|
|
|
|
|
|
| 32 |
- The object should be recognizable but creatively interpreted
|
| 33 |
|
| 34 |
Rarity assessment: Common objects = weaker monsters. Unique/rare objects = stronger monsters.
|
|
@@ -42,7 +45,7 @@ Include:
|
|
| 42 |
const IMAGE_GENERATION_PROMPT = (concept: string) => `Convert this monster concept into a clear and succinct description of its appearance:
|
| 43 |
"${concept}"
|
| 44 |
|
| 45 |
-
Include all of its visual details, format the description as a single long sentence.`;
|
| 46 |
|
| 47 |
const MONSTER_STATS_PROMPT = (concept: string) => `Convert the following monster concept into a JSON object with stats:
|
| 48 |
|
|
|
|
| 28 |
|
| 29 |
Guidelines:
|
| 30 |
- Take the object's key visual elements (colors, shapes, materials) and incorporate them into a creature design
|
| 31 |
+
- Add eyes (can be glowing, mechanical, multiple, etc.) positioned where they make sense
|
| 32 |
+
- Include limbs (legs, arms, wings, tentacles) that grow from or replace parts of the object
|
| 33 |
+
- Add a mouth, beak, or feeding apparatus if appropriate
|
| 34 |
+
- Add creature elements like tail, fins, claws, or horns where fitting
|
| 35 |
- The object should be recognizable but creatively interpreted
|
| 36 |
|
| 37 |
Rarity assessment: Common objects = weaker monsters. Unique/rare objects = stronger monsters.
|
|
|
|
| 45 |
const IMAGE_GENERATION_PROMPT = (concept: string) => `Convert this monster concept into a clear and succinct description of its appearance:
|
| 46 |
"${concept}"
|
| 47 |
|
| 48 |
+
Include all of its visual details, most importantly include its creature features, format the description as a single long sentence.`;
|
| 49 |
|
| 50 |
const MONSTER_STATS_PROMPT = (concept: string) => `Convert the following monster concept into a JSON object with stats:
|
| 51 |
|
src/lib/utils/imageProcessing.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
| 1 |
/**
|
| 2 |
* Converts an image URL to a base64 data URL with white background made transparent
|
|
|
|
| 3 |
*/
|
| 4 |
export async function makeWhiteTransparent(imageUrl: string): Promise<string> {
|
| 5 |
return new Promise((resolve, reject) => {
|
|
@@ -24,23 +25,99 @@ export async function makeWhiteTransparent(imageUrl: string): Promise<string> {
|
|
| 24 |
// Get image data
|
| 25 |
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
| 26 |
const data = imageData.data;
|
|
|
|
|
|
|
| 27 |
|
| 28 |
-
//
|
| 29 |
-
const
|
| 30 |
|
| 31 |
-
//
|
| 32 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
const r = data[i];
|
| 34 |
const g = data[i + 1];
|
| 35 |
const b = data[i + 2];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 36 |
|
| 37 |
-
// Check
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 41 |
|
| 42 |
-
|
| 43 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 44 |
}
|
| 45 |
}
|
| 46 |
|
|
|
|
| 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) => {
|
|
|
|
| 25 |
// Get image data
|
| 26 |
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
| 27 |
const data = imageData.data;
|
| 28 |
+
const width = canvas.width;
|
| 29 |
+
const height = canvas.height;
|
| 30 |
|
| 31 |
+
// Create a mask to track which pixels to make transparent
|
| 32 |
+
const mask = new Uint8Array(width * height);
|
| 33 |
|
| 34 |
+
// Define white threshold with some tolerance
|
| 35 |
+
const whiteThreshold = 240;
|
| 36 |
+
const tolerance = 20;
|
| 37 |
+
|
| 38 |
+
// Helper function to check if a pixel is white-ish
|
| 39 |
+
const isWhite = (index: number): boolean => {
|
| 40 |
+
const i = index * 4;
|
| 41 |
const r = data[i];
|
| 42 |
const g = data[i + 1];
|
| 43 |
const b = data[i + 2];
|
| 44 |
+
return r > whiteThreshold && g > whiteThreshold && b > whiteThreshold;
|
| 45 |
+
};
|
| 46 |
+
|
| 47 |
+
// Helper function to check if colors are similar
|
| 48 |
+
const colorSimilar = (i1: number, i2: number): boolean => {
|
| 49 |
+
const idx1 = i1 * 4;
|
| 50 |
+
const idx2 = i2 * 4;
|
| 51 |
+
return Math.abs(data[idx1] - data[idx2]) < tolerance &&
|
| 52 |
+
Math.abs(data[idx1 + 1] - data[idx2 + 1]) < tolerance &&
|
| 53 |
+
Math.abs(data[idx1 + 2] - data[idx2 + 2]) < tolerance;
|
| 54 |
+
};
|
| 55 |
+
|
| 56 |
+
// Flood fill from edges
|
| 57 |
+
const queue: number[] = [];
|
| 58 |
+
|
| 59 |
+
// Add all edge pixels that are white to the queue
|
| 60 |
+
// Top and bottom edges
|
| 61 |
+
for (let x = 0; x < width; x++) {
|
| 62 |
+
if (isWhite(x)) {
|
| 63 |
+
queue.push(x);
|
| 64 |
+
mask[x] = 1;
|
| 65 |
+
}
|
| 66 |
+
const bottomIdx = (height - 1) * width + x;
|
| 67 |
+
if (isWhite(bottomIdx)) {
|
| 68 |
+
queue.push(bottomIdx);
|
| 69 |
+
mask[bottomIdx] = 1;
|
| 70 |
+
}
|
| 71 |
+
}
|
| 72 |
+
|
| 73 |
+
// Left and right edges
|
| 74 |
+
for (let y = 1; y < height - 1; y++) {
|
| 75 |
+
const leftIdx = y * width;
|
| 76 |
+
if (isWhite(leftIdx)) {
|
| 77 |
+
queue.push(leftIdx);
|
| 78 |
+
mask[leftIdx] = 1;
|
| 79 |
+
}
|
| 80 |
+
const rightIdx = y * width + width - 1;
|
| 81 |
+
if (isWhite(rightIdx)) {
|
| 82 |
+
queue.push(rightIdx);
|
| 83 |
+
mask[rightIdx] = 1;
|
| 84 |
+
}
|
| 85 |
+
}
|
| 86 |
+
|
| 87 |
+
// Flood fill
|
| 88 |
+
while (queue.length > 0) {
|
| 89 |
+
const idx = queue.pop()!;
|
| 90 |
+
const x = idx % width;
|
| 91 |
+
const y = Math.floor(idx / width);
|
| 92 |
|
| 93 |
+
// Check 4 neighbors
|
| 94 |
+
const neighbors = [
|
| 95 |
+
{ dx: -1, dy: 0 }, // left
|
| 96 |
+
{ dx: 1, dy: 0 }, // right
|
| 97 |
+
{ dx: 0, dy: -1 }, // up
|
| 98 |
+
{ dx: 0, dy: 1 } // down
|
| 99 |
+
];
|
| 100 |
+
|
| 101 |
+
for (const { dx, dy } of neighbors) {
|
| 102 |
+
const nx = x + dx;
|
| 103 |
+
const ny = y + dy;
|
| 104 |
|
| 105 |
+
if (nx >= 0 && nx < width && ny >= 0 && ny < height) {
|
| 106 |
+
const nIdx = ny * width + nx;
|
| 107 |
+
|
| 108 |
+
// If not already marked and color is similar to current pixel
|
| 109 |
+
if (!mask[nIdx] && isWhite(nIdx) && colorSimilar(idx, nIdx)) {
|
| 110 |
+
mask[nIdx] = 1;
|
| 111 |
+
queue.push(nIdx);
|
| 112 |
+
}
|
| 113 |
+
}
|
| 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 |
|