valetudo-map-parser 0.1.9b49__tar.gz → 0.1.9b51__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {valetudo_map_parser-0.1.9b49 → valetudo_map_parser-0.1.9b51}/PKG-INFO +1 -1
- {valetudo_map_parser-0.1.9b49 → valetudo_map_parser-0.1.9b51}/SCR/valetudo_map_parser/__init__.py +2 -0
- valetudo_map_parser-0.1.9b51/SCR/valetudo_map_parser/config/color_utils.py +105 -0
- {valetudo_map_parser-0.1.9b49 → valetudo_map_parser-0.1.9b51}/SCR/valetudo_map_parser/config/colors.py +55 -71
- {valetudo_map_parser-0.1.9b49 → valetudo_map_parser-0.1.9b51}/SCR/valetudo_map_parser/config/drawable.py +55 -37
- valetudo_map_parser-0.1.9b51/SCR/valetudo_map_parser/config/drawable_elements.py +307 -0
- {valetudo_map_parser-0.1.9b49 → valetudo_map_parser-0.1.9b51}/SCR/valetudo_map_parser/config/enhanced_drawable.py +75 -193
- {valetudo_map_parser-0.1.9b49 → valetudo_map_parser-0.1.9b51}/SCR/valetudo_map_parser/config/shared.py +0 -1
- {valetudo_map_parser-0.1.9b49 → valetudo_map_parser-0.1.9b51}/SCR/valetudo_map_parser/config/types.py +19 -33
- {valetudo_map_parser-0.1.9b49 → valetudo_map_parser-0.1.9b51}/SCR/valetudo_map_parser/config/utils.py +0 -67
- {valetudo_map_parser-0.1.9b49 → valetudo_map_parser-0.1.9b51}/SCR/valetudo_map_parser/hypfer_draw.py +222 -57
- {valetudo_map_parser-0.1.9b49 → valetudo_map_parser-0.1.9b51}/SCR/valetudo_map_parser/hypfer_handler.py +48 -155
- valetudo_map_parser-0.1.9b51/SCR/valetudo_map_parser/hypfer_rooms_handler.py +406 -0
- {valetudo_map_parser-0.1.9b49 → valetudo_map_parser-0.1.9b51}/SCR/valetudo_map_parser/map_data.py +0 -30
- {valetudo_map_parser-0.1.9b49 → valetudo_map_parser-0.1.9b51}/SCR/valetudo_map_parser/rand25_handler.py +2 -84
- {valetudo_map_parser-0.1.9b49 → valetudo_map_parser-0.1.9b51}/pyproject.toml +1 -1
- valetudo_map_parser-0.1.9b49/SCR/valetudo_map_parser/config/color_utils.py +0 -51
- valetudo_map_parser-0.1.9b49/SCR/valetudo_map_parser/config/drawable_elements.py +0 -983
- {valetudo_map_parser-0.1.9b49 → valetudo_map_parser-0.1.9b51}/LICENSE +0 -0
- {valetudo_map_parser-0.1.9b49 → valetudo_map_parser-0.1.9b51}/NOTICE.txt +0 -0
- {valetudo_map_parser-0.1.9b49 → valetudo_map_parser-0.1.9b51}/README.md +0 -0
- {valetudo_map_parser-0.1.9b49 → valetudo_map_parser-0.1.9b51}/SCR/valetudo_map_parser/config/__init__.py +0 -0
- {valetudo_map_parser-0.1.9b49 → valetudo_map_parser-0.1.9b51}/SCR/valetudo_map_parser/config/auto_crop.py +0 -0
- {valetudo_map_parser-0.1.9b49 → valetudo_map_parser-0.1.9b51}/SCR/valetudo_map_parser/config/optimized_element_map.py +0 -0
- {valetudo_map_parser-0.1.9b49 → valetudo_map_parser-0.1.9b51}/SCR/valetudo_map_parser/config/rand25_parser.py +0 -0
- {valetudo_map_parser-0.1.9b49 → valetudo_map_parser-0.1.9b51}/SCR/valetudo_map_parser/config/room_outline.py +0 -0
- {valetudo_map_parser-0.1.9b49 → valetudo_map_parser-0.1.9b51}/SCR/valetudo_map_parser/py.typed +0 -0
- {valetudo_map_parser-0.1.9b49 → valetudo_map_parser-0.1.9b51}/SCR/valetudo_map_parser/reimg_draw.py +0 -0
{valetudo_map_parser-0.1.9b49 → valetudo_map_parser-0.1.9b51}/SCR/valetudo_map_parser/__init__.py
RENAMED
@@ -15,11 +15,13 @@ from .config.types import (
|
|
15
15
|
TrimCropData,
|
16
16
|
UserLanguageStore,
|
17
17
|
)
|
18
|
+
from .hypfer_rooms_handler import HypferRoomsHandler
|
18
19
|
from .hypfer_handler import HypferMapImageHandler
|
19
20
|
from .rand25_handler import ReImageHandler
|
20
21
|
|
21
22
|
|
22
23
|
__all__ = [
|
24
|
+
"HypferRoomsHandler",
|
23
25
|
"HypferMapImageHandler",
|
24
26
|
"ReImageHandler",
|
25
27
|
"RRMapParser",
|
@@ -0,0 +1,105 @@
|
|
1
|
+
"""Utility functions for color operations in the map parser."""
|
2
|
+
|
3
|
+
from typing import Tuple, Optional
|
4
|
+
|
5
|
+
from .colors import ColorsManagement
|
6
|
+
from .types import NumpyArray, Color
|
7
|
+
|
8
|
+
|
9
|
+
def get_blended_color(
|
10
|
+
x0: int,
|
11
|
+
y0: int,
|
12
|
+
x1: int,
|
13
|
+
y1: int,
|
14
|
+
arr: Optional[NumpyArray],
|
15
|
+
color: Color,
|
16
|
+
) -> Color:
|
17
|
+
"""
|
18
|
+
Get a blended color for a pixel based on the current element map and the new element to draw.
|
19
|
+
|
20
|
+
This function:
|
21
|
+
1. Gets the background colors at the start and end points (with offset to avoid sampling already drawn pixels)
|
22
|
+
2. Directly blends the foreground color with the background using straight alpha
|
23
|
+
3. Returns the average of the two blended colors
|
24
|
+
|
25
|
+
Returns:
|
26
|
+
Blended RGBA color to use for drawing
|
27
|
+
"""
|
28
|
+
# Extract foreground color components
|
29
|
+
fg_r, fg_g, fg_b, fg_a = color
|
30
|
+
fg_alpha = fg_a / 255.0 # Convert to 0-1 range
|
31
|
+
|
32
|
+
# Fast path for fully opaque or transparent foreground
|
33
|
+
if fg_a == 255:
|
34
|
+
return color
|
35
|
+
if fg_a == 0:
|
36
|
+
# Sample background at midpoint
|
37
|
+
mid_x, mid_y = (x0 + x1) // 2, (y0 + y1) // 2
|
38
|
+
if 0 <= mid_y < arr.shape[0] and 0 <= mid_x < arr.shape[1]:
|
39
|
+
return tuple(arr[mid_y, mid_x])
|
40
|
+
return (0, 0, 0, 0) # Default if out of bounds
|
41
|
+
|
42
|
+
# Calculate direction vector for offset sampling
|
43
|
+
dx = x1 - x0
|
44
|
+
dy = y1 - y0
|
45
|
+
length = max(1, (dx**2 + dy**2) ** 0.5) # Avoid division by zero
|
46
|
+
offset = 5 # 5-pixel offset to avoid sampling already drawn pixels
|
47
|
+
|
48
|
+
# Calculate offset coordinates for start point (move away from the line)
|
49
|
+
offset_x0 = int(x0 - (offset * dx / length))
|
50
|
+
offset_y0 = int(y0 - (offset * dy / length))
|
51
|
+
|
52
|
+
# Calculate offset coordinates for end point (move away from the line)
|
53
|
+
offset_x1 = int(x1 + (offset * dx / length))
|
54
|
+
offset_y1 = int(y1 + (offset * dy / length))
|
55
|
+
|
56
|
+
# Sample background at offset start point
|
57
|
+
if 0 <= offset_y0 < arr.shape[0] and 0 <= offset_x0 < arr.shape[1]:
|
58
|
+
bg_color_start = arr[offset_y0, offset_x0]
|
59
|
+
# Direct straight alpha blending
|
60
|
+
start_r = int(fg_r * fg_alpha + bg_color_start[0] * (1 - fg_alpha))
|
61
|
+
start_g = int(fg_g * fg_alpha + bg_color_start[1] * (1 - fg_alpha))
|
62
|
+
start_b = int(fg_b * fg_alpha + bg_color_start[2] * (1 - fg_alpha))
|
63
|
+
start_a = int(fg_a + bg_color_start[3] * (1 - fg_alpha))
|
64
|
+
start_blended_color = (start_r, start_g, start_b, start_a)
|
65
|
+
else:
|
66
|
+
# If offset point is out of bounds, try original point
|
67
|
+
if 0 <= y0 < arr.shape[0] and 0 <= x0 < arr.shape[1]:
|
68
|
+
bg_color_start = arr[y0, x0]
|
69
|
+
start_r = int(fg_r * fg_alpha + bg_color_start[0] * (1 - fg_alpha))
|
70
|
+
start_g = int(fg_g * fg_alpha + bg_color_start[1] * (1 - fg_alpha))
|
71
|
+
start_b = int(fg_b * fg_alpha + bg_color_start[2] * (1 - fg_alpha))
|
72
|
+
start_a = int(fg_a + bg_color_start[3] * (1 - fg_alpha))
|
73
|
+
start_blended_color = (start_r, start_g, start_b, start_a)
|
74
|
+
else:
|
75
|
+
start_blended_color = color
|
76
|
+
|
77
|
+
# Sample background at offset end point
|
78
|
+
if 0 <= offset_y1 < arr.shape[0] and 0 <= offset_x1 < arr.shape[1]:
|
79
|
+
bg_color_end = arr[offset_y1, offset_x1]
|
80
|
+
# Direct straight alpha blending
|
81
|
+
end_r = int(fg_r * fg_alpha + bg_color_end[0] * (1 - fg_alpha))
|
82
|
+
end_g = int(fg_g * fg_alpha + bg_color_end[1] * (1 - fg_alpha))
|
83
|
+
end_b = int(fg_b * fg_alpha + bg_color_end[2] * (1 - fg_alpha))
|
84
|
+
end_a = int(fg_a + bg_color_end[3] * (1 - fg_alpha))
|
85
|
+
end_blended_color = (end_r, end_g, end_b, end_a)
|
86
|
+
else:
|
87
|
+
# If offset point is out of bounds, try original point
|
88
|
+
if 0 <= y1 < arr.shape[0] and 0 <= x1 < arr.shape[1]:
|
89
|
+
bg_color_end = arr[y1, x1]
|
90
|
+
end_r = int(fg_r * fg_alpha + bg_color_end[0] * (1 - fg_alpha))
|
91
|
+
end_g = int(fg_g * fg_alpha + bg_color_end[1] * (1 - fg_alpha))
|
92
|
+
end_b = int(fg_b * fg_alpha + bg_color_end[2] * (1 - fg_alpha))
|
93
|
+
end_a = int(fg_a + bg_color_end[3] * (1 - fg_alpha))
|
94
|
+
end_blended_color = (end_r, end_g, end_b, end_a)
|
95
|
+
else:
|
96
|
+
end_blended_color = color
|
97
|
+
|
98
|
+
# Use the average of the two blended colors
|
99
|
+
blended_color = (
|
100
|
+
(start_blended_color[0] + end_blended_color[0]) // 2,
|
101
|
+
(start_blended_color[1] + end_blended_color[1]) // 2,
|
102
|
+
(start_blended_color[2] + end_blended_color[2]) // 2,
|
103
|
+
(start_blended_color[3] + end_blended_color[3]) // 2,
|
104
|
+
)
|
105
|
+
return blended_color
|
@@ -139,7 +139,6 @@ color_array = [
|
|
139
139
|
]
|
140
140
|
|
141
141
|
|
142
|
-
|
143
142
|
class SupportedColor(StrEnum):
|
144
143
|
"""Color of a supported map element."""
|
145
144
|
|
@@ -408,61 +407,56 @@ class ColorsManagement:
|
|
408
407
|
def blend_colors(background: Color, foreground: Color) -> Color:
|
409
408
|
"""
|
410
409
|
Blend foreground color with background color based on alpha values.
|
411
|
-
|
412
|
-
This is used when drawing elements that overlap on the map.
|
413
|
-
The alpha channel determines how much of the foreground color is visible.
|
414
|
-
Uses optimized calculations for better performance.
|
410
|
+
Optimized version with more fast paths and simplified calculations.
|
415
411
|
|
416
412
|
:param background: Background RGBA color (r,g,b,a)
|
417
413
|
:param foreground: Foreground RGBA color (r,g,b,a) to blend on top
|
418
414
|
:return: Blended RGBA color
|
419
415
|
"""
|
420
|
-
#
|
421
|
-
|
422
|
-
fg_r, fg_g, fg_b, fg_a = foreground
|
416
|
+
# Fast paths for common cases
|
417
|
+
fg_a = foreground[3]
|
423
418
|
|
424
|
-
|
425
|
-
if fg_a == 255:
|
419
|
+
if fg_a == 255: # Fully opaque foreground
|
426
420
|
return foreground
|
427
|
-
if fg_a == 0:
|
428
|
-
return background
|
429
421
|
|
430
|
-
#
|
431
|
-
|
432
|
-
fg_alpha = fg_a / 255.0
|
433
|
-
bg_alpha = bg_a / 255.0
|
422
|
+
if fg_a == 0: # Fully transparent foreground
|
423
|
+
return background
|
434
424
|
|
435
|
-
|
436
|
-
|
425
|
+
bg_a = background[3]
|
426
|
+
if bg_a == 0: # Fully transparent background
|
427
|
+
return foreground
|
437
428
|
|
438
|
-
#
|
439
|
-
|
440
|
-
|
429
|
+
# Extract components (only after fast paths)
|
430
|
+
bg_r, bg_g, bg_b = background[:3]
|
431
|
+
fg_r, fg_g, fg_b = foreground[:3]
|
441
432
|
|
442
|
-
#
|
443
|
-
|
444
|
-
|
445
|
-
inv_alpha_ratio = 1.0 - alpha_ratio
|
433
|
+
# Pre-calculate the blend factor once (avoid repeated division)
|
434
|
+
blend = fg_a / 255.0
|
435
|
+
inv_blend = 1.0 - blend
|
446
436
|
|
447
|
-
|
448
|
-
|
449
|
-
|
437
|
+
# Simple linear interpolation for RGB channels
|
438
|
+
# This is faster than the previous implementation
|
439
|
+
out_r = int(fg_r * blend + bg_r * inv_blend)
|
440
|
+
out_g = int(fg_g * blend + bg_g * inv_blend)
|
441
|
+
out_b = int(fg_b * blend + bg_b * inv_blend)
|
450
442
|
|
451
|
-
#
|
452
|
-
out_a = int(
|
443
|
+
# Alpha blending - simplified calculation
|
444
|
+
out_a = int(fg_a + bg_a * inv_blend)
|
453
445
|
|
454
|
-
#
|
455
|
-
|
456
|
-
out_g = max(0, min(255, out_g))
|
457
|
-
out_b = max(0, min(255, out_b))
|
446
|
+
# No need for min/max checks as the blend math keeps values in range
|
447
|
+
# when input values are valid (0-255)
|
458
448
|
|
459
449
|
return [out_r, out_g, out_b, out_a]
|
460
450
|
|
451
|
+
# Cache for recently sampled background colors
|
452
|
+
_bg_color_cache = {}
|
453
|
+
_cache_size = 1024 # Limit cache size to avoid memory issues
|
454
|
+
|
461
455
|
@staticmethod
|
462
456
|
def sample_and_blend_color(array, x: int, y: int, foreground: Color) -> Color:
|
463
457
|
"""
|
464
458
|
Sample the background color from the array at coordinates (x,y) and blend with foreground color.
|
465
|
-
|
459
|
+
Optimized version with caching and faster sampling.
|
466
460
|
|
467
461
|
Args:
|
468
462
|
array: The RGBA numpy array representing the image
|
@@ -473,53 +467,43 @@ class ColorsManagement:
|
|
473
467
|
Returns:
|
474
468
|
Blended RGBA color
|
475
469
|
"""
|
476
|
-
#
|
470
|
+
# Fast path for fully opaque foreground - no need to sample or blend
|
471
|
+
if foreground[3] == 255:
|
472
|
+
return foreground
|
473
|
+
|
474
|
+
# Ensure array exists
|
477
475
|
if array is None:
|
478
476
|
return foreground
|
479
477
|
|
478
|
+
# Check if coordinates are within bounds
|
480
479
|
height, width = array.shape[:2]
|
481
480
|
if not (0 <= y < height and 0 <= x < width):
|
482
|
-
return foreground # Return foreground if coordinates are out of bounds
|
483
|
-
|
484
|
-
# Fast path for fully opaque foreground
|
485
|
-
if foreground[3] == 255:
|
486
481
|
return foreground
|
487
482
|
|
488
|
-
#
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
coordinates = np.array([[y, x]])
|
497
|
-
|
498
|
-
# Sample each channel separately with nearest neighbor interpolation
|
499
|
-
# This is faster than sampling all channels at once for large arrays
|
500
|
-
r = ndimage.map_coordinates(
|
501
|
-
array[..., 0], coordinates.T, order=0, mode="nearest"
|
502
|
-
)[0]
|
503
|
-
g = ndimage.map_coordinates(
|
504
|
-
array[..., 1], coordinates.T, order=0, mode="nearest"
|
505
|
-
)[0]
|
506
|
-
b = ndimage.map_coordinates(
|
507
|
-
array[..., 2], coordinates.T, order=0, mode="nearest"
|
508
|
-
)[0]
|
509
|
-
a = ndimage.map_coordinates(
|
510
|
-
array[..., 3], coordinates.T, order=0, mode="nearest"
|
511
|
-
)[0]
|
512
|
-
background = (int(r), int(g), int(b), int(a))
|
513
|
-
else:
|
514
|
-
# For smaller arrays, direct indexing is faster
|
515
|
-
background = tuple(array[y, x])
|
516
|
-
except (IndexError, ValueError):
|
517
|
-
# Fallback to direct indexing if ndimage fails
|
483
|
+
# Check cache for this coordinate
|
484
|
+
cache_key = (id(array), x, y)
|
485
|
+
cache = ColorsManagement._bg_color_cache
|
486
|
+
|
487
|
+
if cache_key in cache:
|
488
|
+
background = cache[cache_key]
|
489
|
+
else:
|
490
|
+
# Sample the background color using direct indexing (fastest method)
|
518
491
|
try:
|
519
|
-
background = tuple(array[y, x])
|
492
|
+
background = tuple(map(int, array[y, x]))
|
493
|
+
|
494
|
+
# Update cache (with simple LRU-like behavior)
|
495
|
+
if len(cache) >= ColorsManagement._cache_size:
|
496
|
+
# Remove a random entry if cache is full
|
497
|
+
cache.pop(next(iter(cache)))
|
498
|
+
cache[cache_key] = background
|
499
|
+
|
520
500
|
except (IndexError, ValueError):
|
521
501
|
return foreground
|
522
502
|
|
503
|
+
# Fast path for fully transparent foreground
|
504
|
+
if foreground[3] == 0:
|
505
|
+
return background
|
506
|
+
|
523
507
|
# Blend the colors
|
524
508
|
return ColorsManagement.blend_colors(background, foreground)
|
525
509
|
|
@@ -75,7 +75,7 @@ class Drawable:
|
|
75
75
|
# Get the region to update
|
76
76
|
region_slice = (
|
77
77
|
slice(row, row + pixel_size),
|
78
|
-
slice(col + i * pixel_size, col + (i + 1) * pixel_size)
|
78
|
+
slice(col + i * pixel_size, col + (i + 1) * pixel_size),
|
79
79
|
)
|
80
80
|
|
81
81
|
if need_blending:
|
@@ -84,8 +84,10 @@ class Drawable:
|
|
84
84
|
center_x = col + i * pixel_size + pixel_size // 2
|
85
85
|
|
86
86
|
# Only blend if coordinates are valid
|
87
|
-
if (
|
88
|
-
0 <=
|
87
|
+
if (
|
88
|
+
0 <= center_y < image_array.shape[0]
|
89
|
+
and 0 <= center_x < image_array.shape[1]
|
90
|
+
):
|
89
91
|
# Get blended color
|
90
92
|
blended_color = ColorsManagement.sample_and_blend_color(
|
91
93
|
image_array, center_x, center_y, full_color
|
@@ -296,9 +298,11 @@ class Drawable:
|
|
296
298
|
for x, y in zip(x_coords, y_coords):
|
297
299
|
for i in range(-half_width, half_width + 1):
|
298
300
|
for j in range(-half_width, half_width + 1):
|
299
|
-
if (
|
300
|
-
|
301
|
-
0 <=
|
301
|
+
if (
|
302
|
+
i * i + j * j <= half_width * half_width # Make it round
|
303
|
+
and 0 <= x + i < layer.shape[1]
|
304
|
+
and 0 <= y + j < layer.shape[0]
|
305
|
+
):
|
302
306
|
layer[y + j, x + i] = blended_color
|
303
307
|
|
304
308
|
return layer
|
@@ -371,7 +375,7 @@ class Drawable:
|
|
371
375
|
dist_sq = (y_indices - y) ** 2 + (x_indices - x) ** 2
|
372
376
|
|
373
377
|
# Create masks for the circle and outline
|
374
|
-
circle_mask = dist_sq <= radius
|
378
|
+
circle_mask = dist_sq <= radius**2
|
375
379
|
|
376
380
|
# Apply the fill color
|
377
381
|
image[min_y:max_y, min_x:max_x][circle_mask] = color
|
@@ -468,7 +472,7 @@ class Drawable:
|
|
468
472
|
adjusted_points = [(p[0] - min_x, p[1] - min_y) for p in points]
|
469
473
|
|
470
474
|
# Create a grid of coordinates and use it to test all points at once
|
471
|
-
y_indices, x_indices = np.mgrid[0:mask.shape[0], 0:mask.shape[1]]
|
475
|
+
y_indices, x_indices = np.mgrid[0 : mask.shape[0], 0 : mask.shape[1]]
|
472
476
|
|
473
477
|
# Test each point in the grid
|
474
478
|
for i in range(mask.shape[0]):
|
@@ -476,7 +480,7 @@ class Drawable:
|
|
476
480
|
mask[i, j] = Drawable.point_inside(j, i, adjusted_points)
|
477
481
|
|
478
482
|
# Apply the fill color to the masked region
|
479
|
-
arr[min_y:max_y+1, min_x:max_x+1][mask] = fill_color
|
483
|
+
arr[min_y : max_y + 1, min_x : max_x + 1][mask] = fill_color
|
480
484
|
|
481
485
|
return arr
|
482
486
|
|
@@ -530,7 +534,7 @@ class Drawable:
|
|
530
534
|
y_indices, x_indices = np.ogrid[y_min:y_max, x_min:x_max]
|
531
535
|
|
532
536
|
# Create a circular mask
|
533
|
-
mask = (y_indices - y)**2 + (x_indices - x)**2 <= dot_radius**2
|
537
|
+
mask = (y_indices - y) ** 2 + (x_indices - x) ** 2 <= dot_radius**2
|
534
538
|
|
535
539
|
# Apply the color to the masked region
|
536
540
|
layers[y_min:y_max, x_min:x_max][mask] = blended_color
|
@@ -607,8 +611,12 @@ class Drawable:
|
|
607
611
|
y2 = int(tmp_y + r_cover * math.cos(a2))
|
608
612
|
|
609
613
|
# Draw the direction line
|
610
|
-
if (
|
611
|
-
0 <=
|
614
|
+
if (
|
615
|
+
0 <= x1 < tmp_width
|
616
|
+
and 0 <= y1 < tmp_height
|
617
|
+
and 0 <= x2 < tmp_width
|
618
|
+
and 0 <= y2 < tmp_height
|
619
|
+
):
|
612
620
|
tmp_layer = Drawable._line(tmp_layer, x1, y1, x2, y2, outline, width=1)
|
613
621
|
|
614
622
|
# Draw the lidar indicator
|
@@ -694,14 +702,14 @@ class Drawable:
|
|
694
702
|
# Get image dimensions
|
695
703
|
height, width = image.shape[:2]
|
696
704
|
|
697
|
-
if element_type ==
|
705
|
+
if element_type == "circle":
|
698
706
|
# Extract circle centers and radii
|
699
707
|
centers = []
|
700
708
|
radii = []
|
701
709
|
for elem in elements:
|
702
|
-
if isinstance(elem, dict) and
|
703
|
-
centers.append(elem[
|
704
|
-
radii.append(elem[
|
710
|
+
if isinstance(elem, dict) and "center" in elem and "radius" in elem:
|
711
|
+
centers.append(elem["center"])
|
712
|
+
radii.append(elem["radius"])
|
705
713
|
elif isinstance(elem, (list, tuple)) and len(elem) >= 3:
|
706
714
|
# Format: (x, y, radius)
|
707
715
|
centers.append((elem[0], elem[1]))
|
@@ -709,7 +717,9 @@ class Drawable:
|
|
709
717
|
|
710
718
|
# Process circles with the same radius together
|
711
719
|
for radius in set(radii):
|
712
|
-
same_radius_centers = [
|
720
|
+
same_radius_centers = [
|
721
|
+
centers[i] for i in range(len(centers)) if radii[i] == radius
|
722
|
+
]
|
713
723
|
if same_radius_centers:
|
714
724
|
# Create a combined mask for all circles with this radius
|
715
725
|
mask = np.zeros((height, width), dtype=bool)
|
@@ -725,20 +735,22 @@ class Drawable:
|
|
725
735
|
y_indices, x_indices = np.ogrid[min_y:max_y, min_x:max_x]
|
726
736
|
|
727
737
|
# Add this circle to the mask
|
728
|
-
circle_mask = (y_indices - cy)**2 + (
|
738
|
+
circle_mask = (y_indices - cy) ** 2 + (
|
739
|
+
x_indices - cx
|
740
|
+
) ** 2 <= radius**2
|
729
741
|
mask[min_y:max_y, min_x:max_x] |= circle_mask
|
730
742
|
|
731
743
|
# Apply color to all circles at once
|
732
744
|
image[mask] = color
|
733
745
|
|
734
|
-
elif element_type ==
|
746
|
+
elif element_type == "line":
|
735
747
|
# Extract line endpoints
|
736
748
|
lines = []
|
737
749
|
widths = []
|
738
750
|
for elem in elements:
|
739
|
-
if isinstance(elem, dict) and
|
740
|
-
lines.append((elem[
|
741
|
-
widths.append(elem.get(
|
751
|
+
if isinstance(elem, dict) and "start" in elem and "end" in elem:
|
752
|
+
lines.append((elem["start"], elem["end"]))
|
753
|
+
widths.append(elem.get("width", 1))
|
742
754
|
elif isinstance(elem, (list, tuple)) and len(elem) >= 4:
|
743
755
|
# Format: (x1, y1, x2, y2, [width])
|
744
756
|
lines.append(((elem[0], elem[1]), (elem[2], elem[3])))
|
@@ -746,19 +758,25 @@ class Drawable:
|
|
746
758
|
|
747
759
|
# Process lines with the same width together
|
748
760
|
for width in set(widths):
|
749
|
-
same_width_lines = [
|
761
|
+
same_width_lines = [
|
762
|
+
lines[i] for i in range(len(lines)) if widths[i] == width
|
763
|
+
]
|
750
764
|
if same_width_lines:
|
751
765
|
# Create a combined mask for all lines with this width
|
752
766
|
mask = np.zeros((height, width), dtype=bool)
|
753
767
|
|
754
768
|
# Draw all lines into the mask
|
755
|
-
for
|
769
|
+
for start, end in same_width_lines:
|
756
770
|
x1, y1 = start
|
757
771
|
x2, y2 = end
|
758
772
|
|
759
773
|
# Skip invalid lines
|
760
|
-
if not (
|
761
|
-
|
774
|
+
if not (
|
775
|
+
0 <= x1 < width
|
776
|
+
and 0 <= y1 < height
|
777
|
+
and 0 <= x2 < width
|
778
|
+
and 0 <= y2 < height
|
779
|
+
):
|
762
780
|
continue
|
763
781
|
|
764
782
|
# Use Bresenham's algorithm to get line points
|
@@ -783,8 +801,12 @@ class Drawable:
|
|
783
801
|
max_x = min(width, x + half_width + 1)
|
784
802
|
|
785
803
|
# Create a circular brush
|
786
|
-
y_indices, x_indices = np.ogrid[
|
787
|
-
|
804
|
+
y_indices, x_indices = np.ogrid[
|
805
|
+
min_y:max_y, min_x:max_x
|
806
|
+
]
|
807
|
+
brush = (y_indices - y) ** 2 + (
|
808
|
+
x_indices - x
|
809
|
+
) ** 2 <= half_width**2
|
788
810
|
mask[min_y:max_y, min_x:max_x] |= brush
|
789
811
|
|
790
812
|
# Apply color to all lines at once
|
@@ -826,20 +848,16 @@ class Drawable:
|
|
826
848
|
)
|
827
849
|
|
828
850
|
# Add to centers list with radius
|
829
|
-
centers.append({
|
830
|
-
'center': (x, y),
|
831
|
-
'radius': 6,
|
832
|
-
'color': obstacle_color
|
833
|
-
})
|
851
|
+
centers.append({"center": (x, y), "radius": 6, "color": obstacle_color})
|
834
852
|
except (KeyError, TypeError):
|
835
853
|
continue
|
836
854
|
|
837
855
|
# Draw each obstacle with its blended color
|
838
856
|
if centers:
|
839
857
|
for obstacle in centers:
|
840
|
-
cx, cy = obstacle[
|
841
|
-
radius = obstacle[
|
842
|
-
obs_color = obstacle[
|
858
|
+
cx, cy = obstacle["center"]
|
859
|
+
radius = obstacle["radius"]
|
860
|
+
obs_color = obstacle["color"]
|
843
861
|
|
844
862
|
# Create a small mask for the obstacle
|
845
863
|
min_y = max(0, cy - radius)
|
@@ -851,7 +869,7 @@ class Drawable:
|
|
851
869
|
y_indices, x_indices = np.ogrid[min_y:max_y, min_x:max_x]
|
852
870
|
|
853
871
|
# Create a circular mask
|
854
|
-
mask = (y_indices - cy)**2 + (x_indices - cx)**2 <= radius**2
|
872
|
+
mask = (y_indices - cy) ** 2 + (x_indices - cx) ** 2 <= radius**2
|
855
873
|
|
856
874
|
# Apply the color to the masked region
|
857
875
|
image[min_y:max_y, min_x:max_x][mask] = obs_color
|