|
|
import torch |
|
|
import torch.nn.functional as F |
|
|
import numpy as np |
|
|
import json |
|
|
import base64 |
|
|
import io |
|
|
from PIL import Image |
|
|
import svgwrite |
|
|
from typing import Dict, Any, List, Optional, Union |
|
|
import diffusers |
|
|
from diffusers import StableDiffusionPipeline, DDIMScheduler |
|
|
from transformers import CLIPTextModel, CLIPTokenizer |
|
|
import torchvision.transforms as transforms |
|
|
import random |
|
|
import math |
|
|
|
|
|
class EndpointHandler: |
|
|
def __init__(self, path=""): |
|
|
self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu") |
|
|
self.model_id = "runwayml/stable-diffusion-v1-5" |
|
|
|
|
|
try: |
|
|
|
|
|
self.pipe = StableDiffusionPipeline.from_pretrained( |
|
|
self.model_id, |
|
|
torch_dtype=torch.float16 if torch.cuda.is_available() else torch.float32, |
|
|
safety_checker=None, |
|
|
requires_safety_checker=False |
|
|
).to(self.device) |
|
|
|
|
|
|
|
|
self.pipe.scheduler = DDIMScheduler.from_config(self.pipe.scheduler.config) |
|
|
|
|
|
|
|
|
self.clip_model = self.pipe.text_encoder |
|
|
self.clip_tokenizer = self.pipe.tokenizer |
|
|
|
|
|
print("SVGDreamer handler initialized successfully!") |
|
|
except Exception as e: |
|
|
print(f"Warning: Could not load diffusion model: {e}") |
|
|
self.pipe = None |
|
|
self.clip_model = None |
|
|
self.clip_tokenizer = None |
|
|
|
|
|
def __call__(self, inputs: Union[str, Dict[str, Any]]) -> Image.Image: |
|
|
""" |
|
|
Generate SVG using SVGDreamer approach with multiple particles |
|
|
""" |
|
|
try: |
|
|
|
|
|
if isinstance(inputs, str): |
|
|
prompt = inputs |
|
|
parameters = {} |
|
|
else: |
|
|
prompt = inputs.get("inputs", inputs.get("prompt", "a simple icon")) |
|
|
parameters = inputs.get("parameters", {}) |
|
|
|
|
|
|
|
|
n_particle = parameters.get("n_particle", 4) |
|
|
style = parameters.get("style", "iconography") |
|
|
width = parameters.get("width", 256) |
|
|
height = parameters.get("height", 256) |
|
|
seed = parameters.get("seed", None) |
|
|
|
|
|
if seed is not None: |
|
|
torch.manual_seed(seed) |
|
|
np.random.seed(seed) |
|
|
random.seed(seed) |
|
|
|
|
|
print(f"Generating SVGDreamer for: '{prompt}' with {n_particle} particles, style: {style}") |
|
|
|
|
|
|
|
|
particles = self.generate_svgdreamer_particles( |
|
|
prompt, width, height, n_particle, style |
|
|
) |
|
|
|
|
|
|
|
|
best_particle = self.select_best_particle(particles, prompt) |
|
|
|
|
|
|
|
|
pil_image = self.svg_to_pil_image(best_particle['svg'], width, height) |
|
|
|
|
|
|
|
|
pil_image.info['svg_content'] = best_particle['svg'] |
|
|
pil_image.info['prompt'] = prompt |
|
|
pil_image.info['style'] = style |
|
|
pil_image.info['n_particle'] = str(n_particle) |
|
|
pil_image.info['particles'] = json.dumps(particles) |
|
|
pil_image.info['method'] = 'svgdreamer' |
|
|
|
|
|
return pil_image |
|
|
|
|
|
except Exception as e: |
|
|
print(f"Error in SVGDreamer handler: {e}") |
|
|
|
|
|
fallback_svg = self.create_fallback_svg(prompt if 'prompt' in locals() else "error", 256, 256, "iconography") |
|
|
fallback_image = self.svg_to_pil_image(fallback_svg, 256, 256) |
|
|
fallback_image.info['error'] = str(e) |
|
|
return fallback_image |
|
|
|
|
|
def generate_svgdreamer_particles(self, prompt: str, width: int, height: int, |
|
|
n_particle: int, style: str): |
|
|
""" |
|
|
Generate multiple SVG particles using SVGDreamer approach |
|
|
""" |
|
|
particles = [] |
|
|
|
|
|
|
|
|
text_embeddings = self.get_text_embeddings(prompt) |
|
|
|
|
|
|
|
|
for particle_id in range(n_particle): |
|
|
print(f"Generating particle {particle_id + 1}/{n_particle}") |
|
|
|
|
|
|
|
|
particle_seed = hash(f"{prompt}_{particle_id}_{style}") % 1000000 |
|
|
torch.manual_seed(particle_seed) |
|
|
np.random.seed(particle_seed) |
|
|
random.seed(particle_seed) |
|
|
|
|
|
|
|
|
svg_content = self.generate_particle_by_style( |
|
|
prompt, width, height, style, text_embeddings, particle_id |
|
|
) |
|
|
|
|
|
particle = { |
|
|
'particle_id': particle_id, |
|
|
'svg': svg_content, |
|
|
'svg_base64': base64.b64encode(svg_content.encode('utf-8')).decode('utf-8'), |
|
|
'prompt': prompt, |
|
|
'style': style, |
|
|
'parameters': { |
|
|
'width': width, |
|
|
'height': height, |
|
|
'seed': particle_seed |
|
|
} |
|
|
} |
|
|
|
|
|
particles.append(particle) |
|
|
|
|
|
return particles |
|
|
|
|
|
def generate_particle_by_style(self, prompt: str, width: int, height: int, |
|
|
style: str, text_embeddings: torch.Tensor, particle_id: int): |
|
|
""" |
|
|
Generate SVG particle based on specified style |
|
|
""" |
|
|
if style == "iconography": |
|
|
return self.generate_iconography_svg(prompt, width, height, text_embeddings) |
|
|
elif style == "pixel_art": |
|
|
return self.generate_pixel_art_svg(prompt, width, height, text_embeddings) |
|
|
elif style == "sketch": |
|
|
return self.generate_sketch_svg(prompt, width, height, text_embeddings) |
|
|
elif style == "painting": |
|
|
return self.generate_painting_svg(prompt, width, height, text_embeddings) |
|
|
else: |
|
|
return self.generate_iconography_svg(prompt, width, height, text_embeddings) |
|
|
|
|
|
def generate_iconography_svg(self, prompt: str, width: int, height: int, text_embeddings: torch.Tensor): |
|
|
"""Generate icon-style SVG with simple geometric shapes""" |
|
|
dwg = svgwrite.Drawing(size=(width, height)) |
|
|
dwg.add(dwg.rect(insert=(0, 0), size=(width, height), fill='white')) |
|
|
|
|
|
|
|
|
features = self.extract_semantic_features(prompt) |
|
|
|
|
|
|
|
|
if any(word in prompt.lower() for word in ['animal', 'cat', 'dog', 'bird', 'lion']): |
|
|
self.add_animal_icon_elements(dwg, width, height, features) |
|
|
elif any(word in prompt.lower() for word in ['house', 'building', 'home', 'castle']): |
|
|
self.add_building_icon_elements(dwg, width, height, features) |
|
|
elif any(word in prompt.lower() for word in ['tree', 'flower', 'plant', 'nature']): |
|
|
self.add_nature_icon_elements(dwg, width, height, features) |
|
|
elif any(word in prompt.lower() for word in ['car', 'vehicle', 'transport']): |
|
|
self.add_vehicle_icon_elements(dwg, width, height, features) |
|
|
else: |
|
|
self.add_abstract_icon_elements(dwg, width, height, features) |
|
|
|
|
|
return dwg.tostring() |
|
|
|
|
|
def generate_pixel_art_svg(self, prompt: str, width: int, height: int, text_embeddings: torch.Tensor): |
|
|
"""Generate pixel art style SVG""" |
|
|
dwg = svgwrite.Drawing(size=(width, height)) |
|
|
dwg.add(dwg.rect(insert=(0, 0), size=(width, height), fill='white')) |
|
|
|
|
|
|
|
|
pixel_size = 8 |
|
|
cols = width // pixel_size |
|
|
rows = height // pixel_size |
|
|
|
|
|
|
|
|
features = self.extract_semantic_features(prompt) |
|
|
colors = self.get_style_colors("pixel_art", features) |
|
|
|
|
|
for row in range(rows): |
|
|
for col in range(cols): |
|
|
|
|
|
if self.should_place_pixel(row, col, rows, cols, prompt, features): |
|
|
color = random.choice(colors) |
|
|
x = col * pixel_size |
|
|
y = row * pixel_size |
|
|
dwg.add(dwg.rect( |
|
|
insert=(x, y), |
|
|
size=(pixel_size, pixel_size), |
|
|
fill=color, |
|
|
stroke='none' |
|
|
)) |
|
|
|
|
|
return dwg.tostring() |
|
|
|
|
|
def generate_sketch_svg(self, prompt: str, width: int, height: int, text_embeddings: torch.Tensor): |
|
|
"""Generate sketch-style SVG with loose strokes""" |
|
|
dwg = svgwrite.Drawing(size=(width, height)) |
|
|
dwg.add(dwg.rect(insert=(0, 0), size=(width, height), fill='white')) |
|
|
|
|
|
features = self.extract_semantic_features(prompt) |
|
|
|
|
|
|
|
|
num_strokes = random.randint(15, 40) |
|
|
|
|
|
for i in range(num_strokes): |
|
|
|
|
|
path_data = self.generate_sketchy_path(width, height, features) |
|
|
|
|
|
stroke_color = f"rgb({random.randint(20, 80)},{random.randint(20, 80)},{random.randint(20, 80)})" |
|
|
stroke_width = random.uniform(0.5, 2.5) |
|
|
opacity = random.uniform(0.3, 0.8) |
|
|
|
|
|
dwg.add(dwg.path( |
|
|
d=path_data, |
|
|
stroke=stroke_color, |
|
|
stroke_width=stroke_width, |
|
|
stroke_opacity=opacity, |
|
|
fill='none', |
|
|
stroke_linecap='round', |
|
|
stroke_linejoin='round' |
|
|
)) |
|
|
|
|
|
return dwg.tostring() |
|
|
|
|
|
def generate_painting_svg(self, prompt: str, width: int, height: int, text_embeddings: torch.Tensor): |
|
|
"""Generate painting-style SVG with brush strokes""" |
|
|
dwg = svgwrite.Drawing(size=(width, height)) |
|
|
dwg.add(dwg.rect(insert=(0, 0), size=(width, height), fill='white')) |
|
|
|
|
|
features = self.extract_semantic_features(prompt) |
|
|
colors = self.get_style_colors("painting", features) |
|
|
|
|
|
|
|
|
num_strokes = random.randint(20, 60) |
|
|
|
|
|
for i in range(num_strokes): |
|
|
|
|
|
path_data = self.generate_brush_stroke(width, height, features) |
|
|
|
|
|
color = random.choice(colors) |
|
|
stroke_width = random.uniform(2.0, 8.0) |
|
|
opacity = random.uniform(0.4, 0.9) |
|
|
|
|
|
dwg.add(dwg.path( |
|
|
d=path_data, |
|
|
stroke=color, |
|
|
stroke_width=stroke_width, |
|
|
stroke_opacity=opacity, |
|
|
fill='none', |
|
|
stroke_linecap='round', |
|
|
stroke_linejoin='round' |
|
|
)) |
|
|
|
|
|
return dwg.tostring() |
|
|
|
|
|
def add_animal_icon_elements(self, dwg, width, height, features): |
|
|
"""Add animal-like icon elements""" |
|
|
center_x, center_y = width // 2, height // 2 |
|
|
|
|
|
|
|
|
body_radius = min(width, height) // 4 |
|
|
dwg.add(dwg.circle( |
|
|
center=(center_x, center_y + 10), |
|
|
r=body_radius, |
|
|
fill='#4A90E2', |
|
|
stroke='#2E5C8A', |
|
|
stroke_width=2 |
|
|
)) |
|
|
|
|
|
|
|
|
head_radius = body_radius * 0.7 |
|
|
dwg.add(dwg.circle( |
|
|
center=(center_x, center_y - 20), |
|
|
r=head_radius, |
|
|
fill='#5BA0F2', |
|
|
stroke='#2E5C8A', |
|
|
stroke_width=2 |
|
|
)) |
|
|
|
|
|
|
|
|
eye_size = 4 |
|
|
dwg.add(dwg.circle(center=(center_x - 15, center_y - 25), r=eye_size, fill='black')) |
|
|
dwg.add(dwg.circle(center=(center_x + 15, center_y - 25), r=eye_size, fill='black')) |
|
|
|
|
|
def add_building_icon_elements(self, dwg, width, height, features): |
|
|
"""Add building-like icon elements""" |
|
|
|
|
|
building_width = width * 0.6 |
|
|
building_height = height * 0.7 |
|
|
x = (width - building_width) // 2 |
|
|
y = height - building_height - 20 |
|
|
|
|
|
dwg.add(dwg.rect( |
|
|
insert=(x, y), |
|
|
size=(building_width, building_height), |
|
|
fill='#E74C3C', |
|
|
stroke='#C0392B', |
|
|
stroke_width=2 |
|
|
)) |
|
|
|
|
|
|
|
|
roof_points = [ |
|
|
(x, y), |
|
|
(x + building_width // 2, y - 30), |
|
|
(x + building_width, y) |
|
|
] |
|
|
dwg.add(dwg.polygon( |
|
|
points=roof_points, |
|
|
fill='#8B4513', |
|
|
stroke='#654321', |
|
|
stroke_width=2 |
|
|
)) |
|
|
|
|
|
|
|
|
window_size = 20 |
|
|
for i in range(2): |
|
|
for j in range(3): |
|
|
wx = x + 20 + i * 40 |
|
|
wy = y + 20 + j * 30 |
|
|
dwg.add(dwg.rect( |
|
|
insert=(wx, wy), |
|
|
size=(window_size, window_size), |
|
|
fill='#3498DB', |
|
|
stroke='#2980B9', |
|
|
stroke_width=1 |
|
|
)) |
|
|
|
|
|
def add_nature_icon_elements(self, dwg, width, height, features): |
|
|
"""Add nature-like icon elements""" |
|
|
center_x, center_y = width // 2, height // 2 |
|
|
|
|
|
|
|
|
trunk_width = 20 |
|
|
trunk_height = height // 3 |
|
|
trunk_x = center_x - trunk_width // 2 |
|
|
trunk_y = height - trunk_height - 10 |
|
|
|
|
|
dwg.add(dwg.rect( |
|
|
insert=(trunk_x, trunk_y), |
|
|
size=(trunk_width, trunk_height), |
|
|
fill='#8B4513', |
|
|
stroke='#654321', |
|
|
stroke_width=1 |
|
|
)) |
|
|
|
|
|
|
|
|
crown_radius = min(width, height) // 3 |
|
|
dwg.add(dwg.circle( |
|
|
center=(center_x, center_y - 20), |
|
|
r=crown_radius, |
|
|
fill='#27AE60', |
|
|
stroke='#1E8449', |
|
|
stroke_width=2 |
|
|
)) |
|
|
|
|
|
def add_vehicle_icon_elements(self, dwg, width, height, features): |
|
|
"""Add vehicle-like icon elements""" |
|
|
center_x, center_y = width // 2, height // 2 |
|
|
|
|
|
|
|
|
car_width = width * 0.7 |
|
|
car_height = height * 0.4 |
|
|
car_x = (width - car_width) // 2 |
|
|
car_y = center_y |
|
|
|
|
|
dwg.add(dwg.rect( |
|
|
insert=(car_x, car_y), |
|
|
size=(car_width, car_height), |
|
|
fill='#E74C3C', |
|
|
stroke='#C0392B', |
|
|
stroke_width=2, |
|
|
rx=10 |
|
|
)) |
|
|
|
|
|
|
|
|
wheel_radius = 15 |
|
|
wheel_y = car_y + car_height - 5 |
|
|
dwg.add(dwg.circle(center=(car_x + 30, wheel_y), r=wheel_radius, fill='#2C3E50')) |
|
|
dwg.add(dwg.circle(center=(car_x + car_width - 30, wheel_y), r=wheel_radius, fill='#2C3E50')) |
|
|
|
|
|
def add_abstract_icon_elements(self, dwg, width, height, features): |
|
|
"""Add abstract icon elements""" |
|
|
center_x, center_y = width // 2, height // 2 |
|
|
|
|
|
|
|
|
colors = ['#3498DB', '#E74C3C', '#F39C12', '#27AE60', '#9B59B6'] |
|
|
|
|
|
for i in range(3): |
|
|
shape_type = random.choice(['circle', 'rect', 'polygon']) |
|
|
color = random.choice(colors) |
|
|
|
|
|
if shape_type == 'circle': |
|
|
radius = random.randint(20, 50) |
|
|
x = random.randint(radius, width - radius) |
|
|
y = random.randint(radius, height - radius) |
|
|
dwg.add(dwg.circle(center=(x, y), r=radius, fill=color, opacity=0.7)) |
|
|
elif shape_type == 'rect': |
|
|
w = random.randint(30, 80) |
|
|
h = random.randint(30, 80) |
|
|
x = random.randint(0, width - w) |
|
|
y = random.randint(0, height - h) |
|
|
dwg.add(dwg.rect(insert=(x, y), size=(w, h), fill=color, opacity=0.7)) |
|
|
|
|
|
def get_style_colors(self, style: str, features: Dict): |
|
|
"""Get color palette for specific style""" |
|
|
if style == "pixel_art": |
|
|
return ['#FF6B6B', '#4ECDC4', '#45B7D1', '#96CEB4', '#FFEAA7', '#DDA0DD'] |
|
|
elif style == "painting": |
|
|
return ['#8B4513', '#228B22', '#4169E1', '#DC143C', '#FFD700', '#9370DB'] |
|
|
elif style == "iconography": |
|
|
return ['#3498DB', '#E74C3C', '#F39C12', '#27AE60', '#9B59B6', '#34495E'] |
|
|
else: |
|
|
return ['#333333', '#666666', '#999999', '#CCCCCC'] |
|
|
|
|
|
def should_place_pixel(self, row: int, col: int, rows: int, cols: int, prompt: str, features: Dict): |
|
|
"""Determine if a pixel should be placed at given position""" |
|
|
center_row, center_col = rows // 2, cols // 2 |
|
|
distance_from_center = math.sqrt((row - center_row)**2 + (col - center_col)**2) |
|
|
max_distance = math.sqrt(center_row**2 + center_col**2) |
|
|
|
|
|
|
|
|
if any(word in prompt.lower() for word in ['circle', 'round', 'ball']): |
|
|
return distance_from_center < max_distance * 0.4 |
|
|
elif any(word in prompt.lower() for word in ['square', 'box', 'cube']): |
|
|
return abs(row - center_row) < rows * 0.3 and abs(col - center_col) < cols * 0.3 |
|
|
else: |
|
|
|
|
|
probability = 1.0 - (distance_from_center / max_distance) * 0.7 |
|
|
return random.random() < probability |
|
|
|
|
|
def generate_sketchy_path(self, width: int, height: int, features: Dict): |
|
|
"""Generate a sketchy path with natural variations""" |
|
|
|
|
|
start_x = random.uniform(width * 0.1, width * 0.9) |
|
|
start_y = random.uniform(height * 0.1, height * 0.9) |
|
|
|
|
|
|
|
|
path_data = f"M {start_x},{start_y}" |
|
|
|
|
|
current_x, current_y = start_x, start_y |
|
|
num_segments = random.randint(2, 5) |
|
|
|
|
|
for i in range(num_segments): |
|
|
|
|
|
dx = random.uniform(-width * 0.3, width * 0.3) |
|
|
dy = random.uniform(-height * 0.3, height * 0.3) |
|
|
|
|
|
end_x = max(0, min(width, current_x + dx)) |
|
|
end_y = max(0, min(height, current_y + dy)) |
|
|
|
|
|
|
|
|
cp_x = current_x + dx * 0.5 + random.uniform(-20, 20) |
|
|
cp_y = current_y + dy * 0.5 + random.uniform(-20, 20) |
|
|
|
|
|
path_data += f" Q {cp_x},{cp_y} {end_x},{end_y}" |
|
|
current_x, current_y = end_x, end_y |
|
|
|
|
|
return path_data |
|
|
|
|
|
def generate_brush_stroke(self, width: int, height: int, features: Dict): |
|
|
"""Generate a painterly brush stroke""" |
|
|
|
|
|
start_x = random.uniform(width * 0.1, width * 0.9) |
|
|
start_y = random.uniform(height * 0.1, height * 0.9) |
|
|
|
|
|
|
|
|
length = random.uniform(30, 100) |
|
|
angle = random.uniform(0, 2 * math.pi) |
|
|
end_x = start_x + length * math.cos(angle) |
|
|
end_y = start_y + length * math.sin(angle) |
|
|
|
|
|
|
|
|
end_x = max(0, min(width, end_x)) |
|
|
end_y = max(0, min(height, end_y)) |
|
|
|
|
|
|
|
|
mid_x = (start_x + end_x) / 2 + random.uniform(-20, 20) |
|
|
mid_y = (start_y + end_y) / 2 + random.uniform(-20, 20) |
|
|
|
|
|
return f"M {start_x},{start_y} Q {mid_x},{mid_y} {end_x},{end_y}" |
|
|
|
|
|
def get_text_embeddings(self, prompt: str): |
|
|
"""Get CLIP text embeddings for the prompt""" |
|
|
if self.clip_model is None or self.clip_tokenizer is None: |
|
|
|
|
|
return torch.zeros((1, 77, 768)) |
|
|
|
|
|
try: |
|
|
with torch.no_grad(): |
|
|
text_inputs = self.clip_tokenizer( |
|
|
prompt, |
|
|
padding="max_length", |
|
|
max_length=self.clip_tokenizer.model_max_length, |
|
|
truncation=True, |
|
|
return_tensors="pt" |
|
|
).to(self.device) |
|
|
|
|
|
text_embeddings = self.clip_model(text_inputs.input_ids)[0] |
|
|
|
|
|
return text_embeddings |
|
|
except Exception as e: |
|
|
print(f"Error getting text embeddings: {e}") |
|
|
return torch.zeros((1, 77, 768)) |
|
|
|
|
|
def extract_semantic_features(self, prompt: str): |
|
|
"""Extract semantic features from prompt""" |
|
|
features = { |
|
|
'complexity': 'medium', |
|
|
'organic': False, |
|
|
'geometric': False, |
|
|
'colorful': False, |
|
|
'minimal': False |
|
|
} |
|
|
|
|
|
prompt_lower = prompt.lower() |
|
|
|
|
|
|
|
|
if any(word in prompt_lower for word in ['simple', 'minimal', 'clean']): |
|
|
features['minimal'] = True |
|
|
features['complexity'] = 'low' |
|
|
elif any(word in prompt_lower for word in ['detailed', 'complex', 'intricate']): |
|
|
features['complexity'] = 'high' |
|
|
|
|
|
if any(word in prompt_lower for word in ['colorful', 'bright', 'vibrant']): |
|
|
features['colorful'] = True |
|
|
|
|
|
if any(word in prompt_lower for word in ['organic', 'natural', 'flowing']): |
|
|
features['organic'] = True |
|
|
|
|
|
if any(word in prompt_lower for word in ['geometric', 'angular', 'structured']): |
|
|
features['geometric'] = True |
|
|
|
|
|
return features |
|
|
|
|
|
def select_best_particle(self, particles: List[Dict], prompt: str): |
|
|
"""Select the best particle from generated options""" |
|
|
|
|
|
|
|
|
return particles[0] if particles else self.create_fallback_particle(prompt) |
|
|
|
|
|
def create_fallback_particle(self, prompt: str): |
|
|
"""Create a fallback particle""" |
|
|
fallback_svg = self.create_fallback_svg(prompt, 256, 256, "iconography") |
|
|
return { |
|
|
'particle_id': 0, |
|
|
'svg': fallback_svg, |
|
|
'svg_base64': base64.b64encode(fallback_svg.encode('utf-8')).decode('utf-8'), |
|
|
'prompt': prompt, |
|
|
'style': 'iconography', |
|
|
'parameters': {'width': 256, 'height': 256, 'seed': 0} |
|
|
} |
|
|
|
|
|
def svg_to_pil_image(self, svg_content: str, width: int, height: int): |
|
|
"""Convert SVG content to PIL Image""" |
|
|
try: |
|
|
import cairosvg |
|
|
|
|
|
|
|
|
png_bytes = cairosvg.svg2png( |
|
|
bytestring=svg_content.encode('utf-8'), |
|
|
output_width=width, |
|
|
output_height=height |
|
|
) |
|
|
|
|
|
|
|
|
image = Image.open(io.BytesIO(png_bytes)).convert('RGB') |
|
|
return image |
|
|
|
|
|
except ImportError: |
|
|
print("cairosvg not available, creating simple image representation") |
|
|
|
|
|
image = Image.new('RGB', (width, height), 'white') |
|
|
return image |
|
|
except Exception as e: |
|
|
print(f"Error converting SVG to image: {e}") |
|
|
|
|
|
image = Image.new('RGB', (width, height), 'white') |
|
|
return image |
|
|
|
|
|
def create_fallback_svg(self, prompt: str, width: int, height: int, style: str): |
|
|
"""Create simple fallback SVG""" |
|
|
dwg = svgwrite.Drawing(size=(width, height)) |
|
|
dwg.add(dwg.rect(insert=(0, 0), size=(width, height), fill='white')) |
|
|
|
|
|
|
|
|
dwg.add(dwg.text( |
|
|
f"SVGDreamer\n{style}\n{prompt[:20]}...", |
|
|
insert=(width/2, height/2), |
|
|
text_anchor="middle", |
|
|
font_size="12px", |
|
|
fill="black" |
|
|
)) |
|
|
|
|
|
return dwg.tostring() |