87 lines
2.8 KiB
Python
87 lines
2.8 KiB
Python
"""Color and pixel analysis utilities.
|
|
|
|
Provides tools for reading health/mana bars, detecting UI states
|
|
via color sampling, and pixel-level game state detection.
|
|
"""
|
|
|
|
from typing import Tuple, Optional, List
|
|
import logging
|
|
|
|
import numpy as np
|
|
import cv2
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class ColorAnalyzer:
|
|
"""Analyze pixel colors and UI bar states."""
|
|
|
|
@staticmethod
|
|
def get_pixel_color(screen: np.ndarray, x: int, y: int) -> Tuple[int, int, int]:
|
|
"""Get BGR color at pixel position."""
|
|
return tuple(screen[y, x].tolist())
|
|
|
|
@staticmethod
|
|
def get_pixel_hsv(screen: np.ndarray, x: int, y: int) -> Tuple[int, int, int]:
|
|
"""Get HSV color at pixel position."""
|
|
hsv = cv2.cvtColor(screen[y:y+1, x:x+1], cv2.COLOR_BGR2HSV)
|
|
return tuple(hsv[0, 0].tolist())
|
|
|
|
@staticmethod
|
|
def color_matches(
|
|
color: Tuple[int, int, int],
|
|
target: Tuple[int, int, int],
|
|
tolerance: int = 20,
|
|
) -> bool:
|
|
"""Check if a color matches target within tolerance."""
|
|
return all(abs(c - t) <= tolerance for c, t in zip(color, target))
|
|
|
|
@staticmethod
|
|
def read_bar_percentage(
|
|
screen: np.ndarray,
|
|
bar_region: Tuple[int, int, int, int],
|
|
filled_color_hsv: Tuple[Tuple[int, int, int], Tuple[int, int, int]],
|
|
) -> float:
|
|
"""Read a horizontal bar's fill percentage (health, mana, xp, etc.).
|
|
|
|
Args:
|
|
screen: Screenshot in BGR
|
|
bar_region: (x, y, width, height) of the bar
|
|
filled_color_hsv: (lower_hsv, upper_hsv) range of the filled portion
|
|
|
|
Returns:
|
|
Fill percentage 0.0 to 1.0
|
|
"""
|
|
x, y, w, h = bar_region
|
|
bar = screen[y:y+h, x:x+w]
|
|
hsv = cv2.cvtColor(bar, cv2.COLOR_BGR2HSV)
|
|
|
|
lower, upper = filled_color_hsv
|
|
mask = cv2.inRange(hsv, np.array(lower), np.array(upper))
|
|
|
|
# Scan columns left to right to find the fill boundary
|
|
col_fill = np.mean(mask, axis=0) / 255.0
|
|
|
|
# Find the rightmost column that's mostly filled
|
|
threshold = 0.3
|
|
filled_cols = np.where(col_fill > threshold)[0]
|
|
|
|
if len(filled_cols) == 0:
|
|
return 0.0
|
|
|
|
return (filled_cols[-1] + 1) / w
|
|
|
|
@staticmethod
|
|
def sample_region_dominant_color(
|
|
screen: np.ndarray,
|
|
region: Tuple[int, int, int, int],
|
|
) -> Tuple[int, int, int]:
|
|
"""Get the dominant BGR color in a region."""
|
|
x, y, w, h = region
|
|
roi = screen[y:y+h, x:x+w]
|
|
pixels = roi.reshape(-1, 3).astype(np.float32)
|
|
|
|
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 10, 1.0)
|
|
_, labels, centers = cv2.kmeans(pixels, 1, None, criteria, 3, cv2.KMEANS_RANDOM_CENTERS)
|
|
|
|
return tuple(centers[0].astype(int).tolist())
|