mirror of
https://github.com/bdnugget/runekit.git
synced 2026-11-07 09:07:30 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6edf87ba7b |
No files matched your search
@@ -14,6 +14,22 @@ The `game` package is the only one allowed to use platform-dependent code. All o
|
||||
- game: Interact with the game window. Since accessing other application's window is not abstracted by Qt, this package is the only one allowed to be platform dependent
|
||||
- image: Image processing tools
|
||||
|
||||
### Image format
|
||||
The library has two internal image formats:
|
||||
|
||||
- numpy.ndarray of shape (height, width, 4)
|
||||
- Current standard
|
||||
- Store pixels in BGRA32 format (ARGB 8 bit per channel in little endian)
|
||||
- Should be faster as no channel swapping is needed
|
||||
- Requires copying when accessing raw buffer
|
||||
- Support OpenCV operation
|
||||
- PIL Image
|
||||
- Deprecated
|
||||
- Store pixels in RGBA32 format
|
||||
- Slower as when communicating with Alt1 we need to swap to BGRA
|
||||
- Can access raw buffer without copying
|
||||
- Easier to work with for basic operations
|
||||
|
||||
## App
|
||||
|
||||
Packages related to Alt1 app.
|
||||
|
||||
Generated
+150
-1
@@ -79,6 +79,14 @@ category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
|
||||
|
||||
[[package]]
|
||||
name = "future"
|
||||
version = "0.18.2"
|
||||
description = "Clean single-source support for Python 3 and 2"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "2.10"
|
||||
@@ -106,6 +114,36 @@ category = "dev"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[[package]]
|
||||
name = "numpy"
|
||||
version = "1.20.1"
|
||||
description = "NumPy is the fundamental package for array computing with Python."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[[package]]
|
||||
name = "opencv-python"
|
||||
version = "4.5.1.48"
|
||||
description = "Wrapper package for OpenCV python bindings."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[package.dependencies]
|
||||
numpy = ">=1.19.3"
|
||||
|
||||
[[package]]
|
||||
name = "opencv-python-headless"
|
||||
version = "4.5.1.48"
|
||||
description = "Wrapper package for OpenCV python bindings."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[package.dependencies]
|
||||
numpy = ">=1.19.3"
|
||||
|
||||
[[package]]
|
||||
name = "pathspec"
|
||||
version = "0.8.1"
|
||||
@@ -114,6 +152,17 @@ category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||
|
||||
[[package]]
|
||||
name = "pefile"
|
||||
version = "2019.4.18"
|
||||
description = "Python PE parsing module"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[package.dependencies]
|
||||
future = "*"
|
||||
|
||||
[[package]]
|
||||
name = "pillow"
|
||||
version = "8.1.0"
|
||||
@@ -152,7 +201,9 @@ python-versions = ">=3.5"
|
||||
[package.dependencies]
|
||||
altgraph = "*"
|
||||
macholib = {version = ">=1.8", markers = "sys_platform == \"darwin\""}
|
||||
pefile = {version = ">=2017.8.1", markers = "sys_platform == \"win32\""}
|
||||
pyinstaller-hooks-contrib = ">=2020.6"
|
||||
pywin32-ctypes = {version = ">=0.2.0", markers = "sys_platform == \"win32\""}
|
||||
|
||||
[package.extras]
|
||||
encryption = ["tinyaes (>=1.0.0)"]
|
||||
@@ -234,6 +285,14 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <3.10"
|
||||
[package.dependencies]
|
||||
shiboken2 = "5.15.2"
|
||||
|
||||
[[package]]
|
||||
name = "pywin32-ctypes"
|
||||
version = "0.2.0"
|
||||
description = ""
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "2020.11.13"
|
||||
@@ -336,7 +395,7 @@ six = "*"
|
||||
[metadata]
|
||||
lock-version = "1.1"
|
||||
python-versions = "^3.9,<3.10"
|
||||
content-hash = "e03d69fddabf9d01440a8ce93b9da739a4a0a664c3888c12f0970ce956338a05"
|
||||
content-hash = "077116eae8dbd00c752035808cd49c268cc8124c705c841a2da69b68d577adee"
|
||||
|
||||
[metadata.files]
|
||||
altgraph = [
|
||||
@@ -437,6 +496,9 @@ cython = [
|
||||
{file = "Cython-0.29.21-py2.py3-none-any.whl", hash = "sha256:5c4276fdcbccdf1e3c1756c7aeb8395e9a36874fa4d30860e7694f43d325ae13"},
|
||||
{file = "Cython-0.29.21.tar.gz", hash = "sha256:e57acb89bd55943c8d8bf813763d20b9099cc7165c0f16b707631a7654be9cad"},
|
||||
]
|
||||
future = [
|
||||
{file = "future-0.18.2.tar.gz", hash = "sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d"},
|
||||
]
|
||||
idna = [
|
||||
{file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"},
|
||||
{file = "idna-2.10.tar.gz", hash = "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6"},
|
||||
@@ -449,10 +511,93 @@ mypy-extensions = [
|
||||
{file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"},
|
||||
{file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"},
|
||||
]
|
||||
numpy = [
|
||||
{file = "numpy-1.20.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:ae61f02b84a0211abb56462a3b6cd1e7ec39d466d3160eb4e1da8bf6717cdbeb"},
|
||||
{file = "numpy-1.20.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:65410c7f4398a0047eea5cca9b74009ea61178efd78d1be9847fac1d6716ec1e"},
|
||||
{file = "numpy-1.20.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:2d7e27442599104ee08f4faed56bb87c55f8b10a5494ac2ead5c98a4b289e61f"},
|
||||
{file = "numpy-1.20.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:4ed8e96dc146e12c1c5cdd6fb9fd0757f2ba66048bf94c5126b7efebd12d0090"},
|
||||
{file = "numpy-1.20.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:ecb5b74c702358cdc21268ff4c37f7466357871f53a30e6f84c686952bef16a9"},
|
||||
{file = "numpy-1.20.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:b9410c0b6fed4a22554f072a86c361e417f0258838957b78bd063bde2c7f841f"},
|
||||
{file = "numpy-1.20.1-cp37-cp37m-win32.whl", hash = "sha256:3d3087e24e354c18fb35c454026af3ed8997cfd4997765266897c68d724e4845"},
|
||||
{file = "numpy-1.20.1-cp37-cp37m-win_amd64.whl", hash = "sha256:89f937b13b8dd17b0099c7c2e22066883c86ca1575a975f754babc8fbf8d69a9"},
|
||||
{file = "numpy-1.20.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a1d7995d1023335e67fb070b2fae6f5968f5be3802b15ad6d79d81ecaa014fe0"},
|
||||
{file = "numpy-1.20.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:60759ab15c94dd0e1ed88241fd4fa3312db4e91d2c8f5a2d4cf3863fad83d65b"},
|
||||
{file = "numpy-1.20.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:125a0e10ddd99a874fd357bfa1b636cd58deb78ba4a30b5ddb09f645c3512e04"},
|
||||
{file = "numpy-1.20.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:c26287dfc888cf1e65181f39ea75e11f42ffc4f4529e5bd19add57ad458996e2"},
|
||||
{file = "numpy-1.20.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:7199109fa46277be503393be9250b983f325880766f847885607d9b13848f257"},
|
||||
{file = "numpy-1.20.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:72251e43ac426ff98ea802a931922c79b8d7596480300eb9f1b1e45e0543571e"},
|
||||
{file = "numpy-1.20.1-cp38-cp38-win32.whl", hash = "sha256:c91ec9569facd4757ade0888371eced2ecf49e7982ce5634cc2cf4e7331a4b14"},
|
||||
{file = "numpy-1.20.1-cp38-cp38-win_amd64.whl", hash = "sha256:13adf545732bb23a796914fe5f891a12bd74cf3d2986eed7b7eba2941eea1590"},
|
||||
{file = "numpy-1.20.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:104f5e90b143dbf298361a99ac1af4cf59131218a045ebf4ee5990b83cff5fab"},
|
||||
{file = "numpy-1.20.1-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:89e5336f2bec0c726ac7e7cdae181b325a9c0ee24e604704ed830d241c5e47ff"},
|
||||
{file = "numpy-1.20.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:032be656d89bbf786d743fee11d01ef318b0781281241997558fa7950028dd29"},
|
||||
{file = "numpy-1.20.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:66b467adfcf628f66ea4ac6430ded0614f5cc06ba530d09571ea404789064adc"},
|
||||
{file = "numpy-1.20.1-cp39-cp39-win32.whl", hash = "sha256:12e4ba5c6420917571f1a5becc9338abbde71dd811ce40b37ba62dec7b39af6d"},
|
||||
{file = "numpy-1.20.1-cp39-cp39-win_amd64.whl", hash = "sha256:9c94cab5054bad82a70b2e77741271790304651d584e2cdfe2041488e753863b"},
|
||||
{file = "numpy-1.20.1-pp37-pypy37_pp73-manylinux2010_x86_64.whl", hash = "sha256:9eb551d122fadca7774b97db8a112b77231dcccda8e91a5bc99e79890797175e"},
|
||||
{file = "numpy-1.20.1.zip", hash = "sha256:3bc63486a870294683980d76ec1e3efc786295ae00128f9ea38e2c6e74d5a60a"},
|
||||
]
|
||||
opencv-python = [
|
||||
{file = "opencv-python-4.5.1.48.tar.gz", hash = "sha256:78a6db8467639383caedf1d111da3510a4ee1a0aacf2117821cae2ee8f92ce37"},
|
||||
{file = "opencv_python-4.5.1.48-cp36-cp36m-macosx_10_13_x86_64.whl", hash = "sha256:bcb27773cfd5340b2b599b303d9f5499838ef4780c20c038f6030175408c64df"},
|
||||
{file = "opencv_python-4.5.1.48-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:9646875c501788b1b098f282d777b667d6da69801739504f1b2fd1268970d1da"},
|
||||
{file = "opencv_python-4.5.1.48-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:ebe83901971a6755512424c4fe9f63341cca501b7c497bf608dd38ee31ba3f4c"},
|
||||
{file = "opencv_python-4.5.1.48-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:d8aefcb30b71064dbbaa2b0ace161a36464c29375a83998fbda39a1d1740f942"},
|
||||
{file = "opencv_python-4.5.1.48-cp36-cp36m-win32.whl", hash = "sha256:32dee1c9fd3e31e28edef7b56f868e2b40e280b7062304f9fb8a14dbc51547d5"},
|
||||
{file = "opencv_python-4.5.1.48-cp36-cp36m-win_amd64.whl", hash = "sha256:9c77d508e6822f1f40c727d21b822d017622d8305dce7eccf0ab06caac16d5c6"},
|
||||
{file = "opencv_python-4.5.1.48-cp37-cp37m-macosx_10_13_x86_64.whl", hash = "sha256:4982fa8ccc38310a2bd93e06334ba090b12b6aff2f6fcb8ff9613e3c9bc48f48"},
|
||||
{file = "opencv_python-4.5.1.48-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:c0503bfaa2b7b743d6ff5d81f1dd8428dbf4c33e7e4f836456d11be20c2e7721"},
|
||||
{file = "opencv_python-4.5.1.48-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:e27d062fa1098d90f48b6c047351c89816492a08906a021c973ce510b04a7b9d"},
|
||||
{file = "opencv_python-4.5.1.48-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:6d8434a45e8f75c4da5fd0068ce001f4f8e35771cc851d746d4721eeaf517e25"},
|
||||
{file = "opencv_python-4.5.1.48-cp37-cp37m-win32.whl", hash = "sha256:e2c17714da59d9d516ceef0450766ff9557ee232d62f702665af905193557582"},
|
||||
{file = "opencv_python-4.5.1.48-cp37-cp37m-win_amd64.whl", hash = "sha256:efac9893d9e21cfb599828801c755ecde8f1e657f05ec6f002efe19422456d5a"},
|
||||
{file = "opencv_python-4.5.1.48-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:e77d0feaff37326f62b127098264e2a7099deb476e38432b1083ce11cdedf560"},
|
||||
{file = "opencv_python-4.5.1.48-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:ffc75c614b8dc3d8102f3ba15dafd6ec0400c7ffa71a91953d41511964ee50e0"},
|
||||
{file = "opencv_python-4.5.1.48-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:c1159d91f29a85c3333edef6ca420284566d9bcdae46dda2fe7282515b48c8b6"},
|
||||
{file = "opencv_python-4.5.1.48-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:d16144c435b816c5536d5ff012c1a2b7e93155017db7103942ff7efb98c4df1f"},
|
||||
{file = "opencv_python-4.5.1.48-cp38-cp38-win32.whl", hash = "sha256:b2b9ac86aec5f2dd531545cebdea1a1ef4f81ef1fb1760d78b4725f9575504f9"},
|
||||
{file = "opencv_python-4.5.1.48-cp38-cp38-win_amd64.whl", hash = "sha256:30edebc81b260bcfeb760b3600c367c5261dfb2fe41e5d1408d5357d0867b40d"},
|
||||
{file = "opencv_python-4.5.1.48-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:e38fbd7b2db03204ec09930609b7313d6b6d2b271c8fe2c0aa271fa69b726a1b"},
|
||||
{file = "opencv_python-4.5.1.48-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:fc1472b825d26c8a4f1cfb172a90c3cc47733e4af7522276c1c2efe8f6006a8b"},
|
||||
{file = "opencv_python-4.5.1.48-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:c4ea4f8b217f3e8be6247fc0787fb81797d85202c722523f41070124a7a621c7"},
|
||||
{file = "opencv_python-4.5.1.48-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:a1dfa0486db367594510c0c799ec7481247dc86e651b69008806d875ab731471"},
|
||||
{file = "opencv_python-4.5.1.48-cp39-cp39-win32.whl", hash = "sha256:5172cb37dfd8a0b4945b071a493eb36e5f17675a160637fa380f9c1d9d80535c"},
|
||||
{file = "opencv_python-4.5.1.48-cp39-cp39-win_amd64.whl", hash = "sha256:c8cc1f5ff3c352ebe756119014c4e4ec7ae5ac536d1f66b0316667ced37637c8"},
|
||||
]
|
||||
opencv-python-headless = [
|
||||
{file = "opencv-python-headless-4.5.1.48.tar.gz", hash = "sha256:d16825755e7b5a6d8737f93e116670229e1510199e0af9213004e187ae0dbcc5"},
|
||||
{file = "opencv_python_headless-4.5.1.48-cp36-cp36m-macosx_10_13_x86_64.whl", hash = "sha256:f2011ecb3980bbed283d17d43e0f1221bac88c0cac1a6fb59a056544de2df2f7"},
|
||||
{file = "opencv_python_headless-4.5.1.48-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:777fb596e04331f73ef5b0c1faa4d33348f29ca58216d9286355c16f5489c939"},
|
||||
{file = "opencv_python_headless-4.5.1.48-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:924aa4c34ead0b817309f42291dc526b2a7755476afe3009d1e275fc3090d92d"},
|
||||
{file = "opencv_python_headless-4.5.1.48-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:0e02809db2968e54f3c23340be6ff9a1428b3f03f5dca7cd5aceda66e319ce86"},
|
||||
{file = "opencv_python_headless-4.5.1.48-cp36-cp36m-win32.whl", hash = "sha256:522f12dd994e064a30562adfd63b9439099bd7c80819f5261c37ebe593283c9e"},
|
||||
{file = "opencv_python_headless-4.5.1.48-cp36-cp36m-win_amd64.whl", hash = "sha256:e3027e0d1b71b68b5cfe1ea9c627e323dff71112c854ba19805258d8fc6c630e"},
|
||||
{file = "opencv_python_headless-4.5.1.48-cp37-cp37m-macosx_10_13_x86_64.whl", hash = "sha256:f5e40a06116460ef2fd2d1c24be3b65f8bfb5fcdfe433f3fc01bcb4c2eb485bf"},
|
||||
{file = "opencv_python_headless-4.5.1.48-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:ba2f0bd46e9534f29969e39f7895cbea9764173102b0c04b7818b8a9910d66e4"},
|
||||
{file = "opencv_python_headless-4.5.1.48-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:f7f8e4f7c63c8e95eb210f4cba88d0069a0a964d6335d7a35b07f0d0baa13558"},
|
||||
{file = "opencv_python_headless-4.5.1.48-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:aa562c520f46283423ba8fac29099458e42deab697a9abd0491622e421c5c454"},
|
||||
{file = "opencv_python_headless-4.5.1.48-cp37-cp37m-win32.whl", hash = "sha256:657cdd9fbbbbb7e898ae3d9b0649b367d0e440d429c714104d069c5612d578bb"},
|
||||
{file = "opencv_python_headless-4.5.1.48-cp37-cp37m-win_amd64.whl", hash = "sha256:fe02a943b1a28b505e954fbce24e867119a3eb4351f93adad55c6cfe81a70484"},
|
||||
{file = "opencv_python_headless-4.5.1.48-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:526b9e19cf6300f0891d8f427eac1048091912332bb781eb4957a4994bfbe608"},
|
||||
{file = "opencv_python_headless-4.5.1.48-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:5f0c7d34fa9953706c2a1a6d2760a91dc5a68cab3df16af61609894c6c8586f1"},
|
||||
{file = "opencv_python_headless-4.5.1.48-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:1f7c14f5d4e5af4dc4669fc6b4a983b36072a934c439ac11266b930496da8255"},
|
||||
{file = "opencv_python_headless-4.5.1.48-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:7c75680ffdf32d7044415a215d1fc60dbec14a7f2f0b59a85f0f74ef5efcb6ad"},
|
||||
{file = "opencv_python_headless-4.5.1.48-cp38-cp38-win32.whl", hash = "sha256:2560dcf3c1158226b066302f777bfe0f65282410b8d90871dd872306c967d1f1"},
|
||||
{file = "opencv_python_headless-4.5.1.48-cp38-cp38-win_amd64.whl", hash = "sha256:9aa04a491c534531029fbac61da961fae0bf4abb1786eed9c91befde4ca7bd81"},
|
||||
{file = "opencv_python_headless-4.5.1.48-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:96d1da6ad061d8f3509668d398d14dd8265e529d6407f87eb26e7f4ddf043cc0"},
|
||||
{file = "opencv_python_headless-4.5.1.48-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:243aea91cc1e36a47c46da4cc408071af39444a48df1fe1539ea8d7990500fd2"},
|
||||
{file = "opencv_python_headless-4.5.1.48-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:2e6a9a88617a0ef7219cff24ba78a58416670a77e6ac63975f9009af3319ab63"},
|
||||
{file = "opencv_python_headless-4.5.1.48-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:372149d007e20bf556b7687591d22b58b56b3c225f492da051d81587e5dc7411"},
|
||||
{file = "opencv_python_headless-4.5.1.48-cp39-cp39-win32.whl", hash = "sha256:65c9ea57be5ebcaed009b3fee14fe59f3b6aa1e573fe08c5039ff057ad593c53"},
|
||||
{file = "opencv_python_headless-4.5.1.48-cp39-cp39-win_amd64.whl", hash = "sha256:a322d4df14c3a5f19701a023b3da91e9b8af8653b0d2ee0c50b0b341212f7343"},
|
||||
]
|
||||
pathspec = [
|
||||
{file = "pathspec-0.8.1-py2.py3-none-any.whl", hash = "sha256:aa0cb481c4041bf52ffa7b0d8fa6cd3e88a2ca4879c533c9153882ee2556790d"},
|
||||
{file = "pathspec-0.8.1.tar.gz", hash = "sha256:86379d6b86d75816baba717e64b1a3a3469deb93bb76d613c9ce79edc5cb68fd"},
|
||||
]
|
||||
pefile = [
|
||||
{file = "pefile-2019.4.18.tar.gz", hash = "sha256:a5d6e8305c6b210849b47a6174ddf9c452b2888340b8177874b862ba6c207645"},
|
||||
]
|
||||
pillow = [
|
||||
{file = "Pillow-8.1.0-cp36-cp36m-macosx_10_10_x86_64.whl", hash = "sha256:d355502dce85ade85a2511b40b4c61a128902f246504f7de29bbeec1ae27933a"},
|
||||
{file = "Pillow-8.1.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:93a473b53cc6e0b3ce6bf51b1b95b7b1e7e6084be3a07e40f79b42e83503fbf2"},
|
||||
@@ -572,6 +717,10 @@ pyside2 = [
|
||||
{file = "PySide2-5.15.2-5.15.2-cp35.cp36.cp37.cp38.cp39-none-win32.whl", hash = "sha256:087a0b719bb967405ea85fd202757c761f1fc73d0e2397bc3a6a15376782ee75"},
|
||||
{file = "PySide2-5.15.2-5.15.2-cp35.cp36.cp37.cp38.cp39-none-win_amd64.whl", hash = "sha256:1316aa22dd330df096daf7b0defe9c00297a66e0b4907f057aaa3e88c53d1aff"},
|
||||
]
|
||||
pywin32-ctypes = [
|
||||
{file = "pywin32-ctypes-0.2.0.tar.gz", hash = "sha256:24ffc3b341d457d48e8922352130cf2644024a4ff09762a2261fd34c36ee5942"},
|
||||
{file = "pywin32_ctypes-0.2.0-py2.py3-none-any.whl", hash = "sha256:9dc2d991b3479cc2df15930958b674a48a227d5361d413827a4cfd0b5876fc98"},
|
||||
]
|
||||
regex = [
|
||||
{file = "regex-2020.11.13-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:8b882a78c320478b12ff024e81dc7d43c1462aa4a3341c754ee65d857a521f85"},
|
||||
{file = "regex-2020.11.13-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:a63f1a07932c9686d2d416fb295ec2c01ab246e89b4d58e5fa468089cab44b70"},
|
||||
|
||||
@@ -17,6 +17,8 @@ pyobjc-framework-ApplicationServices = [{ version="7.1", platform="darwin" }]
|
||||
pyobjc-framework-CoreText = [{ version="7.1", platform="darwin" }]
|
||||
click = "^7.1.2"
|
||||
psutil = "^5.8.0"
|
||||
opencv-python = "^4.5.1"
|
||||
opencv-python-headless = "^4.5.1"
|
||||
|
||||
[tool.poetry.dev-dependencies]
|
||||
Cython = "^0.29.21"
|
||||
|
||||
@@ -321,7 +321,7 @@
|
||||
// return -1;
|
||||
// },
|
||||
// bindFindSubImg(id, imgstr, imgwidth, x, y, w, h) {
|
||||
// return '';
|
||||
// return syncRpc({func: 'bindFindSubImg', id: id, subimg: imgstr, img_width: imgwidth, x: x, y: y, w: w, h: h});
|
||||
// },
|
||||
addOCRFont(){
|
||||
},
|
||||
|
||||
+48
-5
@@ -2,7 +2,8 @@ import base64
|
||||
import json
|
||||
import logging
|
||||
import secrets
|
||||
from typing import TYPE_CHECKING, Dict, Callable, List
|
||||
import time
|
||||
from typing import TYPE_CHECKING, Dict, Callable, List, NamedTuple
|
||||
from urllib.parse import urljoin
|
||||
|
||||
from PySide2.QtCore import (
|
||||
@@ -27,13 +28,21 @@ from runekit.browser.utils import (
|
||||
ApiPermissionDeniedException,
|
||||
image_to_stream,
|
||||
encode_mouse,
|
||||
decode_image,
|
||||
)
|
||||
from runekit.game.instance import ImageType
|
||||
from runekit.image.cv import find_subimages
|
||||
from runekit.image.np_utils import ensure_np_image, np_crop
|
||||
from runekit.ui.tray import tray_icon
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from runekit.app.app import App
|
||||
|
||||
|
||||
class BoundedRegion(NamedTuple):
|
||||
image: ImageType
|
||||
|
||||
|
||||
class Alt1Api(QObject):
|
||||
app: "App"
|
||||
rpc_funcs: Dict[str, Callable]
|
||||
@@ -41,7 +50,7 @@ class Alt1Api(QObject):
|
||||
alt1Signal = Signal(int)
|
||||
|
||||
_screen_info: QRect
|
||||
_bound_regions: List
|
||||
_bound_regions: List[BoundedRegion]
|
||||
_game_active = False
|
||||
_game_position: QRect
|
||||
_game_scaling: float
|
||||
@@ -52,11 +61,19 @@ class Alt1Api(QObject):
|
||||
self.app = app
|
||||
self._bound_regions = []
|
||||
self._overlay = OverlayApi(self, parent=self)
|
||||
self.logger = logging.getLogger(
|
||||
__name__
|
||||
+ "."
|
||||
+ self.__class__.__name__
|
||||
+ "."
|
||||
+ self.app.manifest["appName"]
|
||||
)
|
||||
|
||||
self.rpc_funcs = {
|
||||
"getRegion": self.get_region,
|
||||
"bindRegion": self.bind_region,
|
||||
"bindGetRegion": self.bind_get_region,
|
||||
"bindFindSubImg": self.bind_find_subimage,
|
||||
}
|
||||
|
||||
self._update_screen_info()
|
||||
@@ -197,7 +214,8 @@ class Alt1Api(QObject):
|
||||
return 0
|
||||
|
||||
# Alt1 only support one region
|
||||
self._bound_regions = [self.app.game_instance.grab_region(x, y, w, h)]
|
||||
bound_data = self.app.game_instance.grab_region(x, y, w, h)
|
||||
self._bound_regions = [BoundedRegion(image=bound_data)]
|
||||
return 1
|
||||
|
||||
def bind_get_region(self, id, x, y, w, h):
|
||||
@@ -210,10 +228,35 @@ class Alt1Api(QObject):
|
||||
try:
|
||||
image = self._bound_regions[id - 1]
|
||||
except IndexError:
|
||||
print("no img index %d" % id)
|
||||
self.logger.warning("bindGetRegion(%d) but image not bound", id)
|
||||
return ""
|
||||
|
||||
return image_to_stream(image, x, y, w, h)
|
||||
return image_to_stream(image.image, x, y, w, h)
|
||||
|
||||
def bind_find_subimage(
|
||||
self, id: int, subimg: str, img_width: int, x: int, y: int, w: int, h: int
|
||||
):
|
||||
if id == 0:
|
||||
return ""
|
||||
|
||||
assert x >= 0
|
||||
assert y >= 0
|
||||
|
||||
try:
|
||||
image = self._bound_regions[id - 1]
|
||||
except IndexError:
|
||||
self.logger.warning("bindGetRegion(%d) but image not bound", id)
|
||||
return ""
|
||||
|
||||
image = ensure_np_image(image.image)
|
||||
image = np_crop(image, x, y, w, h)
|
||||
subimg = decode_image(subimg, img_width)
|
||||
|
||||
start = time.monotonic()
|
||||
out = find_subimages(image, subimg)
|
||||
self.logger.debug("OCR took %fs", time.monotonic() - start)
|
||||
out = [{"x": int(i[0]), "y": int(i[1])} for i in out]
|
||||
return json.dumps(out)
|
||||
|
||||
# endregion
|
||||
|
||||
|
||||
+29
-13
@@ -1,8 +1,12 @@
|
||||
import base64
|
||||
from typing import Union
|
||||
|
||||
import numpy as np
|
||||
from PIL import Image
|
||||
from PySide2.QtGui import QColor
|
||||
|
||||
from runekit.image.np_utils import np_crop
|
||||
|
||||
TRANSFER_LIMIT = 4_000_000
|
||||
|
||||
|
||||
@@ -16,24 +20,29 @@ class ApiPermissionDeniedException(Exception):
|
||||
self.required_permission = required_permission
|
||||
|
||||
|
||||
def image_to_stream(image: Image, x=0, y=0, width=None, height=None) -> bytes:
|
||||
assert image.mode == "RGB" or image.mode == "RGBA"
|
||||
def image_to_stream(
|
||||
image: Union[Image.Image, np.ndarray], x=0, y=0, width=None, height=None
|
||||
) -> bytes:
|
||||
if isinstance(image, np.ndarray):
|
||||
return base64.b64encode(np_crop(image, x, y, width, height).tobytes())
|
||||
else:
|
||||
assert image.mode == "RGB" or image.mode == "RGBA"
|
||||
|
||||
if width is None:
|
||||
width = image.width
|
||||
if height is None:
|
||||
height = image.height
|
||||
if width is None:
|
||||
width = image.width
|
||||
if height is None:
|
||||
height = image.height
|
||||
|
||||
if width * height * 4 > TRANSFER_LIMIT:
|
||||
return ""
|
||||
if width * height * 4 > TRANSFER_LIMIT:
|
||||
return ""
|
||||
|
||||
if image.mode == "RGB":
|
||||
image = image.convert("RGBA")
|
||||
if image.mode == "RGB":
|
||||
image = image.convert("RGBA")
|
||||
|
||||
r, g, b, a = image.crop((x, y, x + width, y + height)).split()
|
||||
image = Image.merge("RGBA", (b, g, r, a))
|
||||
r, g, b, a = image.crop((x, y, x + width, y + height)).split()
|
||||
image = Image.merge("RGBA", (b, g, r, a))
|
||||
|
||||
return base64.b64encode(image.tobytes())
|
||||
return base64.b64encode(image.tobytes())
|
||||
|
||||
|
||||
def encode_mouse(x: int, y: int) -> int:
|
||||
@@ -46,3 +55,10 @@ def decode_color(color: int) -> QColor:
|
||||
b = (color >> 0) & 0xFF
|
||||
a = (color >> 24) & 0xFF
|
||||
return QColor.fromRgb(r, g, b, a)
|
||||
|
||||
|
||||
def decode_image(img: str, width: int) -> np.ndarray:
|
||||
img = base64.b64decode(img)
|
||||
img = np.frombuffer(img, "<B")
|
||||
img.shape = (-1, width, 4)
|
||||
return img
|
||||
@@ -1,16 +1,20 @@
|
||||
import abc
|
||||
import time
|
||||
from typing import TYPE_CHECKING, Literal, Optional
|
||||
from typing import TYPE_CHECKING, Literal, Optional, Union
|
||||
|
||||
import numpy as np
|
||||
from PIL import Image
|
||||
from PySide2.QtCore import QObject, Signal, Property, QRect, Slot
|
||||
from PySide2.QtGui import QWindow
|
||||
from PySide2.QtWidgets import QGraphicsItem
|
||||
|
||||
from ..image.np_utils import np_crop
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .manager import GameManager
|
||||
|
||||
PROGRESS_TYPE = Literal["RESET", "IN_PROGRESS", "ERROR", "LOADING", "PAUSED"]
|
||||
ImageType = Union[np.ndarray, Image.Image]
|
||||
|
||||
|
||||
class GameInstance(QObject):
|
||||
@@ -64,16 +68,20 @@ class GameInstance(QObject):
|
||||
world = Property(int, get_world, notify=worldChanged)
|
||||
|
||||
@abc.abstractmethod
|
||||
def grab_game(self) -> Image:
|
||||
def grab_game(self) -> Union[Image.Image, np.ndarray]:
|
||||
...
|
||||
|
||||
@abc.abstractmethod
|
||||
def grab_desktop(self, x: int, y: int, w: int, h: int) -> Image:
|
||||
def grab_desktop(self, x: int, y: int, w: int, h: int) -> ImageType:
|
||||
...
|
||||
|
||||
def grab_region(self, x: int, y: int, w: int, h: int) -> Image:
|
||||
def grab_region(self, x: int, y: int, w: int, h: int) -> ImageType:
|
||||
image = self.grab_game()
|
||||
return image.crop((x, y, x + w, y + h))
|
||||
|
||||
if isinstance(image, np.ndarray):
|
||||
return np_crop(image, x, y, w, h)
|
||||
else:
|
||||
return image.crop((x, y, x + w, y + h))
|
||||
|
||||
def set_taskbar_progress(self, type_: PROGRESS_TYPE, progress: float):
|
||||
pass
|
||||
|
||||
+32
-8
@@ -1,13 +1,37 @@
|
||||
import logging
|
||||
import time
|
||||
|
||||
from PIL import Image
|
||||
from PySide2.QtGui import QGuiApplication, QWindow
|
||||
import numpy as np
|
||||
from PySide2.QtCore import QBuffer, QIODevice
|
||||
from PySide2.QtGui import QGuiApplication, QWindow, QPixmap
|
||||
import cv2
|
||||
|
||||
from runekit.image.np_utils import np_save_image
|
||||
|
||||
_debug_dump_file = False
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def qpixmap_to_np(im: QPixmap):
|
||||
# from PIL.ImageQt.fromqimage
|
||||
buffer = QBuffer()
|
||||
buffer.open(QIODevice.ReadWrite)
|
||||
if im.hasAlphaChannel():
|
||||
im.save(buffer, "png")
|
||||
else:
|
||||
im.save(buffer, "ppm")
|
||||
|
||||
npbuf = np.frombuffer(buffer.data(), "<B")
|
||||
buffer.close()
|
||||
|
||||
out = cv2.imdecode(npbuf, cv2.IMREAD_UNCHANGED)
|
||||
if out.shape[2] == 3:
|
||||
# Add alpha channel
|
||||
out = np.pad(out, ((0, 0), (0, 0), (0, 1)), constant_values=0xFF)
|
||||
|
||||
return out
|
||||
|
||||
|
||||
class QtBaseMixin:
|
||||
qwindow: QWindow
|
||||
|
||||
@@ -18,30 +42,30 @@ class QtGrabMixin(QtBaseMixin):
|
||||
__game_last_grab = 0.0
|
||||
__game_last_image = None
|
||||
|
||||
def grab_game(self) -> Image:
|
||||
def grab_game(self) -> np.ndarray:
|
||||
if (time.monotonic() - self.__game_last_grab) * 1000 < self.refresh_rate:
|
||||
return self.__game_last_image
|
||||
|
||||
screen = QGuiApplication.primaryScreen()
|
||||
pixmap = screen.grabWindow(self.qwindow.winId())
|
||||
image = Image.fromqpixmap(pixmap)
|
||||
image = qpixmap_to_np(pixmap)
|
||||
|
||||
if _debug_dump_file:
|
||||
logger.debug("dumping file")
|
||||
pixmap.save("/tmp/qtshot.bmp", None, 100)
|
||||
np_save_image(image, "/tmp/qtshot.bmp")
|
||||
|
||||
self.__game_last_image = image
|
||||
self.__game_last_grab = time.monotonic()
|
||||
return self.__game_last_image
|
||||
|
||||
def grab_desktop(self, x, y, w, h) -> Image:
|
||||
def grab_desktop(self, x, y, w, h) -> np.ndarray:
|
||||
screen = QGuiApplication.primaryScreen()
|
||||
pixmap = screen.grabWindow(0, x, y, w, h)
|
||||
image = Image.fromqpixmap(pixmap)
|
||||
image = qpixmap_to_np(pixmap)
|
||||
|
||||
if _debug_dump_file:
|
||||
logger.debug("dumping file")
|
||||
pixmap.save("/tmp/qtshot.bmp", None, 100)
|
||||
np_save_image(image, "/tmp/qtshot.bmp")
|
||||
|
||||
return image
|
||||
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
import sysv_ipc
|
||||
import numpy as np
|
||||
from PIL import ImageOps, Image
|
||||
|
||||
|
||||
def zpixmap_shm_to_image(
|
||||
shm: sysv_ipc.SharedMemory, size: int, width: int, height: int
|
||||
) -> Image:
|
||||
out = Image.frombytes(
|
||||
"RGBA",
|
||||
(width, height),
|
||||
shm.read(size),
|
||||
"raw",
|
||||
)
|
||||
b, g, r, a = out.split()
|
||||
a = ImageOps.invert(a)
|
||||
return Image.merge("RGBA", (r, g, b, a))
|
||||
) -> np.ndarray:
|
||||
out = np.frombuffer(shm.read(size), "<B").copy()
|
||||
out.shape = (height, width, 4)
|
||||
out[:, :, 3] = 0xFF # Convert BGRX to BGRA
|
||||
|
||||
return out
|
||||
@@ -1,27 +0,0 @@
|
||||
import logging
|
||||
import struct
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from PIL import Image
|
||||
|
||||
try:
|
||||
from .format import image_to_bgra
|
||||
except ImportError:
|
||||
logging.warning("Cannot import native image code, using pure python")
|
||||
|
||||
image_8bpp = struct.Struct("BBBB")
|
||||
|
||||
def image_to_bgra(image: "Image", x, y, w, h) -> bytes:
|
||||
buf = bytearray(w * h * image_8bpp.size)
|
||||
index = 0
|
||||
for p in image.crop((x, y, x + w, y + h)).getdata():
|
||||
r = p[0]
|
||||
g = p[1]
|
||||
b = p[2]
|
||||
a = p[3] if len(p) == 4 else 255
|
||||
|
||||
image_8bpp.pack_into(buf, index, b, g, r, a)
|
||||
index += image_8bpp.size
|
||||
|
||||
return buf
|
||||
@@ -0,0 +1,59 @@
|
||||
from typing import Tuple, List
|
||||
|
||||
import numpy as np
|
||||
import cv2
|
||||
|
||||
_debug_save_images = False
|
||||
|
||||
|
||||
def find_subimages(image: np.ndarray, subimage: np.ndarray) -> List[Tuple[int, int]]:
|
||||
"""
|
||||
Detect BGRA subimage in BGRA image with the following limitations
|
||||
|
||||
* Image transparency is not supported (alpha channel is removed)
|
||||
* Subimage semi-translucency is not supported
|
||||
"""
|
||||
mask = subimage[:, :, 3]
|
||||
image_bgr = image[:, :, :3]
|
||||
subimage_bgr = subimage[:, :, :3]
|
||||
matched = cv2.matchTemplate(image_bgr, subimage_bgr, cv2.TM_SQDIFF, mask=mask).get()
|
||||
|
||||
# allow up to 30 pixel per pixel
|
||||
threshold = (30 ** 2) * subimage.shape[0] * subimage.shape[1]
|
||||
loc = np.where((matched < threshold) & (matched != np.inf))
|
||||
out = []
|
||||
for pos in zip(*loc[::-1]):
|
||||
out.append(pos)
|
||||
|
||||
if _debug_save_images:
|
||||
from runekit.image.np_utils import np_save_image
|
||||
import hashlib
|
||||
|
||||
cv2.normalize(matched, matched, 0, 0xFF, cv2.NORM_MINMAX)
|
||||
matched = matched.astype("uint8")
|
||||
image_bgr = image_bgr.copy()
|
||||
|
||||
subimg_hash = hashlib.blake2b(subimage.data, digest_size=8).hexdigest()
|
||||
|
||||
for pos in out:
|
||||
cv2.rectangle(
|
||||
matched,
|
||||
pos,
|
||||
(pos[0] + subimage.shape[1], pos[1] + subimage.shape[0]),
|
||||
0,
|
||||
2,
|
||||
)
|
||||
cv2.rectangle(
|
||||
image_bgr,
|
||||
pos,
|
||||
(pos[0] + subimage.shape[1], pos[1] + subimage.shape[0]),
|
||||
(0, 255, 0),
|
||||
2,
|
||||
)
|
||||
|
||||
np_save_image(image_bgr, f"/tmp/debug-image-{subimg_hash}.png")
|
||||
np_save_image(subimage_bgr, f"/tmp/debug-subimage-{subimg_hash}.png")
|
||||
np_save_image(mask, f"/tmp/debug-mask-{subimg_hash}.png")
|
||||
np_save_image(matched, f"/tmp/debug-result-{subimg_hash}.png")
|
||||
|
||||
return out
|
||||
@@ -1,46 +0,0 @@
|
||||
from cpython.mem cimport PyMem_Malloc, PyMem_Free
|
||||
|
||||
cdef struct ImageRGBA:
|
||||
unsigned char r
|
||||
unsigned char g
|
||||
unsigned char b
|
||||
unsigned char a
|
||||
|
||||
def image_to_bgra(image, long ix, long iy, unsigned long w, unsigned long h):
|
||||
ptr = dict(image.im.unsafe_ptrs)['image32']
|
||||
cdef size_t ptr_int = <size_t> ptr
|
||||
cdef ImageRGBA **image32 = <ImageRGBA**> ptr_int
|
||||
|
||||
if ix < 0:
|
||||
ix = 0
|
||||
if iy < 0:
|
||||
iy = 0
|
||||
if ix + w > image.width:
|
||||
w = image.width - ix
|
||||
if iy + h > image.height:
|
||||
h = image.height - iy
|
||||
|
||||
cdef size_t size = w * h * 4
|
||||
cdef unsigned char *buf = <unsigned char*> PyMem_Malloc(size)
|
||||
|
||||
cdef size_t index, x, y
|
||||
cdef unsigned char r, g, b, a
|
||||
|
||||
with nogil:
|
||||
for y in range(iy, iy+h):
|
||||
for x in range(ix, ix+w):
|
||||
index = (((y-iy) * w) + (x-ix)) * 4
|
||||
r = image32[y][x].r
|
||||
g = image32[y][x].g
|
||||
b = image32[y][x].b
|
||||
a = image32[y][x].a
|
||||
|
||||
buf[index] = b
|
||||
buf[index+1] = g
|
||||
buf[index+2] = r
|
||||
buf[index+3] = a
|
||||
|
||||
try:
|
||||
return buf[:size]
|
||||
finally:
|
||||
PyMem_Free(buf)
|
||||
@@ -0,0 +1,63 @@
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import numpy as np
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from runekit.game.instance import ImageType
|
||||
|
||||
|
||||
def np_crop(image: np.ndarray, x: int, y: int, w: int, h: int) -> np.ndarray:
|
||||
shape = image.shape
|
||||
img_width = shape[1]
|
||||
img_height = shape[0]
|
||||
if w is None:
|
||||
w = img_width
|
||||
if h is None:
|
||||
h = img_height
|
||||
|
||||
x_pad_left = max(0, -x)
|
||||
y_pad_left = max(0, -y)
|
||||
|
||||
x1 = max(0, x)
|
||||
y1 = max(0, y)
|
||||
x2 = min(x1 + w - x_pad_left, img_width - x_pad_left)
|
||||
y2 = min(y1 + h - y_pad_left, img_height - y_pad_left)
|
||||
image = image[y1:y2, x1:x2]
|
||||
|
||||
x_pad = (x_pad_left, w - (x2 - x1) - x_pad_left)
|
||||
y_pad = (y_pad_left, h - (y2 - y1) - y_pad_left)
|
||||
if x_pad != (0, 0) or y_pad != (0, 0):
|
||||
image = np.pad(image, (y_pad, x_pad, (0, 0)))
|
||||
|
||||
return image
|
||||
|
||||
|
||||
def np_save_image(image: np.ndarray, out: str):
|
||||
from PIL import Image
|
||||
|
||||
size = (image.shape[1], image.shape[0])
|
||||
if len(image.shape) == 2:
|
||||
# Grayscale
|
||||
Image.frombuffer("L", size, image.tobytes(), "raw", "L", 0, 1).save(out)
|
||||
elif image.shape[2] == 3:
|
||||
# RGB
|
||||
Image.frombuffer(
|
||||
"RGB", size, image[:, :, ::-1].tobytes(), "raw", "RGB", 0, 1
|
||||
).save(out)
|
||||
else:
|
||||
# RGBA
|
||||
Image.frombuffer(
|
||||
"RGBA", size, image[:, :, [2, 1, 0, 3]].tobytes(), "raw", "RGBA", 0, 1
|
||||
).save(out)
|
||||
|
||||
|
||||
def ensure_np_image(image: "ImageType") -> np.ndarray:
|
||||
if isinstance(image, np.ndarray):
|
||||
return image
|
||||
|
||||
if image.mode == "RGB":
|
||||
image = image.convert("RGBA")
|
||||
|
||||
out = np.array(image)
|
||||
out = out[:, :, [2, 1, 0, 3]] # B G R A
|
||||
return out
|
||||
Reference in new issue
Block a user