Skip to content

Commit 84c018b

Browse files
authored
Native Windows ARM64 builds (#330)
1 parent 53be3d9 commit 84c018b

File tree

5 files changed

+105
-101
lines changed

5 files changed

+105
-101
lines changed

.github/workflows/lint-and-build.yml

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@ env:
3131
GITHUB_HEAD_REPOSITORY: ${{ github.event.pull_request.head.repo.full_name }}
3232
GITHUB_EXCLUDE_BUILD_NUMBER: ${{ inputs.excludeBuildNumber }}
3333
UV_NO_SYNC: true # Avoid accidentally pulling in dependency-groups with uv run
34+
# https://github.com/opencv/opencv-python#source-distributions
35+
# Allows building OpenCV on Windows ARM64
36+
# https://github.com/opencv/opencv-python/issues/1092#issuecomment-2862538656
37+
CMAKE_ARGS: "-DBUILD_opencv_dnn=OFF -DENABLE_NEON=OFF"
3438

3539
concurrency:
3640
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
@@ -75,14 +79,13 @@ jobs:
7579
fail-fast: false
7680
# Only the Python version we plan on shipping matters.
7781
matrix:
78-
# OpenCV doesn't support windows-11-arm yet https://github.com/opencv/opencv-python/issues/806
79-
os: [windows-latest, ubuntu-22.04, ubuntu-22.04-arm]
82+
os: [windows-latest, windows-11-arm, ubuntu-22.04, ubuntu-22.04-arm]
8083
python-version: ["3.13"]
8184
steps:
8285
- uses: actions/checkout@v4
8386
# region pyinstaller/pyinstaller#9012 + astral-sh/uv#12906
8487
- name: Set up Python for PyInstaller tk and ARM64 issue
85-
if: ${{ startsWith(matrix.os, 'ubuntu') }}
88+
if: matrix.os != 'windows-latest'
8689
uses: actions/setup-python@v5
8790
with:
8891
allow-prereleases: true
@@ -91,7 +94,7 @@ jobs:
9194
uses: astral-sh/setup-uv@v6
9295
with:
9396
enable-cache: true
94-
python-version: ${{ !startsWith(matrix.os, 'ubuntu') && matrix.python-version || null }}
97+
python-version: ${{ matrix.os == 'windows-latest' && matrix.python-version || null }}
9598
# endregion
9699
- run: scripts/install.ps1
97100
shell: pwsh
@@ -100,26 +103,27 @@ jobs:
100103
- name: Add empty profile
101104
run: echo "" > dist/settings.toml
102105
- name: Extract AutoSplit version
103-
id: autosplit_version
106+
id: artifact_vars
104107
working-directory: src
105-
run: |
108+
run: | # This also serves as a sanity check for imports
106109
$Env:AUTOSPLIT_VERSION=uv run python -c "import utils; print(utils.AUTOSPLIT_VERSION)"
107110
echo "AUTOSPLIT_VERSION=$Env:AUTOSPLIT_VERSION" >> $Env:GITHUB_OUTPUT
111+
echo "OS=$([System.Runtime.InteropServices.RuntimeInformation]::RuntimeIdentifier)" >> $Env:GITHUB_OUTPUT
108112
shell: pwsh
109113
- name: Upload Build Artifact
110114
uses: actions/upload-artifact@v4
111115
with:
112116
name: >
113-
AutoSplit v${{ steps.autosplit_version.outputs.AUTOSPLIT_VERSION }}
114-
for ${{ matrix.os }} (Python ${{ matrix.python-version }})
117+
AutoSplit v${{ steps.artifact_vars.outputs.AUTOSPLIT_VERSION }}
118+
for ${{ steps.artifact_vars.outputs.OS }} (Python ${{ matrix.python-version }})
115119
path: |
116120
dist/AutoSplit*
117121
dist/settings.toml
118122
if-no-files-found: error
119123
- name: Upload Build logs
120124
uses: actions/upload-artifact@v4
121125
with:
122-
name: Build logs for ${{ matrix.os }} (Python ${{ matrix.python-version }})
126+
name: Build logs for ${{ steps.artifact_vars.outputs.OS }} (Python ${{ matrix.python-version }})
123127
path: |
124128
build/AutoSplit/*.toc
125129
build/AutoSplit/*.txt

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,12 +44,12 @@ To understand how to use AutoSplit and how it works in-depth, please read the [t
4444

4545
### Compatibility
4646

47-
- Windows 10 and 11. (x64 only)
47+
- Windows 10 and 11.
4848
- Linux (still in early development)
49-
- Both x64 and ARM64 architectures \* (see [Known Limitations](#known-limitations) for ARM64)
5049
- Should work on Ubuntu 22.04+
5150
- Wayland is not currently supported
5251
- WSL2/WSLg requires an additional Desktop Environment, external X11 server, and/or systemd
52+
- x64 and ARM64 architectures \* (see [Known Limitations](#known-limitations) for ARM64)
5353
- If you'd like to run the project directly in Python from the source code, refer to the [build instructions](/docs/build%20instructions.md).
5454

5555
## Timer Integration
@@ -75,6 +75,7 @@ See the [installation instructions](https://github.com/Toufool/LiveSplit.AutoSpl
7575
- Incompatible with LiveSplitOne on Linux (see <https://github.com/LiveSplit/LiveSplitOne/issues/1025>)
7676
- Antivirus false positives. Please read <https://github.com/pyinstaller/pyinstaller/blob/develop/.github/ISSUE_TEMPLATE/antivirus.md>
7777
- The Perceptual Hash Comparison Method similarity may differ by 3.125% on ARM64. (this will be solved eventually, we have to use a fallback method for now)
78+
- Native ARM64 builds go completely untested. There may be unforseen issues.
7879

7980
## Resources
8081

pyproject.toml

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,9 @@ dependencies = [
1717
# scipy is used for pHash calculation as a smaller, but still fast implementation.
1818
# However, scipy is not available on all environments yet.
1919
# In those cases, we're falling back to opencv-contrib-python's cv2.img_hash
20-
"opencv-contrib-python-headless >=4.10; platform_machine == 'aarch64'", # NumPy 2 support
21-
"opencv-python-headless >=4.10; platform_machine != 'aarch64'", # NumPy 2 support
22-
"scipy >=1.14.1; platform_machine != 'aarch64'", # Python 3.13 support
20+
"opencv-contrib-python-headless; platform_machine == 'ARM64' or platform_machine == 'aarch64'",
21+
"opencv-python-headless; platform_machine != 'ARM64' and platform_machine != 'aarch64'",
22+
"scipy >=1.14.1; platform_machine != 'ARM64' and platform_machine != 'aarch64'", # Python 3.13 support
2323

2424
#
2525
# Build and compile resources
@@ -32,14 +32,16 @@ dependencies = [
3232
"pygrabber >=0.2; sys_platform == 'win32'", # Completed types
3333
"pywin32 >=307; sys_platform == 'win32'", # Python 3.13 support
3434
"typed-D3DShot[numpy] >=1.0.1; sys_platform == 'win32'",
35-
"winrt-Windows.Foundation >=2.2.0; sys_platform == 'win32'", # Python 3.13 support
36-
"winrt-Windows.Graphics >=2.2.0; sys_platform == 'win32'", # Python 3.13 support
37-
"winrt-Windows.Graphics.Capture >=3.0.0; sys_platform == 'win32'", # Type hints are no longer typing.Optional by default.
38-
"winrt-Windows.Graphics.Capture.Interop >=2.3.0; sys_platform == 'win32'", # Python 3.13 support
39-
"winrt-Windows.Graphics.DirectX >=2.3.0; sys_platform == 'win32'", # Python 3.13 support
40-
"winrt-Windows.Graphics.DirectX.Direct3D11 >=2.3.0; sys_platform == 'win32'", # Python 3.13 support
41-
"winrt-Windows.Graphics.DirectX.Direct3D11.Interop >=2.3.0; sys_platform == 'win32'",
42-
"winrt-Windows.Graphics.Imaging >=2.3.0; sys_platform == 'win32'", # Python 3.13 support
35+
# winrt: winrt-runtime didn't have a working Python 3.14 sdist build until 3.2.0
36+
# TODO: Bump when 3.14 wheels are released
37+
"winrt-Windows.Foundation >=3.2.0; sys_platform == 'win32'",
38+
"winrt-Windows.Graphics >=3.2.0; sys_platform == 'win32'",
39+
"winrt-Windows.Graphics.Capture >=3.2.0; sys_platform == 'win32'",
40+
"winrt-Windows.Graphics.Capture.Interop >=3.2.0; sys_platform == 'win32'",
41+
"winrt-Windows.Graphics.DirectX >=3.2.0; sys_platform == 'win32'",
42+
"winrt-Windows.Graphics.DirectX.Direct3D11 >=3.2.0; sys_platform == 'win32'",
43+
"winrt-Windows.Graphics.DirectX.Direct3D11.Interop >=3.2.0; sys_platform == 'win32'",
44+
"winrt-Windows.Graphics.Imaging >=3.2.0; sys_platform == 'win32'",
4345
#
4446
# Linux-only dependencies
4547
"PyScreeze >=1.0.0; sys_platform == 'linux'",
@@ -59,7 +61,6 @@ dev = [
5961
"ruff >=0.11.13",
6062
#
6163
# Types
62-
"opencv-contrib-python-headless",
6364
"scipy-stubs >=1.14.1.1",
6465
"types-PyAutoGUI",
6566
"types-PyScreeze; sys_platform == 'linux'",

src/compare.py

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from collections.abc import Iterable
22
from math import sqrt
3+
from typing import TYPE_CHECKING
34

45
import cv2
56
import Levenshtein
@@ -102,27 +103,28 @@ def __cv2_scipy_compute_phash(image: MatLike, hash_size: int, highfreq_factor: i
102103
median = np.median(dct_low_frequency)
103104
return dct_low_frequency > median
104105

105-
def __cv2_phash(source: MatLike, capture: MatLike, hash_size: int = 8): # pyright: ignore[reportRedeclaration]
106+
def __cv2_phash(source: MatLike, capture: MatLike, hash_size: int = 8):
106107
source_hash = __cv2_scipy_compute_phash(source, hash_size)
107108
capture_hash = __cv2_scipy_compute_phash(capture, hash_size)
108109
hash_diff = np.count_nonzero(source_hash != capture_hash)
109110
return 1 - (hash_diff / 64.0)
110111

111112
except ModuleNotFoundError:
112-
113-
def __cv2_phash(source: MatLike, capture: MatLike, hash_size: int = 8):
114-
# OpenCV has its own pHash comparison implementation in `cv2.img_hash`,
115-
# but it requires contrib/extra modules and is inaccurate
116-
# unless we precompute the size with a specific interpolation.
117-
# See: https://github.com/opencv/opencv_contrib/issues/3295#issuecomment-1172878684
118-
#
119-
phash = cv2.img_hash.PHash.create()
120-
source = cv2.resize(source, (hash_size, hash_size), interpolation=cv2.INTER_AREA)
121-
capture = cv2.resize(capture, (hash_size, hash_size), interpolation=cv2.INTER_AREA)
122-
source_hash = phash.compute(source)
123-
capture_hash = phash.compute(capture)
124-
hash_diff = phash.compare(source_hash, capture_hash)
125-
return 1 - (hash_diff / 64.0)
113+
if not TYPE_CHECKING: # opencv-contrib-python-headless being installed is based on architecture
114+
115+
def __cv2_phash(source: MatLike, capture: MatLike, hash_size: int = 8):
116+
# OpenCV has its own pHash comparison implementation in `cv2.img_hash`,
117+
# but it requires contrib/extra modules and is inaccurate
118+
# unless we precompute the size with a specific interpolation.
119+
# See: https://github.com/opencv/opencv_contrib/issues/3295#issuecomment-1172878684
120+
#
121+
phash = cv2.img_hash.PHash.create()
122+
source = cv2.resize(source, (hash_size, hash_size), interpolation=cv2.INTER_AREA)
123+
capture = cv2.resize(capture, (hash_size, hash_size), interpolation=cv2.INTER_AREA)
124+
source_hash = phash.compute(source)
125+
capture_hash = phash.compute(capture)
126+
hash_diff = phash.compare(source_hash, capture_hash)
127+
return 1 - (hash_diff / 64.0)
126128

127129

128130
def compare_phash(source: MatLike, capture: MatLike, mask: MatLike | None = None):

0 commit comments

Comments
 (0)