2024-06-14 16:00:28 +00:00
|
|
|
import cv2
|
2024-06-12 15:12:45 +00:00
|
|
|
import numpy as np
|
|
|
|
from scipy import signal
|
|
|
|
|
|
|
|
from module.base.button import ButtonWrapper, ClickButton
|
|
|
|
from module.base.timer import Timer
|
|
|
|
from module.exception import ScriptError
|
|
|
|
from module.logger import logger
|
|
|
|
from tasks.base.ui import UI
|
|
|
|
|
|
|
|
|
|
|
|
class Slider:
|
|
|
|
def __init__(
|
|
|
|
self,
|
|
|
|
main: UI,
|
|
|
|
slider: ButtonWrapper,
|
|
|
|
parameters: dict = None,
|
|
|
|
background=5
|
|
|
|
):
|
|
|
|
"""
|
|
|
|
Args:
|
|
|
|
main:
|
|
|
|
slider: Slider button
|
|
|
|
parameters (dict): Parameters passing to scipy.find_peaks
|
|
|
|
background (int):
|
|
|
|
"""
|
|
|
|
self.main = main
|
|
|
|
self.slider = slider
|
|
|
|
|
|
|
|
if parameters is None:
|
|
|
|
parameters = {}
|
|
|
|
self.parameters = parameters
|
|
|
|
self.background = background
|
|
|
|
|
|
|
|
# Actual slider area
|
|
|
|
self.area = self.slider.area
|
|
|
|
|
|
|
|
def cal_slider(self):
|
|
|
|
"""
|
|
|
|
Slider always have its left side fixed, right side movable depending on the maximum number
|
|
|
|
Calculate the right side, similar to AdaptiveScroll.match_color
|
|
|
|
"""
|
|
|
|
area = self.slider.area
|
|
|
|
area = (area[0], area[1] - self.background, area[2], area[3] + self.background)
|
|
|
|
image = self.main.image_crop(area, copy=False)
|
2024-06-14 16:00:28 +00:00
|
|
|
r, g, _ = cv2.split(image)
|
|
|
|
image = r.flatten('F')
|
2024-06-12 15:12:45 +00:00
|
|
|
wlen = area[3] - area[1]
|
|
|
|
total = area[2] - area[0]
|
|
|
|
|
|
|
|
parameters = {
|
|
|
|
'prominence': 15,
|
|
|
|
'wlen': wlen,
|
2024-06-14 16:00:28 +00:00
|
|
|
'width': 2,
|
2024-06-12 15:12:45 +00:00
|
|
|
'distance': wlen / 2,
|
|
|
|
}
|
|
|
|
parameters.update(self.parameters)
|
|
|
|
peaks, _ = signal.find_peaks(image, **parameters)
|
|
|
|
peaks //= wlen
|
2024-06-14 16:00:28 +00:00
|
|
|
diff = np.diff(peaks)
|
|
|
|
|
|
|
|
# Mask controller
|
|
|
|
# Controller has orange border (240, 150, 57) and white center
|
|
|
|
r = cv2.reduce(r, 0, cv2.REDUCE_AVG).flatten()
|
|
|
|
g = cv2.reduce(g, 0, cv2.REDUCE_AVG).flatten()
|
|
|
|
mask = np.all([r > 160, g > 80], axis=0)
|
|
|
|
try:
|
|
|
|
index = np.where(mask == True)
|
|
|
|
left = max(index[0][0] - 3, 0)
|
|
|
|
right = min(index[0][-1] + 3, len(peaks))
|
|
|
|
diff[left:right] = 0
|
|
|
|
except IndexError:
|
|
|
|
pass
|
2024-06-12 15:12:45 +00:00
|
|
|
|
|
|
|
# Ignore non-continuous peaks, which may be the letter to the right of slider
|
|
|
|
try:
|
2024-06-14 16:00:28 +00:00
|
|
|
right = np.where(diff >= 3)[0][0]
|
2024-06-12 15:12:45 +00:00
|
|
|
peaks = peaks[:right + 1]
|
|
|
|
except IndexError:
|
|
|
|
pass
|
2024-06-14 16:00:28 +00:00
|
|
|
|
2024-06-12 15:12:45 +00:00
|
|
|
# Calculate actual slider area
|
|
|
|
try:
|
|
|
|
length = peaks[-1]
|
|
|
|
self.area = (area[0], area[1], area[0] + length + 1, area[3])
|
|
|
|
except IndexError:
|
|
|
|
length = total
|
|
|
|
self.area = self.slider.area
|
|
|
|
logger.info(f'Slider length: {length}/{total}')
|
|
|
|
|
2024-06-14 16:00:28 +00:00
|
|
|
if length < total / 2:
|
|
|
|
logger.warning('Detected slider too short')
|
|
|
|
# self.main.device.image_save()
|
|
|
|
|
2024-06-12 15:12:45 +00:00
|
|
|
def set(self, value: int, total: int, skip_first_screenshot=True):
|
|
|
|
"""
|
|
|
|
Args:
|
|
|
|
value: Value to set
|
|
|
|
total: Maximum amount of slider
|
|
|
|
skip_first_screenshot:
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
bool: If success
|
|
|
|
"""
|
|
|
|
logger.info(f'Slider set {value}/{total}')
|
|
|
|
if value > total:
|
|
|
|
raise ScriptError(f'Slider.set value {value} > total {total}')
|
|
|
|
if total <= 0:
|
|
|
|
raise ScriptError(f'Slider.set total {total} <= 0')
|
|
|
|
if value <= 0:
|
|
|
|
raise ScriptError(f'Slider.set value {value} <= 0')
|
|
|
|
if value == total == 1:
|
|
|
|
logger.info('Slider set total==1, no need to set')
|
|
|
|
return True
|
|
|
|
# if value == 1:
|
|
|
|
# logger.info('Slider set value==1, no need to set')
|
|
|
|
# return True
|
|
|
|
|
|
|
|
self.cal_slider()
|
|
|
|
|
|
|
|
# 18px is the width of controller
|
|
|
|
length = self.area[2] - self.area[0] - 18
|
|
|
|
|
|
|
|
left = int((value - 2) / (total - 1) * length) + self.area[0]
|
|
|
|
right = int((value - 1) / (total - 1) * length) + self.area[0]
|
|
|
|
if right <= left:
|
|
|
|
right = left + 1
|
|
|
|
# 10px is the radius of slider controller
|
|
|
|
# When you somewhere on the slider, left edge of controller is where you click
|
|
|
|
# if right - left < 10:
|
|
|
|
# logger.info('Setting high maximum slider')
|
|
|
|
# left -= 10
|
|
|
|
# right -= 10
|
|
|
|
|
|
|
|
# Limit in slider
|
|
|
|
left = max(min(self.area[2] - 1, left), self.area[0])
|
|
|
|
right = max(min(self.area[2], right), self.area[0] + 1)
|
|
|
|
# Click the right half
|
|
|
|
left = int((left + right) / 2)
|
|
|
|
button = ClickButton(
|
|
|
|
(left, self.area[1], right, self.area[3]),
|
|
|
|
name=f'Slider_{value}_{total}')
|
|
|
|
# Pad click area to search
|
|
|
|
pad = 15
|
|
|
|
detect = (right - pad, self.area[1], right + pad, self.area[3])
|
|
|
|
logger.info(f'Controller button={button.button}, detect={detect}')
|
|
|
|
|
|
|
|
interval = Timer(1, count=3)
|
|
|
|
trial = 0
|
|
|
|
while 1:
|
|
|
|
if skip_first_screenshot:
|
|
|
|
skip_first_screenshot = False
|
|
|
|
else:
|
|
|
|
self.main.device.screenshot()
|
|
|
|
|
|
|
|
# End
|
|
|
|
if trial > 3:
|
|
|
|
logger.warning('Slider.set failed after 3 trial')
|
|
|
|
return False
|
|
|
|
if self.main.image_color_count(detect, color=(255, 255, 255), threshold=221, count=50):
|
|
|
|
logger.info('Slider set done')
|
|
|
|
return True
|
|
|
|
|
|
|
|
# Click
|
|
|
|
if interval.reached():
|
|
|
|
self.main.device.click(button)
|
|
|
|
interval.reset()
|
|
|
|
trial += 1
|