mirror of
https://github.com/LmeSzinc/StarRailCopilot.git
synced 2024-11-16 06:25:24 +00:00
Opt: [ALAS] Reuse image array to improve memory performance
This commit is contained in:
parent
a553d4d7eb
commit
1298605c65
@ -128,7 +128,7 @@ class Adb(Connection):
|
||||
if image is None:
|
||||
raise ImageTruncated('Empty image after cv2.imdecode')
|
||||
|
||||
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
|
||||
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB, dst=image)
|
||||
if image is None:
|
||||
raise ImageTruncated('Empty image after cv2.cvtColor')
|
||||
|
||||
|
@ -165,11 +165,11 @@ class AScreenCap(Connection):
|
||||
# ValueError: cannot reshape array of size 0 into shape (720,1280,4)
|
||||
raise ImageTruncated(str(e))
|
||||
|
||||
image = cv2.flip(image, 0)
|
||||
cv2.flip(image, 0, dst=image)
|
||||
if image is None:
|
||||
raise ImageTruncated('Empty image after cv2.flip')
|
||||
|
||||
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
|
||||
cv2.cvtColor(image, cv2.COLOR_BGR2RGB, dst=image)
|
||||
if image is None:
|
||||
raise ImageTruncated('Empty image after cv2.cvtColor')
|
||||
|
||||
|
@ -6,11 +6,11 @@ import numpy as np
|
||||
import requests
|
||||
from adbutils.errors import AdbError
|
||||
|
||||
from module.base.decorator import Config, cached_property, del_cached_property
|
||||
from module.base.decorator import cached_property, del_cached_property
|
||||
from module.base.timer import Timer
|
||||
from module.device.method.uiautomator_2 import Uiautomator2, ProcessInfo
|
||||
from module.device.method.utils import (retry_sleep, RETRY_TRIES, handle_adb_error,
|
||||
ImageTruncated, PackageNotInstalled)
|
||||
from module.device.method.uiautomator_2 import ProcessInfo, Uiautomator2
|
||||
from module.device.method.utils import (
|
||||
ImageTruncated, PackageNotInstalled, RETRY_TRIES, handle_adb_error, retry_sleep)
|
||||
from module.exception import RequestHumanTakeover
|
||||
from module.logger import logger
|
||||
|
||||
@ -91,7 +91,7 @@ def retry(func):
|
||||
class DroidCast(Uiautomator2):
|
||||
"""
|
||||
DroidCast, another screenshot method, https://github.com/rayworks/DroidCast
|
||||
DroidCast_raw, a modified version of DroidCast sending raw bitmap https://github.com/Torther/DroidCastS
|
||||
DroidCast_raw, a modified version of DroidCast sending raw bitmap and png, https://github.com/Torther/DroidCastS
|
||||
"""
|
||||
|
||||
_droidcast_port: int = 0
|
||||
@ -103,92 +103,73 @@ class DroidCast(Uiautomator2):
|
||||
self._droidcast_port = self.adb_forward('tcp:53516')
|
||||
return session
|
||||
|
||||
def droidcast_url(self, url='/screenshot?format=png'):
|
||||
"""
|
||||
Check APIs from source code:
|
||||
https://github.com/rayworks/DroidCast/blob/master/app/src/main/java/com/rayworks/droidcast/Main.java
|
||||
|
||||
Available APIs:
|
||||
- /screenshot
|
||||
To get JPG screenshots.
|
||||
- /screenshot?format=png
|
||||
To get PNG screenshots.
|
||||
- /screenshot?format=webp
|
||||
To get WEBP screenshots.
|
||||
- /src
|
||||
Websocket to get JPG screenshots.
|
||||
|
||||
Note that /screenshot?format=jpg is unavailable.
|
||||
"""
|
||||
"""
|
||||
Check APIs from source code:
|
||||
https://github.com/Torther/DroidCast_raw/blob/DroidCast_raw/app/src/main/java/ink/mol/droidcast_raw/KtMain.kt
|
||||
Available APIs:
|
||||
- /screenshot
|
||||
To get a RGB565 bitmap
|
||||
- /preview
|
||||
To get PNG screenshots.
|
||||
"""
|
||||
def droidcast_url(self, url='/preview'):
|
||||
return f'http://127.0.0.1:{self._droidcast_port}{url}'
|
||||
|
||||
def droidcast_raw_url(self, url='/screenshot'):
|
||||
return f'http://127.0.0.1:{self._droidcast_port}{url}'
|
||||
|
||||
@Config.when(DROIDCAST_VERSION='DroidCast')
|
||||
def droidcast_init(self):
|
||||
logger.hr('Droidcast init')
|
||||
logger.hr('DroidCast init')
|
||||
self.droidcast_stop()
|
||||
|
||||
logger.info('Pushing DroidCast apk')
|
||||
self.adb_push(self.config.DROIDCAST_FILEPATH_LOCAL, self.config.DROIDCAST_FILEPATH_REMOTE)
|
||||
|
||||
logger.info('Starting DroidCast apk')
|
||||
# CLASSPATH=/data/local/tmp/DroidCast.apk app_process / com.rayworks.droidcast.Main > /dev/null
|
||||
# DroidCast_raw-release-1.0.apk
|
||||
# CLASSPATH=/data/local/tmp/DroidCast_raw.apk app_process / ink.mol.droidcast_raw.Main > /dev/null
|
||||
# adb shell CLASSPATH=/data/local/tmp/DroidCast_raw.apk app_process / ink.mol.droidcast_raw.Main
|
||||
resp = self.u2_shell_background([
|
||||
'CLASSPATH=/data/local/tmp/DroidCast.apk',
|
||||
'CLASSPATH=/data/local/tmp/DroidCast_raw.apk',
|
||||
'app_process',
|
||||
'/',
|
||||
'com.rayworks.droidcast.Main',
|
||||
'ink.mol.droidcast_raw.Main',
|
||||
'>',
|
||||
'/dev/null'
|
||||
])
|
||||
logger.info(resp)
|
||||
|
||||
del_cached_property(self, 'droidcast_session')
|
||||
_ = self.droidcast_session
|
||||
logger.attr('DroidCast', self.droidcast_url())
|
||||
self.droidcast_wait_startup()
|
||||
|
||||
@Config.when(DROIDCAST_VERSION='DroidCast_raw')
|
||||
def droidcast_init(self):
|
||||
logger.hr('Droidcast init')
|
||||
self.resolution_check_uiautomator2()
|
||||
self.droidcast_stop()
|
||||
|
||||
logger.info('Pushing DroidCast apk')
|
||||
self.adb_push(self.config.DROIDCAST_RAW_FILEPATH_LOCAL, self.config.DROIDCAST_RAW_FILEPATH_REMOTE)
|
||||
|
||||
logger.info('Starting DroidCast apk')
|
||||
# DroidCastS-release-1.1.5.apk
|
||||
# CLASSPATH=/data/local/tmp/DroidCastS-release-1.1.5.apk app_process / com.torther.droidcasts.Main > /dev/null
|
||||
resp = self.u2_shell_background([
|
||||
'CLASSPATH=/data/local/tmp/DroidCastS.apk',
|
||||
'app_process',
|
||||
'/',
|
||||
'com.torther.droidcasts.Main',
|
||||
'>',
|
||||
'/dev/null'
|
||||
])
|
||||
logger.info(resp)
|
||||
|
||||
del_cached_property(self, 'droidcast_session')
|
||||
_ = self.droidcast_session
|
||||
logger.attr('DroidCast', self.droidcast_url())
|
||||
self.droidcast_wait_startup()
|
||||
if self.config.DROIDCAST_VERSION == 'DroidCast':
|
||||
logger.attr('DroidCast', self.droidcast_url())
|
||||
self.droidcast_wait_startup()
|
||||
elif self.config.DROIDCAST_VERSION == 'DroidCast_raw':
|
||||
logger.attr('DroidCast_raw', self.droidcast_raw_url())
|
||||
self.droidcast_wait_startup()
|
||||
else:
|
||||
logger.error(f'Unknown DROIDCAST_VERSION: {self.config.DROIDCAST_VERSION}')
|
||||
|
||||
@retry
|
||||
def screenshot_droidcast(self):
|
||||
self.config.DROIDCAST_VERSION = 'DroidCast'
|
||||
image = self.droidcast_session.get(self.droidcast_url(), timeout=3).content
|
||||
resp = self.droidcast_session.get(self.droidcast_url(), timeout=3)
|
||||
if resp.status_code == 404:
|
||||
raise DroidCastVersionIncompatible('DroidCast server does not have /preview')
|
||||
image = resp.content
|
||||
image = np.frombuffer(image, np.uint8)
|
||||
if image is None:
|
||||
raise ImageTruncated('Empty image after reading from buffer')
|
||||
if image.shape == (1843200,):
|
||||
raise DroidCastVersionIncompatible('Requesting screenshots from `DroidCast` but server is `DroidCast_raw`')
|
||||
if image.size < 500:
|
||||
logger.warning(f'Unexpected screenshot: {resp.content}')
|
||||
|
||||
image = cv2.imdecode(image, cv2.IMREAD_COLOR)
|
||||
if image is None:
|
||||
raise ImageTruncated('Empty image after cv2.imdecode')
|
||||
|
||||
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
|
||||
cv2.cvtColor(image, cv2.COLOR_BGR2RGB, dst=image)
|
||||
if image is None:
|
||||
raise ImageTruncated('Empty image after cv2.cvtColor')
|
||||
|
||||
@ -197,12 +178,14 @@ class DroidCast(Uiautomator2):
|
||||
@retry
|
||||
def screenshot_droidcast_raw(self):
|
||||
self.config.DROIDCAST_VERSION = 'DroidCast_raw'
|
||||
image = self.droidcast_session.get(self.droidcast_url(), timeout=3).content
|
||||
image = self.droidcast_session.get(self.droidcast_raw_url(), timeout=3).content
|
||||
# DroidCast_raw returns a RGB565 bitmap
|
||||
|
||||
try:
|
||||
arr = np.frombuffer(image, dtype=np.uint16).reshape((720, 1280))
|
||||
except ValueError as e:
|
||||
if len(image) < 500:
|
||||
logger.warning(f'Unexpected screenshot: {image}')
|
||||
# Try to load as `DroidCast`
|
||||
image = np.frombuffer(image, np.uint8)
|
||||
if image is not None:
|
||||
@ -228,14 +211,25 @@ class DroidCast(Uiautomator2):
|
||||
# image = cv2.merge([r, g, b])
|
||||
|
||||
# The same as the code above but costs about 5ms instead of 10ms.
|
||||
r = cv2.multiply(arr & 0b1111100000000000, 0.00390625).astype(np.uint8)
|
||||
g = cv2.multiply(arr & 0b0000011111100000, 0.125).astype(np.uint8)
|
||||
b = cv2.multiply(arr & 0b0000000000011111, 8).astype(np.uint8)
|
||||
r = cv2.add(r, cv2.multiply(r, 0.03125))
|
||||
g = cv2.add(g, cv2.multiply(g, 0.015625))
|
||||
b = cv2.add(b, cv2.multiply(b, 0.03125))
|
||||
image = cv2.merge([r, g, b])
|
||||
r = cv2.bitwise_and(arr, 0b1111100000000000)
|
||||
cv2.multiply(r, 0.00390625, dst=r)
|
||||
r = np.uint8(r)
|
||||
m = cv2.multiply(r, 0.03125)
|
||||
cv2.add(r, m, dst=r)
|
||||
|
||||
g = cv2.bitwise_and(arr, 0b0000011111100000)
|
||||
cv2.multiply(g, 0.125, dst=g)
|
||||
g = np.uint8(g)
|
||||
m = cv2.multiply(g, 0.015625)
|
||||
cv2.add(g, m, dst=g)
|
||||
|
||||
b = cv2.bitwise_and(arr, 0b0000000000011111)
|
||||
cv2.multiply(b, 8, dst=b)
|
||||
b = np.uint8(b)
|
||||
m = cv2.multiply(b, 0.03125)
|
||||
cv2.add(b, m, dst=b)
|
||||
|
||||
image = cv2.merge([r, g, b])
|
||||
return image
|
||||
|
||||
def droidcast_wait_startup(self):
|
||||
@ -262,13 +256,12 @@ class DroidCast(Uiautomator2):
|
||||
|
||||
def droidcast_uninstall(self):
|
||||
"""
|
||||
Stop all DroidCast processes and remove DroidCast APK.
|
||||
DroidCast has't been installed but a JAVA class call, uninstall is a file delete.
|
||||
Stop DroidCast processes and remove DroidCast APK.
|
||||
DroidCast hasn't been installed but a JAVA class call, uninstall is a file delete.
|
||||
"""
|
||||
self.droidcast_stop()
|
||||
logger.info('Removing DroidCast')
|
||||
self.adb_shell(["rm", self.config.DROIDCAST_FILEPATH_REMOTE])
|
||||
self.adb_shell(["rm", self.config.DROIDCAST_RAW_FILEPATH_REMOTE])
|
||||
|
||||
def _iter_droidcast_proc(self) -> t.Iterable[ProcessInfo]:
|
||||
"""
|
||||
@ -280,10 +273,12 @@ class DroidCast(Uiautomator2):
|
||||
yield proc
|
||||
if 'com.torther.droidcasts.Main' in proc.cmdline:
|
||||
yield proc
|
||||
if 'ink.mol.droidcast_raw.Main' in proc.cmdline:
|
||||
yield proc
|
||||
|
||||
def droidcast_stop(self):
|
||||
"""
|
||||
Stop all DroidCast processes.
|
||||
Stop DroidCast processes.
|
||||
"""
|
||||
logger.info('Stopping DroidCast')
|
||||
for proc in self._iter_droidcast_proc():
|
||||
|
@ -122,7 +122,7 @@ class Uiautomator2(Connection):
|
||||
if image is None:
|
||||
raise ImageTruncated('Empty image after cv2.imdecode')
|
||||
|
||||
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
|
||||
cv2.cvtColor(image, cv2.COLOR_BGR2RGB, dst=image)
|
||||
if image is None:
|
||||
raise ImageTruncated('Empty image after cv2.cvtColor')
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user