Merge pull request #155 from fishyboteso/wip/screenshot_lib_selector

screenshot library selected
This commit is contained in:
Adam Saudagar 2023-03-07 15:46:20 +05:30 committed by GitHub
commit 70af635025
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 205 additions and 102 deletions

View File

@ -5,8 +5,10 @@ chalutier = ("Chalutier", "https://www.esoui.com/downloads/dl2934/Chalutier_1.1.
# addons used
lam2 = ("LibAddonMenu-2.0", "https://cdn.esoui.com/downloads/file7/LibAddonMenu-2.0r34.zip", 34)
fishyqr = ("FishyQR", "https://github.com/fishyboteso/FishyQR/releases/download/v1.5/FishyQR.zip", 150)
fishyqr = ("FishyQR", "https://github.com/fishyboteso/FishyQR/releases/download/v1.6/FishyQR.zip", 151)
libgps = ("LibGPS", "https://cdn.esoui.com/downloads/file601/LibGPS_3_2_0.zip", 32)
libmapping = ("LibMapPing", "https://cdn.esoui.com/downloads/file1302/LibMapPing_2_0_0.zip", 200)
libdl = ("LibDebugLogger", "https://cdn.esoui.com/downloads/file2275/LibDebugLogger_2_4_1.zip", 241)
libchatmsg = ("LibChatMessage", "https://cdn.esoui.com/downloads/file2382/LibChatMessage_1_2_0.zip", 120)
d3dshot_git = "git+https://github.com/fauskanger/D3DShot.git#egg=D3DShot"

View File

@ -65,7 +65,7 @@ class IEngine:
# noinspection PyBroadException
def _crash_safe(self):
logging.debug(f"starting {self.name} engine thread")
self.window = WindowClient(color=cv2.COLOR_RGB2GRAY, show_name=f"{self.name} debug")
self.window = WindowClient()
self.gui.bot_started(True)
try:
self.run()

View File

@ -0,0 +1,104 @@
import logging
import subprocess
from abc import ABC, abstractmethod
from functools import partial
from typing import Optional
import numpy as np
from numpy import ndarray
from fishy import constants
from fishy.helper.config import config
from fishy.osservices.os_services import os_services
class IScreenShot(ABC):
@abstractmethod
def setup(self) -> bool:
...
@abstractmethod
def grab(self) -> ndarray:
...
def get_monitor_id(monitors_iterator, get_top_left) -> Optional[int]:
monitor_rect = os_services.get_monitor_rect()
if monitor_rect is None:
logging.error("Game window not found")
return None
for i, m in enumerate(monitors_iterator):
top, left = get_top_left(m)
if top == monitor_rect[1] and left == monitor_rect[0]:
return i
return None
class MSS(IScreenShot):
def __init__(self):
from mss import mss
self.monitor_id = None
self.sct = mss()
def setup(self) -> bool:
self.monitor_id = get_monitor_id(self.sct.monitors, lambda m: (m["top"], m["left"]))
return self.monitor_id is not None
# noinspection PyTypeChecker
def grab(self) -> ndarray:
sct_img = self.sct.grab(self.sct.monitors[self.monitor_id])
return np.array(sct_img)
class PyAutoGUI(IScreenShot):
def __init__(self):
self.monitor_rect = None
def setup(self) -> bool:
from PIL import ImageGrab
ImageGrab.grab = partial(ImageGrab.grab, all_screens=True)
self.monitor_rect = os_services.get_monitor_rect()
return True
def grab(self) -> ndarray:
import pyautogui
image = pyautogui.screenshot()
img = np.array(image)
crop = self.monitor_rect
img = img[crop[1]:crop[3], crop[0]:crop[2]]
return img
class D3DShot(IScreenShot):
# noinspection PyPackageRequirements
def __init__(self):
try:
import d3dshot
except ImportError:
logging.info("Installing d3dshot please wait...")
subprocess.call(["python", "-m", "pip", "install", constants.d3dshot_git])
import d3dshot
self.d3 = d3dshot.create(capture_output="numpy")
def setup(self) -> bool:
monitor_id = get_monitor_id(self.d3.displays, lambda m: (m.position["top"], m.position["left"]))
if monitor_id is None:
return False
self.d3.display = self.d3.displays[monitor_id]
return True
def grab(self) -> ndarray:
return self.d3.screenshot()
LIBS = [MSS, PyAutoGUI, D3DShot]
def create() -> IScreenShot:
lib = LIBS[config.get("sslib", 0)]
logging.debug(f"Using {lib.__name__} screenshot lib")
return lib()

View File

@ -1,4 +1,5 @@
import logging
import uuid
from typing import List
import cv2
@ -7,26 +8,52 @@ import imutils
from fishy.engine.common import window_server
from fishy.engine.common.window_server import Status, WindowServer
from fishy.helper import helper
from fishy.helper.config import config
class WindowClient:
clients: List['WindowClient'] = []
def __init__(self, crop=None, color=None, scale=None, show_name=None):
def __init__(self):
"""
create a window instance with these pre process
:param crop: [x1,y1,x2,y2] array defining the boundaries to crop
:param color: color to use example cv2.COLOR_RGB2HSV
:param scale: scaling the window
"""
self.color = color
self.crop = crop
self.scale = scale
self.show_name = show_name
self.crop = None
self.scale = None
self.show_name = f"window client {len(WindowClient.clients)}"
if len(WindowClient.clients) == 0:
window_server.start()
WindowClient.clients.append(self)
if len(WindowClient.clients) > 0 and WindowServer.status != Status.RUNNING:
window_server.start()
@staticmethod
def running():
return WindowServer.status == Status.RUNNING
def processed_image(self, func=None):
"""
processes the image using the function provided
:param func: function to process image
:return: processed image
"""
if WindowServer.status == Status.CRASHED:
return None
img = self._get_capture()
if img is None:
return None
if func:
img = func(img)
if config.get("show_grab", 0):
self._show(img)
return img
def destroy(self):
if self in WindowClient.clients:
@ -34,11 +61,7 @@ class WindowClient:
if len(WindowClient.clients) == 0:
window_server.stop()
@staticmethod
def running():
return WindowServer.status == Status.RUNNING
def get_capture(self):
def _get_capture(self):
"""
copies the recorded screen and then pre processes its
:return: game window image
@ -56,8 +79,7 @@ class WindowClient:
if temp_img is None or temp_img.size == 0:
return None
if self.color is not None:
temp_img = cv2.cvtColor(temp_img, self.color)
temp_img = cv2.cvtColor(temp_img, cv2.COLOR_RGB2GRAY)
if self.crop is not None:
temp_img = temp_img[self.crop[1]:self.crop[3], self.crop[0]:self.crop[2]]
@ -71,49 +93,12 @@ class WindowClient:
return temp_img
def processed_image(self, func=None):
"""
processes the image using the function provided
:param func: function to process image
:return: processed image
"""
if WindowServer.status == Status.CRASHED:
return None
img = self.get_capture()
if img is None:
return None
if func is None:
return img
return func(img)
def show(self, to_show, resize=None, func=None):
# noinspection PyUnresolvedReferences
def _show(self, img):
"""
Displays the processed image for debugging purposes
:param to_show: false to destroy the window
:param resize: scale the image to make small images more visible
:param func: function to process the image
"""
if WindowServer.status == Status.CRASHED:
return
if not self.show_name:
logging.warning("You need to assign a name first")
return
if not to_show:
cv2.destroyWindow(self.show_name)
return
img = self.processed_image(func)
if img is None:
return
if resize is not None:
img = imutils.resize(img, width=resize)
cv2.imshow(self.show_name, img)
cv2.waitKey(25)
helper.save_img(self.show_name, img)

View File

@ -2,11 +2,13 @@ import logging
from enum import Enum
from threading import Thread
import cv2
import numpy as np
from mss import mss
from mss.base import MSSBase
from fishy.engine.common import screenshot
from fishy.helper import helper
from fishy.helper.config import config
from fishy.helper.helper import print_exc
from fishy.osservices.os_services import os_services
@ -24,10 +26,8 @@ class WindowServer:
Screen: np.ndarray = None
windowOffset = None
status = Status.STOPPED
sct: MSSBase = None
sslib = None
crop = None
monitor_id = -1
def init():
@ -35,30 +35,31 @@ def init():
Executed once before the main loop,
Finds the game window, and calculates the offset to remove the title bar
"""
WindowServer.sslib = screenshot.create()
WindowServer.status = Status.RUNNING
WindowServer.sct = mss()
WindowServer.crop = os_services.get_game_window_rect()
monitor_rect = os_services.get_monitor_rect()
if monitor_rect is None or WindowServer.crop is None:
logging.error("Game window not found")
if WindowServer.crop is None or not WindowServer.sslib.setup():
logging.error("Game window not found by window_server")
WindowServer.status = Status.CRASHED
return
for i, m in enumerate(WindowServer.sct.monitors):
if m["top"] == monitor_rect[0] and m["left"] == monitor_rect[1]:
WindowServer.monitor_id = i
def get_cropped_screenshot():
sct_img = WindowServer.sct.grab(WindowServer.sct.monitors[WindowServer.monitor_id])
# noinspection PyTypeChecker
ss = np.array(sct_img)
ss = WindowServer.sslib.grab()
if config.get("show_grab", 0):
helper.save_img("full screen", ss)
crop = WindowServer.crop
cropped_ss = ss[crop[1]:crop[3], crop[0]:crop[2]]
if cropped_ss.size == 0:
return None
if config.get("show_grab", 0):
helper.save_img("Game window", cropped_ss)
return cropped_ss

View File

@ -79,12 +79,6 @@ class FullAuto(IEngine):
logging.error("exception occurred while running full auto mode")
print_exc()
def start_show(self):
def func():
while self.start and WindowClient.running():
self.window.show(self.show_crop)
Thread(target=func).start()
def stop_on_inactive(self):
def func():
logging.debug("stop on inactive started")

View File

@ -20,26 +20,6 @@ kb = keyboard.Controller()
offset = 0
def get_crop_coords(window):
img = window.get_capture()
img = cv2.inRange(img, 0, 1)
cnt, h = cv2.findContours(img, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
"""
code from https://stackoverflow.com/a/45770227/4512396
"""
for i in range(len(cnt)):
area = cv2.contourArea(cnt[i])
if 5000 < area < 100000:
mask = np.zeros_like(img)
cv2.drawContours(mask, cnt, i, 255, -1)
x, y, w, h = cv2.boundingRect(cnt[i])
return x, y + offset, x + w, y + h - offset
return None
def _update_factor(key, value):
full_auto_factors = config.get("full_auto_factors", {})
full_auto_factors[key] = value

View File

@ -3,6 +3,7 @@ import time
import tkinter as tk
import tkinter.ttk as ttk
import typing
from functools import partial
from fishy.gui import update_dialog
from ttkthemes import ThemedTk
@ -11,6 +12,7 @@ from fishy import helper
from fishy.web import web
from ..constants import fishyqr
from ..engine.common import screenshot
from ..helper.config import config
from .discord_login import discord_login
from ..helper.hotkey.hotkey_process import hotkey
@ -62,6 +64,7 @@ def _create(gui: 'GUI'):
def update():
config.delete("dont_ask_update")
update_dialog.check_update(gui, True)
filemenu.add_command(label="Update", command=update)
def installer():
@ -71,6 +74,7 @@ def _create(gui: 'GUI'):
else:
helper.install_required_addons(True)
filemenu.entryconfigure(4, label="Remove FishyQR")
chaEntry = "Remove FishyQR" if helper.addon_exists(fishyqr[0]) else "Install FishyQR"
filemenu.add_command(label=chaEntry, command=installer)
menubar.add_cascade(label="Options", menu=filemenu)
@ -79,6 +83,26 @@ def _create(gui: 'GUI'):
debug_menu.add_command(label="Check QR Value",
command=lambda: gui.engine.check_qr_val())
def toggle_show_grab():
new_val = 1 - config.get("show_grab", 0)
show_grab_var.set(new_val)
config.set("show_grab", new_val)
show_grab_var = tk.IntVar()
show_grab_var.set(config.get("show_grab", 0))
debug_menu.add_checkbutton(label="Show Grab Window", variable=show_grab_var, command=lambda: toggle_show_grab(), onvalue=1)
def select_sslib(selected_i):
config.set("sslib", selected_i)
sslib_var.set(selected_i)
sslib = tk.Menu(debug_menu, tearoff=False)
sslib_var = tk.IntVar()
sslib_var.set(config.get("sslib", 0))
for i, lib in enumerate(screenshot.LIBS):
sslib.add_checkbutton(label=lib.__name__, variable=sslib_var,
command=partial(select_sslib, i), onvalue=i)
debug_menu.add_cascade(label="Screenshot Lib", menu=sslib)
debug_var = tk.IntVar()
debug_var.set(int(config.get('debug', False)))
@ -90,7 +114,8 @@ def _create(gui: 'GUI'):
menubar.add_cascade(label="Debug", menu=debug_menu)
help_menu = tk.Menu(menubar, tearoff=0)
help_menu.add_command(label="Need Help?", command=lambda: helper.open_web("https://github.com/fishyboteso/fishyboteso/wiki"))
help_menu.add_command(label="Need Help?",
command=lambda: helper.open_web("https://github.com/fishyboteso/fishyboteso/wiki"))
help_menu.add_command(label="Donate", command=lambda: helper.open_web("https://paypal.me/AdamSaudagar"))
menubar.add_cascade(label="Help", menu=help_menu)

View File

@ -1,8 +1,6 @@
"""
no imports from fishy itself here, or anything which depends on fishy
"""
def singleton_proxy(instance_name):
def decorator(root_cls):
if not hasattr(root_cls, instance_name):

View File

@ -7,12 +7,14 @@ import threading
import time
import traceback
import webbrowser
from datetime import datetime
from hashlib import md5
from io import BytesIO
from threading import Thread
from uuid import uuid1
from zipfile import ZipFile
import cv2
import requests
from playsound import playsound
@ -205,3 +207,14 @@ def kill_thread(thread):
def print_exc():
logging.error(traceback.format_exc())
traceback.print_exc()
def save_img(show_name, img):
img_path = os.path.join(os_services.get_documents_path(), "fishy_debug", "imgs", show_name)
if not os.path.exists(img_path):
os.makedirs(img_path)
t = time.strftime("%Y.%m.%d.%H.%M.%S")
cv2.imwrite(
os.path.join(img_path, f"{t}.png"),
img)

View File

@ -14,4 +14,5 @@ keyboard
playsound
event-scheduler
mouse
pyautogui
mss