valetudo-map-parser 0.1.9b50__py3-none-any.whl → 0.1.9b51__py3-none-any.whl

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.
@@ -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",
@@ -18,26 +18,82 @@ def get_blended_color(
18
18
  Get a blended color for a pixel based on the current element map and the new element to draw.
19
19
 
20
20
  This function:
21
- 1. Gets the current element at position (x,y) from the element map
22
- 2. Gets the color for that element from the colors manager
23
- 3. Blends the new color with the existing color based on alpha values
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
24
 
25
25
  Returns:
26
26
  Blended RGBA color to use for drawing
27
27
  """
28
- # Sample background color at the endpoints and blend with foreground color
29
- # This is more efficient than sampling at every pixel
30
- if 0 <= y0 < arr.shape[0] and 0 <= x0 < arr.shape[1]:
31
- start_blended_color = ColorsManagement.sample_and_blend_color(
32
- arr, x0, y0, color
33
- )
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)
34
65
  else:
35
- start_blended_color = color
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
36
76
 
37
- if 0 <= y1 < arr.shape[0] and 0 <= x1 < arr.shape[1]:
38
- end_blended_color = ColorsManagement.sample_and_blend_color(arr, x1, y1, color)
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)
39
86
  else:
40
- end_blended_color = color
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
41
97
 
42
98
  # Use the average of the two blended colors
43
99
  blended_color = (
@@ -407,61 +407,56 @@ class ColorsManagement:
407
407
  def blend_colors(background: Color, foreground: Color) -> Color:
408
408
  """
409
409
  Blend foreground color with background color based on alpha values.
410
-
411
- This is used when drawing elements that overlap on the map.
412
- The alpha channel determines how much of the foreground color is visible.
413
- Uses optimized calculations for better performance.
410
+ Optimized version with more fast paths and simplified calculations.
414
411
 
415
412
  :param background: Background RGBA color (r,g,b,a)
416
413
  :param foreground: Foreground RGBA color (r,g,b,a) to blend on top
417
414
  :return: Blended RGBA color
418
415
  """
419
- # Extract components
420
- bg_r, bg_g, bg_b, bg_a = background
421
- fg_r, fg_g, fg_b, fg_a = foreground
416
+ # Fast paths for common cases
417
+ fg_a = foreground[3]
422
418
 
423
- # Fast path for common cases
424
- if fg_a == 255:
419
+ if fg_a == 255: # Fully opaque foreground
425
420
  return foreground
426
- if fg_a == 0:
427
- return background
428
421
 
429
- # Calculate alpha blending
430
- # Convert alpha from [0-255] to [0-1] for calculations
431
- fg_alpha = fg_a / 255.0
432
- bg_alpha = bg_a / 255.0
422
+ if fg_a == 0: # Fully transparent foreground
423
+ return background
433
424
 
434
- # Calculate resulting alpha
435
- out_alpha = fg_alpha + bg_alpha * (1 - fg_alpha)
425
+ bg_a = background[3]
426
+ if bg_a == 0: # Fully transparent background
427
+ return foreground
436
428
 
437
- # Avoid division by zero
438
- if out_alpha < 0.0001:
439
- return Color[0, 0, 0, 0] # Fully transparent result
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]
440
432
 
441
- # Calculate blended RGB components
442
- # Using a more efficient calculation method
443
- alpha_ratio = fg_alpha / out_alpha
444
- 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
445
436
 
446
- out_r = int(fg_r * alpha_ratio + bg_r * inv_alpha_ratio)
447
- out_g = int(fg_g * alpha_ratio + bg_g * inv_alpha_ratio)
448
- out_b = int(fg_b * alpha_ratio + bg_b * inv_alpha_ratio)
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)
449
442
 
450
- # Convert alpha back to [0-255] range
451
- out_a = int(out_alpha * 255)
443
+ # Alpha blending - simplified calculation
444
+ out_a = int(fg_a + bg_a * inv_blend)
452
445
 
453
- # Ensure values are in valid range (using min/max for efficiency)
454
- out_r = max(0, min(255, out_r))
455
- out_g = max(0, min(255, out_g))
456
- 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)
457
448
 
458
449
  return [out_r, out_g, out_b, out_a]
459
450
 
451
+ # Cache for recently sampled background colors
452
+ _bg_color_cache = {}
453
+ _cache_size = 1024 # Limit cache size to avoid memory issues
454
+
460
455
  @staticmethod
461
456
  def sample_and_blend_color(array, x: int, y: int, foreground: Color) -> Color:
462
457
  """
463
458
  Sample the background color from the array at coordinates (x,y) and blend with foreground color.
464
- Uses scipy.ndimage for efficient sampling when appropriate.
459
+ Optimized version with caching and faster sampling.
465
460
 
466
461
  Args:
467
462
  array: The RGBA numpy array representing the image
@@ -472,53 +467,43 @@ class ColorsManagement:
472
467
  Returns:
473
468
  Blended RGBA color
474
469
  """
475
- # Ensure coordinates are within bounds
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
476
475
  if array is None:
477
476
  return foreground
478
477
 
478
+ # Check if coordinates are within bounds
479
479
  height, width = array.shape[:2]
480
480
  if not (0 <= y < height and 0 <= x < width):
481
- return foreground # Return foreground if coordinates are out of bounds
482
-
483
- # Fast path for fully opaque foreground
484
- if foreground[3] == 255:
485
481
  return foreground
486
482
 
487
- # The array is in RGBA format with shape (height, width, 4)
488
- try:
489
- # Use scipy.ndimage for sampling with boundary handling
490
- # This is more efficient for large arrays and handles edge cases better
491
- if (
492
- array.size > 1000000
493
- ): # Only use for larger arrays where the overhead is worth it
494
- # Create coordinates array for the sampling point
495
- coordinates = np.array([[y, x]])
496
-
497
- # Sample each channel separately with nearest neighbor interpolation
498
- # This is faster than sampling all channels at once for large arrays
499
- r = ndimage.map_coordinates(
500
- array[..., 0], coordinates.T, order=0, mode="nearest"
501
- )[0]
502
- g = ndimage.map_coordinates(
503
- array[..., 1], coordinates.T, order=0, mode="nearest"
504
- )[0]
505
- b = ndimage.map_coordinates(
506
- array[..., 2], coordinates.T, order=0, mode="nearest"
507
- )[0]
508
- a = ndimage.map_coordinates(
509
- array[..., 3], coordinates.T, order=0, mode="nearest"
510
- )[0]
511
- background = (int(r), int(g), int(b), int(a))
512
- else:
513
- # For smaller arrays, direct indexing is faster
514
- background = tuple(array[y, x])
515
- except (IndexError, ValueError):
516
- # 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)
517
491
  try:
518
- 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
+
519
500
  except (IndexError, ValueError):
520
501
  return foreground
521
502
 
503
+ # Fast path for fully transparent foreground
504
+ if foreground[3] == 0:
505
+ return background
506
+
522
507
  # Blend the colors
523
508
  return ColorsManagement.blend_colors(background, foreground)
524
509