jree423 commited on
Commit
8f849bf
·
verified ·
1 Parent(s): 5c522de

Fix DiffSketcher handler to properly generate SVG sketches

Browse files
Files changed (1) hide show
  1. handler.py +371 -137
handler.py CHANGED
@@ -1,171 +1,405 @@
1
- from PIL import Image, ImageDraw
2
- import math
 
 
 
 
 
 
 
 
 
3
  import random
 
4
 
5
  class EndpointHandler:
6
  def __init__(self, path=""):
7
  """Initialize DiffSketcher handler for Hugging Face Inference API"""
8
- pass
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
 
10
  def __call__(self, data):
11
- """Generate sketch from text prompt"""
12
- # Extract prompt
13
- inputs = data.get("inputs", "")
14
- if isinstance(inputs, dict):
15
- prompt = inputs.get("prompt", inputs.get("text", ""))
16
- else:
17
- prompt = str(inputs)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
 
19
- if not prompt:
20
- prompt = "a simple sketch"
 
21
 
22
- # Generate sketch
23
- image = self.generate_sketch(prompt)
 
24
 
25
- # Return PIL Image directly for HF Inference API
26
- return image
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
 
28
- def generate_sketch(self, prompt):
29
- """Generate a sketch based on the prompt"""
30
- # Create 224x224 white canvas
31
- img = Image.new('RGB', (224, 224), 'white')
32
- draw = ImageDraw.Draw(img)
33
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
34
  prompt_lower = prompt.lower()
35
 
36
- if any(word in prompt_lower for word in ['mountain', 'landscape', 'nature']):
37
- self._draw_landscape(draw)
38
- elif any(word in prompt_lower for word in ['car', 'vehicle', 'automobile']):
39
- self._draw_car(draw)
40
- elif any(word in prompt_lower for word in ['house', 'building', 'home']):
41
- self._draw_house(draw)
42
- elif any(word in prompt_lower for word in ['flower', 'rose', 'bloom']):
43
- self._draw_flower(draw)
44
- elif any(word in prompt_lower for word in ['face', 'portrait', 'person']):
45
- self._draw_face(draw)
46
- elif any(word in prompt_lower for word in ['cat', 'animal', 'pet']):
47
- self._draw_cat(draw)
48
  else:
49
- self._draw_abstract(draw, prompt)
50
 
51
- return img
52
 
53
- def _draw_landscape(self, draw):
54
- """Draw mountain landscape"""
55
- # Sky
56
- draw.rectangle([0, 0, 224, 120], fill=(200, 220, 255))
57
-
58
- # Mountains
59
- points = [(0, 120)]
60
- for x in range(0, 224, 30):
61
- y = 120 + 40 * math.sin(x * 0.02) + 20 * math.sin(x * 0.05)
62
- points.append((x, int(y)))
63
- points.extend([(224, 224), (0, 224)])
64
- draw.polygon(points, fill=(100, 150, 100), outline=(50, 100, 50))
65
-
66
- # Trees
67
- for i in range(4):
68
- x = 40 + i * 45
69
- y = 140 + i * 5
70
- # Trunk
71
- draw.rectangle([x-3, y, x+3, y+30], fill=(101, 67, 33))
72
- # Leaves
73
- draw.ellipse([x-15, y-20, x+15, y+5], fill=(34, 139, 34), outline=(0, 100, 0))
74
-
75
- def _draw_car(self, draw):
76
- """Draw car sketch"""
77
- # Main body
78
- draw.rectangle([50, 120, 174, 160], fill=(150, 150, 150), outline=(0, 0, 0), width=2)
79
- # Roof
80
- draw.rectangle([70, 100, 154, 120], fill=(120, 120, 120), outline=(0, 0, 0), width=2)
81
- # Wheels
82
- draw.ellipse([60, 150, 80, 170], fill=(50, 50, 50), outline=(0, 0, 0), width=2)
83
- draw.ellipse([144, 150, 164, 170], fill=(50, 50, 50), outline=(0, 0, 0), width=2)
84
- # Windows
85
- draw.rectangle([75, 105, 100, 115], fill=(150, 200, 255), outline=(0, 0, 0))
86
- draw.rectangle([124, 105, 149, 115], fill=(150, 200, 255), outline=(0, 0, 0))
87
 
88
- def _draw_house(self, draw):
89
- """Draw house sketch"""
90
- # Base
91
- draw.rectangle([60, 130, 164, 190], fill=(200, 180, 140), outline=(0, 0, 0), width=2)
 
 
 
 
 
 
 
 
 
 
 
 
92
  # Roof
93
- draw.polygon([(60, 130), (112, 80), (164, 130)], fill=(180, 100, 100), outline=(0, 0, 0), width=2)
94
- # Door
95
- draw.rectangle([100, 160, 124, 190], fill=(139, 69, 19), outline=(0, 0, 0), width=2)
96
- # Windows
97
- draw.rectangle([70, 145, 90, 165], fill=(150, 200, 255), outline=(0, 0, 0), width=2)
98
- draw.rectangle([134, 145, 154, 165], fill=(150, 200, 255), outline=(0, 0, 0), width=2)
99
 
100
- def _draw_flower(self, draw):
101
- """Draw flower sketch"""
102
- center = (112, 112)
 
103
  # Stem
104
- draw.line([center[0], center[1]+20, center[0], 200], fill=(34, 139, 34), width=4)
 
 
 
 
 
 
105
  # Petals
106
  for angle in range(0, 360, 45):
107
- x = center[0] + 25 * math.cos(math.radians(angle))
108
- y = center[1] + 25 * math.sin(math.radians(angle))
109
- draw.ellipse([x-8, y-15, x+8, y+5], fill=(255, 100, 150), outline=(200, 50, 100))
 
 
 
 
 
 
 
110
  # Center
111
- draw.ellipse([center[0]-8, center[1]-8, center[0]+8, center[1]+8],
112
- fill=(255, 255, 0), outline=(200, 200, 0))
113
-
114
- def _draw_face(self, draw):
115
- """Draw face sketch"""
116
- center = (112, 112)
117
- # Head
118
- draw.ellipse([center[0]-40, center[1]-50, center[0]+40, center[1]+30],
119
- fill=(255, 220, 177), outline=(0, 0, 0), width=2)
120
- # Eyes
121
- draw.ellipse([center[0]-20, center[1]-20, center[0]-10, center[1]-10],
122
- fill=(255, 255, 255), outline=(0, 0, 0))
123
- draw.ellipse([center[0]+10, center[1]-20, center[0]+20, center[1]-10],
124
- fill=(255, 255, 255), outline=(0, 0, 0))
125
- # Pupils
126
- draw.ellipse([center[0]-17, center[1]-17, center[0]-13, center[1]-13], fill=(0, 0, 0))
127
- draw.ellipse([center[0]+13, center[1]-17, center[0]+17, center[1]-13], fill=(0, 0, 0))
128
- # Nose
129
- draw.line([center[0], center[1]-5, center[0]-3, center[1]+5], fill=(0, 0, 0), width=2)
130
- # Mouth
131
- draw.arc([center[0]-15, center[1]+5, center[0]+15, center[1]+20], 0, 180, fill=(0, 0, 0), width=2)
132
-
133
- def _draw_cat(self, draw):
134
- """Draw cat sketch"""
135
- center = (112, 112)
136
- # Body
137
- draw.ellipse([center[0]-30, center[1], center[0]+30, center[1]+50],
138
- fill=(200, 200, 200), outline=(0, 0, 0), width=2)
139
- # Head
140
- draw.ellipse([center[0]-20, center[1]-30, center[0]+20, center[1]+5],
141
- fill=(200, 200, 200), outline=(0, 0, 0), width=2)
142
- # Ears
143
- draw.polygon([(center[0]-15, center[1]-25), (center[0]-10, center[1]-40), (center[0]-5, center[1]-30)],
144
- fill=(200, 200, 200), outline=(0, 0, 0), width=2)
145
- draw.polygon([(center[0]+5, center[1]-30), (center[0]+10, center[1]-40), (center[0]+15, center[1]-25)],
146
- fill=(200, 200, 200), outline=(0, 0, 0), width=2)
147
- # Eyes
148
- draw.ellipse([center[0]-10, center[1]-20, center[0]-5, center[1]-15], fill=(0, 0, 0))
149
- draw.ellipse([center[0]+5, center[1]-20, center[0]+10, center[1]-15], fill=(0, 0, 0))
150
- # Nose
151
- draw.polygon([(center[0]-1, center[1]-10), (center[0]+1, center[1]-10), (center[0], center[1]-7)], fill=(255, 100, 100))
152
 
153
- def _draw_abstract(self, draw, prompt):
154
- """Draw abstract sketch"""
 
155
  prompt_hash = hash(prompt) % 100
156
 
157
- # Create flowing lines
158
  for i in range(8):
159
  points = []
160
- start_x = (i * 30 + prompt_hash) % 200 + 12
161
- start_y = (i * 25 + prompt_hash) % 180 + 22
162
 
163
  for j in range(4):
164
  x = start_x + j * 25 + 15 * math.sin((i + j + prompt_hash) * 0.5)
165
  y = start_y + j * 20 + 15 * math.cos((i + j + prompt_hash) * 0.3)
166
- points.append((int(x) % 224, int(y) % 224))
167
 
168
- # Draw connected lines
169
- for k in range(len(points)-1):
170
- color = (50 + (i * 25) % 150, 50 + (i * 35) % 150, 50 + (i * 45) % 150)
171
- draw.line([points[k], points[k+1]], fill=color, width=2)
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import sys
3
+ import torch
4
+ import base64
5
+ import io
6
+ import json
7
+ from PIL import Image
8
+ import svgwrite
9
+ import numpy as np
10
+ from diffusers import StableDiffusionPipeline
11
+ from transformers import CLIPTextModel, CLIPTokenizer
12
  import random
13
+ import math
14
 
15
  class EndpointHandler:
16
  def __init__(self, path=""):
17
  """Initialize DiffSketcher handler for Hugging Face Inference API"""
18
+ self.device = "cuda" if torch.cuda.is_available() else "cpu"
19
+ print(f"Using device: {self.device}")
20
+
21
+ # Initialize Stable Diffusion pipeline
22
+ try:
23
+ self.pipe = StableDiffusionPipeline.from_pretrained(
24
+ "runwayml/stable-diffusion-v1-5",
25
+ torch_dtype=torch.float16 if self.device == "cuda" else torch.float32,
26
+ safety_checker=None,
27
+ requires_safety_checker=False
28
+ )
29
+ self.pipe = self.pipe.to(self.device)
30
+ print("Stable Diffusion pipeline loaded successfully")
31
+ except Exception as e:
32
+ print(f"Error loading pipeline: {e}")
33
+ self.pipe = None
34
+
35
+ # Initialize tokenizer and text encoder
36
+ try:
37
+ self.tokenizer = CLIPTokenizer.from_pretrained("openai/clip-vit-base-patch32")
38
+ self.text_encoder = CLIPTextModel.from_pretrained("openai/clip-vit-base-patch32")
39
+ self.text_encoder = self.text_encoder.to(self.device)
40
+ print("Text encoder loaded successfully")
41
+ except Exception as e:
42
+ print(f"Error loading text encoder: {e}")
43
+ self.tokenizer = None
44
+ self.text_encoder = None
45
 
46
  def __call__(self, data):
47
+ """Generate SVG sketch from text prompt"""
48
+ try:
49
+ # Extract inputs
50
+ inputs = data.get("inputs", "")
51
+ parameters = data.get("parameters", {})
52
+
53
+ if isinstance(inputs, dict):
54
+ prompt = inputs.get("prompt", inputs.get("text", ""))
55
+ else:
56
+ prompt = str(inputs)
57
+
58
+ if not prompt:
59
+ prompt = "a simple sketch"
60
+
61
+ # Extract parameters
62
+ num_paths = parameters.get("num_paths", 96)
63
+ num_iter = parameters.get("num_iter", 500)
64
+ guidance_scale = parameters.get("guidance_scale", 7.5)
65
+ width = parameters.get("width", 224)
66
+ height = parameters.get("height", 224)
67
+ seed = parameters.get("seed", 42)
68
+
69
+ # Set seed for reproducibility
70
+ torch.manual_seed(seed)
71
+ np.random.seed(seed)
72
+ random.seed(seed)
73
+
74
+ print(f"Generating SVG for prompt: '{prompt}' with {num_paths} paths")
75
+
76
+ # Generate SVG
77
+ svg_content = self.generate_svg_sketch(
78
+ prompt, num_paths, num_iter, guidance_scale, width, height
79
+ )
80
+
81
+ # Convert SVG to base64 for transmission
82
+ svg_base64 = base64.b64encode(svg_content.encode('utf-8')).decode('utf-8')
83
+
84
+ # Return result
85
+ result = {
86
+ "svg": svg_content,
87
+ "svg_base64": svg_base64,
88
+ "prompt": prompt,
89
+ "parameters": {
90
+ "num_paths": num_paths,
91
+ "num_iter": num_iter,
92
+ "guidance_scale": guidance_scale,
93
+ "width": width,
94
+ "height": height,
95
+ "seed": seed
96
+ }
97
+ }
98
+
99
+ return result
100
+
101
+ except Exception as e:
102
+ print(f"Error in handler: {e}")
103
+ # Return a simple fallback SVG
104
+ fallback_svg = self.create_fallback_svg(prompt, width, height)
105
+ return {
106
+ "svg": fallback_svg,
107
+ "svg_base64": base64.b64encode(fallback_svg.encode('utf-8')).decode('utf-8'),
108
+ "prompt": prompt,
109
+ "error": str(e)
110
+ }
111
+
112
+ def generate_svg_sketch(self, prompt, num_paths, num_iter, guidance_scale, width, height):
113
+ """Generate SVG sketch using simplified DiffSketcher approach"""
114
+ try:
115
+ # Get text embeddings
116
+ text_embeddings = self.get_text_embeddings(prompt)
117
+
118
+ # Generate attention maps (simplified)
119
+ attention_maps = self.generate_attention_maps(prompt, width, height)
120
+
121
+ # Initialize SVG paths based on attention
122
+ paths = self.initialize_paths_from_attention(attention_maps, num_paths, width, height)
123
+
124
+ # Optimize paths (simplified version)
125
+ optimized_paths = self.optimize_paths(paths, text_embeddings, num_iter, guidance_scale)
126
+
127
+ # Create SVG
128
+ svg_content = self.create_svg_from_paths(optimized_paths, width, height)
129
+
130
+ return svg_content
131
+
132
+ except Exception as e:
133
+ print(f"Error in generate_svg_sketch: {e}")
134
+ return self.create_fallback_svg(prompt, width, height)
135
+
136
+ def get_text_embeddings(self, prompt):
137
+ """Get text embeddings from CLIP"""
138
+ if self.tokenizer is None or self.text_encoder is None:
139
+ return None
140
+
141
+ try:
142
+ inputs = self.tokenizer(prompt, return_tensors="pt", padding=True, truncation=True)
143
+ inputs = {k: v.to(self.device) for k, v in inputs.items()}
144
+
145
+ with torch.no_grad():
146
+ embeddings = self.text_encoder(**inputs).last_hidden_state
147
+
148
+ return embeddings
149
+ except Exception as e:
150
+ print(f"Error getting text embeddings: {e}")
151
+ return None
152
+
153
+ def generate_attention_maps(self, prompt, width, height):
154
+ """Generate simplified attention maps"""
155
+ # Create attention maps based on prompt keywords
156
+ attention_map = np.zeros((height, width))
157
+
158
+ # Simple keyword-based attention
159
+ keywords = prompt.lower().split()
160
+
161
+ for i, keyword in enumerate(keywords[:5]): # Limit to 5 keywords
162
+ # Create attention region for each keyword
163
+ center_x = (i + 1) * width // (len(keywords) + 1)
164
+ center_y = height // 2
165
+
166
+ # Create Gaussian-like attention
167
+ y, x = np.ogrid[:height, :width]
168
+ mask = ((x - center_x) ** 2 + (y - center_y) ** 2) < (min(width, height) // 4) ** 2
169
+ attention_map[mask] += 1.0
170
+
171
+ # Normalize
172
+ if attention_map.max() > 0:
173
+ attention_map = attention_map / attention_map.max()
174
+
175
+ return attention_map
176
+
177
+ def initialize_paths_from_attention(self, attention_map, num_paths, width, height):
178
+ """Initialize SVG paths based on attention maps"""
179
+ paths = []
180
 
181
+ # Find high attention regions
182
+ threshold = 0.3
183
+ high_attention = attention_map > threshold
184
 
185
+ if not np.any(high_attention):
186
+ # Fallback: create random paths
187
+ return self.create_random_paths(num_paths, width, height)
188
 
189
+ # Get coordinates of high attention regions
190
+ y_coords, x_coords = np.where(high_attention)
191
+
192
+ for i in range(num_paths):
193
+ if len(x_coords) > 0:
194
+ # Sample random points from high attention regions
195
+ idx = np.random.choice(len(x_coords), size=min(4, len(x_coords)), replace=False)
196
+ path_points = [(x_coords[j], y_coords[j]) for j in idx]
197
+
198
+ # Sort points to create a reasonable path
199
+ path_points.sort(key=lambda p: p[0])
200
+
201
+ paths.append(path_points)
202
+ else:
203
+ # Fallback to random path
204
+ paths.append(self.create_single_random_path(width, height))
205
+
206
+ return paths
207
+
208
+ def create_random_paths(self, num_paths, width, height):
209
+ """Create random paths as fallback"""
210
+ paths = []
211
+ for i in range(num_paths):
212
+ paths.append(self.create_single_random_path(width, height))
213
+ return paths
214
+
215
+ def create_single_random_path(self, width, height):
216
+ """Create a single random path"""
217
+ num_points = random.randint(3, 6)
218
+ points = []
219
+ for _ in range(num_points):
220
+ x = random.randint(0, width)
221
+ y = random.randint(0, height)
222
+ points.append((x, y))
223
+ return points
224
 
225
+ def optimize_paths(self, paths, text_embeddings, num_iter, guidance_scale):
226
+ """Simplified path optimization"""
227
+ # For now, just add some smoothing and variation
228
+ optimized_paths = []
 
229
 
230
+ for path in paths:
231
+ if len(path) < 2:
232
+ optimized_paths.append(path)
233
+ continue
234
+
235
+ # Add some smoothing
236
+ smoothed_path = []
237
+ for i in range(len(path)):
238
+ if i == 0 or i == len(path) - 1:
239
+ smoothed_path.append(path[i])
240
+ else:
241
+ # Simple smoothing
242
+ prev_x, prev_y = path[i-1]
243
+ curr_x, curr_y = path[i]
244
+ next_x, next_y = path[i+1]
245
+
246
+ smooth_x = (prev_x + curr_x + next_x) / 3
247
+ smooth_y = (prev_y + curr_y + next_y) / 3
248
+
249
+ smoothed_path.append((smooth_x, smooth_y))
250
+
251
+ optimized_paths.append(smoothed_path)
252
+
253
+ return optimized_paths
254
+
255
+ def create_svg_from_paths(self, paths, width, height):
256
+ """Create SVG content from optimized paths"""
257
+ dwg = svgwrite.Drawing(size=(width, height))
258
+
259
+ # Add white background
260
+ dwg.add(dwg.rect(insert=(0, 0), size=(width, height), fill='white'))
261
+
262
+ # Add paths
263
+ for i, path in enumerate(paths):
264
+ if len(path) < 2:
265
+ continue
266
+
267
+ # Create path string
268
+ path_str = f"M {path[0][0]},{path[0][1]}"
269
+ for point in path[1:]:
270
+ path_str += f" L {point[0]},{point[1]}"
271
+
272
+ # Vary stroke properties
273
+ stroke_width = random.uniform(0.5, 3.0)
274
+ stroke_color = f"rgb({random.randint(0, 100)},{random.randint(0, 100)},{random.randint(0, 100)})"
275
+
276
+ dwg.add(dwg.path(
277
+ d=path_str,
278
+ stroke=stroke_color,
279
+ stroke_width=stroke_width,
280
+ fill='none',
281
+ stroke_linecap='round',
282
+ stroke_linejoin='round'
283
+ ))
284
+
285
+ return dwg.tostring()
286
+
287
+ def create_fallback_svg(self, prompt, width=224, height=224):
288
+ """Create a simple fallback SVG"""
289
+ dwg = svgwrite.Drawing(size=(width, height))
290
+
291
+ # Add white background
292
+ dwg.add(dwg.rect(insert=(0, 0), size=(width, height), fill='white'))
293
+
294
+ # Add simple sketch based on prompt
295
  prompt_lower = prompt.lower()
296
 
297
+ if any(word in prompt_lower for word in ['mountain', 'landscape']):
298
+ self._add_mountain_sketch(dwg, width, height)
299
+ elif any(word in prompt_lower for word in ['house', 'building']):
300
+ self._add_house_sketch(dwg, width, height)
301
+ elif any(word in prompt_lower for word in ['flower', 'plant']):
302
+ self._add_flower_sketch(dwg, width, height)
 
 
 
 
 
 
303
  else:
304
+ self._add_abstract_sketch(dwg, width, height, prompt)
305
 
306
+ return dwg.tostring()
307
 
308
+ def _add_mountain_sketch(self, dwg, width, height):
309
+ """Add mountain sketch to SVG"""
310
+ # Mountain outline
311
+ points = [(0, height*0.7)]
312
+ for x in range(0, width, 20):
313
+ y = height * 0.7 + 30 * math.sin(x * 0.02) + 15 * math.sin(x * 0.05)
314
+ points.append((x, y))
315
+ points.append((width, height))
316
+ points.append((0, height))
317
+
318
+ dwg.add(dwg.polygon(points, fill='lightgray', stroke='black', stroke_width=2))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
319
 
320
+ def _add_house_sketch(self, dwg, width, height):
321
+ """Add house sketch to SVG"""
322
+ # House base
323
+ house_width = width * 0.6
324
+ house_height = height * 0.4
325
+ house_x = (width - house_width) / 2
326
+ house_y = height * 0.4
327
+
328
+ dwg.add(dwg.rect(
329
+ insert=(house_x, house_y),
330
+ size=(house_width, house_height),
331
+ fill='lightblue',
332
+ stroke='black',
333
+ stroke_width=2
334
+ ))
335
+
336
  # Roof
337
+ roof_points = [
338
+ (house_x, house_y),
339
+ (house_x + house_width/2, house_y - house_height*0.3),
340
+ (house_x + house_width, house_y)
341
+ ]
342
+ dwg.add(dwg.polygon(roof_points, fill='red', stroke='black', stroke_width=2))
343
 
344
+ def _add_flower_sketch(self, dwg, width, height):
345
+ """Add flower sketch to SVG"""
346
+ center_x, center_y = width/2, height/2
347
+
348
  # Stem
349
+ dwg.add(dwg.line(
350
+ start=(center_x, center_y + 20),
351
+ end=(center_x, height - 20),
352
+ stroke='green',
353
+ stroke_width=4
354
+ ))
355
+
356
  # Petals
357
  for angle in range(0, 360, 45):
358
+ x = center_x + 25 * math.cos(math.radians(angle))
359
+ y = center_y + 25 * math.sin(math.radians(angle))
360
+ dwg.add(dwg.circle(
361
+ center=(x, y),
362
+ r=8,
363
+ fill='pink',
364
+ stroke='red',
365
+ stroke_width=1
366
+ ))
367
+
368
  # Center
369
+ dwg.add(dwg.circle(
370
+ center=(center_x, center_y),
371
+ r=8,
372
+ fill='yellow',
373
+ stroke='orange',
374
+ stroke_width=2
375
+ ))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
376
 
377
+ def _add_abstract_sketch(self, dwg, width, height, prompt):
378
+ """Add abstract sketch to SVG"""
379
+ # Create flowing lines based on prompt hash
380
  prompt_hash = hash(prompt) % 100
381
 
 
382
  for i in range(8):
383
  points = []
384
+ start_x = (i * 30 + prompt_hash) % (width - 40) + 20
385
+ start_y = (i * 25 + prompt_hash) % (height - 40) + 20
386
 
387
  for j in range(4):
388
  x = start_x + j * 25 + 15 * math.sin((i + j + prompt_hash) * 0.5)
389
  y = start_y + j * 20 + 15 * math.cos((i + j + prompt_hash) * 0.3)
390
+ points.append((max(0, min(width, x)), max(0, min(height, y))))
391
 
392
+ # Create path
393
+ if len(points) > 1:
394
+ path_str = f"M {points[0][0]},{points[0][1]}"
395
+ for point in points[1:]:
396
+ path_str += f" L {point[0]},{point[1]}"
397
+
398
+ color_val = (i * 30) % 200 + 50
399
+ dwg.add(dwg.path(
400
+ d=path_str,
401
+ stroke=f"rgb({color_val},{color_val//2},{color_val//3})",
402
+ stroke_width=2,
403
+ fill='none',
404
+ stroke_linecap='round'
405
+ ))