Bladeren bron

change pattern preview to png

Tuan Nguyen 9 maanden geleden
bovenliggende
commit
8dd838ce9c
100 gewijzigde bestanden met toevoegingen van 146 en 111 verwijderingen
  1. 1 0
      .gitignore
  2. 58 59
      app.py
  3. 29 31
      modules/core/cache_manager.py
  4. 58 21
      modules/core/preview.py
  5. BIN
      patterns/cached_images/0-0-rotating-hearts.thr.png
  6. BIN
      patterns/cached_images/0-1-hubcap.thr.png
  7. BIN
      patterns/cached_images/03 pnuttrellis (E) (N N).thr.png
  8. BIN
      patterns/cached_images/1-0-open-your-heart-and-mind.thr.png
  9. BIN
      patterns/cached_images/1-0-spirograph-medallion.thr.png
  10. BIN
      patterns/cached_images/1-1-alien-sludge.thr.png
  11. BIN
      patterns/cached_images/1-1-ibex.thr.png
  12. BIN
      patterns/cached_images/1-1-pizza-slice-swirl.thr.png
  13. BIN
      patterns/cached_images/10_sided_polygon.thr.png
  14. BIN
      patterns/cached_images/13b Battlesbury (C C).thr.png
  15. BIN
      patterns/cached_images/19 Itsyourmove (E) (C NW).thr.png
  16. BIN
      patterns/cached_images/33 Labyrinth1 (S S)_preview.thr.png
  17. BIN
      patterns/cached_images/6_sided_polygon.thr.png
  18. BIN
      patterns/cached_images/AcklingDykeDorset 6-4-2018.thr.png
  19. BIN
      patterns/cached_images/BattlesburyCampWilts 7-5-2017.thr.png
  20. BIN
      patterns/cached_images/BucklandDownDorset 5-26-2018.thr.png
  21. BIN
      patterns/cached_images/Butterfly.thr.png
  22. BIN
      patterns/cached_images/ChartresLabyrinthe.thr.png
  23. BIN
      patterns/cached_images/Cheesefoot Head 8-9-2012.thr.png
  24. BIN
      patterns/cached_images/CleyHill 7-18-2017.thr.png
  25. BIN
      patterns/cached_images/Fractal.thr.png
  26. BIN
      patterns/cached_images/HackpenHill 6-9-2018.thr.png
  27. BIN
      patterns/cached_images/Hampton-on-Lucy 8-8-2015.thr.png
  28. BIN
      patterns/cached_images/HamptonHillsCross 2-18-2015.thr.png
  29. BIN
      patterns/cached_images/HexagonAlley.thr.png
  30. BIN
      patterns/cached_images/Hosta.thr.png
  31. BIN
      patterns/cached_images/KeysleyDown 6-10-2018.thr.png
  32. BIN
      patterns/cached_images/LiddingtonCastle 6-24-2001.thr.png
  33. BIN
      patterns/cached_images/LongwoodWarrenHants 7-10-2018.thr.png
  34. BIN
      patterns/cached_images/MilkHill 6-2-2009.thr.png
  35. BIN
      patterns/cached_images/MilkHill 7-8-2011.thr.png
  36. BIN
      patterns/cached_images/Muncombe Hill 7-14-2018.thr.png
  37. BIN
      patterns/cached_images/OareWiltshire 6-21-2010.thr.png
  38. BIN
      patterns/cached_images/Petalar.thr.png
  39. BIN
      patterns/cached_images/SierpinskiTriangle (1).thr.png
  40. BIN
      patterns/cached_images/SimpleRadiance.thr.png
  41. BIN
      patterns/cached_images/SineVsBezier2.thr.png
  42. BIN
      patterns/cached_images/SixPennyHandley 4-6-2009.thr.png
  43. BIN
      patterns/cached_images/Spiral6.thr.png
  44. BIN
      patterns/cached_images/SpiralBezier (1).thr.png
  45. BIN
      patterns/cached_images/SpiralGyrations-2.thr.png
  46. BIN
      patterns/cached_images/Sponge.thr.png
  47. BIN
      patterns/cached_images/StarryNight.thr.png
  48. BIN
      patterns/cached_images/Swirly1.thr.png
  49. BIN
      patterns/cached_images/SwoopyRadiance.thr.png
  50. BIN
      patterns/cached_images/TriangleSwoop.thr.png
  51. BIN
      patterns/cached_images/WiltonWindmill 5-22-2010.thr.png
  52. BIN
      patterns/cached_images/WinterbourneBassett 6-1-1997.thr.png
  53. BIN
      patterns/cached_images/WinterbourneStokeDown 7-18-2018.thr.png
  54. BIN
      patterns/cached_images/atwood-quote.thr.png
  55. BIN
      patterns/cached_images/beach.thr.png
  56. BIN
      patterns/cached_images/bear.thr.png
  57. BIN
      patterns/cached_images/chartres_labyrinthe.thr.png
  58. BIN
      patterns/cached_images/circle-packer-rings.thr.png
  59. BIN
      patterns/cached_images/circle_normalized.thr.png
  60. BIN
      patterns/cached_images/circle_packer.thr.png
  61. BIN
      patterns/cached_images/clear_from_in.thr.png
  62. BIN
      patterns/cached_images/clear_from_in_mini.thr.png
  63. BIN
      patterns/cached_images/clear_from_in_pro.thr.png
  64. BIN
      patterns/cached_images/clear_from_out.thr.png
  65. BIN
      patterns/cached_images/clear_from_out_mini.thr.png
  66. BIN
      patterns/cached_images/clear_from_out_pro.thr.png
  67. BIN
      patterns/cached_images/clear_sideway.thr.png
  68. BIN
      patterns/cached_images/clear_sideway_mini.thr.png
  69. BIN
      patterns/cached_images/clear_sideway_pro.thr.png
  70. BIN
      patterns/cached_images/dither_cells.thr.png
  71. BIN
      patterns/cached_images/dither_eccentricerase.thr.png
  72. BIN
      patterns/cached_images/dither_gears.thr.png
  73. BIN
      patterns/cached_images/dither_gosper4.thr.png
  74. BIN
      patterns/cached_images/dither_hypnogrid.thr.png
  75. BIN
      patterns/cached_images/dither_itsyourmove.thr.png
  76. BIN
      patterns/cached_images/dither_nautilus.thr.png
  77. BIN
      patterns/cached_images/dither_parallel.thr.png
  78. BIN
      patterns/cached_images/dither_sierpinski6.thr.png
  79. BIN
      patterns/cached_images/dither_sierpinski8.thr.png
  80. BIN
      patterns/cached_images/dither_sisinf.thr.png
  81. BIN
      patterns/cached_images/dither_sisinfturn.thr.png
  82. BIN
      patterns/cached_images/dither_strangthang.thr.png
  83. BIN
      patterns/cached_images/dither_sunburst.thr.png
  84. BIN
      patterns/cached_images/dither_tri4.thr.png
  85. BIN
      patterns/cached_images/dither_wormhole.thr.png
  86. BIN
      patterns/cached_images/dither_yinyang.thr.png
  87. BIN
      patterns/cached_images/dune_weaver.thr.png
  88. BIN
      patterns/cached_images/even_sparser_sawtooth.thr.png
  89. BIN
      patterns/cached_images/fancy-bars.thr.png
  90. BIN
      patterns/cached_images/fancy-tiles.thr.png
  91. BIN
      patterns/cached_images/feather.thr.png
  92. BIN
      patterns/cached_images/flower-bubbles.thr.png
  93. BIN
      patterns/cached_images/flower.thr.png
  94. BIN
      patterns/cached_images/flowsnake.thr.png
  95. BIN
      patterns/cached_images/fractal2.thr.png
  96. BIN
      patterns/cached_images/full-moon.thr.png
  97. BIN
      patterns/cached_images/happy-holidays.thr.png
  98. BIN
      patterns/cached_images/hero_11loop2.thr.png
  99. BIN
      patterns/cached_images/hero_13wave1.thr.png
  100. BIN
      patterns/cached_images/hero_5loop1.thr.png

+ 1 - 0
.gitignore

@@ -7,3 +7,4 @@ __pycache__/
 *.json
 *.json
 .venv/
 .venv/
 patterns/cached_svg/
 patterns/cached_svg/
+patterns/cached_images/custom_*

+ 58 - 59
app.py

@@ -10,6 +10,7 @@ import logging
 from datetime import datetime, time
 from datetime import datetime, time
 from modules.connection import connection_manager
 from modules.connection import connection_manager
 from modules.core import pattern_manager
 from modules.core import pattern_manager
+from modules.core.pattern_manager import parse_theta_rho_file, THETA_RHO_DIR
 from modules.core import playlist_manager
 from modules.core import playlist_manager
 from modules.update import update_manager
 from modules.update import update_manager
 from modules.core.state import state
 from modules.core.state import state
@@ -20,7 +21,7 @@ import asyncio
 from contextlib import asynccontextmanager
 from contextlib import asynccontextmanager
 from modules.led.led_controller import LEDController, effect_idle
 from modules.led.led_controller import LEDController, effect_idle
 import math
 import math
-from modules.core.svg_cache_manager import generate_all_svg_previews, get_cache_path, generate_svg_preview
+from modules.core.cache_manager import generate_all_image_previews, get_cache_path, generate_image_preview
 
 
 # Configure logging
 # Configure logging
 logging.basicConfig(
 logging.basicConfig(
@@ -51,12 +52,12 @@ async def lifespan(app: FastAPI):
     except Exception as e:
     except Exception as e:
         logger.warning(f"Failed to initialize MQTT: {str(e)}")
         logger.warning(f"Failed to initialize MQTT: {str(e)}")
     
     
-    # Generate SVG previews for all patterns
+    # Generate image previews for all patterns
     try:
     try:
-        logger.info("Starting SVG cache generation...")
-        await generate_all_svg_previews()
+        logger.info("Starting image cache generation...")
+        await generate_all_image_previews()
     except Exception as e:
     except Exception as e:
-        logger.warning(f"Failed to generate SVG cache: {str(e)}")
+        logger.warning(f"Failed to generate image cache: {str(e)}")
 
 
     yield  # This separates startup from shutdown code
     yield  # This separates startup from shutdown code
 
 
@@ -206,12 +207,18 @@ async def upload_theta_rho(file: UploadFile = File(...)):
     """Upload a theta-rho file."""
     """Upload a theta-rho file."""
     try:
     try:
         # Save the file
         # Save the file
-        file_path = os.path.join(pattern_manager.THETA_RHO_DIR, "custom_patterns", file.filename)
-        with open(file_path, "wb") as f:
+        # Ensure custom_patterns directory exists
+        custom_patterns_dir = os.path.join(pattern_manager.THETA_RHO_DIR, "custom_patterns")
+        os.makedirs(custom_patterns_dir, exist_ok=True)
+        
+        file_path_in_patterns_dir = os.path.join("custom_patterns", file.filename)
+        full_file_path = os.path.join(pattern_manager.THETA_RHO_DIR, file_path_in_patterns_dir)
+        
+        with open(full_file_path, "wb") as f:
             f.write(await file.read())
             f.write(await file.read())
         
         
-        # Generate SVG preview for the new file
-        await generate_svg_preview(os.path.join("custom_patterns", file.filename))
+        # Generate image preview for the new file
+        await generate_image_preview(file_path_in_patterns_dir)
         
         
         return {"success": True, "message": f"File {file.filename} uploaded successfully"}
         return {"success": True, "message": f"File {file.filename} uploaded successfully"}
     except Exception as e:
     except Exception as e:
@@ -356,63 +363,55 @@ async def preview_thr(request: DeleteFileRequest):
         logger.warning("Preview theta-rho request received without filename")
         logger.warning("Preview theta-rho request received without filename")
         raise HTTPException(status_code=400, detail="No file name provided")
         raise HTTPException(status_code=400, detail="No file name provided")
 
 
-    file_path = os.path.join(pattern_manager.THETA_RHO_DIR, request.file_name)
-    if not os.path.exists(file_path):
-        logger.error(f"Attempted to preview non-existent file: {file_path}")
-        raise HTTPException(status_code=404, detail="File not found")
+    # Construct the full path to the pattern file to check existence
+    pattern_file_path = os.path.join(pattern_manager.THETA_RHO_DIR, request.file_name)
+    if not os.path.exists(pattern_file_path):
+        logger.error(f"Attempted to preview non-existent pattern file: {pattern_file_path}")
+        raise HTTPException(status_code=404, detail="Pattern file not found")
 
 
     try:
     try:
-        # Check if cached SVG exists
         cache_path = get_cache_path(request.file_name)
         cache_path = get_cache_path(request.file_name)
-        if os.path.exists(cache_path):
-            with open(cache_path, 'r', encoding='utf-8') as f:
-                svg_content = f.read()
-                
-            # Parse coordinates for first and last points
-            coordinates = pattern_manager.parse_theta_rho_file(file_path)
-            first_coord = coordinates[0] if coordinates else None
-            last_coord = coordinates[-1] if coordinates else None
-            
-            return {
-                "svg": svg_content,
-                "first_coordinate": first_coord,
-                "last_coordinate": last_coord
-            }
-        
-        # If not cached, generate SVG as before
-        coordinates = pattern_manager.parse_theta_rho_file(file_path)
-        
-        # Convert polar coordinates to SVG path
-        svg_path = []
-        for i, (theta, rho) in enumerate(coordinates):
-            x = 100 - rho * 90 * math.cos(theta)
-            y = 100 - rho * 90 * math.sin(theta)
-            
-            if i == 0:
-                svg_path.append(f"M {x:.2f} {y:.2f}")
-            else:
-                svg_path.append(f"L {x:.2f} {y:.2f}")
-        
-        svg = f'''<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<svg width="100%" height="100%" viewBox="0 0 200 200" preserveAspectRatio="xMidYMid meet" xmlns="http://www.w3.org/2000/svg">
-    <path d="{' '.join(svg_path)}" 
-          fill="none" 
-          stroke="currentColor" 
-          stroke-width="0.5"/>
-</svg>'''
-        
-        # Cache the SVG for future use
-        with open(cache_path, 'w', encoding='utf-8') as f:
-            f.write(svg)
         
         
+        if not os.path.exists(cache_path):
+            logger.info(f"Cache miss for {request.file_name}. Generating preview...")
+            # Attempt to generate the preview if it's missing
+            success = await generate_image_preview(request.file_name)
+            if not success or not os.path.exists(cache_path):
+                logger.error(f"Failed to generate or find preview for {request.file_name} after attempting generation.")
+                raise HTTPException(status_code=500, detail="Failed to generate preview image.")
+
+        # Get the coordinates for display
+        coordinates = parse_theta_rho_file(pattern_file_path)
+        first_coord = coordinates[0] if coordinates else None
+        last_coord = coordinates[-1] if coordinates else None
+
+        # Return JSON with preview URL and coordinates
+        # URL encode the file_name for the preview URL
+        encoded_filename = request.file_name.replace('/', '--')
         return {
         return {
-            "svg": svg,
-            "first_coordinate": coordinates[0] if coordinates else None,
-            "last_coordinate": coordinates[-1] if coordinates else None
+            "preview_url": f"/preview/{encoded_filename}",
+            "first_coordinate": first_coord,
+            "last_coordinate": last_coord
         }
         }
+
+    except HTTPException:
+        raise
     except Exception as e:
     except Exception as e:
-        logger.error(f"Failed to generate preview for {request.file_name}: {str(e)}")
-        raise HTTPException(status_code=500, detail=str(e))
+        logger.error(f"Failed to generate or serve preview for {request.file_name}: {str(e)}")
+        raise HTTPException(status_code=500, detail=f"Failed to serve preview image: {str(e)}")
+
+@app.get("/preview/{encoded_filename}")
+async def serve_preview(encoded_filename: str):
+    """Serve a preview image for a pattern file."""
+    # Decode the filename by replacing -- with /
+    file_name = encoded_filename.replace('--', '/')
+    cache_path = get_cache_path(file_name)
+    
+    if not os.path.exists(cache_path):
+        logger.error(f"Preview image not found for {file_name}")
+        raise HTTPException(status_code=404, detail="Preview image not found")
+        
+    return FileResponse(cache_path, media_type="image/png")
 
 
 @app.post("/send_coordinate")
 @app.post("/send_coordinate")
 async def send_coordinate(request: CoordinateRequest):
 async def send_coordinate(request: CoordinateRequest):

+ 29 - 31
modules/core/svg_cache_manager.py → modules/core/cache_manager.py

@@ -1,4 +1,4 @@
-"""SVG Cache Manager for pre-generating and managing SVG previews."""
+"""Image Cache Manager for pre-generating and managing image previews."""
 import os
 import os
 import json
 import json
 import asyncio
 import asyncio
@@ -9,20 +9,16 @@ from modules.core.pattern_manager import list_theta_rho_files, THETA_RHO_DIR
 logger = logging.getLogger(__name__)
 logger = logging.getLogger(__name__)
 
 
 # Constants
 # Constants
-CACHE_DIR = os.path.join(THETA_RHO_DIR, "cached_svg")
+CACHE_DIR = os.path.join(THETA_RHO_DIR, "cached_images")
 
 
 def ensure_cache_dir():
 def ensure_cache_dir():
     """Ensure the cache directory exists with proper permissions."""
     """Ensure the cache directory exists with proper permissions."""
     try:
     try:
         Path(CACHE_DIR).mkdir(parents=True, exist_ok=True)
         Path(CACHE_DIR).mkdir(parents=True, exist_ok=True)
         
         
-        # Walk through the cache directory and set permissions for all files and subdirectories
         for root, dirs, files in os.walk(CACHE_DIR):
         for root, dirs, files in os.walk(CACHE_DIR):
             try:
             try:
-                # Set 777 for directories
                 os.chmod(root, 0o777)
                 os.chmod(root, 0o777)
-                
-                # Set 666 for files
                 for file in files:
                 for file in files:
                     file_path = os.path.join(root, file)
                     file_path = os.path.join(root, file)
                     try:
                     try:
@@ -35,54 +31,57 @@ def ensure_cache_dir():
                 
                 
     except Exception as e:
     except Exception as e:
         logger.error(f"Failed to set cache directory permissions: {str(e)}")
         logger.error(f"Failed to set cache directory permissions: {str(e)}")
-        # Continue even if permissions can't be set
         pass
         pass
 
 
 def get_cache_path(pattern_file):
 def get_cache_path(pattern_file):
     """Get the cache path for a pattern file."""
     """Get the cache path for a pattern file."""
-    # Convert the pattern file path to a safe filename
-    safe_name = pattern_file.replace('/', '_').replace('\\', '_')
-    return os.path.join(CACHE_DIR, f"{safe_name}.svg")
+    # Create subdirectories in cache to match the pattern file structure
+    cache_subpath = os.path.dirname(pattern_file)
+    cache_dir = os.path.join(CACHE_DIR, cache_subpath)
+    
+    # Ensure the subdirectory exists
+    os.makedirs(cache_dir, exist_ok=True)
+    try:
+        os.chmod(cache_dir, 0o777)
+    except Exception as e:
+        logger.error(f"Failed to set permissions for cache subdirectory {cache_dir}: {str(e)}")
+    
+    # Use just the filename part for the cache file
+    filename = os.path.basename(pattern_file)
+    safe_name = filename.replace('\\', '_')
+    return os.path.join(cache_dir, f"{safe_name}.png")
 
 
 def needs_cache(pattern_file):
 def needs_cache(pattern_file):
     """Check if a pattern file needs its cache generated."""
     """Check if a pattern file needs its cache generated."""
     cache_path = get_cache_path(pattern_file)
     cache_path = get_cache_path(pattern_file)
     return not os.path.exists(cache_path)
     return not os.path.exists(cache_path)
 
 
-async def generate_svg_preview(pattern_file):
-    """Generate SVG preview for a single pattern file."""
-    from modules.core.preview import generate_preview_svg
+async def generate_image_preview(pattern_file):
+    """Generate image preview for a single pattern file."""
+    from modules.core.preview import generate_preview_image
     try:
     try:
-        # Generate the SVG
-        svg_content = await generate_preview_svg(pattern_file)
+        image_content = await generate_preview_image(pattern_file)
         
         
-        # Save to cache
         cache_path = get_cache_path(pattern_file)
         cache_path = get_cache_path(pattern_file)
-        with open(cache_path, 'w', encoding='utf-8') as f:
-            f.write(svg_content)
+        with open(cache_path, 'wb') as f:
+            f.write(image_content)
         
         
-        # Set file permissions to 666 to allow any user to read/write
         try:
         try:
             os.chmod(cache_path, 0o666)
             os.chmod(cache_path, 0o666)
         except Exception as e:
         except Exception as e:
             logger.error(f"Failed to set cache file permissions for {pattern_file}: {str(e)}")
             logger.error(f"Failed to set cache file permissions for {pattern_file}: {str(e)}")
-            # Continue even if permissions can't be set
             pass
             pass
         
         
         return True
         return True
     except Exception as e:
     except Exception as e:
-        # Only log the error message, not the full SVG content
-        logger.error(f"Failed to generate SVG for {pattern_file}")
+        logger.error(f"Failed to generate image for {pattern_file}: {str(e)}")
         return False
         return False
 
 
-async def generate_all_svg_previews():
-    """Generate SVG previews for all pattern files."""
+async def generate_all_image_previews():
+    """Generate image previews for all pattern files."""
     ensure_cache_dir()
     ensure_cache_dir()
     
     
-    # Get all pattern files and filter for .thr files only
     pattern_files = [f for f in list_theta_rho_files() if f.endswith('.thr')]
     pattern_files = [f for f in list_theta_rho_files() if f.endswith('.thr')]
-    
-    # Filter out patterns that already have cache
     patterns_to_cache = [f for f in pattern_files if needs_cache(f)]
     patterns_to_cache = [f for f in pattern_files if needs_cache(f)]
     total_files = len(patterns_to_cache)
     total_files = len(patterns_to_cache)
     
     
@@ -90,15 +89,14 @@ async def generate_all_svg_previews():
         logger.info("All patterns are already cached")
         logger.info("All patterns are already cached")
         return
         return
         
         
-    logger.info(f"Generating SVG cache for {total_files} uncached .thr patterns...")
+    logger.info(f"Generating image cache for {total_files} uncached .thr patterns...")
     
     
-    # Process files concurrently in batches to avoid overwhelming the system
     batch_size = 5
     batch_size = 5
     successful = 0
     successful = 0
     for i in range(0, total_files, batch_size):
     for i in range(0, total_files, batch_size):
         batch = patterns_to_cache[i:i + batch_size]
         batch = patterns_to_cache[i:i + batch_size]
-        tasks = [generate_svg_preview(file) for file in batch]
+        tasks = [generate_image_preview(file) for file in batch]
         results = await asyncio.gather(*tasks)
         results = await asyncio.gather(*tasks)
         successful += sum(1 for r in results if r)
         successful += sum(1 for r in results if r)
     
     
-    logger.info(f"SVG cache generation completed: {successful}/{total_files} patterns cached") 
+    logger.info(f"Image cache generation completed: {successful}/{total_files} patterns cached")

+ 58 - 21
modules/core/preview.py

@@ -1,30 +1,67 @@
-"""Preview module for generating SVG previews of patterns."""
+"""Preview module for generating image previews of patterns."""
 import os
 import os
 import math
 import math
+from io import BytesIO
+from PIL import Image, ImageDraw
 from modules.core.pattern_manager import parse_theta_rho_file, THETA_RHO_DIR
 from modules.core.pattern_manager import parse_theta_rho_file, THETA_RHO_DIR
 
 
-async def generate_preview_svg(pattern_file):
-    """Generate an SVG preview for a pattern file."""
+async def generate_preview_image(pattern_file):
+    """Generate a PNG preview for a pattern file, optimized for a 300x300 view."""
     file_path = os.path.join(THETA_RHO_DIR, pattern_file)
     file_path = os.path.join(THETA_RHO_DIR, pattern_file)
-    coordinates = parse_theta_rho_file(file_path)
+    coordinates = parse_theta_rho_file(file_path) 
     
     
-    # Convert polar coordinates to SVG path
-    svg_path = []
-    for i, (theta, rho) in enumerate(coordinates):
-        x = 100 - rho * 90 * math.cos(theta)
-        y = 100 - rho * 90 * math.sin(theta)
+    # Use 1000x1000 for high quality rendering
+    RENDER_SIZE = 1000
+    # Final display size
+    DISPLAY_SIZE = 300
+    
+    if not coordinates:
+        # Create an image with "No pattern data" text
+        img = Image.new('RGBA', (DISPLAY_SIZE, DISPLAY_SIZE), (255, 255, 255, 0)) # Transparent background
+        draw = ImageDraw.Draw(img)
+        text = "No pattern data"
+        try:
+            bbox = draw.textbbox((0, 0), text)
+            text_width = bbox[2] - bbox[0]
+            text_height = bbox[3] - bbox[1]
+            text_x = (DISPLAY_SIZE - text_width) / 2
+            text_y = (DISPLAY_SIZE - text_height) / 2
+        except:
+            text_x = DISPLAY_SIZE / 4 
+            text_y = DISPLAY_SIZE / 2
+        draw.text((text_x, text_y), text, fill="black")
         
         
-        if i == 0:
-            svg_path.append(f"M {x:.2f} {y:.2f}")
-        else:
-            svg_path.append(f"L {x:.2f} {y:.2f}")
+        img_byte_arr = BytesIO()
+        img.save(img_byte_arr, format='PNG')
+        img_byte_arr.seek(0)
+        return img_byte_arr.getvalue()
+
+    # Image drawing parameters
+    img = Image.new('RGBA', (RENDER_SIZE, RENDER_SIZE), (255, 255, 255, 0)) # Transparent background
+    draw = ImageDraw.Draw(img)
     
     
-    svg = f'''<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<svg width="100%" height="100%" viewBox="0 0 200 200" preserveAspectRatio="xMidYMid meet" xmlns="http://www.w3.org/2000/svg">
-    <path d="{' '.join(svg_path)}" 
-          fill="none" 
-          stroke="currentColor" 
-          stroke-width="0.5"/>
-</svg>'''
+    CENTER = RENDER_SIZE / 2.0
+    SCALE_FACTOR = (RENDER_SIZE / 2.0) - 10.0 
+    LINE_COLOR = "black" 
+    STROKE_WIDTH = 2  # Increased stroke width for better visibility after scaling
+
+    points_to_draw = []
+    for theta, rho in coordinates:
+        x = CENTER - rho * SCALE_FACTOR * math.cos(theta)
+        y = CENTER - rho * SCALE_FACTOR * math.sin(theta)
+        points_to_draw.append((x, y))
     
     
-    return svg 
+    if len(points_to_draw) > 1:
+        draw.line(points_to_draw, fill=LINE_COLOR, width=STROKE_WIDTH, joint="curve")
+    elif len(points_to_draw) == 1:
+        r = 4  # Larger radius for single point to remain visible after scaling
+        x, y = points_to_draw[0]
+        draw.ellipse([(x-r, y-r), (x+r, y+r)], fill=LINE_COLOR)
+
+    # Scale down to display size with high-quality resampling
+    img = img.resize((DISPLAY_SIZE, DISPLAY_SIZE), Image.Resampling.LANCZOS)
+
+    img_byte_arr = BytesIO()
+    img.save(img_byte_arr, format='PNG')
+    img_byte_arr.seek(0)
+    return img_byte_arr.getvalue()

BIN
patterns/cached_images/0-0-rotating-hearts.thr.png


BIN
patterns/cached_images/0-1-hubcap.thr.png


BIN
patterns/cached_images/03 pnuttrellis (E) (N N).thr.png


BIN
patterns/cached_images/1-0-open-your-heart-and-mind.thr.png


BIN
patterns/cached_images/1-0-spirograph-medallion.thr.png


BIN
patterns/cached_images/1-1-alien-sludge.thr.png


BIN
patterns/cached_images/1-1-ibex.thr.png


BIN
patterns/cached_images/1-1-pizza-slice-swirl.thr.png


BIN
patterns/cached_images/10_sided_polygon.thr.png


BIN
patterns/cached_images/13b Battlesbury (C C).thr.png


BIN
patterns/cached_images/19 Itsyourmove (E) (C NW).thr.png


BIN
patterns/cached_images/33 Labyrinth1 (S S)_preview.thr.png


BIN
patterns/cached_images/6_sided_polygon.thr.png


BIN
patterns/cached_images/AcklingDykeDorset 6-4-2018.thr.png


BIN
patterns/cached_images/BattlesburyCampWilts 7-5-2017.thr.png


BIN
patterns/cached_images/BucklandDownDorset 5-26-2018.thr.png


BIN
patterns/cached_images/Butterfly.thr.png


BIN
patterns/cached_images/ChartresLabyrinthe.thr.png


BIN
patterns/cached_images/Cheesefoot Head 8-9-2012.thr.png


BIN
patterns/cached_images/CleyHill 7-18-2017.thr.png


BIN
patterns/cached_images/Fractal.thr.png


BIN
patterns/cached_images/HackpenHill 6-9-2018.thr.png


BIN
patterns/cached_images/Hampton-on-Lucy 8-8-2015.thr.png


BIN
patterns/cached_images/HamptonHillsCross 2-18-2015.thr.png


BIN
patterns/cached_images/HexagonAlley.thr.png


BIN
patterns/cached_images/Hosta.thr.png


BIN
patterns/cached_images/KeysleyDown 6-10-2018.thr.png


BIN
patterns/cached_images/LiddingtonCastle 6-24-2001.thr.png


BIN
patterns/cached_images/LongwoodWarrenHants 7-10-2018.thr.png


BIN
patterns/cached_images/MilkHill 6-2-2009.thr.png


BIN
patterns/cached_images/MilkHill 7-8-2011.thr.png


BIN
patterns/cached_images/Muncombe Hill 7-14-2018.thr.png


BIN
patterns/cached_images/OareWiltshire 6-21-2010.thr.png


BIN
patterns/cached_images/Petalar.thr.png


BIN
patterns/cached_images/SierpinskiTriangle (1).thr.png


BIN
patterns/cached_images/SimpleRadiance.thr.png


BIN
patterns/cached_images/SineVsBezier2.thr.png


BIN
patterns/cached_images/SixPennyHandley 4-6-2009.thr.png


BIN
patterns/cached_images/Spiral6.thr.png


BIN
patterns/cached_images/SpiralBezier (1).thr.png


BIN
patterns/cached_images/SpiralGyrations-2.thr.png


BIN
patterns/cached_images/Sponge.thr.png


BIN
patterns/cached_images/StarryNight.thr.png


BIN
patterns/cached_images/Swirly1.thr.png


BIN
patterns/cached_images/SwoopyRadiance.thr.png


BIN
patterns/cached_images/TriangleSwoop.thr.png


BIN
patterns/cached_images/WiltonWindmill 5-22-2010.thr.png


BIN
patterns/cached_images/WinterbourneBassett 6-1-1997.thr.png


BIN
patterns/cached_images/WinterbourneStokeDown 7-18-2018.thr.png


BIN
patterns/cached_images/atwood-quote.thr.png


BIN
patterns/cached_images/beach.thr.png


BIN
patterns/cached_images/bear.thr.png


BIN
patterns/cached_images/chartres_labyrinthe.thr.png


BIN
patterns/cached_images/circle-packer-rings.thr.png


BIN
patterns/cached_images/circle_normalized.thr.png


BIN
patterns/cached_images/circle_packer.thr.png


BIN
patterns/cached_images/clear_from_in.thr.png


BIN
patterns/cached_images/clear_from_in_mini.thr.png


BIN
patterns/cached_images/clear_from_in_pro.thr.png


BIN
patterns/cached_images/clear_from_out.thr.png


BIN
patterns/cached_images/clear_from_out_mini.thr.png


BIN
patterns/cached_images/clear_from_out_pro.thr.png


BIN
patterns/cached_images/clear_sideway.thr.png


BIN
patterns/cached_images/clear_sideway_mini.thr.png


BIN
patterns/cached_images/clear_sideway_pro.thr.png


BIN
patterns/cached_images/dither_cells.thr.png


BIN
patterns/cached_images/dither_eccentricerase.thr.png


BIN
patterns/cached_images/dither_gears.thr.png


BIN
patterns/cached_images/dither_gosper4.thr.png


BIN
patterns/cached_images/dither_hypnogrid.thr.png


BIN
patterns/cached_images/dither_itsyourmove.thr.png


BIN
patterns/cached_images/dither_nautilus.thr.png


BIN
patterns/cached_images/dither_parallel.thr.png


BIN
patterns/cached_images/dither_sierpinski6.thr.png


BIN
patterns/cached_images/dither_sierpinski8.thr.png


BIN
patterns/cached_images/dither_sisinf.thr.png


BIN
patterns/cached_images/dither_sisinfturn.thr.png


BIN
patterns/cached_images/dither_strangthang.thr.png


BIN
patterns/cached_images/dither_sunburst.thr.png


BIN
patterns/cached_images/dither_tri4.thr.png


BIN
patterns/cached_images/dither_wormhole.thr.png


BIN
patterns/cached_images/dither_yinyang.thr.png


BIN
patterns/cached_images/dune_weaver.thr.png


BIN
patterns/cached_images/even_sparser_sawtooth.thr.png


BIN
patterns/cached_images/fancy-bars.thr.png


BIN
patterns/cached_images/fancy-tiles.thr.png


BIN
patterns/cached_images/feather.thr.png


BIN
patterns/cached_images/flower-bubbles.thr.png


BIN
patterns/cached_images/flower.thr.png


BIN
patterns/cached_images/flowsnake.thr.png


BIN
patterns/cached_images/fractal2.thr.png


BIN
patterns/cached_images/full-moon.thr.png


BIN
patterns/cached_images/happy-holidays.thr.png


BIN
patterns/cached_images/hero_11loop2.thr.png


BIN
patterns/cached_images/hero_13wave1.thr.png


BIN
patterns/cached_images/hero_5loop1.thr.png


Some files were not shown because too many files changed in this diff