Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
|
@@ -589,7 +589,7 @@ def analyze_for_vectorization(pil_img):
|
|
| 589 |
return params, is_flat_illustration, n_colors
|
| 590 |
|
| 591 |
|
| 592 |
-
def detect_primitive_circles(pil_img):
|
| 593 |
"""Detecta círculos grandes y bien definidos (por ejemplo, un fondo
|
| 594 |
circular detrás de un logo) con la transformada de Hough — no hace
|
| 595 |
falta ningún modelo de IA para esto, es un problema ya resuelto por
|
|
@@ -605,6 +605,18 @@ def detect_primitive_circles(pil_img):
|
|
| 605 |
minRadius=int(min(h, w) * 0.15), maxRadius=int(min(h, w) * 0.6),
|
| 606 |
)
|
| 607 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 608 |
detected = []
|
| 609 |
if circles is not None:
|
| 610 |
for x, y, r in circles[0]:
|
|
@@ -613,9 +625,13 @@ def detect_primitive_circles(pil_img):
|
|
| 613 |
sample_x = int(min(w - 1, max(0, x)))
|
| 614 |
sample_y = int(min(h - 1, max(0, y - r * 0.85)))
|
| 615 |
color = arr[sample_y, sample_x]
|
|
|
|
|
|
|
|
|
|
|
|
|
| 616 |
detected.append({
|
| 617 |
"cx": float(x), "cy": float(y), "r": float(r),
|
| 618 |
-
"color":
|
| 619 |
})
|
| 620 |
return detected
|
| 621 |
|
|
@@ -710,12 +726,14 @@ def consolidate_svg_colors(svg_content, max_colors=10):
|
|
| 710 |
"""VTracer puede volver a generar variaciones de color al trazar
|
| 711 |
los bordes, aunque la imagen de entrada ya viniera con pocos
|
| 712 |
colores. Esto agarra el SVG ya generado y fuerza que use como
|
| 713 |
-
máximo max_colors colores de verdad, agrupando los parecidos.
|
|
|
|
|
|
|
| 714 |
try:
|
| 715 |
ET.register_namespace("", "http://www.w3.org/2000/svg")
|
| 716 |
root = ET.fromstring(svg_content)
|
| 717 |
except ET.ParseError:
|
| 718 |
-
return svg_content
|
| 719 |
|
| 720 |
paths = root.findall(".//{http://www.w3.org/2000/svg}path") or root.findall(".//path")
|
| 721 |
|
|
@@ -730,7 +748,8 @@ def consolidate_svg_colors(svg_content, max_colors=10):
|
|
| 730 |
continue
|
| 731 |
|
| 732 |
if len(colors) < max_colors:
|
| 733 |
-
|
|
|
|
| 734 |
|
| 735 |
pixels = np.array(colors, dtype=np.float32)
|
| 736 |
k = min(max_colors, len(np.unique(pixels.astype(np.uint8), axis=0)))
|
|
@@ -748,7 +767,8 @@ def consolidate_svg_colors(svg_content, max_colors=10):
|
|
| 748 |
path.set("fill", "#{:02x}{:02x}{:02x}".format(*center))
|
| 749 |
i += 1
|
| 750 |
|
| 751 |
-
|
|
|
|
| 752 |
|
| 753 |
|
| 754 |
@app.post("/vectorize")
|
|
@@ -783,9 +803,9 @@ async def vectorize(file: UploadFile = File(...)):
|
|
| 783 |
with open(out_file.name, "r") as f:
|
| 784 |
svg_content = f.read()
|
| 785 |
|
| 786 |
-
svg_content = consolidate_svg_colors(svg_content, max_colors=10)
|
| 787 |
|
| 788 |
-
detected_circles = detect_primitive_circles(vtracer_input)
|
| 789 |
svg_content = inject_primitive_shapes(svg_content, detected_circles)
|
| 790 |
|
| 791 |
except Exception as e:
|
|
@@ -798,4 +818,4 @@ async def vectorize(file: UploadFile = File(...)):
|
|
| 798 |
"X-Vectorize-Colors-Detected": str(n_colors),
|
| 799 |
"X-Vectorize-Type": "ilustracion" if is_flat_illustration else "foto-detallada",
|
| 800 |
},
|
| 801 |
-
)
|
|
|
|
| 589 |
return params, is_flat_illustration, n_colors
|
| 590 |
|
| 591 |
|
| 592 |
+
def detect_primitive_circles(pil_img, palette=None):
|
| 593 |
"""Detecta círculos grandes y bien definidos (por ejemplo, un fondo
|
| 594 |
circular detrás de un logo) con la transformada de Hough — no hace
|
| 595 |
falta ningún modelo de IA para esto, es un problema ya resuelto por
|
|
|
|
| 605 |
minRadius=int(min(h, w) * 0.15), maxRadius=int(min(h, w) * 0.6),
|
| 606 |
)
|
| 607 |
|
| 608 |
+
def closest_palette_color(hex_color, palette):
|
| 609 |
+
if not palette:
|
| 610 |
+
return hex_color
|
| 611 |
+
target = tuple(int(hex_color.lstrip("#")[i:i + 2], 16) for i in (0, 2, 4))
|
| 612 |
+
best, best_dist = hex_color, None
|
| 613 |
+
for p in palette:
|
| 614 |
+
c = tuple(int(p.lstrip("#")[i:i + 2], 16) for i in (0, 2, 4))
|
| 615 |
+
dist = sum((a - b) ** 2 for a, b in zip(target, c))
|
| 616 |
+
if best_dist is None or dist < best_dist:
|
| 617 |
+
best, best_dist = p, dist
|
| 618 |
+
return best
|
| 619 |
+
|
| 620 |
detected = []
|
| 621 |
if circles is not None:
|
| 622 |
for x, y, r in circles[0]:
|
|
|
|
| 625 |
sample_x = int(min(w - 1, max(0, x)))
|
| 626 |
sample_y = int(min(h - 1, max(0, y - r * 0.85)))
|
| 627 |
color = arr[sample_y, sample_x]
|
| 628 |
+
hex_color = "#{:02x}{:02x}{:02x}".format(*color)
|
| 629 |
+
# Lo ajustamos al color más parecido de la paleta final del
|
| 630 |
+
# SVG, para que coincida exacto con lo que quedó ahí.
|
| 631 |
+
hex_color = closest_palette_color(hex_color, palette)
|
| 632 |
detected.append({
|
| 633 |
"cx": float(x), "cy": float(y), "r": float(r),
|
| 634 |
+
"color": hex_color,
|
| 635 |
})
|
| 636 |
return detected
|
| 637 |
|
|
|
|
| 726 |
"""VTracer puede volver a generar variaciones de color al trazar
|
| 727 |
los bordes, aunque la imagen de entrada ya viniera con pocos
|
| 728 |
colores. Esto agarra el SVG ya generado y fuerza que use como
|
| 729 |
+
máximo max_colors colores de verdad, agrupando los parecidos.
|
| 730 |
+
Devuelve también esa paleta final, para poder buscar formas
|
| 731 |
+
geométricas (círculos, etc.) usando esos mismos colores exactos."""
|
| 732 |
try:
|
| 733 |
ET.register_namespace("", "http://www.w3.org/2000/svg")
|
| 734 |
root = ET.fromstring(svg_content)
|
| 735 |
except ET.ParseError:
|
| 736 |
+
return svg_content, []
|
| 737 |
|
| 738 |
paths = root.findall(".//{http://www.w3.org/2000/svg}path") or root.findall(".//path")
|
| 739 |
|
|
|
|
| 748 |
continue
|
| 749 |
|
| 750 |
if len(colors) < max_colors:
|
| 751 |
+
palette = ["#{:02x}{:02x}{:02x}".format(*c) for c in {tuple(c) for c in colors}]
|
| 752 |
+
return svg_content, palette
|
| 753 |
|
| 754 |
pixels = np.array(colors, dtype=np.float32)
|
| 755 |
k = min(max_colors, len(np.unique(pixels.astype(np.uint8), axis=0)))
|
|
|
|
| 767 |
path.set("fill", "#{:02x}{:02x}{:02x}".format(*center))
|
| 768 |
i += 1
|
| 769 |
|
| 770 |
+
palette = ["#{:02x}{:02x}{:02x}".format(*c) for c in centers]
|
| 771 |
+
return ET.tostring(root, encoding="unicode"), palette
|
| 772 |
|
| 773 |
|
| 774 |
@app.post("/vectorize")
|
|
|
|
| 803 |
with open(out_file.name, "r") as f:
|
| 804 |
svg_content = f.read()
|
| 805 |
|
| 806 |
+
svg_content, final_palette = consolidate_svg_colors(svg_content, max_colors=10)
|
| 807 |
|
| 808 |
+
detected_circles = detect_primitive_circles(vtracer_input, palette=final_palette)
|
| 809 |
svg_content = inject_primitive_shapes(svg_content, detected_circles)
|
| 810 |
|
| 811 |
except Exception as e:
|
|
|
|
| 818 |
"X-Vectorize-Colors-Detected": str(n_colors),
|
| 819 |
"X-Vectorize-Type": "ilustracion" if is_flat_illustration else "foto-detallada",
|
| 820 |
},
|
| 821 |
+
)
|