diff --git a/.circleci/config.yml b/.circleci/config.yml
index b15d7d70ef..ef0a373535 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -28,14 +28,9 @@ jobs:
environment:
# these environment variables will be passed to the docker container
- - CIBW_ENVIRONMENT: SDL_VIDEODRIVER=dummy SDL_AUDIODRIVER=disk
- - CIBW_BUILD: "cp3{8,9,10,11,12}-* pp3{8,9,10}-*"
- CIBW_ARCHS: aarch64
- - CIBW_SKIP: '*-musllinux_*'
- CIBW_MANYLINUX_AARCH64_IMAGE: manylinux2014_base_aarch64
- CIBW_MANYLINUX_PYPY_AARCH64_IMAGE: manylinux2014_base_aarch64
- - CIBW_TEST_COMMAND: python -m pygame.tests -v --exclude opengl,music,timing --time_out 300
- - CIBW_BUILD_VERBOSITY: 2
steps:
- checkout
@@ -47,7 +42,7 @@ jobs:
- run:
name: Build the Linux wheels.
command: |
- pip3 install --user cibuildwheel==2.19.1
+ pip3 install --user cibuildwheel==2.20.0
PATH="$HOME/.local/bin:$PATH" cibuildwheel --output-dir wheelhouse
- store_artifacts:
@@ -57,5 +52,15 @@ jobs:
# See: https://circleci.com/docs/2.0/configuration-reference/#workflows
workflows:
build-arm:
+ when:
+ equal: [ "", << pipeline.parameters.GHA_Actor >> ]
+ jobs:
+ - linux-arm-wheels
+
+ # run a separate, identical release job only if triggered
+ build-arm-release:
+ when:
+ not:
+ equal: [ "", << pipeline.parameters.GHA_Actor >> ]
jobs:
- linux-arm-wheels
diff --git a/.github/workflows/build-macos.yml b/.github/workflows/build-macos.yml
index 585bc3e7ff..71ef4fb7bd 100644
--- a/.github/workflows/build-macos.yml
+++ b/.github/workflows/build-macos.yml
@@ -76,58 +76,18 @@ jobs:
# path: ${{ github.workspace }}/pygame_mac_deps_${{ matrix.macarch }}
build:
- name: ${{ matrix.name }}
+ name: ${{ matrix.macarch }}
needs: deps
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false # if a particular matrix build fails, don't skip the rest
matrix:
- # Split job into 5 matrix builds, because GH actions provides 5 concurrent
- # builds on macOS. This needs to be manually kept updated so that each
- # of these builds take roughly the same time
include:
- - {
- name: "x86_64 (CPython 3.9 - 3.12)",
- macarch: x86_64,
- os: macos-13,
- pyversions: "cp3{9,10,11,12}-*",
- }
-
- - {
- name: "x86_64 (Python 3.8)",
- macarch: x86_64,
- os: macos-13,
- # CPython/PyPy 3.8
- pyversions: "?p38-*",
- }
-
- - {
- name: "x86_64 (PyPy 3.9 and 3.10)",
- macarch: x86_64,
- os: macos-13,
- pyversions: "pp39-* pp310-*",
- }
-
- - {
- name: "arm64 (CPython 3.8 - 3.10)",
- macarch: arm64,
- os: macos-14,
- pyversions: "cp3{8,9,10}-*",
- }
-
- - {
- name: "arm64 (CPython 3.11 - 3.12)",
- macarch: arm64,
- os: macos-14,
- pyversions: "cp3{11,12}-*",
- }
+ - { macarch: arm64, os: macos-14 }
+ - { macarch: x86_64, os: macos-13 }
env:
MAC_ARCH: ${{ matrix.macarch }}
- # load pip config from this file. Define this in 'CIBW_ENVIRONMENT'
- # because this should not affect cibuildwheel machinery
- # also define environment variables needed for testing
- CIBW_ENVIRONMENT: SDL_VIDEODRIVER=dummy SDL_AUDIODRIVER=disk
# Explicitly tell CIBW what the wheel arch deployment target should be
# There seems to be no better way to set this than this env
@@ -140,8 +100,6 @@ jobs:
# should be for 10.11 on x86
MACOSX_DEPLOYMENT_TARGET: ${{ matrix.macarch == 'x86_64' && '10.11' || '11.0' }}
- CIBW_BUILD: ${{ matrix.pyversions }}
-
CIBW_ARCHS: ${{ matrix.macarch }}
# Setup macOS dependencies
@@ -152,17 +110,11 @@ jobs:
bash ./install_mac_deps.sh
CIBW_BEFORE_BUILD: |
- pip install numpy
cp -r ${{ github.workspace }}/pygame_mac_deps_${{ matrix.macarch }} ${{ github.workspace }}/pygame_mac_deps
# To remove any speculations about the wheel not being self-contained
CIBW_BEFORE_TEST: rm -rf ${{ github.workspace }}/pygame_mac_deps
- CIBW_TEST_COMMAND: python -m pygame.tests -v --exclude opengl,timing --time_out 300
-
- # Increase pip debugging output
- CIBW_BUILD_VERBOSITY: 2
-
steps:
- uses: actions/checkout@v4.1.7
@@ -170,7 +122,7 @@ jobs:
uses: actions/cache@v4.0.2
with:
path: ~/Library/Caches/pip # This cache path is only right on mac
- key: pip-cache-${{ matrix.name }}
+ key: pip-cache-${{ matrix.macarch }}-${{ matrix.os }}
- name: Fetch Mac deps
id: macdep-cache
@@ -180,12 +132,17 @@ jobs:
key: macdep-${{ hashFiles('buildconfig/manylinux-build/**') }}-${{ hashFiles('buildconfig/macdependencies/*.sh') }}-${{ matrix.macarch }}
fail-on-cache-miss: true
+ - name: Install uv for speed
+ uses: yezz123/setup-uv@v4
+ with:
+ uv-version: "0.2.22"
+
- name: Build and test wheels
- uses: pypa/cibuildwheel@v2.19.1
+ uses: pypa/cibuildwheel@v2.20.0
- uses: actions/upload-artifact@v4
with:
- name: pygame-wheels-macos-${{ matrix.name }}
+ name: pygame-wheels-macos-${{ matrix.macarch }}
path: ./wheelhouse/*.whl
compression-level: 0 # wheels are already zip files, no need for more compression
diff --git a/.github/workflows/build-manylinux.yml b/.github/workflows/build-manylinux.yml
index de78395099..981d7f7676 100644
--- a/.github/workflows/build-manylinux.yml
+++ b/.github/workflows/build-manylinux.yml
@@ -49,35 +49,13 @@ jobs:
arch: [x86_64, i686]
env:
- # load pip config from this file. Define this in 'CIBW_ENVIRONMENT'
- # because this should not affect cibuildwheel machinery
- # also define environment variables needed for testing
- CIBW_ENVIRONMENT: SDL_VIDEODRIVER=dummy SDL_AUDIODRIVER=disk
-
- CIBW_BUILD: "cp3{8,9,10,11,12}-* pp3{8,9,10}-*"
CIBW_ARCHS: ${{ matrix.arch }}
- # skip musllinux for now
- CIBW_SKIP: '*-musllinux_*'
-
- CIBW_TEST_COMMAND: python -m pygame.tests -v --exclude opengl,music,timing --time_out 300
-
- # To 'solve' this issue:
- # >>> process 338: D-Bus library appears to be incorrectly set up; failed to read
- # machine uuid: Failed to open "/var/lib/dbus/machine-id": No such file or directory
- CIBW_BEFORE_TEST: |
- if [ ! -f /var/lib/dbus/machine-id ]; then
- dbus-uuidgen > /var/lib/dbus/machine-id
- fi
-
- # Increase pip debugging output
- CIBW_BUILD_VERBOSITY: 2
-
steps:
- uses: actions/checkout@v4.1.7
- name: Log in to the Container registry
- uses: docker/login-action@0d4c9c5ea7693da7b068278f7b52bda2a190a446
+ uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567
with:
registry: ghcr.io
username: ${{ github.actor }}
@@ -97,7 +75,7 @@ jobs:
- name: Build and push Docker image
if: steps.inspect.outcome == 'failure'
- uses: docker/build-push-action@ca052bb54ab0790a636c9b5f226502c73d547a25
+ uses: docker/build-push-action@5cd11c3a4ced054e52742c5fd54dca954e0edd85
with:
context: ${{ github.workspace }}/buildconfig/manylinux-build/docker_base
file: ${{ github.workspace }}/buildconfig/manylinux-build/docker_base/Dockerfile-${{ matrix.arch }}
@@ -113,7 +91,7 @@ jobs:
CIBW_MANYLINUX_I686_IMAGE: ghcr.io/${{ github.repository }}_i686:${{ steps.meta.outputs.version }}
CIBW_MANYLINUX_PYPY_I686_IMAGE: ghcr.io/${{ github.repository }}_i686:${{ steps.meta.outputs.version }}
- uses: pypa/cibuildwheel@v2.19.1
+ uses: pypa/cibuildwheel@v2.20.0
# We upload the generated files under github actions assets
- name: Upload dist
diff --git a/.github/workflows/build-ubuntu-coverage.yml b/.github/workflows/build-ubuntu-coverage.yml
new file mode 100644
index 0000000000..b01f623c2f
--- /dev/null
+++ b/.github/workflows/build-ubuntu-coverage.yml
@@ -0,0 +1,95 @@
+# this workflow generates C code coverage information from the unit test
+# suite. Note that for intrinsics, it only runs what gets compiled
+# and would naturally run. It also is limited to what can run in
+# a CI environment
+# IMPORTANT: binaries are not to be uploaded from this workflow!
+
+name: Ubuntu coverage
+
+# Run CI only when a release is created, on changes to main branch, or any PR
+# to main. Do not run CI on any other branch. Also, skip any non-source changes
+# from running on CI
+on:
+ push:
+ branches: main
+ paths-ignore:
+ - 'docs/**'
+ - 'examples/**'
+ - '.gitignore'
+ - '*.rst'
+ - '*.md'
+ - '.github/workflows/*.yml'
+ # gcov/lcov only gets C coverage
+ - 'src_py/**'
+ # re-include current file to not be excluded
+ - '!.github/workflows/build-ubuntu-coverage.yml'
+
+ pull_request:
+ branches: main
+ paths-ignore:
+ - 'docs/**'
+ - 'examples/**'
+ - '.gitignore'
+ - '*.rst'
+ - '*.md'
+ - '.github/workflows/*.yml'
+ # gcov/lcov only gets C coverage
+ - 'src_py/**'
+ # re-include current file to not be excluded
+ - '!.github/workflows/build-ubuntu-coverage.yml'
+
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}-ubuntu-coverage
+ cancel-in-progress: true
+
+jobs:
+ gen_coverage:
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: false # if a particular matrix build fails, don't skip the rest
+ matrix:
+ os: [ubuntu-22.04]
+
+ steps:
+ - uses: actions/checkout@v4.1.7
+
+ - name: Install deps
+ # install numpy from pip and not apt because the one from pip is newer,
+ # and has typestubs
+ # https://github.com/actions/runner-images/issues/7192
+ # https://github.com/orgs/community/discussions/47863
+ run: |
+ sudo apt-get update --fix-missing
+ sudo apt-get install lcov -y
+ sudo apt-get install libsdl2-dev libsdl2-image-dev libsdl2-mixer-dev libsdl2-ttf-dev libfreetype6-dev libportmidi-dev python3-dev -y
+ pip3 install --upgrade pip
+ pip3 install meson-python ninja cython "sphinx<=7.2.6" # because we are doing --no-build-isolation
+ pip3 install numpy>=1.21.0
+
+ - name: Build with coverage hooks and install
+ id: build
+ run: |
+ pip3 install -e . --no-build-isolation -Cbuild-dir=./.mesonpy-rel -Csetup-args=-Dcoverage=true
+
+ - name: Run tests
+ env:
+ SDL_VIDEODRIVER: "dummy"
+ SDL_AUDIODRIVER: "disk"
+ run: python3 -m pygame.tests -v --exclude opengl,music,timing --time_out 300
+
+ - name: Generate coverage
+ id: gen-coverage
+ # want to continue regardless of whether a test failed or not as long as the job wasn't cancelled
+ if: ${{ steps.build.conclusion == 'success' && !cancelled() }}
+ run: |
+ lcov --capture --directory . --output-file ./coverage.info
+ genhtml ./coverage.info --output-directory ./out
+
+ # We upload the generated files under github actions assets
+ - name: Upload coverage html
+ # want to continue only if the coverage generation was successful
+ if: ${{ steps.gen-coverage.conclusion == 'success' && !cancelled() }}
+ uses: actions/upload-artifact@v4
+ with:
+ name: pygame-wheels-coverage
+ path: ./out
diff --git a/.github/workflows/build-windows.yml b/.github/workflows/build-windows.yml
index b23ad66dff..064fd9cc88 100644
--- a/.github/workflows/build-windows.yml
+++ b/.github/workflows/build-windows.yml
@@ -37,108 +37,18 @@ concurrency:
jobs:
build:
- name: ${{ matrix.name }}
+ name: ${{ matrix.winarch }}
runs-on: windows-latest
strategy:
fail-fast: false # if a particular matrix build fails, don't skip the rest
matrix:
include:
- - {
- name: "CPython 3.12 (64 bit)",
- winarch: AMD64,
- msvc-dev-arch: x86_amd64,
- pyversions: "cp312-*"
- }
- - {
- name: "CPython 3.11 (64 bit)",
- winarch: AMD64,
- msvc-dev-arch: x86_amd64,
- pyversions: "cp311-*"
- }
- - {
- name: "CPython 3.10 (64 bit)",
- winarch: AMD64,
- msvc-dev-arch: x86_amd64,
- pyversions: "cp310-*"
- }
- - {
- name: "CPython 3.9 (64 bit)",
- winarch: AMD64,
- msvc-dev-arch: x86_amd64,
- pyversions: "cp39-*"
- }
- - {
- name: "CPython 3.8 (64 bit)",
- winarch: AMD64,
- msvc-dev-arch: x86_amd64,
- pyversions: "cp38-*"
- }
- - {
- name: "CPython 3.12 (32 bit)",
- winarch: x86,
- msvc-dev-arch: x86,
- pyversions: "cp312-win32*"
- }
- - {
- name: "CPython 3.11 (32 bit)",
- winarch: x86,
- msvc-dev-arch: x86,
- pyversions: "cp311-win32"
- }
- - {
- name: "CPython 3.10 (32 bit)",
- winarch: x86,
- msvc-dev-arch: x86,
- pyversions: "cp310-win32"
- }
- - {
- name: "CPython 3.9 (32 bit)",
- winarch: x86,
- msvc-dev-arch: x86,
- pyversions: "cp39-win32"
- }
- - {
- name: "CPython 3.8 (32 bit)",
- winarch: x86,
- msvc-dev-arch: x86,
- pyversions: "cp38-win32"
- }
- - {
- name: "Pypy 3.8",
- winarch: AMD64,
- msvc-dev-arch: x86_amd64,
- pyversions: "pp38-*"
- }
- - {
- name: "Pypy 3.9",
- winarch: AMD64,
- msvc-dev-arch: x86_amd64,
- pyversions: "pp39-*"
- }
- - {
- name: "Pypy 3.10",
- winarch: AMD64,
- msvc-dev-arch: x86_amd64,
- pyversions: "pp310-*"
- }
-
+ - { winarch: AMD64, msvc-dev-arch: x86_amd64 }
+ - { winarch: x86, msvc-dev-arch: x86 }
env:
- # load pip config from this file. Define this in 'CIBW_ENVIRONMENT'
- # because this should not affect cibuildwheel machinery
- # also define environment variables needed for testing
- CIBW_ENVIRONMENT: SDL_VIDEODRIVER=dummy SDL_AUDIODRIVER=disk
-
- CIBW_BUILD: ${{ matrix.pyversions }}
CIBW_ARCHS: ${{ matrix.winarch }}
- CIBW_BEFORE_BUILD: pip install numpy
-
- CIBW_TEST_COMMAND: python -m pygame.tests -v --exclude opengl,timing --time_out 300
-
- # Increase pip debugging output
- CIBW_BUILD_VERBOSITY: 2
-
steps:
- uses: actions/checkout@v4.1.7
@@ -146,11 +56,16 @@ jobs:
with:
arch: ${{ matrix.msvc-dev-arch }}
+ - name: Install uv for speed
+ uses: yezz123/setup-uv@v4
+ with:
+ uv-version: "0.2.22"
+
- name: Build and test wheels
- uses: pypa/cibuildwheel@v2.19.1
+ uses: pypa/cibuildwheel@v2.20.0
- uses: actions/upload-artifact@v4
with:
- name: pygame-wheels-windows-${{ matrix.name }}
+ name: pygame-wheels-windows-${{ matrix.winarch }}
path: ./wheelhouse/*.whl
compression-level: 0 # wheels are already zip files, no need for more compression
diff --git a/.github/workflows/release-gh-draft.yml b/.github/workflows/release-gh-draft.yml
index 05bb1046e2..7e8379d4e4 100644
--- a/.github/workflows/release-gh-draft.yml
+++ b/.github/workflows/release-gh-draft.yml
@@ -32,6 +32,11 @@ jobs:
draft-release:
needs: [manylinux-aarch64, manylinux, macos, windows, sdist]
runs-on: ubuntu-latest
+ permissions:
+ id-token: write
+ attestations: write
+ contents: write
+
steps:
- uses: actions/checkout@v4.1.7
@@ -55,6 +60,11 @@ jobs:
id: ver
run: echo "VER=${GITHUB_REF_NAME#'release/'}" >> $GITHUB_OUTPUT
+ - name: Generate release attestation
+ uses: actions/attest-build-provenance@v1.4.1
+ with:
+ subject-path: "pygame-wheels/*"
+
- name: Draft a release
uses: softprops/action-gh-release@v2
with:
diff --git a/.github/workflows/release-pypi.yml b/.github/workflows/release-pypi.yml
index 151cff57cb..11a6fd6f86 100644
--- a/.github/workflows/release-pypi.yml
+++ b/.github/workflows/release-pypi.yml
@@ -15,7 +15,7 @@ jobs:
- uses: actions/checkout@v4.1.7
- name: Pull all release assets
- uses: robinraju/release-downloader@v1.10
+ uses: robinraju/release-downloader@v1.11
with:
releaseId: ${{ github.event.release.id }}
fileName: "*"
@@ -23,5 +23,18 @@ jobs:
zipBall: false
out-file-path: "dist"
+ - name: Verify release attestation
+ env:
+ GH_TOKEN: ${{ github.token }}
+ run: |
+ for fname in dist/*; do
+ if gh attestation verify $fname -R ${{ github.repository }}; then
+ echo "[ALLOWED] $fname"
+ else
+ rm $fname
+ echo "[DELETED] $fname"
+ fi
+ done
+
- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 7195470ae1..c2e3793dfd 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -22,5 +22,6 @@ repos:
| docs/reST/_static/script.js
| docs/reST/_templates/header.h
| src_c/include/sse2neon.h
+ | src_c/include/pythoncapi_compat.h
| src_c/pypm.c
)$
diff --git a/README.rst b/README.rst
index 1b2241341f..9503d1a0db 100644
--- a/README.rst
+++ b/README.rst
@@ -1,4 +1,5 @@
-.. image:: https://raw.githubusercontent.com/pygame-community/pygame-ce/main/docs/reST/_static/pygame_logo.svg
+.. image:: https://raw.githubusercontent.com/pygame-community/pygame-ce/main/docs/reST/_static/pygame_ce_logo.svg
+ :width: 800
:alt: pygame
:target: https://pyga.me/
@@ -164,6 +165,7 @@ If you have any questions, please feel free to ask in the `Pygame Community Disc
License
-------
+**License Identifier:** LGPL-2.1-or-later
This library is distributed under `GNU LGPL version 2.1`_, which can
be found in the file ``docs/LGPL.txt``. We reserve the right to place
diff --git a/buildconfig/config.py b/buildconfig/config.py
index a84006366c..168baa6c21 100644
--- a/buildconfig/config.py
+++ b/buildconfig/config.py
@@ -19,6 +19,7 @@
import msysio
except ImportError:
import buildconfig.msysio as msysio
+from buildconfig.make_docs import run as docs_run
import sys, os, shutil, logging
import sysconfig
import re
@@ -165,6 +166,11 @@ def main(auto=False):
Only SDL2 is supported now.""")
kwds = {}
+
+ if len(sys.argv) > 1:
+ if sys.argv[1] == "docs":
+ sys.exit(docs_run())
+
if sys.platform == 'win32':
if sys.version_info >= (3, 8) and is_msys2():
print_('Using WINDOWS MSYS2 configuration...\n')
diff --git a/buildconfig/download_win_prebuilt.py b/buildconfig/download_win_prebuilt.py
index aefbce3a8a..134889a7ce 100644
--- a/buildconfig/download_win_prebuilt.py
+++ b/buildconfig/download_win_prebuilt.py
@@ -78,8 +78,8 @@ def get_urls(x86=True, x64=True):
url_sha1 = []
url_sha1.extend([
[
- 'https://github.com/libsdl-org/SDL/releases/download/release-2.30.3/SDL2-devel-2.30.3-VC.zip',
- '2878b4b1fbe9e4b22a317ad52c9d751c70e8df62',
+ 'https://github.com/libsdl-org/SDL/releases/download/release-2.30.6/SDL2-devel-2.30.6-VC.zip',
+ '038ffed56e04fbc4c6236187472c671596963fdb',
],
[
'https://github.com/pygame-community/SDL_image/releases/download/2.8.2-pgce/SDL2_image-devel-2.8.2-VCpgce.zip',
@@ -234,12 +234,12 @@ def copy(src, dst):
copy(
os.path.join(
temp_dir,
- 'SDL2-devel-2.30.3-VC/SDL2-2.30.3'
+ 'SDL2-devel-2.30.6-VC/SDL2-2.30.6'
),
os.path.join(
move_to_dir,
prebuilt_dir,
- 'SDL2-2.30.3'
+ 'SDL2-2.30.6'
)
)
diff --git a/buildconfig/manylinux-build/docker_base/Dockerfile-aarch64 b/buildconfig/manylinux-build/docker_base/Dockerfile-aarch64
index 8eb9c898b1..ed26478e24 100644
--- a/buildconfig/manylinux-build/docker_base/Dockerfile-aarch64
+++ b/buildconfig/manylinux-build/docker_base/Dockerfile-aarch64
@@ -1,4 +1,5 @@
-FROM quay.io/pypa/manylinux2014_aarch64
+# pin version on image for CI stability
+FROM quay.io/pypa/manylinux2014_aarch64:2024.08.03-1
ENV MAKEFLAGS="-j 2"
ENV PG_DEP_PREFIX="/usr/local"
diff --git a/buildconfig/manylinux-build/docker_base/Dockerfile-i686 b/buildconfig/manylinux-build/docker_base/Dockerfile-i686
index d516a4581f..c29e4d33ae 100644
--- a/buildconfig/manylinux-build/docker_base/Dockerfile-i686
+++ b/buildconfig/manylinux-build/docker_base/Dockerfile-i686
@@ -1,4 +1,5 @@
-FROM quay.io/pypa/manylinux2014_i686
+# pin version on image for CI stability
+FROM quay.io/pypa/manylinux2014_i686:2024.08.03-1
ENV MAKEFLAGS="-j 2"
ENV PG_DEP_PREFIX="/usr/local"
diff --git a/buildconfig/manylinux-build/docker_base/Dockerfile-x86_64 b/buildconfig/manylinux-build/docker_base/Dockerfile-x86_64
index e712fe8e3f..6f67121c94 100644
--- a/buildconfig/manylinux-build/docker_base/Dockerfile-x86_64
+++ b/buildconfig/manylinux-build/docker_base/Dockerfile-x86_64
@@ -1,4 +1,5 @@
-FROM quay.io/pypa/manylinux2014_x86_64
+# pin version on image for CI stability
+FROM quay.io/pypa/manylinux2014_x86_64:2024.08.03-1
ENV MAKEFLAGS="-j 2"
ENV PG_DEP_PREFIX="/usr/local"
diff --git a/buildconfig/manylinux-build/docker_base/sdl_libs/build-sdl2-libs.sh b/buildconfig/manylinux-build/docker_base/sdl_libs/build-sdl2-libs.sh
index 746ac2aa42..4e86991280 100644
--- a/buildconfig/manylinux-build/docker_base/sdl_libs/build-sdl2-libs.sh
+++ b/buildconfig/manylinux-build/docker_base/sdl_libs/build-sdl2-libs.sh
@@ -3,7 +3,7 @@ set -e -x
cd $(dirname `readlink -f "$0"`)
-SDL2_VER="2.30.3"
+SDL2_VER="2.30.6"
SDL2="SDL2-$SDL2_VER"
IMG2_VER="2.8.2"
IMG2="SDL2_image-$IMG2_VER"
diff --git a/buildconfig/manylinux-build/docker_base/sdl_libs/sdl2.sha512 b/buildconfig/manylinux-build/docker_base/sdl_libs/sdl2.sha512
index 5a2aa027f9..99e0d02042 100644
--- a/buildconfig/manylinux-build/docker_base/sdl_libs/sdl2.sha512
+++ b/buildconfig/manylinux-build/docker_base/sdl_libs/sdl2.sha512
@@ -1,4 +1,4 @@
-75ddea9ac3c2130e9f6036b4718dbe3d9d1b40dab3cd48d3d488d5a397dd0c226c0573bf18d82eb41498800accf045e259d2ae305f069fad7b34e47a017f2372 SDL2-2.30.3.tar.gz
+c73fd0b52e77d248a5dc2808a660ded9e708b87f68f677970da71e9b4a25ec60cf52f319b8b014ced7c9363560f2360262c04551a895f0f83ee62c3feeafba47 SDL2-2.30.6.tar.gz
0ff345824f95158dfa72f83f9d4a540601c178cd759334bf849c14a2920b5330d0763413b58c08b3deba8d3a4ccb6ea2a8159f87efe4cbb0e8ea850f63d09454 SDL2_image-2.8.2.tar.gz
5ddbc4b0b5fad2e0844a503daa79564b912654192599ef8fa7698531f08323ce01801f6bb17b2b3905020a3df362a967b7566ae725eb085da991578cc0807aad SDL2_mixer-2.8.0.tar.gz
34a1d210d8f1b1e802139d65ba47e36033bb7881e75a8862c1b1c515565bef85e3d81ee42e952aa664de043debef387ba60088a9cf3ba3297413db39a13af912 SDL2_ttf-2.22.0.tar.gz
diff --git a/buildconfig/stubs/gen_stubs.py b/buildconfig/stubs/gen_stubs.py
index 670db2f951..a762693f41 100644
--- a/buildconfig/stubs/gen_stubs.py
+++ b/buildconfig/stubs/gen_stubs.py
@@ -4,6 +4,7 @@
"""
import pathlib
+import shutil
from typing import Any
import pygame.constants
@@ -51,6 +52,7 @@
"system",
"geometry",
"window",
+ "typing",
]
# pygame classes that are autoimported into main namespace are kept in this dict
@@ -151,3 +153,8 @@ def get_all(mod: Any):
for element in get_all(pygame.locals):
constant_type = getattr(pygame.locals, element).__class__.__name__
f.write(f"{element}: {constant_type}\n")
+
+# copy typing.py to typing.pyi for type checkers
+typing_py_file = pathlib.Path(__file__).parent.parent.parent / "src_py" / "typing.py"
+typing_stub_file = pathlib.Path(__file__).parent / "pygame" / "typing.pyi"
+shutil.copyfile(typing_py_file, typing_stub_file)
diff --git a/buildconfig/stubs/mypy_allow_list.txt b/buildconfig/stubs/mypy_allow_list.txt
index c6f3ffa15b..4f2ef7834d 100644
--- a/buildconfig/stubs/mypy_allow_list.txt
+++ b/buildconfig/stubs/mypy_allow_list.txt
@@ -2,10 +2,6 @@
# listed here are not checked by the mypy stubtest program
# This allowlist supports regex
-# This is not a real typestub file, it is used only in the typestubs to export
-# a few utility typestub definitions
-pygame\._common
-
# cython files have this top level dunder
pygame\._sdl2\..*\.__test__
diff --git a/buildconfig/stubs/pygame/__init__.pyi b/buildconfig/stubs/pygame/__init__.pyi
index c05a428fb4..00690f3737 100644
--- a/buildconfig/stubs/pygame/__init__.pyi
+++ b/buildconfig/stubs/pygame/__init__.pyi
@@ -38,6 +38,7 @@ from pygame import (
system as system,
geometry as geometry,
window as window,
+ typing as typing,
)
from .rect import Rect as Rect, FRect as FRect
@@ -183,6 +184,9 @@ from .constants import (
FINGERDOWN as FINGERDOWN,
FINGERMOTION as FINGERMOTION,
FINGERUP as FINGERUP,
+ FLASH_BRIEFLY as FLASH_BRIEFLY,
+ FLASH_CANCEL as FLASH_CANCEL,
+ FLASH_UNTIL_FOCUSED as FLASH_UNTIL_FOCUSED,
FONT_CENTER as FONT_CENTER,
FONT_LEFT as FONT_LEFT,
FONT_RIGHT as FONT_RIGHT,
diff --git a/buildconfig/stubs/pygame/_common.pyi b/buildconfig/stubs/pygame/_common.pyi
deleted file mode 100644
index 19e958a724..0000000000
--- a/buildconfig/stubs/pygame/_common.pyi
+++ /dev/null
@@ -1,104 +0,0 @@
-from os import PathLike
-from typing import IO, Callable, Tuple, Union, TypeVar
-
-from typing_extensions import Literal as Literal, SupportsIndex as SupportsIndex
-from typing_extensions import Protocol
-
-# For functions that take a file name
-AnyPath = Union[str, bytes, PathLike[str], PathLike[bytes]]
-
-# Most pygame functions that take a file argument should be able to handle
-# a FileArg type
-FileArg = Union[AnyPath, IO[bytes], IO[str]]
-
-_T = TypeVar("_T", covariant=True)
-
-class Sequence(Protocol[_T]):
- """
- This is different from the standard python 'Sequence' abc. This is used in places
- where only __getitem__ and __len__ is actually needed (basically almost all places
- where a sequence is used). The standard 'Sequence' abc has some extra methods.
- """
- def __getitem__(self, __i: SupportsIndex) -> _T: ...
- def __len__(self) -> int: ...
-
-# Right now, it isn't possible to annotate sizes (popular tools don't support it) but
-# when it is, the below types should be appropriately annotated
-
-# Yes, float. The reason being, pygame handles float without erroring in a lot of places
-# where a coordinate is expected (usually by rounding to int).
-# Also, 'Union[int, float] == float'
-Coordinate = Sequence[float]
-
-# This is used in places where ints are strictly required
-IntCoordinate = Sequence[int]
-
-# This typehint is used when a function would return an RGBA tuple
-RGBAOutput = Tuple[int, int, int, int]
-ColorValue = Union[int, str, Sequence[int]]
-
-_CanBeRect = Sequence[Union[float, Coordinate]]
-
-class _HasRectAttribute(Protocol):
- # An object that has a rect attribute that is either a rect, or a function
- # that returns a rect confirms to the rect protocol
- rect: Union[RectValue, Callable[[], RectValue]]
-
-RectValue = Union[_CanBeRect, _HasRectAttribute]
-
-"""
-# testing code
-def a(b: Coordinate):
- b[0]
- b[1]
- len(b)
- e1, e2 = b
- for i in b:
- i -= 1
-
-
-import numpy
-from pygame import Vector2
-
-class MyAmoger:
- def __init__(self):
- pass
-
- def __getitem__(self, index):
- if index not in (0, 1):
- raise IndexError()
-
- return 42 if index else 69
-
- def __len__(self):
- return 2
-
-
-# should pass
-a([1, 2])
-a([4.2, 5.2])
-a((1, 2))
-a((1.4, 2.8))
-a(MyAmoger())
-a(range(2, 4)) # yes, range object is a 'Sequence'
-a(numpy.array([1.3, 2.1]))
-a(b"ab") # weird but this actually works in code (this represents (97, 98) btw)
-a(bytearray([1, 2]))
-a(Vector2())
-
-print("Done testing the passes!")
-
-# should technically error, but right now we can't annotate sizes so they pass on
-# type testing
-a([1, 2, 3])
-a([4.2, 5.2, 2, 4])
-a((1,))
-a(numpy.array([1.3, 2.1, 4.2]))
-
-# all of the below should always error
-a({})
-a({1: 2})
-a("abc")
-a({1, 2})
-
-"""
diff --git a/buildconfig/stubs/pygame/_sdl2/video.pyi b/buildconfig/stubs/pygame/_sdl2/video.pyi
index dd12ae5f80..b34970d4eb 100644
--- a/buildconfig/stubs/pygame/_sdl2/video.pyi
+++ b/buildconfig/stubs/pygame/_sdl2/video.pyi
@@ -5,7 +5,7 @@ from pygame.rect import Rect
from pygame.surface import Surface
from pygame.window import Window as Window
-from .._common import ColorValue, RectValue, Coordinate
+from pygame.typing import ColorLike, RectLike, Coordinate
WINDOWPOS_UNDEFINED: int
WINDOWPOS_CENTERED: int
@@ -60,13 +60,13 @@ class Texture:
@property
def color(self) -> Color: ...
@color.setter
- def color(self, value: ColorValue) -> None: ...
+ def color(self, value: ColorLike) -> None: ...
def get_rect(self, **kwargs: Any) -> Rect: ...
def draw(
self,
- srcrect: Optional[RectValue] = None,
- dstrect: Optional[RectValue] = None,
+ srcrect: Optional[RectLike] = None,
+ dstrect: Optional[RectLike] = None,
angle: float = 0.0,
origin: Optional[Iterable[int]] = None,
flip_x: bool = False,
@@ -99,17 +99,17 @@ class Texture:
p3_mod: Iterable[int] = (255, 255, 255, 255),
p4_mod: Iterable[int] = (255, 255, 255, 255),
) -> None: ...
- def update(self, surface: Surface, area: Optional[RectValue] = None) -> None: ...
+ def update(self, surface: Surface, area: Optional[RectLike] = None) -> None: ...
class Image:
def __init__(
self,
texture_or_image: Union[Texture, Image],
- srcrect: Optional[RectValue] = None,
+ srcrect: Optional[RectLike] = None,
) -> None: ...
def get_rect(self) -> Rect: ...
def draw(
- self, srcrect: Optional[RectValue] = None, dstrect: Optional[RectValue] = None
+ self, srcrect: Optional[RectLike] = None, dstrect: Optional[RectLike] = None
) -> None: ...
angle: float
origin: Optional[Iterable[float]]
@@ -123,7 +123,7 @@ class Image:
@property
def color(self) -> Color: ...
@color.setter
- def color(self, value: ColorValue) -> None: ...
+ def color(self, value: ColorLike) -> None: ...
class Renderer:
def __init__(
@@ -140,25 +140,25 @@ class Renderer:
@property
def draw_color(self) -> Color: ...
@draw_color.setter
- def draw_color(self, value: ColorValue) -> None: ...
+ def draw_color(self, value: ColorLike) -> None: ...
def clear(self) -> None: ...
def present(self) -> None: ...
def get_viewport(self) -> Rect: ...
- def set_viewport(self, area: Optional[RectValue]) -> None: ...
+ def set_viewport(self, area: Optional[RectLike]) -> None: ...
logical_size: Iterable[int]
scale: Iterable[float]
target: Optional[Texture]
def blit(
self,
source: Union[Texture, Image],
- dest: Optional[RectValue] = None,
- area: Optional[RectValue] = None,
+ dest: Optional[RectLike] = None,
+ area: Optional[RectLike] = None,
special_flags: int = 0,
) -> Rect: ...
def draw_line(self, p1: Coordinate, p2: Coordinate) -> None: ...
def draw_point(self, point: Coordinate) -> None: ...
- def draw_rect(self, rect: RectValue) -> None: ...
- def fill_rect(self, rect: RectValue) -> None: ...
+ def draw_rect(self, rect: RectLike) -> None: ...
+ def fill_rect(self, rect: RectLike) -> None: ...
def draw_triangle(
self, p1: Coordinate, p2: Coordinate, p3: Coordinate
) -> None: ...
@@ -172,7 +172,7 @@ class Renderer:
self, p1: Coordinate, p2: Coordinate, p3: Coordinate, p4: Coordinate
) -> None: ...
def to_surface(
- self, surface: Optional[Surface] = None, area: Optional[RectValue] = None
+ self, surface: Optional[Surface] = None, area: Optional[RectLike] = None
) -> Surface: ...
@staticmethod
def compose_custom_blend_mode(
diff --git a/buildconfig/stubs/pygame/camera.pyi b/buildconfig/stubs/pygame/camera.pyi
index 5de3a41194..de5eac1767 100644
--- a/buildconfig/stubs/pygame/camera.pyi
+++ b/buildconfig/stubs/pygame/camera.pyi
@@ -1,7 +1,7 @@
from abc import ABC, abstractmethod
from typing import List, Optional, Tuple, Union, Literal
-from ._common import IntCoordinate
+from pygame.typing import IntCoordinate
from pygame.surface import Surface
diff --git a/buildconfig/stubs/pygame/color.pyi b/buildconfig/stubs/pygame/color.pyi
index d53344de1d..635b05d4ea 100644
--- a/buildconfig/stubs/pygame/color.pyi
+++ b/buildconfig/stubs/pygame/color.pyi
@@ -1,7 +1,8 @@
import sys
-from typing import Any, Dict, Iterator, Tuple, Union, overload
+from typing import Any, Dict, Iterator, SupportsIndex, Tuple, Union, overload
+from typing_extensions import deprecated # added in 3.13
-from ._common import ColorValue, SupportsIndex
+from pygame.typing import ColorLike
if sys.version_info >= (3, 9):
from collections.abc import Collection
@@ -27,7 +28,7 @@ class Color(Collection[int]):
@overload
def __init__(self, r: int, g: int, b: int, a: int = 255) -> None: ...
@overload
- def __init__(self, rgbvalue: ColorValue) -> None: ...
+ def __init__(self, rgbvalue: ColorLike) -> None: ...
@overload
def __getitem__(self, i: SupportsIndex) -> int: ...
@overload
@@ -79,11 +80,12 @@ class Color(Collection[int]):
def from_normalized(cls, r: float, g: float, b: float, a: float, /) -> Color: ...
def normalize(self) -> Tuple[float, float, float, float]: ...
def correct_gamma(self, gamma: float, /) -> Color: ...
+ @deprecated("since 2.1.3. Use unpacking instead")
def set_length(self, length: int, /) -> None: ...
- def lerp(self, color: ColorValue, amount: float) -> Color: ...
+ def lerp(self, color: ColorLike, amount: float) -> Color: ...
def premul_alpha(self) -> Color: ...
def grayscale(self) -> Color: ...
@overload
def update(self, r: int, g: int, b: int, a: int = 255, /) -> None: ...
@overload
- def update(self, rgbvalue: ColorValue, /) -> None: ...
+ def update(self, rgbvalue: ColorLike, /) -> None: ...
diff --git a/buildconfig/stubs/pygame/constants.pyi b/buildconfig/stubs/pygame/constants.pyi
index 9ed59ec44f..3d793e3c50 100644
--- a/buildconfig/stubs/pygame/constants.pyi
+++ b/buildconfig/stubs/pygame/constants.pyi
@@ -107,6 +107,9 @@ DROPTEXT: int
FINGERDOWN: int
FINGERMOTION: int
FINGERUP: int
+FLASH_BRIEFLY: int
+FLASH_CANCEL: int
+FLASH_UNTIL_FOCUSED: int
FONT_CENTER: int
FONT_LEFT: int
FONT_RIGHT: int
diff --git a/buildconfig/stubs/pygame/cursors.pyi b/buildconfig/stubs/pygame/cursors.pyi
index d6b610946e..d75a66cc77 100644
--- a/buildconfig/stubs/pygame/cursors.pyi
+++ b/buildconfig/stubs/pygame/cursors.pyi
@@ -1,8 +1,8 @@
-from typing import Any, Iterator, Tuple, Union, overload
+from typing import Any, Iterator, Literal, Tuple, Union, overload
from pygame.surface import Surface
-from ._common import FileArg, Literal, IntCoordinate, Sequence
+from pygame.typing import FileLike, IntCoordinate, SequenceLike
_Small_string = Tuple[
str, str, str, str, str, str, str, str, str, str, str, str, str, str, str, str
@@ -47,13 +47,13 @@ sizer_xy_strings: _Small_string
textmarker_strings: _Small_string
def compile(
- strings: Sequence[str],
+ strings: SequenceLike[str],
black: str = "X",
white: str = ".",
xor: str = "o",
) -> Tuple[Tuple[int, ...], Tuple[int, ...]]: ...
def load_xbm(
- curs: FileArg, mask: FileArg
+ curs: FileLike, mask: FileLike
) -> Tuple[Tuple[int, int], Tuple[int, int], Tuple[int, ...], Tuple[int, ...]]: ...
class Cursor:
@@ -66,8 +66,8 @@ class Cursor:
self,
size: IntCoordinate,
hotspot: IntCoordinate,
- xormasks: Sequence[int],
- andmasks: Sequence[int],
+ xormasks: SequenceLike[int],
+ andmasks: SequenceLike[int],
) -> None: ...
@overload
def __init__(
diff --git a/buildconfig/stubs/pygame/display.pyi b/buildconfig/stubs/pygame/display.pyi
index 399f462ffa..eb9f29a5c1 100644
--- a/buildconfig/stubs/pygame/display.pyi
+++ b/buildconfig/stubs/pygame/display.pyi
@@ -1,17 +1,18 @@
-from typing import Dict, List, Optional, Tuple, Union, overload, Literal
+from typing import Dict, List, Optional, Tuple, Union, overload, Literal, Iterable
+from typing_extensions import deprecated # added in 3.13
from pygame.constants import FULLSCREEN
from pygame.surface import Surface
from pygame._sdl2 import Window
-from ._common import (
- ColorValue,
+from pygame.typing import (
+ ColorLike,
Coordinate,
IntCoordinate,
- RectValue,
- RGBAOutput,
- Sequence,
+ RectLike,
+ RGBATuple,
+ SequenceLike,
)
class _VidInfo:
@@ -20,9 +21,9 @@ class _VidInfo:
video_mem: int
bitsize: int
bytesize: int
- masks: RGBAOutput
- shifts: RGBAOutput
- losses: RGBAOutput
+ masks: RGBATuple
+ shifts: RGBATuple
+ losses: RGBATuple
blit_hw: int
blit_hw_CC: int
blit_hw_A: int
@@ -47,7 +48,7 @@ def get_surface() -> Surface: ...
def flip() -> None: ...
@overload
def update(
- rectangle: Optional[Union[RectValue, Sequence[Optional[RectValue]]]] = None, /
+ rectangle: Optional[Union[RectLike, Iterable[Optional[RectLike]]]] = None, /
) -> None: ...
@overload
def update(x: int, y: int, w: int, h: int, /) -> None: ...
@@ -72,14 +73,16 @@ def gl_set_attribute(flag: int, value: int, /) -> None: ...
def get_active() -> bool: ...
def iconify() -> bool: ...
def toggle_fullscreen() -> int: ...
+@deprecated("since 2.1.4. Removed in SDL3")
def set_gamma(red: float, green: float = ..., blue: float = ..., /) -> int: ...
+@deprecated("since 2.1.4. Removed in SDL3")
def set_gamma_ramp(
- red: Sequence[int], green: Sequence[int], blue: Sequence[int], /
+ red: SequenceLike[int], green: SequenceLike[int], blue: SequenceLike[int], /
) -> int: ...
def set_icon(surface: Surface, /) -> None: ...
def set_caption(title: str, icontitle: Optional[str] = None, /) -> None: ...
def get_caption() -> Tuple[str, str]: ...
-def set_palette(palette: Sequence[ColorValue], /) -> None: ...
+def set_palette(palette: SequenceLike[ColorLike], /) -> None: ...
def get_num_displays() -> int: ...
def get_window_size() -> Tuple[int, int]: ...
def get_window_position() -> Tuple[int, int]:...
@@ -96,7 +99,7 @@ def message_box(
message: Optional[str] = None,
message_type: Literal["info", "warn", "error"] = "info",
parent_window: Optional[Window] = None,
- buttons: Sequence[str] = ("OK",),
+ buttons: SequenceLike[str] = ("OK",),
return_button: int = 0,
escape_button: Optional[int] = None,
) -> int: ...
diff --git a/buildconfig/stubs/pygame/draw.pyi b/buildconfig/stubs/pygame/draw.pyi
index db2c345a4d..57bae66712 100644
--- a/buildconfig/stubs/pygame/draw.pyi
+++ b/buildconfig/stubs/pygame/draw.pyi
@@ -2,12 +2,12 @@ from pygame.rect import Rect
from pygame.surface import Surface
from typing import overload
-from ._common import ColorValue, Coordinate, RectValue, Sequence
+from pygame.typing import ColorLike, Coordinate, RectLike, SequenceLike
def rect(
surface: Surface,
- color: ColorValue,
- rect: RectValue,
+ color: ColorLike,
+ rect: RectLike,
width: int = 0,
border_radius: int = -1,
border_top_left_radius: int = -1,
@@ -17,13 +17,13 @@ def rect(
) -> Rect: ...
def polygon(
surface: Surface,
- color: ColorValue,
- points: Sequence[Coordinate],
+ color: ColorLike,
+ points: SequenceLike[Coordinate],
width: int = 0,
) -> Rect: ...
def circle(
surface: Surface,
- color: ColorValue,
+ color: ColorLike,
center: Coordinate,
radius: float,
width: int = 0,
@@ -35,7 +35,7 @@ def circle(
@overload
def aacircle(
surface: Surface,
- color: ColorValue,
+ color: ColorLike,
center: Coordinate,
radius: float,
width: int = 0,
@@ -43,7 +43,7 @@ def aacircle(
@overload
def aacircle(
surface: Surface,
- color: ColorValue,
+ color: ColorLike,
center: Coordinate,
radius: float,
width: int = 0,
@@ -53,39 +53,39 @@ def aacircle(
draw_bottom_right: bool = False,
) -> Rect: ...
def ellipse(
- surface: Surface, color: ColorValue, rect: RectValue, width: int = 0
+ surface: Surface, color: ColorLike, rect: RectLike, width: int = 0
) -> Rect: ...
def arc(
surface: Surface,
- color: ColorValue,
- rect: RectValue,
+ color: ColorLike,
+ rect: RectLike,
start_angle: float,
stop_angle: float,
width: int = 1,
) -> Rect: ...
def line(
surface: Surface,
- color: ColorValue,
+ color: ColorLike,
start_pos: Coordinate,
end_pos: Coordinate,
width: int = 1,
) -> Rect: ...
def lines(
surface: Surface,
- color: ColorValue,
+ color: ColorLike,
closed: bool,
- points: Sequence[Coordinate],
+ points: SequenceLike[Coordinate],
width: int = 1,
) -> Rect: ...
def aaline(
surface: Surface,
- color: ColorValue,
+ color: ColorLike,
start_pos: Coordinate,
end_pos: Coordinate,
) -> Rect: ...
def aalines(
surface: Surface,
- color: ColorValue,
+ color: ColorLike,
closed: bool,
- points: Sequence[Coordinate],
+ points: SequenceLike[Coordinate],
) -> Rect: ...
diff --git a/buildconfig/stubs/pygame/event.pyi b/buildconfig/stubs/pygame/event.pyi
index dc9a1c852f..1987f96de9 100644
--- a/buildconfig/stubs/pygame/event.pyi
+++ b/buildconfig/stubs/pygame/event.pyi
@@ -7,7 +7,7 @@ from typing import (
final,
)
-from ._common import Sequence
+from pygame.typing import SequenceLike
@final
class Event:
@@ -23,7 +23,7 @@ class Event:
def __delattr__(self, name: str) -> None: ...
def __bool__(self) -> bool: ...
-_EventTypes = Union[int, Sequence[int]]
+_EventTypes = Union[int, SequenceLike[int]]
def pump() -> None: ...
def get(
diff --git a/buildconfig/stubs/pygame/font.pyi b/buildconfig/stubs/pygame/font.pyi
index 683a28ef2b..ea1074cee7 100644
--- a/buildconfig/stubs/pygame/font.pyi
+++ b/buildconfig/stubs/pygame/font.pyi
@@ -1,8 +1,8 @@
-from typing import Callable, Hashable, Iterable, List, Optional, Tuple, Union
+from typing import Callable, Hashable, Iterable, List, Literal, Optional, Tuple, Union
from pygame.surface import Surface
-from ._common import ColorValue, FileArg, Literal
+from pygame.typing import ColorLike, FileLike
# TODO: Figure out a way to type this attribute such that mypy knows it's not
# always defined at runtime
@@ -56,13 +56,13 @@ class Font:
def point_size(self) -> int: ...
@point_size.setter
def point_size(self, value: int) -> None: ...
- def __init__(self, filename: Optional[FileArg] = None, size: int = 20) -> None: ...
+ def __init__(self, filename: Optional[FileLike] = None, size: int = 20) -> None: ...
def render(
self,
text: Union[str, bytes, None],
antialias: bool,
- color: ColorValue,
- bgcolor: Optional[ColorValue] = None,
+ color: ColorLike,
+ bgcolor: Optional[ColorLike] = None,
wraplength: int = 0,
) -> Surface: ...
def size(self, text: Union[str, bytes], /) -> Tuple[int, int]: ...
diff --git a/buildconfig/stubs/pygame/freetype.pyi b/buildconfig/stubs/pygame/freetype.pyi
index 54751f8385..8ce24499b8 100644
--- a/buildconfig/stubs/pygame/freetype.pyi
+++ b/buildconfig/stubs/pygame/freetype.pyi
@@ -1,16 +1,18 @@
from typing import Any, Callable, Iterable, List, Optional, Tuple, Union
+from typing_extensions import deprecated # added in 3.13
from pygame.color import Color
from pygame.rect import Rect
from pygame.surface import Surface
-from ._common import ColorValue, FileArg, RectValue
+from pygame.typing import ColorLike, FileLike, RectLike
def get_error() -> str: ...
def get_version(linked: bool = True) -> Tuple[int, int, int]: ...
def init(cache_size: int = 64, resolution: int = 72) -> None: ...
def quit() -> None: ...
def get_init() -> bool: ...
+@deprecated("Use `pygame.freetype.get_init` instead")
def was_init() -> bool: ...
def get_cache_size() -> int: ...
def get_default_resolution() -> int: ...
@@ -121,18 +123,18 @@ class Font:
@property
def fgcolor(self) -> Color: ...
@fgcolor.setter
- def fgcolor(self, value: ColorValue) -> None: ...
+ def fgcolor(self, value: ColorLike) -> None: ...
@property
def bgcolor(self) -> Color: ...
@bgcolor.setter
- def bgcolor(self, value: ColorValue) -> None: ...
+ def bgcolor(self, value: ColorLike) -> None: ...
@property
def origin(self) -> bool: ...
@origin.setter
def origin(self, value: bool) -> None: ...
def __init__(
self,
- file: Optional[FileArg],
+ file: Optional[FileLike],
size: float = 0,
font_index: int = 0,
resolution: int = 0,
@@ -156,8 +158,8 @@ class Font:
def render(
self,
text: str,
- fgcolor: Optional[ColorValue] = None,
- bgcolor: Optional[ColorValue] = None,
+ fgcolor: Optional[ColorLike] = None,
+ bgcolor: Optional[ColorLike] = None,
style: int = STYLE_DEFAULT,
rotation: int = 0,
size: float = 0,
@@ -165,10 +167,10 @@ class Font:
def render_to(
self,
surf: Surface,
- dest: RectValue,
+ dest: RectLike,
text: str,
- fgcolor: Optional[ColorValue] = None,
- bgcolor: Optional[ColorValue] = None,
+ fgcolor: Optional[ColorLike] = None,
+ bgcolor: Optional[ColorLike] = None,
style: int = STYLE_DEFAULT,
rotation: int = 0,
size: float = 0,
@@ -185,7 +187,7 @@ class Font:
self,
array: Any,
text: str,
- dest: Optional[RectValue] = None,
+ dest: Optional[RectLike] = None,
style: int = STYLE_DEFAULT,
rotation: int = 0,
size: float = 0,
diff --git a/buildconfig/stubs/pygame/geometry.pyi b/buildconfig/stubs/pygame/geometry.pyi
index fb7c8de850..c82da74b2c 100644
--- a/buildconfig/stubs/pygame/geometry.pyi
+++ b/buildconfig/stubs/pygame/geometry.pyi
@@ -4,15 +4,14 @@ from typing import (
Callable,
Protocol,
Tuple,
- Sequence,
)
from pygame import Rect, FRect
-from ._common import Coordinate, RectValue
+from pygame.typing import Coordinate, RectLike, SequenceLike
from .rect import Rect, FRect
from .math import Vector2
-_CanBeCircle = Union[Circle, Tuple[Coordinate, float], Sequence[float]]
+_CanBeCircle = Union[Circle, Tuple[Coordinate, float], SequenceLike[float]]
class _HasCirclettribute(Protocol):
# An object that has a circle attribute that is either a circle, or a function
@@ -88,7 +87,7 @@ class Circle:
@overload
def collidecircle(self, center: Coordinate, r: float, /) -> bool: ...
@overload
- def colliderect(self, rect: RectValue, /) -> bool: ...
+ def colliderect(self, rect: RectLike, /) -> bool: ...
@overload
def colliderect(self, x: float, y: float, w: float, h: float, /) -> bool: ...
@overload
diff --git a/buildconfig/stubs/pygame/gfxdraw.pyi b/buildconfig/stubs/pygame/gfxdraw.pyi
index d3592e6b7c..44a02d7d82 100644
--- a/buildconfig/stubs/pygame/gfxdraw.pyi
+++ b/buildconfig/stubs/pygame/gfxdraw.pyi
@@ -1,28 +1,28 @@
from pygame.surface import Surface
-from ._common import ColorValue, Coordinate, RectValue, Sequence
+from pygame.typing import ColorLike, Coordinate, RectLike, SequenceLike
-def pixel(surface: Surface, x: int, y: int, color: ColorValue, /) -> None: ...
-def hline(surface: Surface, x1: int, x2: int, y: int, color: ColorValue, /) -> None: ...
-def vline(surface: Surface, x: int, y1: int, y2: int, color: ColorValue, /) -> None: ...
+def pixel(surface: Surface, x: int, y: int, color: ColorLike, /) -> None: ...
+def hline(surface: Surface, x1: int, x2: int, y: int, color: ColorLike, /) -> None: ...
+def vline(surface: Surface, x: int, y1: int, y2: int, color: ColorLike, /) -> None: ...
def line(
- surface: Surface, x1: int, y1: int, x2: int, y2: int, color: ColorValue, /
+ surface: Surface, x1: int, y1: int, x2: int, y2: int, color: ColorLike, /
) -> None: ...
-def rectangle(surface: Surface, rect: RectValue, color: ColorValue, /) -> None: ...
-def box(surface: Surface, rect: RectValue, color: ColorValue, /) -> None: ...
-def circle(surface: Surface, x: int, y: int, r: int, color: ColorValue, /) -> None: ...
-def aacircle(surface: Surface, x: int, y: int, r: int, color: ColorValue, /) -> None: ...
+def rectangle(surface: Surface, rect: RectLike, color: ColorLike, /) -> None: ...
+def box(surface: Surface, rect: RectLike, color: ColorLike, /) -> None: ...
+def circle(surface: Surface, x: int, y: int, r: int, color: ColorLike, /) -> None: ...
+def aacircle(surface: Surface, x: int, y: int, r: int, color: ColorLike, /) -> None: ...
def filled_circle(
- surface: Surface, x: int, y: int, r: int, color: ColorValue, /
+ surface: Surface, x: int, y: int, r: int, color: ColorLike, /
) -> None: ...
def ellipse(
- surface: Surface, x: int, y: int, rx: int, ry: int, color: ColorValue, /
+ surface: Surface, x: int, y: int, rx: int, ry: int, color: ColorLike, /
) -> None: ...
def aaellipse(
- surface: Surface, x: int, y: int, rx: int, ry: int, color: ColorValue, /
+ surface: Surface, x: int, y: int, rx: int, ry: int, color: ColorLike, /
) -> None: ...
def filled_ellipse(
- surface: Surface, x: int, y: int, rx: int, ry: int, color: ColorValue, /
+ surface: Surface, x: int, y: int, rx: int, ry: int, color: ColorLike, /
) -> None: ...
def arc(
surface: Surface,
@@ -31,7 +31,7 @@ def arc(
r: int,
start_angle: int,
atp_angle: int,
- color: ColorValue, /
+ color: ColorLike, /
) -> None: ...
def pie(
surface: Surface,
@@ -40,7 +40,7 @@ def pie(
r: int,
start_angle: int,
atp_angle: int,
- color: ColorValue, /
+ color: ColorLike, /
) -> None: ...
def trigon(
surface: Surface,
@@ -50,7 +50,7 @@ def trigon(
y2: int,
x3: int,
y3: int,
- color: ColorValue, /
+ color: ColorLike, /
) -> None: ...
def aatrigon(
surface: Surface,
@@ -60,7 +60,7 @@ def aatrigon(
y2: int,
x3: int,
y3: int,
- color: ColorValue, /
+ color: ColorLike, /
) -> None: ...
def filled_trigon(
surface: Surface,
@@ -70,20 +70,20 @@ def filled_trigon(
y2: int,
x3: int,
y3: int,
- color: ColorValue, /
+ color: ColorLike, /
) -> None: ...
def polygon(
- surface: Surface, points: Sequence[Coordinate], color: ColorValue, /
+ surface: Surface, points: SequenceLike[Coordinate], color: ColorLike, /
) -> None: ...
def aapolygon(
- surface: Surface, points: Sequence[Coordinate], color: ColorValue, /
+ surface: Surface, points: SequenceLike[Coordinate], color: ColorLike, /
) -> None: ...
def filled_polygon(
- surface: Surface, points: Sequence[Coordinate], color: ColorValue, /
+ surface: Surface, points: SequenceLike[Coordinate], color: ColorLike, /
) -> None: ...
def textured_polygon(
- surface: Surface, points: Sequence[Coordinate], texture: Surface, tx: int, ty: int, /
+ surface: Surface, points: SequenceLike[Coordinate], texture: Surface, tx: int, ty: int, /
) -> None: ...
def bezier(
- surface: Surface, points: Sequence[Coordinate], steps: int, color: ColorValue, /
+ surface: Surface, points: SequenceLike[Coordinate], steps: int, color: ColorLike, /
) -> None: ...
diff --git a/buildconfig/stubs/pygame/image.pyi b/buildconfig/stubs/pygame/image.pyi
index a9a00a8504..0e02f02271 100644
--- a/buildconfig/stubs/pygame/image.pyi
+++ b/buildconfig/stubs/pygame/image.pyi
@@ -1,47 +1,48 @@
-from typing import Optional, Tuple, Union
+from typing import Literal, Optional, Tuple, Union
+from typing_extensions import deprecated # added in 3.13
from pygame.bufferproxy import BufferProxy
from pygame.surface import Surface
-from ._common import FileArg, Literal, IntCoordinate, Coordinate
+from pygame.typing import FileLike, IntCoordinate, Coordinate
_BufferStyle = Union[BufferProxy, bytes, bytearray, memoryview]
-_to_string_format = Literal[
- "P", "RGB", "RGBX", "RGBA", "ARGB", "BGRA", "RGBA_PREMULT", "ARGB_PREMULT"
+_to_bytes_format = Literal[
+ "P", "RGB", "RGBX", "RGBA", "ARGB", "BGRA", "ABGR", "RGBA_PREMULT", "ARGB_PREMULT"
]
_from_buffer_format = Literal["P", "RGB", "BGR", "BGRA", "RGBX", "RGBA", "ARGB"]
-_from_string_format = Literal["P", "RGB", "RGBX", "RGBA", "ARGB", "BGRA"]
+_from_bytes_format = Literal["P", "RGB", "RGBX", "RGBA", "ARGB", "BGRA", "ABGR"]
-def load(file: FileArg, namehint: str = "") -> Surface: ...
-def load_sized_svg(file: FileArg, size: Coordinate) -> Surface: ...
-def save(surface: Surface, file: FileArg, namehint: str = "") -> None: ...
+def load(file: FileLike, namehint: str = "") -> Surface: ...
+def load_sized_svg(file: FileLike, size: Coordinate) -> Surface: ...
+def save(surface: Surface, file: FileLike, namehint: str = "") -> None: ...
def get_sdl_image_version(linked: bool = True) -> Optional[Tuple[int, int, int]]: ...
def get_extended() -> bool: ...
+@deprecated("since 2.3.0. Use `pygame.image.tobytes` instead")
def tostring(
surface: Surface,
- format: _to_string_format,
+ format: _to_bytes_format,
flipped: bool = False,
pitch: int = -1,
) -> bytes: ...
+@deprecated("since 2.3.0. Use `pygame.image.frombytes` instead")
def fromstring(
bytes: bytes,
size: IntCoordinate,
- format: _from_string_format,
+ format: _from_bytes_format,
flipped: bool = False,
pitch: int = -1,
) -> Surface: ...
-
-# the use of tobytes/frombytes is preferred over tostring/fromstring
def tobytes(
surface: Surface,
- format: _to_string_format,
+ format: _to_bytes_format,
flipped: bool = False,
pitch: int = -1,
) -> bytes: ...
def frombytes(
bytes: bytes,
size: IntCoordinate,
- format: _from_string_format,
+ format: _from_bytes_format,
flipped: bool = False,
pitch: int = -1,
) -> Surface: ...
@@ -51,6 +52,6 @@ def frombuffer(
format: _from_buffer_format,
pitch: int = -1,
) -> Surface: ...
-def load_basic(file: FileArg, /) -> Surface: ...
-def load_extended(file: FileArg, namehint: str = "") -> Surface: ...
-def save_extended(surface: Surface, file: FileArg, namehint: str = "") -> None: ...
+def load_basic(file: FileLike, /) -> Surface: ...
+def load_extended(file: FileLike, namehint: str = "") -> Surface: ...
+def save_extended(surface: Surface, file: FileLike, namehint: str = "") -> None: ...
diff --git a/buildconfig/stubs/pygame/joystick.pyi b/buildconfig/stubs/pygame/joystick.pyi
index c24a7ea88e..2936e5a2b3 100644
--- a/buildconfig/stubs/pygame/joystick.pyi
+++ b/buildconfig/stubs/pygame/joystick.pyi
@@ -1,4 +1,5 @@
from typing import Tuple, final
+from typing_extensions import deprecated # added in 3.13
def init() -> None: ...
def quit() -> None: ...
@@ -7,9 +8,11 @@ def get_count() -> int: ...
@final
class JoystickType:
def __init__(self, id: int) -> None: ...
+ @deprecated("since 2.0.0. Multiple initializations are not supported anymore")
def init(self) -> None: ...
def quit(self) -> None: ...
def get_init(self) -> bool: ...
+ @deprecated("since 2.0.0. Use `pygame.Joystick.get_instance_id` instead")
def get_id(self) -> int: ...
def get_instance_id(self) -> int: ...
def get_guid(self) -> str: ...
diff --git a/buildconfig/stubs/pygame/key.pyi b/buildconfig/stubs/pygame/key.pyi
index c55f1500d1..09aa380b35 100644
--- a/buildconfig/stubs/pygame/key.pyi
+++ b/buildconfig/stubs/pygame/key.pyi
@@ -1,6 +1,6 @@
from typing import Tuple
-from ._common import RectValue
+from pygame.typing import RectLike
class ScancodeWrapper(Tuple[bool, ...]): ...
@@ -16,4 +16,4 @@ def name(key: int, use_compat: bool = True) -> str: ...
def key_code(name: str) -> int: ...
def start_text_input() -> None: ...
def stop_text_input() -> None: ...
-def set_text_input_rect(rect: RectValue, /) -> None: ...
+def set_text_input_rect(rect: RectLike, /) -> None: ...
diff --git a/buildconfig/stubs/pygame/locals.pyi b/buildconfig/stubs/pygame/locals.pyi
index 8aa04e2610..ef822c62d6 100644
--- a/buildconfig/stubs/pygame/locals.pyi
+++ b/buildconfig/stubs/pygame/locals.pyi
@@ -108,6 +108,9 @@ DROPTEXT: int
FINGERDOWN: int
FINGERMOTION: int
FINGERUP: int
+FLASH_BRIEFLY: int
+FLASH_CANCEL: int
+FLASH_UNTIL_FOCUSED: int
FONT_CENTER: int
FONT_LEFT: int
FONT_RIGHT: int
diff --git a/buildconfig/stubs/pygame/mask.pyi b/buildconfig/stubs/pygame/mask.pyi
index 9ee01c9c07..0ea50adeb6 100644
--- a/buildconfig/stubs/pygame/mask.pyi
+++ b/buildconfig/stubs/pygame/mask.pyi
@@ -3,13 +3,13 @@ from typing import Any, List, Optional, Tuple, Union
from pygame.rect import Rect
from pygame.surface import Surface
-from ._common import ColorValue, Coordinate, RectValue
+from pygame.typing import ColorLike, Coordinate, RectLike
def from_surface(surface: Surface, threshold: int = 127) -> Mask: ...
def from_threshold(
surface: Surface,
- color: ColorValue,
- threshold: ColorValue = (0, 0, 0, 255),
+ color: ColorLike,
+ threshold: ColorLike = (0, 0, 0, 255),
othersurface: Optional[Surface] = None,
palette_colors: int = 1,
) -> Mask: ...
@@ -49,9 +49,9 @@ class Mask:
surface: Optional[Surface] = None,
setsurface: Optional[Surface] = None,
unsetsurface: Optional[Surface] = None,
- setcolor: Optional[ColorValue] = (255, 255, 255, 255),
- unsetcolor: Optional[ColorValue] = (0, 0, 0, 255),
- dest: Union[RectValue, Coordinate] = (0, 0),
+ setcolor: Optional[ColorLike] = (255, 255, 255, 255),
+ unsetcolor: Optional[ColorLike] = (0, 0, 0, 255),
+ dest: Union[RectLike, Coordinate] = (0, 0),
) -> Surface: ...
MaskType = Mask
diff --git a/buildconfig/stubs/pygame/math.pyi b/buildconfig/stubs/pygame/math.pyi
index 963f2bd957..b994bcdab4 100644
--- a/buildconfig/stubs/pygame/math.pyi
+++ b/buildconfig/stubs/pygame/math.pyi
@@ -5,6 +5,7 @@ from typing import (
Iterator,
List,
Literal,
+ SupportsIndex,
Tuple,
Type,
TypeVar,
@@ -13,13 +14,14 @@ from typing import (
overload,
Optional
)
+from typing_extensions import deprecated # added in 3.13
if sys.version_info >= (3, 9):
from collections.abc import Collection
else:
from typing import Collection
-from ._common import SupportsIndex, Sequence
+from pygame.typing import SequenceLike
def clamp(value: float, min: float, max: float, /) -> float: ...
@@ -35,18 +37,18 @@ class _GenericVector(Collection[float]):
@overload
def __setitem__(self, key: int, value: float) -> None: ...
@overload
- def __setitem__(self, key: slice, value: Union[Sequence[float], _TVec]) -> None: ...
+ def __setitem__(self, key: slice, value: Union[SequenceLike[float], _TVec]) -> None: ...
@overload
def __getitem__(self, i: SupportsIndex) -> float: ...
@overload
def __getitem__(self, s: slice) -> List[float]: ...
def __iter__(self) -> VectorIterator: ...
- def __add__(self: _TVec, other: Union[Sequence[float], _TVec]) -> _TVec: ...
- def __radd__(self: _TVec, other: Union[Sequence[float], _TVec]) -> _TVec: ...
- def __sub__(self: _TVec, other: Union[Sequence[float], _TVec]) -> _TVec: ...
- def __rsub__(self: _TVec, other: Union[Sequence[float], _TVec]) -> _TVec: ...
+ def __add__(self: _TVec, other: Union[SequenceLike[float], _TVec]) -> _TVec: ...
+ def __radd__(self: _TVec, other: Union[SequenceLike[float], _TVec]) -> _TVec: ...
+ def __sub__(self: _TVec, other: Union[SequenceLike[float], _TVec]) -> _TVec: ...
+ def __rsub__(self: _TVec, other: Union[SequenceLike[float], _TVec]) -> _TVec: ...
@overload
- def __mul__(self: _TVec, other: Union[Sequence[float], _TVec]) -> float: ...
+ def __mul__(self: _TVec, other: Union[SequenceLike[float], _TVec]) -> float: ...
@overload
def __mul__(self: _TVec, other: float) -> _TVec: ...
def __rmul__(self: _TVec, other: float) -> _TVec: ...
@@ -56,17 +58,17 @@ class _GenericVector(Collection[float]):
def __neg__(self: _TVec) -> _TVec: ...
def __pos__(self: _TVec) -> _TVec: ...
def __bool__(self) -> bool: ...
- def __iadd__(self: _TVec, other: Union[Sequence[float], _TVec]) -> _TVec: ...
- def __isub__(self: _TVec, other: Union[Sequence[float], _TVec]) -> _TVec: ...
+ def __iadd__(self: _TVec, other: Union[SequenceLike[float], _TVec]) -> _TVec: ...
+ def __isub__(self: _TVec, other: Union[SequenceLike[float], _TVec]) -> _TVec: ...
@overload
- def __imul__(self: _TVec, other: Union[Sequence[float], _TVec]) -> float: ...
+ def __imul__(self: _TVec, other: Union[SequenceLike[float], _TVec]) -> float: ...
@overload
def __imul__(self: _TVec, other: float) -> _TVec: ...
def __copy__(self: _TVec) -> _TVec: ...
copy = __copy__
def __safe_for_unpickling__(self) -> Literal[True]: ...
def __contains__(self, other: float) -> bool: ... # type: ignore[override]
- def dot(self: _TVec, other: Union[Sequence[float], _TVec], /) -> float: ...
+ def dot(self: _TVec, other: Union[SequenceLike[float], _TVec], /) -> float: ...
def magnitude(self) -> float: ...
def magnitude_squared(self) -> float: ...
def length(self) -> float: ...
@@ -75,37 +77,37 @@ class _GenericVector(Collection[float]):
def normalize_ip(self) -> None: ...
def is_normalized(self) -> bool: ...
def scale_to_length(self, value: float, /) -> None: ...
- def reflect(self: _TVec, other: Union[Sequence[float], _TVec], /) -> _TVec: ...
- def reflect_ip(self: _TVec, other: Union[Sequence[float], _TVec], /) -> None: ...
- def distance_to(self: _TVec, other: Union[Sequence[float], _TVec], /) -> float: ...
+ def reflect(self: _TVec, other: Union[SequenceLike[float], _TVec], /) -> _TVec: ...
+ def reflect_ip(self: _TVec, other: Union[SequenceLike[float], _TVec], /) -> None: ...
+ def distance_to(self: _TVec, other: Union[SequenceLike[float], _TVec], /) -> float: ...
def distance_squared_to(
- self: _TVec, other: Union[Sequence[float], _TVec], /
+ self: _TVec, other: Union[SequenceLike[float], _TVec], /
) -> float: ...
def lerp(
self: _TVec,
- other: Union[Sequence[float], _TVec],
+ other: Union[SequenceLike[float], _TVec],
value: float, /
) -> _TVec: ...
def slerp(
self: _TVec,
- other: Union[Sequence[float], _TVec],
+ other: Union[SequenceLike[float], _TVec],
value: float, /
) -> _TVec: ...
def smoothstep(
self: _TVec,
- other: Union[Sequence[float], _TVec],
+ other: Union[SequenceLike[float], _TVec],
value: float, /
) -> _TVec: ...
def elementwise(self: _TVec) -> VectorElementwiseProxy[_TVec]: ...
- def angle_to(self: _TVec, other: Union[Sequence[float], _TVec], /) -> float: ...
+ def angle_to(self: _TVec, other: Union[SequenceLike[float], _TVec], /) -> float: ...
def move_towards(
self: _TVec,
- target: Union[Sequence[float], _TVec],
+ target: Union[SequenceLike[float], _TVec],
max_distance: float, /
) -> _TVec: ...
def move_towards_ip(
self: _TVec,
- target: Union[Sequence[float], _TVec],
+ target: Union[SequenceLike[float], _TVec],
max_distance: float, /
) -> None: ...
@overload
@@ -118,7 +120,7 @@ class _GenericVector(Collection[float]):
def clamp_magnitude_ip(self, max_length: float, /) -> None: ...
@overload
def clamp_magnitude_ip(self, min_length: float, max_length: float, /) -> None: ...
- def project(self: _TVec, other: Union[Sequence[float], _TVec], /) -> _TVec: ...
+ def project(self: _TVec, other: Union[SequenceLike[float], _TVec], /) -> _TVec: ...
def __round__(self: _TVec, ndigits: Optional[int] = None, /) -> _TVec: ...
# VectorElementwiseProxy is a generic, it can be an elementwiseproxy object for
@@ -222,7 +224,7 @@ class Vector2(_GenericVector):
@overload
def __init__(
self: _TVec,
- x: Union[str, float, Sequence[float], _TVec] = 0,
+ x: Union[str, float, SequenceLike[float], _TVec] = 0,
) -> None: ...
@overload
def __init__(self, x: float, y: float) -> None: ...
@@ -231,14 +233,15 @@ class Vector2(_GenericVector):
def rotate_rad(self: _TVec, angle: float, /) -> _TVec: ...
def rotate_ip(self, angle: float, /) -> None: ...
def rotate_rad_ip(self, angle: float, /) -> None: ...
+ @deprecated("since 2.1.1. Use `pygame.Vector2.rotate_rad_ip` instead")
def rotate_ip_rad(self, angle: float, /) -> None: ...
- def cross(self: _TVec, other: Union[Sequence[float], _TVec], /) -> float: ...
+ def cross(self: _TVec, other: Union[SequenceLike[float], _TVec], /) -> float: ...
def as_polar(self) -> Tuple[float, float]: ...
- def from_polar(self, polar_value: Sequence[float], /) -> None: ...
+ def from_polar(self, polar_value: SequenceLike[float], /) -> None: ...
@overload
def update(
self: _TVec,
- x: Union[str, float, Sequence[float], _TVec] = 0,
+ x: Union[str, float, SequenceLike[float], _TVec] = 0,
) -> None: ...
@overload
def update(self, x: float = 0, y: float = 0) -> None: ...
@@ -286,48 +289,52 @@ class Vector3(_GenericVector):
@overload
def __init__(
self: _TVec,
- x: Union[str, float, Sequence[float], _TVec] = 0,
+ x: Union[str, float, SequenceLike[float], _TVec] = 0,
) -> None: ...
@overload
def __init__(self, x: float, y: float, z: float) -> None: ...
def __reduce__(self: _TVec) -> Tuple[Type[_TVec], Tuple[float, float, float]]: ...
- def cross(self: _TVec, other: Union[Sequence[float], _TVec], /) -> _TVec: ...
+ def cross(self: _TVec, other: Union[SequenceLike[float], _TVec], /) -> _TVec: ...
def rotate(
- self: _TVec, angle: float, axis: Union[Sequence[float], _TVec], /
+ self: _TVec, angle: float, axis: Union[SequenceLike[float], _TVec], /
) -> _TVec: ...
def rotate_rad(
- self: _TVec, angle: float, axis: Union[Sequence[float], _TVec], /
+ self: _TVec, angle: float, axis: Union[SequenceLike[float], _TVec], /
) -> _TVec: ...
def rotate_ip(
- self: _TVec, angle: float, axis: Union[Sequence[float], _TVec], /
+ self: _TVec, angle: float, axis: Union[SequenceLike[float], _TVec], /
) -> None: ...
def rotate_rad_ip(
- self: _TVec, angle: float, axis: Union[Sequence[float], _TVec], /
+ self: _TVec, angle: float, axis: Union[SequenceLike[float], _TVec], /
) -> None: ...
+ @deprecated("since 2.1.1. Use `pygame.Vector3.rotate_rad_ip` instead")
def rotate_ip_rad(
- self: _TVec, angle: float, axis: Union[Sequence[float], _TVec], /
+ self: _TVec, angle: float, axis: Union[SequenceLike[float], _TVec], /
) -> None: ...
def rotate_x(self: _TVec, angle: float, /) -> _TVec: ...
def rotate_x_rad(self: _TVec, angle: float, /) -> _TVec: ...
def rotate_x_ip(self, angle: float, /) -> None: ...
def rotate_x_rad_ip(self, angle: float, /) -> None: ...
+ @deprecated("since 2.1.1. Use `pygame.Vector3.rotate_x_rad_ip` instead")
def rotate_x_ip_rad(self, angle: float, /) -> None: ...
def rotate_y(self: _TVec, angle: float, /) -> _TVec: ...
def rotate_y_rad(self: _TVec, angle: float, /) -> _TVec: ...
def rotate_y_ip(self, angle: float, /) -> None: ...
def rotate_y_rad_ip(self, angle: float, /) -> None: ...
+ @deprecated("since 2.1.1. Use `pygame.Vector3.rotate_y_rad_ip` instead")
def rotate_y_ip_rad(self, angle: float, /) -> None: ...
def rotate_z(self: _TVec, angle: float, /) -> _TVec: ...
def rotate_z_rad(self: _TVec, angle: float, /) -> _TVec: ...
def rotate_z_ip(self, angle: float, /) -> None: ...
def rotate_z_rad_ip(self, angle: float, /) -> None: ...
+ @deprecated("since 2.1.1. Use `pygame.Vector3.rotate_z_rad_ip` instead")
def rotate_z_ip_rad(self, angle: float, /) -> None: ...
def as_spherical(self) -> Tuple[float, float, float]: ...
def from_spherical(self, spherical: Tuple[float, float, float], /) -> None: ...
@overload
def update(
self: _TVec,
- x: Union[str, float, Sequence[float], _TVec] = 0,
+ x: Union[str, float, SequenceLike[float], _TVec] = 0,
) -> None: ...
@overload
def update(self, x: int, y: int, z: int) -> None: ...
@@ -338,7 +345,7 @@ def invlerp(a: float, b: float, value: float, /) -> float: ...
def remap(i_min: float, i_max: float, o_min: float, o_max: float, value: float, /) -> float: ...
def smoothstep(a: float, b: float, weight: float, /) -> float: ...
-
-# typehints for deprecated functions, to be removed in a future version
+@deprecated("Functionality is removed")
def enable_swizzling() -> None: ...
+@deprecated("Functionality is removed")
def disable_swizzling() -> None: ...
diff --git a/buildconfig/stubs/pygame/midi.pyi b/buildconfig/stubs/pygame/midi.pyi
index 028342508c..22fc749f80 100644
--- a/buildconfig/stubs/pygame/midi.pyi
+++ b/buildconfig/stubs/pygame/midi.pyi
@@ -1,7 +1,7 @@
from typing import List, Tuple, Union
from pygame.event import Event
-from ._common import Sequence
+from pygame.typing import SequenceLike
MIDIIN: int
MIDIOUT: int
@@ -17,7 +17,7 @@ def get_default_input_id() -> int: ...
def get_default_output_id() -> int: ...
def get_device_info(an_id: int) -> Tuple[str, str, int, int, int]: ...
def midis2events(
- midis: Sequence[Sequence[Union[Sequence[int], int]]], device_id: int
+ midis: SequenceLike[SequenceLike[Union[SequenceLike[int], int]]], device_id: int
) -> List[Event]: ...
def time() -> int: ...
def frequency_to_midi(frequency: float) -> int: ...
diff --git a/buildconfig/stubs/pygame/mixer.pyi b/buildconfig/stubs/pygame/mixer.pyi
index 4d1cafc830..84646b419b 100644
--- a/buildconfig/stubs/pygame/mixer.pyi
+++ b/buildconfig/stubs/pygame/mixer.pyi
@@ -5,7 +5,7 @@ import numpy
from pygame.event import Event
from . import mixer_music
-from ._common import FileArg
+from pygame.typing import FileLike
# export mixer_music as mixer.music
music = mixer_music
@@ -44,7 +44,7 @@ def get_sdl_mixer_version(linked: bool = True) -> Tuple[int, int, int]: ...
class Sound:
@overload
- def __init__(self, file: FileArg) -> None: ...
+ def __init__(self, file: FileLike) -> None: ...
@overload
def __init__(
self, buffer: Any
diff --git a/buildconfig/stubs/pygame/mixer_music.pyi b/buildconfig/stubs/pygame/mixer_music.pyi
index 08b2e8a11a..40cf46e2a9 100644
--- a/buildconfig/stubs/pygame/mixer_music.pyi
+++ b/buildconfig/stubs/pygame/mixer_music.pyi
@@ -1,8 +1,8 @@
from typing import Optional, Dict
-from ._common import FileArg
+from pygame.typing import FileLike
-def load(filename: FileArg, namehint: Optional[str] = "") -> None: ...
+def load(filename: FileLike, namehint: Optional[str] = "") -> None: ...
def unload() -> None: ...
def play(loops: int = 0, start: float = 0.0, fade_ms: int = 0) -> None: ...
def rewind() -> None: ...
@@ -15,7 +15,7 @@ def get_volume() -> float: ...
def get_busy() -> bool: ...
def set_pos(pos: float, /) -> None: ...
def get_pos() -> int: ...
-def queue(filename: FileArg, namehint: str = "", loops: int = 0) -> None: ...
+def queue(filename: FileLike, namehint: str = "", loops: int = 0) -> None: ...
def set_endevent(event_type: int, /) -> None: ...
def get_endevent() -> int: ...
-def get_metadata(filename: Optional[FileArg] = None, namehint: str = "") -> Dict[str, str]: ...
+def get_metadata(filename: Optional[FileLike] = None, namehint: str = "") -> Dict[str, str]: ...
diff --git a/buildconfig/stubs/pygame/mouse.pyi b/buildconfig/stubs/pygame/mouse.pyi
index bec7b2899c..42ea298b45 100644
--- a/buildconfig/stubs/pygame/mouse.pyi
+++ b/buildconfig/stubs/pygame/mouse.pyi
@@ -1,9 +1,10 @@
-from typing import Tuple, overload
-from typing_extensions import Literal
+from typing import Literal, Tuple, overload
+from typing_extensions import deprecated # added in 3.13
+
from pygame.cursors import Cursor
from pygame.surface import Surface
-from ._common import Coordinate, Sequence, IntCoordinate
+from pygame.typing import Coordinate, SequenceLike, IntCoordinate
@overload
def get_pressed(num_buttons: Literal[3] = 3) -> Tuple[bool, bool, bool]: ...
@@ -28,12 +29,13 @@ def set_cursor(constant: int) -> None: ...
def set_cursor(
size: IntCoordinate,
hotspot: IntCoordinate,
- xormasks: Sequence[int],
- andmasks: Sequence[int],
+ xormasks: SequenceLike[int],
+ andmasks: SequenceLike[int],
) -> None: ...
@overload
def set_cursor(hotspot: IntCoordinate, surface: Surface) -> None: ...
def get_cursor() -> Cursor: ...
+@deprecated("since 2.2.0. Use `pygame.mouse.set_cursor` instead")
def set_system_cursor(cursor: int, /) -> None: ...
def get_relative_mode() -> bool: ...
def set_relative_mode(enable: bool, /) -> None: ...
diff --git a/buildconfig/stubs/pygame/pixelarray.pyi b/buildconfig/stubs/pygame/pixelarray.pyi
index 935d4c01d6..a10bb66e42 100644
--- a/buildconfig/stubs/pygame/pixelarray.pyi
+++ b/buildconfig/stubs/pygame/pixelarray.pyi
@@ -2,7 +2,7 @@ from typing import Any, Dict, Tuple, Union, overload
from pygame.surface import Surface
-from ._common import ColorValue, Sequence
+from pygame.typing import ColorLike, SequenceLike
class PixelArray:
surface: Surface
@@ -34,22 +34,22 @@ class PixelArray:
def make_surface(self) -> Surface: ...
def replace(
self,
- color: ColorValue,
- repcolor: ColorValue,
+ color: ColorLike,
+ repcolor: ColorLike,
distance: float = 0,
- weights: Sequence[float] = (0.299, 0.587, 0.114),
+ weights: SequenceLike[float] = (0.299, 0.587, 0.114),
) -> None: ...
def extract(
self,
- color: ColorValue,
+ color: ColorLike,
distance: float = 0,
- weights: Sequence[float] = (0.299, 0.587, 0.114),
+ weights: SequenceLike[float] = (0.299, 0.587, 0.114),
) -> PixelArray: ...
def compare(
self,
array: PixelArray,
distance: float = 0,
- weights: Sequence[float] = (0.299, 0.587, 0.114),
+ weights: SequenceLike[float] = (0.299, 0.587, 0.114),
) -> PixelArray: ...
def transpose(self) -> PixelArray: ...
def close(self) -> PixelArray: ...
diff --git a/buildconfig/stubs/pygame/pixelcopy.pyi b/buildconfig/stubs/pygame/pixelcopy.pyi
index 9954816074..4c2f59ce7e 100644
--- a/buildconfig/stubs/pygame/pixelcopy.pyi
+++ b/buildconfig/stubs/pygame/pixelcopy.pyi
@@ -1,9 +1,9 @@
+from typing import Literal
+
import numpy
from pygame.surface import Surface
-from ._common import Literal
-
_kind = Literal["P", "p", "R", "r", "G", "g", "B", "b", "A", "a", "C", "c"]
def surface_to_array(
diff --git a/buildconfig/stubs/pygame/rect.pyi b/buildconfig/stubs/pygame/rect.pyi
index b401f9a930..9899c84270 100644
--- a/buildconfig/stubs/pygame/rect.pyi
+++ b/buildconfig/stubs/pygame/rect.pyi
@@ -2,6 +2,8 @@ import sys
from typing import (
Dict,
List,
+ Literal,
+ SupportsIndex,
Tuple,
TypeVar,
Union,
@@ -10,7 +12,7 @@ from typing import (
Optional,
)
-from ._common import Coordinate, Literal, RectValue, SupportsIndex, Sequence
+from pygame.typing import Coordinate, RectLike, SequenceLike
if sys.version_info >= (3, 11):
from typing import Self
@@ -27,7 +29,7 @@ _K = TypeVar("_K")
_V = TypeVar("_V")
_T = TypeVar("_T")
-_RectTypeCompatible_co = TypeVar("_RectTypeCompatible_co", bound=RectValue, covariant=True)
+_RectTypeCompatible_co = TypeVar("_RectTypeCompatible_co", bound=RectLike, covariant=True)
class _GenericRect(Collection[_N]):
@property
@@ -127,7 +129,7 @@ class _GenericRect(Collection[_N]):
@overload
def __init__(self, left_top: Coordinate, width_height: Coordinate) -> None: ...
@overload
- def __init__(self, single_arg: RectValue) -> None: ...
+ def __init__(self, single_arg: RectLike) -> None: ...
@overload
def __init__(self) -> None: ...
def __len__(self) -> Literal[4]: ...
@@ -139,7 +141,7 @@ class _GenericRect(Collection[_N]):
@overload
def __setitem__(self, key: int, value: float) -> None: ...
@overload
- def __setitem__(self, key: slice, value: Union[float, RectValue]) -> None: ...
+ def __setitem__(self, key: slice, value: Union[float, RectLike]) -> None: ...
def __copy__(self) -> Self: ...
copy = __copy__
@overload
@@ -172,15 +174,15 @@ class _GenericRect(Collection[_N]):
@overload
def update(self, left_top: Coordinate, width_height: Coordinate, /) -> None: ...
@overload
- def update(self, single_arg: RectValue, /) -> None: ...
+ def update(self, single_arg: RectLike, /) -> None: ...
@overload
- def clamp(self, rect: RectValue, /) -> Self: ...
+ def clamp(self, rect: RectLike, /) -> Self: ...
@overload
def clamp(self, left_top: Coordinate, width_height: Coordinate, /) -> Self: ...
@overload
def clamp(self, left: float, top: float, width: float, height: float, /) -> Self: ...
@overload
- def clamp_ip(self, rect: RectValue, /) -> None: ...
+ def clamp_ip(self, rect: RectLike, /) -> None: ...
@overload
def clamp_ip(self, left_top: Coordinate, width_height: Coordinate, /) -> None: ...
@overload
@@ -188,7 +190,7 @@ class _GenericRect(Collection[_N]):
self, left: float, top: float, width: float, height: float, /
) -> None: ...
@overload
- def clip(self, rect: RectValue, /) -> Self: ...
+ def clip(self, rect: RectLike, /) -> Self: ...
@overload
def clip(self, left_top: Coordinate, width_height: Coordinate, /) -> Self: ...
@overload
@@ -203,34 +205,34 @@ class _GenericRect(Collection[_N]):
) -> Union[Tuple[Tuple[_N, _N], Tuple[_N, _N]], Tuple[()]]: ...
@overload
def clipline(
- self, rect_arg: RectValue, /
+ self, rect_arg: RectLike, /
) -> Union[Tuple[Tuple[_N, _N], Tuple[_N, _N]], Tuple[()]]: ...
@overload
- def union(self, rect: RectValue, /) -> Self: ...
+ def union(self, rect: RectLike, /) -> Self: ...
@overload
def union(self, left_top: Coordinate, width_height: Coordinate, /) -> Self: ...
@overload
def union(self, left: float, top: float, width: float, height: float, /) -> Self: ...
@overload
- def union_ip(self, rect: RectValue, /) -> None: ...
+ def union_ip(self, rect: RectLike, /) -> None: ...
@overload
def union_ip(self, left_top: Coordinate, width_height: Coordinate, /) -> None: ...
@overload
def union_ip(
self, left: float, top: float, width: float, height: float, /
) -> None: ...
- def unionall(self, rect: Sequence[_RectTypeCompatible_co], /) -> Self: ...
- def unionall_ip(self, rect_sequence: Sequence[_RectTypeCompatible_co], /) -> None: ...
+ def unionall(self, rect: SequenceLike[_RectTypeCompatible_co], /) -> Self: ...
+ def unionall_ip(self, rect_SequenceLike: SequenceLike[_RectTypeCompatible_co], /) -> None: ...
@overload
- def fit(self, rect: RectValue, /) -> Self: ...
+ def fit(self, rect: RectLike, /) -> Self: ...
@overload
def fit(self, left_top: Coordinate, width_height: Coordinate, /) -> Self: ...
@overload
def fit(self, left: float, top: float, width: float, height: float, /) -> Self: ...
def normalize(self) -> None: ...
- def __contains__(self, rect: Union[RectValue, _N], /) -> bool: ... # type: ignore[override]
+ def __contains__(self, rect: Union[RectLike, _N], /) -> bool: ... # type: ignore[override]
@overload
- def contains(self, rect: RectValue, /) -> bool: ...
+ def contains(self, rect: RectLike, /) -> bool: ...
@overload
def contains(self, left_top: Coordinate, width_height: Coordinate, /) -> bool: ...
@overload
@@ -242,20 +244,20 @@ class _GenericRect(Collection[_N]):
@overload
def collidepoint(self, x_y: Coordinate, /) -> bool: ...
@overload
- def colliderect(self, rect: RectValue, /) -> bool: ...
+ def colliderect(self, rect: RectLike, /) -> bool: ...
@overload
def colliderect(self, left_top: Coordinate, width_height: Coordinate, /) -> bool: ...
@overload
def colliderect(
self, left: float, top: float, width: float, height: float, /
) -> bool: ...
- def collidelist(self, rect_list: Sequence[_RectTypeCompatible_co], /) -> int: ...
- def collidelistall(self, rect_list: Sequence[_RectTypeCompatible_co], /) -> List[int]: ...
+ def collidelist(self, rect_list: SequenceLike[_RectTypeCompatible_co], /) -> int: ...
+ def collidelistall(self, rect_list: SequenceLike[_RectTypeCompatible_co], /) -> List[int]: ...
def collideobjectsall(
- self, objects: Sequence[_T], key: Optional[Callable[[_T], RectValue]] = None
+ self, objects: SequenceLike[_T], key: Optional[Callable[[_T], RectLike]] = None
) -> List[_T]: ...
def collideobjects(
- self, objects: Sequence[_T], key: Optional[Callable[[_T], RectValue]] = None
+ self, objects: SequenceLike[_T], key: Optional[Callable[[_T], RectLike]] = None
) -> Optional[_T]: ...
@overload
def collidedict(
diff --git a/buildconfig/stubs/pygame/rwobject.pyi b/buildconfig/stubs/pygame/rwobject.pyi
index 3688279b6c..38161fab81 100644
--- a/buildconfig/stubs/pygame/rwobject.pyi
+++ b/buildconfig/stubs/pygame/rwobject.pyi
@@ -1,16 +1,16 @@
from typing import Any, Optional, overload, Type
-from ._common import AnyPath
+from pygame.typing import PathLike
def encode_string(
- obj: Optional[AnyPath],
+ obj: Optional[PathLike],
encoding: Optional[str] = "unicode_escape",
errors: Optional[str] = "backslashreplace",
etype: Optional[Type[Exception]] = UnicodeEncodeError,
) -> bytes: ...
@overload
def encode_file_path(
- obj: Optional[AnyPath], etype: Optional[Type[Exception]] = UnicodeEncodeError
+ obj: Optional[PathLike], etype: Optional[Type[Exception]] = UnicodeEncodeError
) -> bytes: ...
@overload
def encode_file_path(
diff --git a/buildconfig/stubs/pygame/scrap.pyi b/buildconfig/stubs/pygame/scrap.pyi
index 5929539a86..edb98e8801 100644
--- a/buildconfig/stubs/pygame/scrap.pyi
+++ b/buildconfig/stubs/pygame/scrap.pyi
@@ -1,13 +1,22 @@
from typing import List, Optional
+from typing_extensions import deprecated # added in 3.13
from collections.abc import ByteString
+@deprecated("since 2.2.0. Use the new API instead, which only requires display init")
def init() -> None: ...
+@deprecated("since 2.2.0. Use the new API instead, which doesn't require scrap init")
def get_init() -> bool: ...
+@deprecated("since 2.2.0. Use the new API instead: `pygame.scrap.get_text`")
def get(data_type: str, /) -> Optional[bytes]: ...
+@deprecated("since 2.2.0. Use the new API instead, which only supports strings")
def get_types() -> List[str]: ...
+@deprecated("since 2.2.0. Use the new API instead: `pygame.scrap.put_text`")
def put(data_type: str, data: ByteString, /) -> None: ...
+@deprecated("since 2.2.0. Use the new API instead: `pygame.scrap.has_text`")
def contains(data_type: str, /) -> bool: ...
+@deprecated("since 2.2.0. Use the new API instead, which uses system clipboard")
def lost() -> bool: ...
+@deprecated("since 2.2.0. Use the new API instead, which only supports strings")
def set_mode(mode: int, /) -> None: ...
def put_text(text: str, /) -> None: ...
def get_text() -> str: ...
diff --git a/buildconfig/stubs/pygame/sndarray.pyi b/buildconfig/stubs/pygame/sndarray.pyi
index 8b0dd65303..bf03698feb 100644
--- a/buildconfig/stubs/pygame/sndarray.pyi
+++ b/buildconfig/stubs/pygame/sndarray.pyi
@@ -1,4 +1,5 @@
from typing import Tuple
+from typing_extensions import deprecated # added in 3.13
import numpy
@@ -7,6 +8,9 @@ from pygame.mixer import Sound
def array(sound: Sound) -> numpy.ndarray: ...
def samples(sound: Sound) -> numpy.ndarray: ...
def make_sound(array: numpy.ndarray) -> Sound: ...
+@deprecated("Only numpy is supported")
def use_arraytype(arraytype: str) -> Sound: ...
+@deprecated("Only numpy is supported")
def get_arraytype() -> str: ...
+@deprecated("Only numpy is supported")
def get_arraytypes() -> Tuple[str]: ...
diff --git a/buildconfig/stubs/pygame/sprite.pyi b/buildconfig/stubs/pygame/sprite.pyi
index fc9c022ac6..9185018c1c 100644
--- a/buildconfig/stubs/pygame/sprite.pyi
+++ b/buildconfig/stubs/pygame/sprite.pyi
@@ -7,20 +7,19 @@ from typing import (
Iterator,
List,
Optional,
+ Protocol,
SupportsFloat,
Tuple,
TypeVar,
Union,
)
-
-# Protocol added in python 3.8
-from typing_extensions import Protocol
+from typing_extensions import deprecated # added in 3.13
from pygame.rect import FRect, Rect
from pygame.surface import Surface
from pygame.mask import Mask
-from ._common import RectValue, Coordinate
+from pygame.typing import RectLike, Coordinate
# non-generic Group, used in Sprite
_Group = AbstractGroup[_SpriteSupportsGroup]
@@ -182,12 +181,15 @@ class Group(AbstractGroup[_TSprite]):
def __init__(
self, *sprites: Union[_TSprite, AbstractGroup[_TSprite], Iterable[_TSprite]]
) -> None: ...
-
+
# these are aliased in the code too
-RenderPlain = Group
-RenderClear = Group
+@deprecated("Use `pygame.sprite.Group` instead")
+class RenderPlain(Group): ...
+@deprecated("Use `pygame.sprite.Group` instead")
+class RenderClear(Group): ...
class RenderUpdates(Group[_TSprite]): ...
+@deprecated("Use `pygame.sprite.RenderUpdates` instead")
class OrderedUpdates(RenderUpdates[_TSprite]): ...
class LayeredUpdates(AbstractGroup[_TSprite]):
@@ -230,14 +232,16 @@ class LayeredDirty(LayeredUpdates[_TDirtySprite]):
) -> List[Union[FRect, Rect]]: ...
# clear breaks Liskov substitution principle in code
def clear(self, surface: Surface, bgd: Surface) -> None: ... # type: ignore[override]
- def repaint_rect(self, screen_rect: RectValue) -> None: ...
- def set_clip(self, screen_rect: Optional[RectValue] = None) -> None: ...
+ def repaint_rect(self, screen_rect: RectLike) -> None: ...
+ def set_clip(self, screen_rect: Optional[RectLike] = None) -> None: ...
def get_clip(self) -> Union[FRect, Rect]: ...
def set_timing_threshold(
self, time_ms: SupportsFloat
) -> None: ... # This actually accept any value
- # deprecated alias
- set_timing_treshold = set_timing_threshold
+ @deprecated("since 2.1.1. Use `pygame.sprite.LayeredDirty.set_timing_threshold` instead")
+ def set_timing_treshold(
+ self, time_ms: SupportsFloat
+ ) -> None: ...
class GroupSingle(AbstractGroup[_TSprite]):
sprite: _TSprite
diff --git a/buildconfig/stubs/pygame/surface.pyi b/buildconfig/stubs/pygame/surface.pyi
index b2c8f6f5fd..03bb9360db 100644
--- a/buildconfig/stubs/pygame/surface.pyi
+++ b/buildconfig/stubs/pygame/surface.pyi
@@ -1,16 +1,16 @@
-from typing import Any, Iterable, List, Optional, Tuple, Union, overload
+from typing import Any, Iterable, List, Literal, Optional, Tuple, Union, overload
+from typing_extensions import deprecated # added in 3.13
from pygame.bufferproxy import BufferProxy
from pygame.color import Color
from pygame.rect import FRect, Rect
-from ._common import (
- ColorValue,
+from pygame.typing import (
+ ColorLike,
Coordinate,
- Literal,
- RectValue,
- RGBAOutput,
- Sequence,
+ RectLike,
+ RGBATuple,
+ SequenceLike,
)
_ViewKind = Literal[
@@ -54,7 +54,7 @@ class Surface:
size: Coordinate,
flags: int = 0,
depth: int = 0,
- masks: Optional[ColorValue] = None,
+ masks: Optional[ColorLike] = None,
) -> None: ...
@overload
def __init__(
@@ -69,24 +69,24 @@ class Surface:
def blit(
self,
source: Surface,
- dest: Union[Coordinate, RectValue],
- area: Optional[RectValue] = None,
+ dest: Union[Coordinate, RectLike] = (0, 0),
+ area: Optional[RectLike] = None,
special_flags: int = 0,
) -> Rect: ...
def blits(
self,
- blit_sequence: Iterable[
+ blit_SequenceLike: Iterable[
Union[
- Tuple[Surface, Union[Coordinate, RectValue]],
- Tuple[Surface, Union[Coordinate, RectValue], Union[RectValue, int]],
- Tuple[Surface, Union[Coordinate, RectValue], RectValue, int],
+ Tuple[Surface, Union[Coordinate, RectLike]],
+ Tuple[Surface, Union[Coordinate, RectLike], Union[RectLike, int]],
+ Tuple[Surface, Union[Coordinate, RectLike], RectLike, int],
]
],
doreturn: Union[int, bool] = 1,
) -> Union[List[Rect], None]: ...
def fblits(
self,
- blit_sequence: Iterable[Tuple[Surface, Union[Coordinate, RectValue]]],
+ blit_SequenceLike: Iterable[Tuple[Surface, Union[Coordinate, RectLike]]],
special_flags: int = 0, /
) -> None: ...
@overload
@@ -94,22 +94,22 @@ class Surface:
@overload
def convert(self, depth: int, flags: int = 0, /) -> Surface: ...
@overload
- def convert(self, masks: ColorValue, flags: int = 0, /) -> Surface: ...
+ def convert(self, masks: ColorLike, flags: int = 0, /) -> Surface: ...
@overload
def convert(self) -> Surface: ...
def convert_alpha(self) -> Surface: ...
def fill(
self,
- color: ColorValue,
- rect: Optional[RectValue] = None,
+ color: ColorLike,
+ rect: Optional[RectLike] = None,
special_flags: int = 0,
) -> Rect: ...
def scroll(self, dx: int = 0, dy: int = 0, /) -> None: ...
@overload
- def set_colorkey(self, color: ColorValue, flags: int = 0, /) -> None: ...
+ def set_colorkey(self, color: ColorLike, flags: int = 0, /) -> None: ...
@overload
def set_colorkey(self, color: None, /) -> None: ...
- def get_colorkey(self) -> Optional[RGBAOutput]: ...
+ def get_colorkey(self) -> Optional[RGBATuple]: ...
@overload
def set_alpha(self, value: int, flags: int = 0, /) -> None: ...
@overload
@@ -121,18 +121,18 @@ class Surface:
def get_locked(self) -> bool: ...
def get_locks(self) -> Tuple[Any, ...]: ...
def get_at(self, x_y: Coordinate, /) -> Color: ...
- def set_at(self, x_y: Coordinate, color: ColorValue, /) -> None: ...
+ def set_at(self, x_y: Coordinate, color: ColorLike, /) -> None: ...
def get_at_mapped(self, x_y: Coordinate, /) -> int: ...
def get_palette(self) -> List[Color]: ...
def get_palette_at(self, index: int, /) -> Color: ...
- def set_palette(self, palette: Sequence[ColorValue], /) -> None: ...
- def set_palette_at(self, index: int, color: ColorValue, /) -> None: ...
- def map_rgb(self, color: ColorValue, /) -> int: ...
+ def set_palette(self, palette: SequenceLike[ColorLike], /) -> None: ...
+ def set_palette_at(self, index: int, color: ColorLike, /) -> None: ...
+ def map_rgb(self, color: ColorLike, /) -> int: ...
def unmap_rgb(self, mapped_int: int, /) -> Color: ...
- def set_clip(self, rect: Optional[RectValue], /) -> None: ...
+ def set_clip(self, rect: Optional[RectLike], /) -> None: ...
def get_clip(self) -> Rect: ...
@overload
- def subsurface(self, rect: RectValue, /) -> Surface: ...
+ def subsurface(self, rect: RectLike, /) -> Surface: ...
@overload
def subsurface(self, left_top: Coordinate, width_height: Coordinate, /) -> Surface: ...
@overload
@@ -152,15 +152,18 @@ class Surface:
def get_bytesize(self) -> int: ...
def get_flags(self) -> int: ...
def get_pitch(self) -> int: ...
- def get_masks(self) -> RGBAOutput: ...
- def set_masks(self, color: ColorValue, /) -> None: ...
- def get_shifts(self) -> RGBAOutput: ...
- def set_shifts(self, color: ColorValue, /) -> None: ...
- def get_losses(self) -> RGBAOutput: ...
+ def get_masks(self) -> RGBATuple: ...
+ @deprecated("since 2.0.0. Immutable in SDL2")
+ def set_masks(self, color: ColorLike, /) -> None: ...
+ def get_shifts(self) -> RGBATuple: ...
+ @deprecated("since 2.0.0. Immutable in SDL2")
+ def set_shifts(self, color: ColorLike, /) -> None: ...
+ def get_losses(self) -> RGBATuple: ...
def get_bounding_rect(self, min_alpha: int = 1) -> Rect: ...
def get_view(self, kind: _ViewKind = "2", /) -> BufferProxy: ...
def get_buffer(self) -> BufferProxy: ...
def get_blendmode(self) -> int: ...
def premul_alpha(self) -> Surface: ...
+ def premul_alpha_ip(self) -> Surface: ...
SurfaceType = Surface
diff --git a/buildconfig/stubs/pygame/surfarray.pyi b/buildconfig/stubs/pygame/surfarray.pyi
index bf15758713..c15f089518 100644
--- a/buildconfig/stubs/pygame/surfarray.pyi
+++ b/buildconfig/stubs/pygame/surfarray.pyi
@@ -1,4 +1,5 @@
from typing import Tuple
+from typing_extensions import deprecated # added in 3.13
import numpy
@@ -26,6 +27,9 @@ def array_colorkey(surface: Surface) -> numpy.ndarray: ...
def make_surface(array: numpy.ndarray) -> Surface: ...
def blit_array(surface: Surface, array: numpy.ndarray) -> None: ...
def map_array(surface: Surface, array: numpy.ndarray) -> numpy.ndarray: ...
+@deprecated("Only numpy is supported")
def use_arraytype(arraytype: str) -> None: ...
+@deprecated("Only numpy is supported")
def get_arraytype() -> str: ...
+@deprecated("Only numpy is supported")
def get_arraytypes() -> Tuple[str]: ...
diff --git a/buildconfig/stubs/pygame/system.pyi b/buildconfig/stubs/pygame/system.pyi
index f9f50452ca..f2e367473f 100644
--- a/buildconfig/stubs/pygame/system.pyi
+++ b/buildconfig/stubs/pygame/system.pyi
@@ -1,6 +1,4 @@
-from typing import List, Optional, final
-
-from typing_extensions import TypedDict
+from typing import List, Optional, TypedDict
from pygame._data_classes import PowerState
diff --git a/buildconfig/stubs/pygame/transform.pyi b/buildconfig/stubs/pygame/transform.pyi
index fd412ec948..64a6fabf16 100644
--- a/buildconfig/stubs/pygame/transform.pyi
+++ b/buildconfig/stubs/pygame/transform.pyi
@@ -3,7 +3,7 @@ from typing import Optional, Union, Literal
from pygame.color import Color
from pygame.surface import Surface
-from ._common import ColorValue, Coordinate, RectValue, Sequence
+from pygame.typing import ColorLike, Coordinate, RectLike, SequenceLike
def flip(surface: Surface, flip_x: bool, flip_y: bool) -> Surface: ...
def scale(
@@ -13,7 +13,7 @@ def scale(
) -> Surface: ...
def scale_by(
surface: Surface,
- factor: Union[float, Sequence[float]],
+ factor: Union[float, SequenceLike[float]],
dest_surface: Optional[Surface] = None,
) -> Surface: ...
def rotate(surface: Surface, angle: float) -> Surface: ...
@@ -27,28 +27,28 @@ def smoothscale(
) -> Surface: ...
def smoothscale_by(
surface: Surface,
- factor: Union[float, Sequence[float]],
+ factor: Union[float, SequenceLike[float]],
dest_surface: Optional[Surface] = None,
) -> Surface: ...
def get_smoothscale_backend() -> Literal["GENERIC", "SSE2", "NEON"]: ...
def set_smoothscale_backend(backend: Literal["GENERIC", "SSE2", "NEON"]) -> None: ...
-def chop(surface: Surface, rect: RectValue) -> Surface: ...
+def chop(surface: Surface, rect: RectLike) -> Surface: ...
def laplacian(surface: Surface, dest_surface: Optional[Surface] = None) -> Surface: ...
def invert(surface: Surface, dest_surface: Optional[Surface] = None) -> Surface: ...
def average_surfaces(
- surfaces: Sequence[Surface],
+ surfaces: SequenceLike[Surface],
dest_surface: Optional[Surface] = None,
palette_colors: Union[bool, int] = 1,
) -> Surface: ...
def average_color(
- surface: Surface, rect: Optional[RectValue] = None, consider_alpha: bool = False
+ surface: Surface, rect: Optional[RectLike] = None, consider_alpha: bool = False
) -> Color: ...
def threshold(
dest_surface: Optional[Surface],
surface: Surface,
- search_color: Optional[ColorValue],
- threshold: ColorValue = (0, 0, 0, 0),
- set_color: Optional[ColorValue] = (0, 0, 0, 0),
+ search_color: Optional[ColorLike],
+ threshold: ColorLike = (0, 0, 0, 0),
+ set_color: Optional[ColorLike] = (0, 0, 0, 0),
set_behavior: int = 1,
search_surf: Optional[Surface] = None,
inverse_set: bool = False,
diff --git a/buildconfig/stubs/pygame/typing.pyi b/buildconfig/stubs/pygame/typing.pyi
new file mode 100644
index 0000000000..883fe2def1
--- /dev/null
+++ b/buildconfig/stubs/pygame/typing.pyi
@@ -0,0 +1,59 @@
+"""Set of common pygame type aliases for proper typehint annotations"""
+
+# NOTE: `src_py/typing.py` and `buildconfig/stubs/pygame/typing.pyi` must be duplicates.
+# Use the command `python buildconfig/stubs/gen_stubs.py` to copy typing.py to typing.pyi
+
+import sys
+from typing import IO, Callable, Tuple, Union, TypeVar, Protocol, SupportsIndex
+
+if sys.version_info >= (3, 9):
+ from os import PathLike as _PathProtocol
+else:
+ _T = TypeVar("_T", bound=Union[str, bytes])
+
+ class _PathProtocol(Protocol[_T]):
+ def __fspath__(self) -> _T: ...
+
+
+# For functions that take a file name
+PathLike = Union[str, bytes, _PathProtocol[str], _PathProtocol[bytes]]
+# Most pygame functions that take a file argument should be able to handle a FileLike type
+FileLike = Union[PathLike, IO[bytes], IO[str]]
+
+_T_co = TypeVar("_T_co", covariant=True)
+
+
+class SequenceLike(Protocol[_T_co]):
+ """
+ Variant of the standard `Sequence` ABC that only requires `__getitem__` and `__len__`.
+ """
+
+ def __getitem__(self, __i: SupportsIndex) -> _T_co: ...
+ def __len__(self) -> int: ...
+
+
+# Modify typehints when it is possible to annotate sizes
+
+# Pygame handles float without errors in most cases where a coordinate is expected,
+# usually rounding to int. Also, 'Union[int, float] == float'
+Coordinate = SequenceLike[float]
+# This is used where ints are strictly required
+IntCoordinate = SequenceLike[int]
+
+# Used for functions that return an RGBA tuple
+RGBATuple = Tuple[int, int, int, int]
+ColorLike = Union[int, str, SequenceLike[int]]
+
+_CanBeRect = SequenceLike[Union[float, Coordinate]]
+
+
+class _HasRectAttribute(Protocol):
+ # An object that has a rect attribute that is either a rect, or a function
+ # that returns a rect conforms to the rect protocol
+ rect: Union["RectLike", Callable[[], "RectLike"]]
+
+
+RectLike = Union[_CanBeRect, _HasRectAttribute]
+
+# cleanup namespace
+del sys, IO, Callable, Tuple, Union, TypeVar, Protocol, SupportsIndex
diff --git a/buildconfig/stubs/pygame/version.pyi b/buildconfig/stubs/pygame/version.pyi
index 69a3b6fe71..3838d68274 100644
--- a/buildconfig/stubs/pygame/version.pyi
+++ b/buildconfig/stubs/pygame/version.pyi
@@ -1,6 +1,4 @@
-from typing import Tuple
-
-from ._common import Literal
+from typing import Literal, Tuple
class SoftwareVersion(Tuple[int, int, int]):
def __new__(cls, major: int, minor: int, patch: int) -> SoftwareVersion: ...
diff --git a/buildconfig/stubs/pygame/window.pyi b/buildconfig/stubs/pygame/window.pyi
index 9b09e76d2d..4b61746de1 100644
--- a/buildconfig/stubs/pygame/window.pyi
+++ b/buildconfig/stubs/pygame/window.pyi
@@ -1,12 +1,13 @@
-from typing import Optional, Tuple, Union, final
+from typing import Optional, Tuple, Union
+from typing_extensions import deprecated # added in 3.13
-from pygame._common import Coordinate, RectValue
+from pygame.typing import Coordinate, RectLike
from pygame.locals import WINDOWPOS_UNDEFINED
from pygame.rect import Rect
from pygame.surface import Surface
def get_grabbed_window() -> Optional[Window]: ...
-@final
+
class Window:
def __init__(
self,
@@ -28,6 +29,7 @@ class Window:
def set_icon(self, icon: Surface, /) -> None: ...
def get_surface(self) -> Surface: ...
def flip(self) -> None: ...
+ def flash(self, operation: int, /) -> None: ...
grab_mouse: bool
grab_keyboard: bool
@@ -47,7 +49,7 @@ class Window:
@property
def mouse_rect(self) -> Optional[Rect]: ...
@mouse_rect.setter
- def mouse_rect(self, value: Optional[RectValue]) -> None: ...
+ def mouse_rect(self, value: Optional[RectLike]) -> None: ...
@property
def size(self) -> Tuple[int, int]: ...
@size.setter
@@ -67,4 +69,5 @@ class Window:
@property
def opengl(self) -> bool: ...
@classmethod
+ @deprecated("since 2.4.0. Use either the display module or the Window class with get_surface and flip. Try not to mix display and Window")
def from_display_module(cls) -> Window: ...
diff --git a/buildconfig/stubs/stubcheck.py b/buildconfig/stubs/stubcheck.py
index 14477a31d5..1389e07595 100644
--- a/buildconfig/stubs/stubcheck.py
+++ b/buildconfig/stubs/stubcheck.py
@@ -11,9 +11,34 @@
STUBS_BASE_DIR = Path(__file__).parent
-def main():
+def typing_check():
"""
- Main entrypoint
+ Ensure type aliases in typing.py work as expected with type checkers
+ """
+ mypy_version_args = [sys.executable, "-m", "mypy"]
+ try:
+ version = subprocess.run(
+ [*mypy_version_args, "--version"], capture_output=True, check=True, text=True
+ ).stdout.strip()
+ except subprocess.CalledProcessError:
+ print("ERROR: could not validate typing.py, make sure you have mypy installed")
+ return
+
+ mypy_args = [*mypy_version_args, "typing_sample_app.py"]
+ cmd = " ".join(mypy_args)
+ print(f"Using mypy invocation: `{cmd}` (version: {version})")
+ prev_dir = os.getcwd()
+ try:
+ os.chdir(STUBS_BASE_DIR)
+ returncode = subprocess.run([*mypy_args]).returncode
+ if returncode != 0:
+ raise RuntimeError(f"mypy process finished with unsuccessful return code {returncode}")
+ finally:
+ os.chdir(prev_dir)
+
+def stubs_check():
+ """
+ Validate the stubs files
"""
for stubtest in ([sys.executable, "-m", "mypy.stubtest"], ["stubtest"]):
try:
@@ -41,5 +66,13 @@ def main():
sys.exit(1)
+def main():
+ """
+ Main entrypoint
+ """
+ typing_check()
+ stubs_check()
+
+
if __name__ == "__main__":
main()
diff --git a/buildconfig/stubs/typing_sample_app.py b/buildconfig/stubs/typing_sample_app.py
new file mode 100644
index 0000000000..dae94bd0ac
--- /dev/null
+++ b/buildconfig/stubs/typing_sample_app.py
@@ -0,0 +1,109 @@
+"""
+Sample app run by mypy to ensure typing.py aliases work as expected
+"""
+from pygame import typing
+import pygame
+import pathlib
+
+# validate SequenceLike
+class MySequence:
+ def __getitem__(self, index):
+ if index > 20:
+ raise IndexError()
+ if index % 2 == 0:
+ return 1
+ return 0
+
+ def __len__(self):
+ return 20
+
+def validator_SequenceLike(sequence: typing.SequenceLike) -> int:
+ return 0
+
+def validator_SequenceLikeTypes(
+ sequence_float: typing.SequenceLike[float],
+ sequence_int: typing.SequenceLike[int],
+ sequence_str: typing.SequenceLike[str],
+ sequence_sequence: typing.SequenceLike[typing.SequenceLike],
+) -> int:
+ return 0
+
+# must pass
+validator_SequenceLike(MySequence())
+validator_SequenceLike([0, 1, 2, 3])
+validator_SequenceLike((0, 1, 2, 3))
+validator_SequenceLike(pygame.Rect(-10, 10, 40, 40))
+validator_SequenceLike(pygame.Vector2())
+validator_SequenceLike("1234567890")
+
+validator_SequenceLikeTypes(
+ (-1.5, -0.5, 0, 0.5, 2.5, 10),
+ (-2, -1, 0, 1, 2, 3),
+ "abcdefghijklmnopqrstuvwxyz",
+ [(0.5, 1.5), (-1, 1), "123", [(), (), ()]]
+)
+
+# validate PathLike
+class MyPath:
+ def __fspath__(self) -> str:
+ return "file.py"
+
+def validator_PathLike(path: typing.PathLike) -> int:
+ return 0
+
+# must pass
+validator_PathLike("file.py")
+validator_PathLike(b"file.py")
+validator_PathLike(pathlib.Path("file.py"))
+validator_PathLike(MyPath())
+
+# validate Coordinate, IntCoordinate
+
+def validator_Coordinate(coordinate: typing.Coordinate) -> int:
+ return 0
+
+def validator_IntCoordinate(coordinate: typing.IntCoordinate) -> int:
+ return 0
+
+# must pass
+validator_Coordinate((1, 2))
+validator_Coordinate([3, -4])
+validator_Coordinate((5, -6.5))
+validator_Coordinate((-6.7, 8.9))
+validator_Coordinate(pygame.Vector2())
+
+validator_IntCoordinate((3, 4))
+validator_IntCoordinate([-4, -3])
+
+# validate RGBATuple, ColorLike
+def validator_RGBATuple(rgba: typing.RGBATuple) -> int:
+ return 0
+
+def validator_ColorLike(color: typing.ColorLike) -> int:
+ return 0
+
+# must pass
+validator_RGBATuple((100, 200, 50, 20))
+validator_ColorLike("green")
+validator_ColorLike(1)
+validator_ColorLike((255, 255, 255, 30))
+validator_ColorLike(pygame.Color(100, 100, 100, 100))
+
+# validate RectLike
+class MyObject1:
+ def __init__(self):
+ self.rect = pygame.Rect(10, 10, 20, 20)
+
+class MyObject2:
+ def __init__(self):
+ self.rect = lambda: pygame.Rect(5, 5, 10, 10)
+
+def validator_RectLike(rect: typing.RectLike) -> int:
+ return 0
+
+# must pass
+validator_RectLike((10, 10, 10, 10))
+validator_RectLike(((5, 5), (30, 30)))
+validator_RectLike(pygame.Rect(1, 2, 3, 4))
+validator_RectLike(MyObject1())
+validator_RectLike(MyObject2())
diff --git a/docs/README.md b/docs/README.md
index ca4a1f25b5..349849b658 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -2,19 +2,20 @@
### Accessing Documentation
-Obviously you can visit pyga.me/docs/ to see the documentation,
+Obviously you can visit pyga.me/docs/ to see the documentation,
but the documentation can also be launched with `python -m pygame.docs`
### Generating the Documentation
Steps:
+
- Install Sphinx (`pip install Sphinx`)
- Fork the pygame-ce repository, download and navigate to it in the terminal
-- Run `python buildconfig/make_docs.py`
+- Run `python -m buildconfig docs`
- If you are using the legacy `python setup.py docs` (which is now deprecated):
- - (Run `python -m pip install -U pip setuptools` first if `ModuleNotFoundError: No module named setuptools` occurs)
+ - (Run `python -m pip install -U pip setuptools` first if `ModuleNotFoundError: No module named setuptools` occurs)
-This will create a new folder under the `docs` folder.
+This will create a new folder under the `docs` folder.
In `docs/generated`, you will find a local copy of the pygame documentation.
You can launch this by clicking on index.html or by running the command
@@ -22,11 +23,18 @@ You can launch this by clicking on index.html or by running the command
__main__.py in `docs/`). The docs launch command will direct you to the
pygame website if there aren't any locally generated docs.
+--- **DEPRECATED** ---
There is also a `docs --fullgeneration` or `docs --f` command for regenerating
everything regardless of whether Sphinx thinks it should be regenerated. This
is useful when editing the theme CSS.
-### Contributing
+
+--- **INSTEAD USE** ---
+There is also `python -m buildconfig docs full_generation` for regenerating
+everything regardless of whether Sphinx thinks it should be regenerated. This
+is useful when editing the theme CSS.
+
+### Contributing
If you see any grammatical mistakes or errors in the documentation,
contributing to the docs is a great way to help out.
@@ -43,15 +51,15 @@ Sphinx has a good ReStructed Text primer to learn the basics:
https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html
Contributing steps:
+
- Have an idea to improve the docs, perhaps create an issue on Github.
- Find the file you want to edit. It will most likely be in `docs/reST/ref`.
-OR
+ OR
- Pygame docs pages have an "Edit on Github" button, which will show you the file.
-- Download the pygame source from Github locally.
- ^ One way to do this is to fork and use a Git client to make that a local repository.
+- Download the pygame source from Github locally.^ One way to do this is to fork and use a Git client to make that a local repository.
- Implement your idea.
- Follow the steps in "Generating the Documentation" above.
- ^ This is important to test that your changes work well.
+ ^ This is important to test that your changes work well.
- Commit your changes, create a pull request.
## Documentation Style
diff --git a/docs/reST/_static/pygame_ce_lofi.png b/docs/reST/_static/pygame_ce_lofi.png
new file mode 100644
index 0000000000..2db62e4cc6
Binary files /dev/null and b/docs/reST/_static/pygame_ce_lofi.png differ
diff --git a/docs/reST/_static/pygame_ce_lofi.svg b/docs/reST/_static/pygame_ce_lofi.svg
new file mode 100644
index 0000000000..5169aad6b5
--- /dev/null
+++ b/docs/reST/_static/pygame_ce_lofi.svg
@@ -0,0 +1,289 @@
+
+
+
diff --git a/docs/reST/_static/pygame_ce_logo.png b/docs/reST/_static/pygame_ce_logo.png
new file mode 100644
index 0000000000..8e377919ea
Binary files /dev/null and b/docs/reST/_static/pygame_ce_logo.png differ
diff --git a/docs/reST/_static/pygame_ce_logo.svg b/docs/reST/_static/pygame_ce_logo.svg
new file mode 100644
index 0000000000..609bc917b6
--- /dev/null
+++ b/docs/reST/_static/pygame_ce_logo.svg
@@ -0,0 +1,289 @@
+
+
+
diff --git a/docs/reST/_static/pygame_ce_powered.png b/docs/reST/_static/pygame_ce_powered.png
new file mode 100644
index 0000000000..2e6ba0cf80
Binary files /dev/null and b/docs/reST/_static/pygame_ce_powered.png differ
diff --git a/docs/reST/_static/pygame_ce_powered.svg b/docs/reST/_static/pygame_ce_powered.svg
new file mode 100644
index 0000000000..57d906c704
--- /dev/null
+++ b/docs/reST/_static/pygame_ce_powered.svg
@@ -0,0 +1,377 @@
+
+
+
diff --git a/docs/reST/_static/pygame_ce_powered_lowres.png b/docs/reST/_static/pygame_ce_powered_lowres.png
new file mode 100644
index 0000000000..6b4590a331
Binary files /dev/null and b/docs/reST/_static/pygame_ce_powered_lowres.png differ
diff --git a/docs/reST/_static/pygame_ce_tiny.png b/docs/reST/_static/pygame_ce_tiny.png
new file mode 100644
index 0000000000..648b2d8018
Binary files /dev/null and b/docs/reST/_static/pygame_ce_tiny.png differ
diff --git a/docs/reST/_static/pygame_lofi.png b/docs/reST/_static/pygame_lofi.png
deleted file mode 100644
index b9e9f56921..0000000000
Binary files a/docs/reST/_static/pygame_lofi.png and /dev/null differ
diff --git a/docs/reST/_static/pygame_lofi.svg b/docs/reST/_static/pygame_lofi.svg
deleted file mode 100644
index 9747a6d5dc..0000000000
--- a/docs/reST/_static/pygame_lofi.svg
+++ /dev/null
@@ -1,234 +0,0 @@
-
-
-
diff --git a/docs/reST/_static/pygame_logo.png b/docs/reST/_static/pygame_logo.png
deleted file mode 100644
index 0de28c4c56..0000000000
Binary files a/docs/reST/_static/pygame_logo.png and /dev/null differ
diff --git a/docs/reST/_static/pygame_logo.svg b/docs/reST/_static/pygame_logo.svg
deleted file mode 100644
index 38fa4ec5cd..0000000000
--- a/docs/reST/_static/pygame_logo.svg
+++ /dev/null
@@ -1,234 +0,0 @@
-
-
-
diff --git a/docs/reST/_static/pygame_powered.png b/docs/reST/_static/pygame_powered.png
deleted file mode 100644
index 4d5bf4bb9d..0000000000
Binary files a/docs/reST/_static/pygame_powered.png and /dev/null differ
diff --git a/docs/reST/_static/pygame_powered.svg b/docs/reST/_static/pygame_powered.svg
deleted file mode 100644
index 2bb4a42a2f..0000000000
--- a/docs/reST/_static/pygame_powered.svg
+++ /dev/null
@@ -1,326 +0,0 @@
-
-
-
diff --git a/docs/reST/_static/pygame_powered_lowres.png b/docs/reST/_static/pygame_powered_lowres.png
deleted file mode 100644
index 642ae7a6e8..0000000000
Binary files a/docs/reST/_static/pygame_powered_lowres.png and /dev/null differ
diff --git a/docs/reST/_static/pygame_tiny.png b/docs/reST/_static/pygame_tiny.png
deleted file mode 100644
index 5ee063a7d9..0000000000
Binary files a/docs/reST/_static/pygame_tiny.png and /dev/null differ
diff --git a/docs/reST/_static/upstream_logos.zip b/docs/reST/_static/upstream_logos.zip
new file mode 100644
index 0000000000..80ffee46ed
Binary files /dev/null and b/docs/reST/_static/upstream_logos.zip differ
diff --git a/docs/reST/c_api/surflock.rst b/docs/reST/c_api/surflock.rst
index 6d806e89a5..9e08034dea 100644
--- a/docs/reST/c_api/surflock.rst
+++ b/docs/reST/c_api/surflock.rst
@@ -15,33 +15,6 @@ This extension module implements SDL surface locking for the
Header file: src_c/include/pygame.h
-.. c:type:: pgLifetimeLockObject
-
- .. c:member:: PyObject *surface
-
- An SDL locked pygame surface.
-
- .. c:member:: PyObject *lockobj
-
- The Python object which owns the lock on the surface.
- This field does not own a reference to the object.
-
- The lifetime lock type instance.
- A lifetime lock pairs a locked pygame surface with
- the Python object that locked the surface for modification.
- The lock is removed automatically when the lifetime lock instance
- is garbage collected.
-
-.. c:var:: PyTypeObject *pgLifetimeLock_Type
-
- The pygame internal surflock lifetime lock object type.
-
-.. c:function:: int pgLifetimeLock_Check(PyObject *x)
-
- Return true if Python object *x* is a :c:data:`pgLifetimeLock_Type` instance,
- false otherwise.
- This will return false on :c:data:`pgLifetimeLock_Type` subclass instances as well.
-
.. c:function:: void pgSurface_Prep(pgSurfaceObject *surfobj)
If *surfobj* is a subsurface, then lock the parent surface with *surfobj*
@@ -72,11 +45,3 @@ Header file: src_c/include/pygame.h
.. c:function:: int pgSurface_UnLockBy(pgSurfaceObject *surfobj, PyObject *lockobj)
Remove the lock on pygame surface *surfobj* owned by Python object *lockobj*.
-
-.. c:function:: PyObject *pgSurface_LockLifetime(PyObject *surfobj, PyObject *lockobj)
-
- Lock pygame surface *surfobj* for Python object *lockobj* and return a
- new :c:data:`pgLifetimeLock_Type` instance for the lock.
-
- This function is not called anywhere within pygame.
- It and pgLifetimeLock_Type are candidates for removal.
diff --git a/docs/reST/conf.py b/docs/reST/conf.py
index cfd4138170..136a723ae3 100644
--- a/docs/reST/conf.py
+++ b/docs/reST/conf.py
@@ -122,7 +122,7 @@
# The name of an image file (relative to this directory) to place at the top
# of the sidebar.
-html_logo = '_static/pygame_tiny.png'
+html_logo = '_static/pygame_ce_tiny.png'
# The name of an image file (within the static path) to use as favicon of the
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
diff --git a/docs/reST/index.rst b/docs/reST/index.rst
index 53121de17a..9bbbf3ac68 100644
--- a/docs/reST/index.rst
+++ b/docs/reST/index.rst
@@ -206,6 +206,9 @@ Reference
:doc:`ref/transform`
Resize and move images.
+:doc:`ref/typing`
+ Provide common typehints
+
:doc:`pygame C API `
The C api shared amongst pygame extension modules.
diff --git a/docs/reST/logos.rst b/docs/reST/logos.rst
index a7ee49311c..f8db3b443f 100644
--- a/docs/reST/logos.rst
+++ b/docs/reST/logos.rst
@@ -1,47 +1,58 @@
*************************************************
- Pygame Logos Page
+ Pygame CE Logos Page
*************************************************
-Pygame Logos
-============
+Pygame CE Logos
+===============
These logos are available for use in your own game projects.
-Please put them up wherever you see fit. The logo was created
-by TheCorruptor on July 29, 2001 and upscaled by Mega_JC on
-August 29, 2021.
+Please put them up wherever you see fit.
+
+The pygame-ce logo and its variants were created by Mega-JC and kadir014 on June 30, 2024.
.. container:: fullwidth
- .. image:: _static/pygame_logo.png
+ .. image:: _static/pygame_ce_logo.png
- | `pygame_logo.svg <_static/pygame_logo.svg>`_
- | `pygame_logo.png <_static/pygame_logo.png>`_ - 1561 x 438
+ | `pygame_ce_logo.svg <_static/pygame_ce_logo.svg>`_
+ | `pygame_ce_logo.png <_static/pygame_ce_logo.png>`_ - 1560 x 800
- .. image:: _static/pygame_lofi.png
+ .. image:: _static/pygame_ce_lofi.png
- | `pygame_lofi.svg <_static/pygame_lofi.svg>`_
- | `pygame_lofi.png <_static/pygame_lofi.png>`_ - 1561 x 438
+ | `pygame_ce_lofi.svg <_static/pygame_ce_lofi.svg>`_
+ | `pygame_ce_lofi.png <_static/pygame_ce_lofi.png>`_ - 1560 x 800
- .. image:: _static/pygame_powered.png
+ .. image:: _static/pygame_ce_powered.png
- | `pygame_powered.svg <_static/pygame_powered.svg>`_
- | `pygame_powered.png <_static/pygame_powered.png>`_ - 1617 x 640
+ | `pygame_ce_powered.svg <_static/pygame_ce_powered.svg>`_
+ | `pygame_ce_powered.png <_static/pygame_ce_powered.png>`_ - 1560 x 824
- .. image:: _static/pygame_tiny.png
+ .. image:: _static/pygame_ce_tiny.png
- | `pygame_tiny.png <_static/pygame_tiny.png>`_ - 214 x 60
+ | `pygame_ce_tiny.png <_static/pygame_ce_tiny.png>`_ - 214 x 110
- .. image:: _static/pygame_powered_lowres.png
+ .. image:: _static/pygame_ce_powered_lowres.png
- | `pygame_powered_lowres.png <_static/pygame_powered_lowres.png>`_ - 101 x 40
+ | `pygame_ce_powered_lowres.png <_static/pygame_ce_powered_lowres.png>`_ - 101 x 53
-There is a higher resolution layered photoshop image
-available `here `_. *(1.3 MB)*
-Legacy logos
-------------
+Upstream & legacy logos
+-----------------------
+
+The pygame logo was originally created
+by TheCorruptor on July 29, 2001.
+
+It was upscaled alongside its "LoFi" and "Powered" variants by Mega-JC on
+August 29, 2021.
+
+.. container:: fullwidth
+
+ | `upstream_logos.zip <_static/upstream_logos.zip>`_ - 699 KB
+
+There is a higher resolution layered photoshop image
+available `here `_. *(1.3 MB)*
.. container:: fullwidth
- `legacy_logos.zip <_static/legacy_logos.zip>`_ - 50.1 KB
\ No newline at end of file
+ | `legacy_logos.zip <_static/legacy_logos.zip>`_ - 50.1 KB
diff --git a/docs/reST/ref/display.rst b/docs/reST/ref/display.rst
index 6122e34d23..310ebb38fa 100644
--- a/docs/reST/ref/display.rst
+++ b/docs/reST/ref/display.rst
@@ -175,9 +175,9 @@ required).
pygame.HIDDEN window is opened in hidden mode
- .. versionadded:: 2.0.0 ``SCALED``, ``SHOWN`` and ``HIDDEN``
+ .. versionaddedold:: 2.0.0 ``SCALED``, ``SHOWN`` and ``HIDDEN``
- .. versionadded:: 2.0.0 ``vsync`` parameter
+ .. versionaddedold:: 2.0.0 ``vsync`` parameter
By setting the ``vsync`` parameter to ``1``, it is possible to get a display
with vertical sync at a constant frame rate determined by the monitor and
@@ -273,7 +273,7 @@ required).
| :sl:`Update all, or a portion, of the display. For non-OpenGL displays.`
| :sg:`update(rectangle=None, /) -> None`
- | :sg:`update(rectangle_list, /) -> None`
+ | :sg:`update(rectangle_iterable, /) -> None`
For non OpenGL display Surfaces, this function is very similar to
``pygame.display.flip()`` with an optional parameter that allows only
@@ -285,8 +285,8 @@ required).
updated. Whereas ``display.update()`` means the whole window is
updated.
- You can pass the function a single rectangle, or a sequence of rectangles.
- Generally you do not want to pass a sequence of rectangles as there is a
+ You can pass the function a single rectangle, or an iterable of rectangles.
+ Generally you do not want to pass an iterable of rectangles as there is a
performance cost per rectangle passed to the function. On modern hardware,
after a very small number of rectangles passed in, the per-rectangle cost
will exceed the saving of updating less pixels. In most applications it is
@@ -294,12 +294,14 @@ required).
means you do not need to keep track of a list of rectangles for each call
to update.
- If passing a sequence of rectangles it is safe to include None
+ If passing an iterable of rectangles it is safe to include None
values in the list, which will be skipped.
This call cannot be used on ``pygame.OPENGL`` displays and will generate an
exception.
+ .. versionchanged:: 2.5.1 Added support for passing an iterable, previously only sequence was allowed
+
.. ## pygame.display.update ##
.. function:: get_driver
@@ -850,13 +852,10 @@ required).
:param str title: A title string.
:param str message: A message string. If this parameter is set to ``None``, the message will be the title.
:param str message_type: Set the type of message_box, could be ``"info"``, ``"warn"`` or ``"error"``.
+ :param Window parent_window: The parent window of the message box.
:param tuple buttons: An optional sequence of button name strings to show to the user.
:param int return_button: Button index to use if the return key is hit, ``0`` by default.
:param int escape_button: Button index to use if the escape key is hit, ``None`` for no button linked by default.
-..
- (Uncomment this after the window API is published)
- :param Window parent_window: The parent window of the message_box
-..
:return: The index of the button that was pushed.
diff --git a/docs/reST/ref/draw.rst b/docs/reST/ref/draw.rst
index a6e09d9319..1c71eebe49 100644
--- a/docs/reST/ref/draw.rst
+++ b/docs/reST/ref/draw.rst
@@ -213,7 +213,7 @@ object around the draw calls (see :func:`pygame.Surface.lock` and
| :sg:`aacircle(surface, color, center, radius, width=0, draw_top_right=None, draw_top_left=None, draw_bottom_left=None, draw_bottom_right=None) -> Rect`
Draws an antialiased circle on the given surface.
- Uses Xaolin Wu Circle Algorithm.
+ Uses Xiaolin Wu Circle Algorithm.
adapted from: https://cgg.mff.cuni.cz/~pepca/ref/WU.pdf
:param Surface surface: surface to draw on
diff --git a/docs/reST/ref/geometry.rst b/docs/reST/ref/geometry.rst
index 8d3fa7cfd0..dc16f6dec3 100644
--- a/docs/reST/ref/geometry.rst
+++ b/docs/reST/ref/geometry.rst
@@ -24,10 +24,10 @@
| :sg:`Circle((x, y), radius) -> Circle`
| :sg:`Circle(x, y, radius) -> Circle`
- The `Circle` class provides many useful methods for collision / transform and intersection.
+ The `Circle` class provides many useful methods for collision testing, transformation and intersection.
A `Circle` can be created from a combination of a pair of coordinates that represent
the center of the circle and a radius. Circles can also be created from python objects that
- are already a `Circle` or have an attribute named "circle".
+ are already a `Circle` (effectively copying the circle) or have an attribute named "circle".
Specifically, to construct a circle you can pass the x, y, and radius values as separate
arguments or inside a sequence(list or tuple).
@@ -38,17 +38,10 @@
((x, y), radius)
(x, y, radius)
+ (vector2, radius)
- It is important to note that you cannot create degenerate circles, which are circles with
- a radius of 0 or less. If you try to create such a circle, the `Circle` object will not be
- created and an error will be raised. This is because a circle with a radius of 0 or
- less is not a valid geometric object.
-
- The `Circle` class has both virtual and non-virtual attributes. Non-virtual attributes
- are attributes that are stored in the `Circle` object itself. Virtual attributes are the
- result of calculations that utilize the Circle's non-virtual attributes.
-
- Here is the list of all the attributes and methods of the Circle class:
+ The `Circle` class only stores the x, y and r attributes, everything else is calculated
+ on the fly based on them.
**Circle Attributes**
@@ -59,9 +52,7 @@
| :sl:`center x coordinate of the circle`
| :sg:`x -> float`
- The `x` coordinate of the center of the circle. It can be reassigned to move the circle.
- Reassigning the `x` attribute will move the circle to the new `x` coordinate.
- The `y` and `r` attributes will not be affected.
+ The horizontal coordinate of the center of the circle. Reassigning it moves the circle.
.. versionadded:: 2.4.0
@@ -72,9 +63,7 @@
| :sl:`center y coordinate of the circle`
| :sg:`y -> float`
- The `y` coordinate of the center of the circle. It can be reassigned to move the circle.
- Reassigning the `y` attribute will move the circle to the new `y` coordinate.
- The `x` and `r` attributes will not be affected.
+ The vertical coordinate of the center of the circle. Reassigning it moves the circle.
.. versionadded:: 2.4.0
@@ -85,12 +74,12 @@
| :sl:`radius of the circle`
| :sg:`r -> float`
- It is not possible to set the radius to a negative value. It can be reassigned.
- If reassigned it will only change the radius of the circle.
- The circle will not be moved from its original position.
+ Represents the size of the circle. It can't be negative. Reassigning it scales the circle.
.. versionadded:: 2.4.0
+ .. versionchanged:: 2.5.1 It is now allowed to create degenerate circles with :math:`r = 0`.
+
.. ## Circle.r ##
.. attribute:: r_sqr
@@ -98,9 +87,7 @@
| :sl:`radius of the circle squared`
| :sg:`r_sqr -> float`
- It's equivalent to `r*r`. It can be reassigned. If reassigned, the radius
- of the circle will be changed to the square root of the new value.
- The circle will not be moved from its original position.
+ It's equivalent to :math:`r^2`. It can't be negative. Reassigning it changes the radius to :math:`r = \sqrt{r_{sqr}}`.
.. versionadded:: 2.4.0
@@ -111,9 +98,8 @@
| :sl:`x and y coordinates of the center of the circle`
| :sg:`center -> (float, float)`
- It's a tuple containing the `x` and `y` coordinates that represent the center
- of the circle. It can be reassigned. If reassigned, the circle will be moved
- to the new position. The radius will not be affected.
+ It's a tuple containing the circle's `x` and `y` coordinates representing its center.
+ Reassigning it moves the circle to the new position.
.. versionadded:: 2.4.0
@@ -124,9 +110,8 @@
| :sl:`diameter of the circle`
| :sg:`diameter -> float`
- It's calculated using the `d=2*r` formula. It can be reassigned. If reassigned
- the radius will be changed to half the diameter.
- The circle will not be moved from its original position.
+ It's equivalent to :math:`2 \cdot r`. It can't be negative. Reassigning it
+ changes the radius to :math:`r = \frac{d}{2}`.
.. versionadded:: 2.4.0
@@ -137,9 +122,8 @@
| :sl:`area of the circle`
| :sg:`area -> float`
- It's calculated using the `area=pi*r*r` formula. It can be reassigned.
- If reassigned the circle radius will be changed to produce a circle with matching
- area. The circle will not be moved from its original position.
+ It's equivalent to :math:`\pi \cdot r^2`. It can't be negative. Reassigning it
+ changes the radius to :math:`r = \sqrt{\frac{area}{\pi}}` producing a circle with matching area.
.. versionadded:: 2.4.0
@@ -150,9 +134,9 @@
| :sl:`circumference of the circle`
| :sg:`circumference -> float`
- It's calculated using the `circumference=2*pi*r` formula. It can be reassigned.
- If reassigned the circle radius will be changed to produce a circle with matching
- circumference. The circle will not be moved from its original position.
+ It's equivalent to :math:`2 \cdot \pi \cdot r`. It can't be negative. Reassigning it
+ changes the radius to :math:`r = \frac{circumference}{2\pi}` producing a circle with
+ matching circumference.
.. versionadded:: 2.4.0
@@ -164,15 +148,14 @@
.. method:: collidepoint
- | :sl:`test if a point is inside the circle`
+ | :sl:`tests if a point is inside the circle`
| :sg:`collidepoint((x, y), /) -> bool`
| :sg:`collidepoint(x, y, /) -> bool`
| :sg:`collidepoint(vector2, /) -> bool`
- The `collidepoint` method tests whether a given point is inside the `Circle`
- (including the edge of the `Circle`). It takes a tuple of (x, y) coordinates, two
- separate x and y coordinates, or a `Vector2` object as its argument, and returns
- `True` if the point is inside the `Circle`, `False` otherwise.
+ Returns `True` if the given point is inside this `Circle` (edge included), `False` otherwise.
+ It takes a tuple of (x, y) coordinates, two separate x and y coordinates, or a `Vector2`
+ object as its argument.
.. versionadded:: 2.4.0
@@ -180,83 +163,35 @@
.. method:: collidecircle
- | :sl:`test if two circles collide`
+ | :sl:`tests if a circle collides with this circle`
| :sg:`collidecircle(circle, /) -> bool`
| :sg:`collidecircle(x, y, radius, /) -> bool`
| :sg:`collidecircle((x, y), radius, /) -> bool`
+ | :sg:`collidecircle(vector2, radius, /) -> bool`
- The `collidecircle` method tests whether two `Circle` objects overlap. It takes either
- a `Circle` object, a tuple of (x, y) coordinates and a radius, or separate x and y
- coordinates and a radius as its argument, and returns `True` if any portion of the two
- `Circle` objects overlap, `False` otherwise.
+ Returns `True` if the given circle intersects with this `Circle`, `False` otherwise.
+ It takes either a `Circle` object, a tuple of (x, y) coordinates and a radius, or separate x and y
+ coordinates and a radius as its argument.
.. note::
- If this method is called with a `Circle` object that is the same as the `Circle`
- it is called on, it will always return `True`.
+ Calling this method with this circle as the argument will always return `True`.
.. versionadded:: 2.4.0
.. ## Circle.collidecircle ##
- .. method:: move
-
- | :sl:`moves the circle by a given amount`
- | :sg:`move((x, y), /) -> Circle`
- | :sg:`move(x, y, /) -> Circle`
- | :sg:`move(vector2, /) -> Circle`
-
- The `move` method allows you to create a new `Circle` object that is moved by a given
- offset from the original `Circle`. This is useful if you want to move a `Circle` without
- modifying the original. The move method takes either a tuple of (x, y) coordinates,
- two separate x and y coordinates, or a `Vector2` object as its argument, and returns
- a new `Circle` object with the updated position.
-
- .. note::
- This method is equivalent(behaviour wise) to the following code:
-
- .. code-block:: python
-
- Circle((circle.x + x, circle.y + y), circle.r)
-
- .. versionadded:: 2.5.0
-
- .. ## Circle.move ##
-
- .. method:: move_ip
-
- | :sl:`moves the circle by a given amount, in place`
- | :sg:`move_ip((x, y), /) -> None`
- | :sg:`move_ip(x, y, /) -> None`
- | :sg:`move_ip(vector2, /) -> None`
-
- The `move_ip` method is similar to the move method, but it moves the `Circle` in place,
- modifying the original `Circle` object. This method takes the same types of arguments
- as move, and it always returns None.
-
- .. note::
- This method is equivalent(behaviour wise) to the following code:
-
- .. code-block:: python
-
- circle.x += x
- circle.y += y
-
- .. versionadded:: 2.5.0
-
- .. ## Circle.move_ip ##
-
.. method:: colliderect
- | :sl:`checks if a rectangle intersects the circle`
+ | :sl:`tests if a rectangle collides with this circle`
| :sg:`colliderect(rect, /) -> bool`
| :sg:`colliderect((x, y, width, height), /) -> bool`
| :sg:`colliderect(x, y, width, height, /) -> bool`
| :sg:`colliderect((x, y), (width, height), /) -> bool`
+ | :sg:`colliderect(vector2, (width, height), /) -> bool`
- The `colliderect` method tests whether a given rectangle intersects the `Circle`. It
- takes either a `Rect` object, a tuple of (x, y, width, height) coordinates, or separate
- x, y coordinates and width, height as its argument. Returns `True` if any portion
- of the rectangle overlaps with the `Circle`, `False` otherwise.
+ Returns `True` if the given rectangle intersects with this `Circle`, `False` otherwise.
+ Takes either a `Rect` object, a tuple of (x, y, width, height) coordinates, or separate
+ x, y coordinates and width, height as its argument.
.. versionadded:: 2.4.0
@@ -264,15 +199,14 @@
.. method:: collideswith
- | :sl:`check if a shape or point collides with the circle`
+ | :sl:`tests if a shape or point collides with this circle`
| :sg:`collideswith(circle, /) -> bool`
| :sg:`collideswith(rect, /) -> bool`
| :sg:`collideswith((x, y), /) -> bool`
| :sg:`collideswith(vector2, /) -> bool`
- The `collideswith` method checks if a shape or point overlaps with a `Circle` object.
- It takes a single argument which can be a `Circle`, `Rect`, `FRect`, or a point.
- It returns `True` if there's an overlap, and `False` otherwise.
+ Returns `True` if the given shape or point intersects with this `Circle`, `False` otherwise.
+ The shape can be a `Circle`, `Rect`, `FRect`.
.. note::
The shape argument must be an actual shape object (`Circle`, `Rect`, or `FRect`).
@@ -285,15 +219,14 @@
.. method:: contains
- | :sl:`check if a shape or point is inside the circle`
+ | :sl:`tests if a shape or point is inside the circle`
| :sg:`contains(circle, /) -> bool`
| :sg:`contains(rect, /) -> bool`
| :sg:`contains((x, y), /) -> bool`
| :sg:`contains(vector2, /) -> bool`
- Checks whether a given shape or point is completely contained within the `Circle`.
- Takes a single argument which can be a `Circle`, `Rect`, `FRect`, or a point.
- Returns `True` if the shape or point is completely contained, and `False` otherwise.
+ Returns `True` if the shape or point is completely contained within this `Circle`, `False` otherwise.
+ The shape can be a `Circle`, `Rect`, `FRect`.
.. note::
The shape argument must be an actual shape object (`Circle`, `Rect`, or `FRect`).
@@ -304,24 +237,65 @@
.. ## Circle.contains ##
+ .. method:: move
+
+ | :sl:`moves the circle by a given amount`
+ | :sg:`move((x, y), /) -> Circle`
+ | :sg:`move(x, y, /) -> Circle`
+ | :sg:`move(vector2, /) -> Circle`
+
+ Returns a copy of this `Circle` moved by the given amounts.
+ Takes either a tuple of (x, y) coordinates, two separate x and y coordinates,
+ or a `Vector2` object as its argument.
+
+ This method is equivalent to the following code:
+
+ .. code-block:: python
+
+ Circle((circle.x + x, circle.y + y), circle.r)
+
+ .. versionadded:: 2.5.0
+
+ .. ## Circle.move ##
+
+ .. method:: move_ip
+
+ | :sl:`moves the circle by a given amount, in place`
+ | :sg:`move_ip((x, y), /) -> None`
+ | :sg:`move_ip(x, y, /) -> None`
+ | :sg:`move_ip(vector2, /) -> None`
+
+ Moves this `Circle` in place by the given amounts.
+ Takes the same types of arguments as :meth:`move` and it always returns `None`.
+
+ This method is equivalent to the following code:
+
+ .. code-block:: python
+
+ circle.x += x
+ circle.y += y
+
+ .. versionadded:: 2.5.0
+
+ .. ## Circle.move_ip ##
+
.. method:: update
| :sl:`updates the circle position and radius`
| :sg:`update((x, y), radius, /) -> None`
| :sg:`update(x, y, radius, /) -> None`
+ | :sg:`update(vector2, radius, /) -> None`
- The `update` method allows you to set the position and radius of a `Circle` object in
- place. This method takes either a tuple of (x, y) coordinates, two separate x and
- y coordinates, and a radius as its arguments, and it always returns `None`.
+ Sets the position and radius of this `Circle` to the provided values.
+ It always returns `None`.
- .. note::
- This method is equivalent(behaviour wise) to the following code:
+ This method is equivalent to the following code:
- .. code-block:: python
+ .. code-block:: python
- circle.x = x
- circle.y = y
- circle.r = radius
+ circle.x = x
+ circle.y = y
+ circle.r = radius
.. versionadded:: 2.4.0
@@ -333,10 +307,9 @@
| :sg:`rotate(angle, rotation_point=Circle.center, /) -> Circle`
| :sg:`rotate(angle, /) -> Circle`
- Returns a new `Circle` that is rotated by the specified angle around a point.
- A positive angle rotates the circle clockwise, while a negative angle rotates it counter-clockwise. Angles should be specified in degrees.
- The rotation point can be a `tuple`, `list`, or `Vector2`.
- If no rotation point is given, the circle will be rotated around its center.
+ Returns a copy of this `Circle` rotated by the specified angle (in degrees) around a point.
+ Positive angles rotate the circle clockwise, counter-clockwise otherwise.
+ The rotation point is optional and defaults to the circle's center.
.. versionadded:: 2.5.0
@@ -348,11 +321,9 @@
| :sg:`rotate_ip(angle, rotation_point=Circle.center, /) -> None`
| :sg:`rotate_ip(angle, /) -> None`
-
- This method rotates the circle by a specified angle around a point.
- A positive angle rotates the circle clockwise, while a negative angle rotates it counter-clockwise. Angles should be specified in degrees.
- The rotation point can be a `tuple`, `list`, or `Vector2`.
- If no rotation point is given, the circle will be rotated around its center.
+ Rotates the circle by a specified angle (in degrees) around a point.
+ Positive angles rotate the circle clockwise, counter-clockwise otherwise.
+ The rotation point is optional and defaults to the circle's center.
.. versionadded:: 2.5.0
@@ -360,20 +331,16 @@
.. method:: as_rect
- | :sl:`returns the smallest pygame.Rect object that contains the circle`
+ | :sl:`returns the smallest Rect containing the circle`
| :sg:`as_rect() -> Rect`
- The `as_rect` method returns a `pygame.Rect` object that represents the smallest
- rectangle that completely contains the `Circle` object. This means that the `Rect`
- object returned by as_rect will have dimensions such that it completely encloses
- the `Circle`, with no part of the `Circle` extending outside of the `Rect`.
+ Returns the smallest `pygame.Rect` object containing this `Circle`.
- .. note::
- This method is equivalent(behaviour wise) to the following code:
+ This method is equivalent to the following code:
- .. code-block:: python
+ .. code-block:: python
- Rect(circle.x - circle.r, circle.y - circle.r, circle.r * 2, circle.r * 2)
+ Rect(circle.x - circle.r, circle.y - circle.r, circle.r * 2, circle.r * 2)
.. versionadded:: 2.5.0
@@ -381,20 +348,16 @@
.. method:: as_frect
- | :sl:`returns the smallest pygame.FRect object that contains the circle`
+ | :sl:`returns the smallest FRect containing the circle`
| :sg:`as_frect() -> FRect`
- The `as_frect` method returns a `pygame.FRect` object that represents the smallest
- rectangle that completely contains the `Circle` object. This means that the `FRect`
- object returned by as_rect will have dimensions such that it completely encloses
- the `Circle`, with no part of the `Circle` extending outside of the `FRect`.
+ Returns the smallest `pygame.FRect` object containing this `Circle`.
- .. note::
- This method is equivalent(behaviour wise) to the following code:
+ This method is equivalent to the following code:
- .. code-block:: python
+ .. code-block:: python
- FRect(circle.x - circle.r, circle.y - circle.r, circle.r * 2, circle.r * 2)
+ FRect(circle.x - circle.r, circle.y - circle.r, circle.r * 2, circle.r * 2)
.. versionadded:: 2.5.0
@@ -402,12 +365,10 @@
.. method:: copy
- | :sl:`returns a copy of the circle`
+ | :sl:`copies the circle`
| :sg:`copy() -> Circle`
- The `copy` method returns a new `Circle` object having the same position and radius
- as the original `Circle` object. The function takes no arguments and returns the
- new `Circle` object.
+ Returns a copy of this `Circle`.
.. versionadded:: 2.4.0
diff --git a/docs/reST/ref/image.rst b/docs/reST/ref/image.rst
index f964f86642..3086f5b999 100644
--- a/docs/reST/ref/image.rst
+++ b/docs/reST/ref/image.rst
@@ -228,13 +228,15 @@ following formats.
* ``RGBA``, 32-bit image with an alpha channel
* ``ARGB``, 32-bit image with alpha channel first
-
- * ``BGRA``, 32-bit image with alpha channel, red and blue channels swapped
+
+ * ``BGRA``, 32-bit image with alpha channel, red and blue channels swapped
+
+ * ``ABGR``, 32-bit image with alpha channel, reverse order
* ``RGBA_PREMULT``, 32-bit image with colors scaled by alpha channel
* ``ARGB_PREMULT``, 32-bit image with colors scaled by alpha channel, alpha channel first
-
+
The 'pitch' argument can be used to specify the pitch/stride per horizontal line
of the image in bytes. It must be equal to or greater than how many bytes
the pixel data of each horizontal line in the image bytes occupies without any
@@ -248,6 +250,7 @@ following formats.
.. versionadded:: 2.1.3
.. versionchanged:: 2.2.0 Now supports keyword arguments.
.. versionchanged:: 2.5.0 Added a 'pitch' argument.
+ .. versionchanged:: 2.5.1 Added support for ABGR image format
.. ## pygame.image.tobytes ##
diff --git a/docs/reST/ref/special_flags_list.rst b/docs/reST/ref/special_flags_list.rst
index 057675257e..9a830cd168 100644
--- a/docs/reST/ref/special_flags_list.rst
+++ b/docs/reST/ref/special_flags_list.rst
@@ -46,15 +46,15 @@ Special Flags List
- ``BLEND_ADD`` / ``BLEND_RGB_ADD``
Adds the source color channels to the destination color channels, clamped to a maximum of 255.
- The result color is always a lighter color.
+ The result color is always a lighter color or the same color.
- ``BLEND_SUB`` / ``BLEND_RGB_SUB``
Subtracts the source color channels from the destination color channels, clamped to a minimum of 0.
- The result color is always a darker color.
+ The result color is always a darker color or the same color.
- ``BLEND_MULT`` / ``BLEND_RGB_MULT``
Multiplies the destination color channels by the source color channels, divided by 256 (or >> 8).
- The result color is always a darker color.
+ The result color is always a darker color or the same color.
- ``BLEND_MIN`` / ``BLEND_RGB_MIN``
Takes the minimum value between the source and destination color channels.
diff --git a/docs/reST/ref/surface.rst b/docs/reST/ref/surface.rst
index a90cf6d1fa..4d0e3be9da 100644
--- a/docs/reST/ref/surface.rst
+++ b/docs/reST/ref/surface.rst
@@ -90,7 +90,7 @@
.. method:: blit
| :sl:`draw another surface onto this one`
- | :sg:`blit(source, dest, area=None, special_flags=0) -> Rect`
+ | :sg:`blit(source, dest=(0, 0), area=None, special_flags=0) -> Rect`
Draws another Surface onto this Surface.
@@ -98,8 +98,8 @@
- ``source``
The ``Surface`` object to draw onto this ``Surface``.
If it has transparency, transparent pixels will be ignored when blittting to an 8-bit ``Surface``.
- - ``dest``
- The ``source`` draw position onto this ``Surface``.
+ - ``dest`` *(optional)*
+ The ``source`` draw position onto this ``Surface``, defaults to (0, 0).
It can be a coordinate ``(x, y)`` or a ``Rect`` (using its top-left corner).
If a ``Rect`` is passed, its size will not affect the blit.
- ``area`` *(optional)*
@@ -137,6 +137,8 @@
- The blit is ignored if the ``source`` is positioned completely outside this ``Surface``'s
clipping area. Otherwise only the overlapping area will be drawn.
+ .. versionchanged:: 2.5.1 The dest argument is optional and defaults to (0, 0)
+
.. ## Surface.blit ##
.. method:: blits
@@ -306,6 +308,11 @@
This will return the affected Surface area.
+ .. note:: As of pygame-ce version 2.5.1, a long-standing bug has been fixed!
+ Now when passing in a ``Rect`` with negative ``x`` or negative ``y`` (or both),
+ the ``Rect`` filled will no longer be shifted to ``(0, 0)``, but instead only the
+ part of the ``Rect`` overlapping the window's ``Rect`` will be filled.
+
.. ## Surface.fill ##
.. method:: scroll
@@ -1034,6 +1041,23 @@
.. ## Surface.premul_alpha ##
+ .. method:: premul_alpha_ip
+
+ | :sl:`multiplies the RGB channels by the surface alpha channel.`
+ | :sg:`premul_alpha_ip() -> Surface`
+
+ Multiplies the RGB channels of the surface by the alpha channel in place and returns the surface.
+
+ Surfaces without an alpha channel cannot use this method and will return an error if you use
+ it on them. It is best used on 32 bit surfaces (the default on most platforms) as the blitting
+ on these surfaces can be accelerated by SIMD versions of the pre-multiplied blitter.
+
+ Refer to the :meth:`premul_alpha` method for more information.
+
+ .. versionadded:: 2.5.1
+
+ .. ## Surface.premul_alpha_ip ##
+
.. attribute:: width
| :sl:`Surface width in pixels (read-only)`
diff --git a/docs/reST/ref/typing.rst b/docs/reST/ref/typing.rst
new file mode 100644
index 0000000000..764f05a28d
--- /dev/null
+++ b/docs/reST/ref/typing.rst
@@ -0,0 +1,85 @@
+.. include:: common.txt
+
+:mod:`pygame.typing`
+====================
+
+.. module:: pygame.typing
+ :synopsis: pygame module providing common typehints
+
+| :sl:`pygame module providing common typehints`
+
+.. versionadded:: 2.5.2
+
+A lot of pygame functions and methods allow the user to provide different types
+for the same value like colors or coordinates. This module exports the most common
+type aliases for proper typehint annotations.
+
+ .. data:: PathLike
+
+ An object representing a file path, i.e.:
+
+ * ``"my/string/path.txt"``
+ * ``pathlib.Path("my/pathlib/path.txt")``
+ * ``b"my/bytes/path.txt"``
+ * Any object implementing the path protocol (with a ``__fspath__`` magic method)
+
+ .. data:: FileLike
+
+ An object representing a file. Same as :mod:`pygame.typing.PathLike` with
+ the addition of file buffers (``IO`` of strings or bytes) such as the
+ return value of ``open()``.
+
+ .. data:: SequenceLike
+
+ A variant of the standard ``Sequence`` ABC only requiring ``__getitem__``
+ and ``__len__``. This includes custom sequences or builtin ones, i.e.:
+
+ * ``"abcdefg"``
+ * ``[a, b, c, d, ...]``
+ * ``(a, b, c, d, ...)``
+
+ Being a generic, subscribing it will signal further precision such as
+ ``SequenceLike[str]`` or ``SequenceLike[float]``.
+
+ .. data:: Coordinate
+
+ A sequence of two numbers (floats or ints), i.e:
+
+ * ``pygame.Vector2(a, b)``
+ * ``[a, b]``
+ * ``(a, b)``
+
+ .. data:: IntCoordinate
+
+ A sequence of strictly two integers such as ``[a, b]`` or ``(a, b)``.
+
+ .. data:: RGBATuple
+
+ A tuple of four integers ``(r, g, b, a)`` in range 0-255 such as ``(20, 255, 0, 100)``.
+
+ .. data:: ColorLike
+
+ An object representing a color such as a mapped integer, a string or
+ a sequence of three or four integers in range 0-255, types supported by
+ every function accepting a color argument. i.e.:
+
+ * ``pygame.Color(ColorLike)``
+ * ``(r, g, b)``
+ * ``(r, g, b, a)``
+ * ``[r, g, b, a]``
+ * ``"green"``
+ * ``0`` (mapped color)
+
+ .. data:: RectLike
+
+ An object representing a rect such as a sequence of numbers or coordinates
+ or an object with a rect attribute or a method returning a rect. These types
+ are supported by every function accepting a rect as argument. i.e.:
+
+ * ``(x, y, w, h)``
+ * ``(Coordinate, Coordinate)``
+ * ``pygame.Rect(RectLike)``
+ * Any object with a ``.rect`` attribute which is a ``RectLike`` or a function
+ returning a ``RectLike``
+
+.. ## pygame.typing ##
diff --git a/docs/reST/ref/window.rst b/docs/reST/ref/window.rst
index 1365b636aa..d6ef9cbcfc 100644
--- a/docs/reST/ref/window.rst
+++ b/docs/reST/ref/window.rst
@@ -46,6 +46,7 @@
.. versionadded:: 2.4.0
.. versionchanged:: 2.5.0 when ``opengl`` is ``True``, the ``Window`` has an OpenGL context created by pygame
+ .. versionchanged:: 2.5.1 Window is now a base class, allowing subclassing
.. attribute:: grab_mouse
@@ -387,4 +388,35 @@
.. note:: This function is only supported on X11.
+ .. method:: flash
+
+ | :sl:`Flash a window to demand attention from the user`
+ | :sg:`flash(operation, /) -> None`
+
+ :param int operation: The flash operation.
+
+ Supported flash operations are:
+ * ``pygame.FLASH_CANCEL``: Cancel the current flash state if present
+ * ``pygame.FLASH_BRIEFLY``: Flash for a short amount of time to get attention
+ * ``pygame.FLASH_UNTIL_FOCUSED``: Keep flashing until the window is focused
+
+ Window flashing requires SDL 2.0.16+. A :mod:`pygame.error` exception will be raised
+ otherwise.
+
+ .. note:: This function is only supported on Windows, X11, Wayland and Cocoa (MacOS).
+ A :mod:`pygame.error` exception will be raised if it's not supported therefore it's
+ advised to wrap it in a try block.
+
+ .. code-block:: python
+
+ import pygame
+ window = pygame.Window()
+
+ try:
+ window.flash(pygame.FLASH_BRIEFLY)
+ except pygame.error:
+ print("Window flashing not supported")
+
+ .. versionadded:: 2.5.2
+
.. ## pygame.Window ##
diff --git a/docs/reST/themes/classic/elements.html b/docs/reST/themes/classic/elements.html
index be82e27801..486525a5db 100644
--- a/docs/reST/themes/classic/elements.html
+++ b/docs/reST/themes/classic/elements.html
@@ -106,6 +106,4 @@ pygame-ce documentation
{%- endblock %}
-{%- block relbaritems %}
-
-{% endblock %}
+
diff --git a/docs/reST/themes/classic/static/pygame.css_t b/docs/reST/themes/classic/static/pygame.css_t
index cc71502270..7b84148903 100644
--- a/docs/reST/themes/classic/static/pygame.css_t
+++ b/docs/reST/themes/classic/static/pygame.css_t
@@ -120,15 +120,17 @@ div.header .logo {
align-items: center;
justify-content: center;
flex-direction: column;
+ min-width: 214px;
}
.dark-theme div.header .logo {
background-color: {{ theme_dark_logobgcolor }};
border: none;
+ min-width: 214px;
}
div.header .logo img {
- min-width: 200px;
- min-height: 60px;
border-style: none;
+ height: 80px;
+ min-width: 156px;
}
div.header .pagelinks {
@@ -601,6 +603,15 @@ dt code {
color: {{ theme_dark_codecomment }};
}
+.dark-theme .highlight .nf,
+.dark-theme .highlight .fm {
+ color: {{ theme_dark_functiondefinecolor }};
+}
+
+.dark-theme .highlight .nc {
+ color: {{ theme_dark_classdefinecolor }}; font-weight: bold
+}
+
code.download span.pre {
font-family: inherit;
font-weight: normal;
@@ -637,17 +648,17 @@ div.body h6 {
line-height: 130%;
}
-div.body h1 {
+div.body h1 {
font-size: 1.5em; color: {{ theme_h1color }};
}
-.dark-theme div.body h1 {
+.dark-theme div.body h1 {
color: {{ theme_dark_h1color }};
}
-div.body h2 {
+div.body h2 {
font-size: 1.4em;
color: {{ theme_h2color }};
}
-.dark-theme div.body h2 {
+.dark-theme div.body h2 {
color: {{ theme_dark_h2color }};
}
div.body h3 { font-size: 1.3em; }
diff --git a/docs/reST/themes/classic/theme.conf b/docs/reST/themes/classic/theme.conf
index dc06790366..c0bd7ad019 100644
--- a/docs/reST/themes/classic/theme.conf
+++ b/docs/reST/themes/classic/theme.conf
@@ -56,6 +56,8 @@ dark_notebgcolor = #555349
dark_notebordercolor = #6e6b5e
dark_cautionbgcolor = rgb(255 25 25 / 30%)
dark_cautionbordercolor = rgb(255 60 60)
+dark_functiondefinecolor = #fee32d
+dark_classdefinecolor = #50b0fa
relbarbgcolor = #6aee28
relbartextcolor = #000000
diff --git a/docs/reST/tutorials/en/chimp-explanation.rst b/docs/reST/tutorials/en/chimp-explanation.rst
index a5bd19397a..ef713b20c3 100644
--- a/docs/reST/tutorials/en/chimp-explanation.rst
+++ b/docs/reST/tutorials/en/chimp-explanation.rst
@@ -65,11 +65,6 @@ It also checks for the availability of some of the optional pygame modules. ::
import os
import pygame
- if not pygame.font:
- print("Warning, fonts disabled")
- if not pygame.mixer:
- print("Warning, sound disabled")
-
main_dir = os.path.split(os.path.abspath(__file__))[0]
data_dir = os.path.join(main_dir, "data")
@@ -79,12 +74,6 @@ us to do things like create platform independent file paths.
In the next line, we import the pygame package.
-Some pygame modules are optional, and if they aren't found,
-they evaluate to ``False``. Because of that, we decide to print
-a nice warning message if the :mod:`font` or
-:mod:`mixer ` modules in pygame are not available.
-(Although they will only be unavailable in very uncommon situations).
-
Lastly, we prepare two paths for the rest of the code to use.
``main_dir`` uses the `os.path` module and the `__file__` variable provided
by Python to locate the game's python file, and extract the folder from
@@ -101,12 +90,10 @@ look at each function individually in this section. ::
def load_image(name, colorkey=None, scale=1):
fullname = os.path.join(data_dir, name)
image = pygame.image.load(fullname)
+ image = image.convert()
- size = image.get_size()
- size = (size[0] * scale, size[1] * scale)
- image = pygame.transform.scale(image, size)
+ image = pygame.transform.scale_by(image, scale)
- image = image.convert()
if colorkey is not None:
if colorkey == -1:
colorkey = image.get_at((0, 0))
@@ -130,9 +117,8 @@ call to the `convert()` function. This makes a new copy of a Surface and convert
its color format and depth to match the display. This means blitting the
image to the screen will happen as quickly as possible.
-We then scale the image, using the :func:`pygame.transform.scale` function.
-This function takes a Surface and the size it should be scaled to. To scale
-by a scalar, we can get the size and scale the x and y by the scalar.
+We then scale the image, using the :func:`pygame.transform.scale_by` function.
+This function takes a Surface and the scale factor it should be scaled of.
Last, we set the colorkey for the image. If the user supplied an argument
for the colorkey argument we use that value as the colorkey for the image.
@@ -146,7 +132,7 @@ that color for the colorkey. ::
def play(self):
pass
- if not pygame.mixer or not pygame.mixer.get_init():
+ if not pygame.mixer.get_init():
return NoneSound()
fullname = os.path.join(data_dir, name)
@@ -156,7 +142,7 @@ that color for the colorkey. ::
Next is the function to load a sound file. The first thing this function
-does is check to see if the :mod:`pygame.mixer` module was imported correctly.
+does is check to see if the :mod:`pygame.mixer` module was initialized.
If not, it returns a small class instance that has a placeholder play method.
This will act enough like a normal Sound object for this game to run without
any extra error checking.
@@ -367,15 +353,12 @@ input formats. See the :mod:`pygame.Color` for all the color formats.
Put Text On The Background, Centered
------------------------------------
-Now that we have a background surface, lets get the text rendered to it. We
-only do this if we see the :mod:`pygame.font` module has imported properly.
-If not, we just skip this section. ::
+Now that we have a background surface, lets get the text rendered to it. ::
- if pygame.font:
- font = pygame.font.Font(None, 64)
- text = font.render("Pummel The Chimp, And Win $$$", True, (10, 10, 10))
- textpos = text.get_rect(centerx=background.get_width() / 2, y=10)
- background.blit(text, textpos)
+ font = pygame.font.Font(None, 64)
+ text = font.render("Pummel The Chimp, And Win $$$", True, (10, 10, 10))
+ textpos = text.get_rect(centerx=background.get_width() / 2, y=10)
+ background.blit(text, textpos)
As you see, there are a couple steps to getting this done. First we
must create the font object and render it into a new surface. We then find
@@ -428,19 +411,15 @@ Here we create all the objects that the game is going to need.
punch_sound = load_sound("punch.wav")
chimp = Chimp()
fist = Fist()
- allsprites = pygame.sprite.RenderPlain((chimp, fist))
- clock = pygame.time.Clock()
+ all_sprites = pygame.sprite.Group((chimp, fist))
+ clock = pygame.Clock()
First we load two sound effects using the `load_sound` function we defined
above. Then we create an instance of each of our sprite classes. And lastly
we create a sprite :class:`Group ` which will contain all
our sprites.
-We actually use a special sprite group named :class:`RenderPlain
-`. This sprite group can draw all the sprites it
-contains to the screen. It is called `RenderPlain` because there are actually
-more advanced Render groups. But for our game, we just need simple drawing. We
-create the group named "allsprites" by passing a list with all the sprites that
+We create the group named "all_sprites" by passing a list with all the sprites that
should belong in the group. We could later on add or remove sprites from this
group, but in this game we won't need to.
@@ -501,7 +480,7 @@ Update the Sprites
::
- allsprites.update()
+ all_sprites.update()
Sprite groups have an `update()` method, which simply calls the update method
for all the sprites it contains. Each of the objects will move around, depending
@@ -515,16 +494,14 @@ Draw The Entire Scene
Now that all the objects are in the right place, time to draw them. ::
screen.blit(background, (0, 0))
- allsprites.draw(screen)
+ all_sprites.draw(screen)
pygame.display.flip()
The first blit call will draw the background onto the entire screen. This
erases everything we saw from the previous frame (slightly inefficient, but
good enough for this game). Next we call the `draw()` method of the sprite
-container. Since this sprite container is really an instance of the "RenderPlain"
-sprite group, it knows how to draw our sprites. Lastly, we `flip()` the contents
-of pygame's software double buffer to the screen. This makes everything we've
-drawn visible all at once.
+container. Lastly, we `flip()` the contentsof pygame's software double buffer
+to the screen. This makes everything we've drawn visible all at once.
Game Over
@@ -536,4 +513,4 @@ User has quit, time to clean up. ::
Cleaning up the running game in *pygame* is extremely simple.
Since all variables are automatically destructed, we don't really have to do
-anything, but calling `pygame.quit()` explicitly cleans up pygame's internals.
+anything, but calling `pygame.quit()` explicitly cleans up pygame's internals.
\ No newline at end of file
diff --git a/docs/readmes/README.es.rst b/docs/readmes/README.es.rst
index 2d2753d427..f947c8bc01 100644
--- a/docs/readmes/README.es.rst
+++ b/docs/readmes/README.es.rst
@@ -1,4 +1,4 @@
-.. image:: https://raw.githubusercontent.com/pygame-community/pygame-ce/main/docs/reST/_static/pygame_logo.svg
+.. image:: https://raw.githubusercontent.com/pygame-community/pygame-ce/main/docs/reST/_static/pygame_ce_logo.svg
:alt: pygame
:target: https://pyga.me/
@@ -129,6 +129,7 @@ Versiones de dependencia:
Licencia
--------
+**Identificador de licencia:** LGPL-2.1-or-later
La biblioteca se distribuye bajo la licencia `GNU LGPL version 2.1`_, que se puede encontrar en el archivo ``docs/LGPL.txt``. Nos reservamos el derecho de licenciar versiones futuras de esta biblioteca bajo una licencia diferente.
diff --git a/docs/readmes/README.fa.rst b/docs/readmes/README.fa.rst
index 3c06c9db01..37f820cda5 100644
--- a/docs/readmes/README.fa.rst
+++ b/docs/readmes/README.fa.rst
@@ -1,4 +1,4 @@
-.. image:: https://raw.githubusercontent.com/pygame-community/pygame-ce/main/docs/reST/_static/pygame_logo.svg
+.. image:: https://raw.githubusercontent.com/pygame-community/pygame-ce/main/docs/reST/_static/pygame_ce_logo.svg
:alt: pygame
:target: https://pyga.me/
@@ -203,6 +203,8 @@ Dependencies (وابستگی ها)
License
-------
+LGPL-2.1-or-later **شناسه مجوز:**
+
این کتابخانه با استفاده از
`GNU LGPL version 2.1`_
لایسنس شده است که در فایل
diff --git a/docs/readmes/README.fr.rst b/docs/readmes/README.fr.rst
index 419a4d4b50..9d36a7514f 100644
--- a/docs/readmes/README.fr.rst
+++ b/docs/readmes/README.fr.rst
@@ -1,4 +1,4 @@
-.. image:: https://raw.githubusercontent.com/pygame-community/pygame-ce/main/docs/reST/_static/pygame_logo.svg
+.. image:: https://raw.githubusercontent.com/pygame-community/pygame-ce/main/docs/reST/_static/pygame_ce_logo.svg
:alt: pygame
:target: https://pyga.me/
@@ -158,6 +158,7 @@ Versions des dépendances:
Licence
-------
+**Identifiant de licence:** LGPL-2.1-or-later
La bibliothèque est distribuée sous la licence `GNU LGPL version 2.1`_, qui
peut être retrouvée dans le fichier ``docs/LGPL.txt``. Nous nous réservons
diff --git a/docs/readmes/README.zh-cn.rst b/docs/readmes/README.zh-cn.rst
index f5aed2f18f..8ca54bfe85 100644
--- a/docs/readmes/README.zh-cn.rst
+++ b/docs/readmes/README.zh-cn.rst
@@ -1,4 +1,4 @@
-.. image:: https://raw.githubusercontent.com/pygame-community/pygame-ce/main/docs/reST/_static/pygame_logo.svg
+.. image:: https://raw.githubusercontent.com/pygame-community/pygame-ce/main/docs/reST/_static/pygame_ce_logo.svg
:alt: pygame
:target: https://pyga.me/
@@ -122,6 +122,7 @@ pygame显然依赖于SDL和Python。此外pygame还嵌入了几个较小的库
许可证
-------
+**许可证标识符:** LGPL-2.1-or-later
本库在 `GNU LGPL version 2.1`_ 下发布,许可文件: ``docs/LGPL.txt`` 。我们保留将此库的未来版本置于其他许可证下的权利。
diff --git a/examples/aacircle.py b/examples/aacircle.py
index 651df18b50..24fc2aad47 100644
--- a/examples/aacircle.py
+++ b/examples/aacircle.py
@@ -10,7 +10,7 @@ def main():
pygame.init()
screen = pygame.display.set_mode((500, 500))
screen.fill((255, 0, 0))
- s = pygame.Surface(screen.get_size(), pygame.SRCALPHA, 32)
+ s = pygame.Surface(screen.get_size(), pygame.SRCALPHA)
pygame.draw.line(s, (0, 0, 0), (250, 250), (250 + 200, 250))
width = 1
@@ -23,18 +23,23 @@ def main():
pygame.draw.circle(screen, "green", (50, 100), 10)
pygame.draw.circle(screen, "black", (50, 100), 10, 1)
- pygame.display.flip()
- try:
- while True:
- event = pygame.event.wait()
+ running = True
+
+ clock = pygame.Clock()
+
+ while running:
+ clock.tick(10)
+
+ for event in pygame.event.get():
if event.type == pygame.QUIT:
- break
- if event.type == pygame.KEYDOWN:
- if event.key == pygame.K_ESCAPE or event.unicode == "q":
- break
- pygame.display.flip()
- finally:
- pygame.quit()
+ running = False
+ elif event.type == pygame.KEYDOWN:
+ if event.key == pygame.K_q:
+ running = False
+
+ pygame.display.flip()
+
+ pygame.quit()
if __name__ == "__main__":
diff --git a/examples/aliens.py b/examples/aliens.py
index f5da030b05..5d1fc65e08 100644
--- a/examples/aliens.py
+++ b/examples/aliens.py
@@ -83,9 +83,10 @@ class Player(pygame.sprite.Sprite):
bounce = 24
gun_offset = -11
images = []
+ containers = None
def __init__(self):
- pygame.sprite.Sprite.__init__(self, self.containers)
+ super().__init__(self.containers)
self.image = self.images[0]
self.rect = self.image.get_rect(midbottom=SCREENRECT.midbottom)
self.reloading = False
@@ -114,6 +115,7 @@ class Alien(pygame.sprite.Sprite):
speed = 13
animcycle = 12
images = []
+ containers = None
def __init__(self):
pygame.sprite.Sprite.__init__(self, self.containers)
@@ -140,6 +142,7 @@ class Explosion(pygame.sprite.Sprite):
defaultlife = 12
animcycle = 3
images = []
+ containers = None
def __init__(self, actor):
pygame.sprite.Sprite.__init__(self, self.containers)
@@ -166,6 +169,7 @@ class Shot(pygame.sprite.Sprite):
speed = -11
images = []
+ containers = None
def __init__(self, pos):
pygame.sprite.Sprite.__init__(self, self.containers)
@@ -187,6 +191,7 @@ class Bomb(pygame.sprite.Sprite):
speed = 9
images = []
+ containers = None
def __init__(self, alien):
pygame.sprite.Sprite.__init__(self, self.containers)
@@ -214,7 +219,7 @@ class Score(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.font = pygame.Font(None, 20)
- self.font.set_italic(1)
+ self.font.set_italic(True)
self.color = "white"
self.lastscore = -1
self.update()
@@ -225,10 +230,10 @@ def update(self):
if SCORE != self.lastscore:
self.lastscore = SCORE
msg = "Score: %d" % SCORE
- self.image = self.font.render(msg, 0, self.color)
+ self.image = self.font.render(msg, False, self.color)
-def main(winstyle=0):
+def main():
# Initialize pygame
pygame.mixer.pre_init(44100, 32, 2, 1024)
pygame.init()
@@ -244,9 +249,9 @@ def main(winstyle=0):
# Load images, assign to sprite classes
# (do this before the classes are used, after screen setup)
img = load_image("player1.gif")
- Player.images = [img, pygame.transform.flip(img, 1, 0)]
+ Player.images = [img, pygame.transform.flip(img, True, False)]
img = load_image("explosion1.gif")
- Explosion.images = [img, pygame.transform.flip(img, 1, 1)]
+ Explosion.images = [img, pygame.transform.flip(img, True, True)]
Alien.images = [load_image(im) for im in ("alien1.gif", "alien2.gif", "alien3.gif")]
Bomb.images = [load_image("bomb.gif")]
Shot.images = [load_image("shot.gif")]
@@ -255,14 +260,13 @@ def main(winstyle=0):
icon = pygame.transform.scale(Alien.images[0], (32, 32))
pygame.display.set_icon(icon)
pygame.display.set_caption("Pygame Aliens")
- pygame.mouse.set_visible(0)
+ pygame.mouse.set_visible(False)
# create the background, tile the bgd image
bgdtile = load_image("background.gif")
background = pygame.Surface(SCREENRECT.size)
for x in range(0, SCREENRECT.width, bgdtile.get_width()):
background.blit(bgdtile, (x, 0))
- screen.blit(background, (0, 0))
pygame.display.flip()
# load the sound effects
@@ -277,19 +281,18 @@ def main(winstyle=0):
aliens = pygame.sprite.Group()
shots = pygame.sprite.Group()
bombs = pygame.sprite.Group()
- all = pygame.sprite.RenderUpdates()
+ all_sprites = pygame.sprite.Group()
lastalien = pygame.sprite.GroupSingle()
# assign default groups to each sprite class
- Player.containers = all
- Alien.containers = aliens, all, lastalien
- Shot.containers = shots, all
- Bomb.containers = bombs, all
- Explosion.containers = all
- Score.containers = all
+ Player.containers = all_sprites
+ Alien.containers = aliens, all_sprites, lastalien
+ Shot.containers = shots, all_sprites
+ Bomb.containers = bombs, all_sprites
+ Explosion.containers = all_sprites
+ Score.containers = all_sprites
# Create Some Starting Values
- global score
alienreload = ALIEN_RELOAD
clock = pygame.Clock()
@@ -298,10 +301,13 @@ def main(winstyle=0):
player = Player()
Alien() # note, this 'lives' because it goes into a sprite group
if pygame.font:
- all.add(Score())
+ all_sprites.add(Score())
# Run our main loop whilst the player is alive.
while player.alive():
+ # Erase last frame by blitting the background to the screen
+ screen.blit(background, (0, 0))
+
# get input
for event in pygame.event.get():
if event.type == pygame.QUIT:
@@ -314,7 +320,8 @@ def main(winstyle=0):
print("Changing to FULLSCREEN")
screen_backup = screen.copy()
screen = pygame.display.set_mode(
- SCREENRECT.size, winstyle | pygame.FULLSCREEN
+ SCREENRECT.size,
+ winstyle | pygame.FULLSCREEN | pygame.SCALED,
)
screen.blit(screen_backup, (0, 0))
else:
@@ -327,11 +334,8 @@ def main(winstyle=0):
keystate = pygame.key.get_pressed()
- # clear/erase the last drawn sprites
- all.clear(screen, background)
-
# update all the sprites
- all.update()
+ all_sprites.update()
# handle player input
direction = keystate[pygame.K_RIGHT] - keystate[pygame.K_LEFT]
@@ -355,7 +359,7 @@ def main(winstyle=0):
Bomb(lastalien.sprite)
# Detect collisions between aliens and players.
- for alien in pygame.sprite.spritecollide(player, aliens, 1):
+ for alien in pygame.sprite.spritecollide(player, aliens, True):
if pygame.mixer:
boom_sound.play()
Explosion(alien)
@@ -364,14 +368,14 @@ def main(winstyle=0):
player.kill()
# See if shots hit the aliens.
- for alien in pygame.sprite.groupcollide(aliens, shots, 1, 1).keys():
+ for alien in pygame.sprite.groupcollide(aliens, shots, True, True).keys():
if pygame.mixer:
boom_sound.play()
Explosion(alien)
SCORE = SCORE + 1
# See if alien bombs hit the player.
- for bomb in pygame.sprite.spritecollide(player, bombs, 1):
+ for bomb in pygame.sprite.spritecollide(player, bombs, True):
if pygame.mixer:
boom_sound.play()
Explosion(player)
@@ -379,8 +383,8 @@ def main(winstyle=0):
player.kill()
# draw the scene
- dirty = all.draw(screen)
- pygame.display.update(dirty)
+ all_sprites.draw(screen)
+ pygame.display.flip()
# cap the framerate at 40fps. Also called 40HZ or 40 times per second.
clock.tick(40)
diff --git a/examples/chimp.py b/examples/chimp.py
index d1d63e1d86..1f0fecddfe 100644
--- a/examples/chimp.py
+++ b/examples/chimp.py
@@ -26,9 +26,7 @@ def load_image(name, colorkey=None, scale=1):
image = pygame.image.load(fullname)
image = image.convert()
- size = image.get_size()
- size = (size[0] * scale, size[1] * scale)
- image = pygame.transform.scale(image, size)
+ image = pygame.transform.scale_by(image, scale)
if colorkey is not None:
if colorkey == -1:
@@ -42,7 +40,7 @@ class NoneSound:
def play(self):
pass
- if not pygame.mixer or not pygame.mixer.get_init():
+ if not pygame.mixer.get_init():
return NoneSound()
fullname = os.path.join(data_dir, name)
@@ -146,11 +144,10 @@ def main():
background.fill((170, 238, 187))
# Put Text On The Background, Centered
- if pygame.font:
- font = pygame.Font(None, 64)
- text = font.render("Pummel The Chimp, And Win $$$", True, (10, 10, 10))
- textpos = text.get_rect(centerx=background.get_width() / 2, y=10)
- background.blit(text, textpos)
+ font = pygame.Font(None, 64)
+ text = font.render("Pummel The Chimp, And Win $$$", True, (10, 10, 10))
+ textpos = text.get_rect(centerx=background.get_width() / 2, y=10)
+ background.blit(text, textpos)
# Display The Background
screen.blit(background, (0, 0))
@@ -161,7 +158,7 @@ def main():
punch_sound = load_sound("punch.wav")
chimp = Chimp()
fist = Fist()
- allsprites = pygame.sprite.RenderPlain((chimp, fist))
+ all_sprites = pygame.sprite.Group(chimp, fist)
clock = pygame.Clock()
# Main Loop
@@ -184,11 +181,11 @@ def main():
elif event.type == pygame.MOUSEBUTTONUP:
fist.unpunch()
- allsprites.update()
+ all_sprites.update()
# Draw Everything
screen.blit(background, (0, 0))
- allsprites.draw(screen)
+ all_sprites.draw(screen)
pygame.display.flip()
pygame.quit()
diff --git a/examples/dropevent.py b/examples/dropevent.py
index 20a47d7ede..055d767dc7 100644
--- a/examples/dropevent.py
+++ b/examples/dropevent.py
@@ -17,49 +17,49 @@
def main():
- Running = True
+ running = True
screen_size = (640, 480)
surf = pygame.display.set_mode(screen_size)
font = pygame.font.SysFont("Arial", 24)
font.align = pygame.FONT_CENTER
clock = pygame.Clock()
- spr_file_text = font.render("Feed me some file or image!", 1, (255, 255, 255))
+ spr_file_text = font.render("Feed me some file or image!", True, (255, 255, 255))
spr_file_text_rect = spr_file_text.get_rect()
spr_file_text_rect.center = surf.get_rect().center
spr_file_image = None
spr_file_image_rect = None
- while Running:
- for ev in pygame.event.get():
- if ev.type == pygame.QUIT:
- Running = False
- elif ev.type == pygame.DROPBEGIN:
- print(ev)
+ while running:
+ for event in pygame.event.get():
+ if event.type == pygame.QUIT:
+ running = False
+ elif event.type == pygame.DROPBEGIN:
+ print(event)
print("File drop begin!")
- elif ev.type == pygame.DROPCOMPLETE:
- print(ev)
+ elif event.type == pygame.DROPCOMPLETE:
+ print(event)
print("File drop complete!")
- elif ev.type == pygame.DROPTEXT:
- print(ev)
+ elif event.type == pygame.DROPTEXT:
+ print(event)
spr_file_text = font.render(
- ev.text, 1, (255, 255, 255), wraplength=screen_size[0] - 10
+ event.text, True, (255, 255, 255), wraplength=screen_size[0] - 10
)
spr_file_text_rect = spr_file_text.get_rect()
spr_file_text_rect.center = surf.get_rect().center
- elif ev.type == pygame.DROPFILE:
- print(ev)
+ elif event.type == pygame.DROPFILE:
+ print(event)
spr_file_text = font.render(
- ev.file, 1, (255, 255, 255), None, screen_size[0] - 10
+ event.file, True, (255, 255, 255), None, screen_size[0] - 10
)
spr_file_text_rect = spr_file_text.get_rect()
spr_file_text_rect.center = surf.get_rect().center
# Try to open the file if it's an image
- filetype = ev.file[-3:]
+ filetype = event.file[-3:]
if filetype in ["png", "bmp", "jpg"]:
- spr_file_image = pygame.image.load(ev.file).convert()
+ spr_file_image = pygame.image.load(event.file).convert()
spr_file_image.set_alpha(127)
spr_file_image_rect = spr_file_image.get_rect()
spr_file_image_rect.center = surf.get_rect().center
diff --git a/examples/liquid.py b/examples/liquid.py
index 0b2bd7f3e3..516690a335 100644
--- a/examples/liquid.py
+++ b/examples/liquid.py
@@ -16,7 +16,6 @@
import pygame
import os
from math import sin
-import time
main_dir = os.path.split(os.path.abspath(__file__))[0]
@@ -44,10 +43,15 @@ def main():
# mainloop
xblocks = range(0, 640, 20)
yblocks = range(0, 480, 20)
- stopevents = pygame.QUIT, pygame.KEYDOWN, pygame.MOUSEBUTTONDOWN
+ stop_events = pygame.QUIT, pygame.KEYDOWN, pygame.MOUSEBUTTONDOWN
+
+ clock = pygame.Clock()
+
while True:
- for e in pygame.event.get():
- if e.type in stopevents:
+ clock.tick(60)
+
+ for event in pygame.event.get():
+ if event.type in stop_events:
return
anim = anim + 0.02
@@ -58,7 +62,6 @@ def main():
screen.blit(bitmap, (x, y), (xpos, ypos, 20, 20))
pygame.display.flip()
- time.sleep(0.01)
if __name__ == "__main__":
diff --git a/examples/music_drop_fade.py b/examples/music_drop_fade.py
index 8091bde09c..2fd9d8aa33 100644
--- a/examples/music_drop_fade.py
+++ b/examples/music_drop_fade.py
@@ -16,7 +16,8 @@
"""
import pygame
-import os, sys
+import os
+import sys
VOLUME_CHANGE_AMOUNT = 0.02 # how fast should up and down arrows change the volume?
SCREEN_SIZE = (640, 480)
diff --git a/examples/playmus.py b/examples/playmus.py
index 8a8daf378d..5358e38edd 100644
--- a/examples/playmus.py
+++ b/examples/playmus.py
@@ -21,142 +21,105 @@
import sys
import pygame
-import pygame.freetype
+pygame.init()
-class Window:
- """The application's Pygame window
- A Window instance manages the creation of and drawing to a
- window. It is a singleton class. Only one instance can exist.
+def write_lines(
+ dest: pygame.Surface,
+ text: str,
+ font=pygame.Font(None, 30),
+ color=(254, 231, 21, 255),
+ starting_line=0,
+):
+ text_surface = font.render(text, True, color)
- """
+ y_offset = 0
+ if starting_line >= 0:
+ y_offset = font.get_height() * starting_line
+ else:
+ y_offset = dest.get_height() + starting_line * font.get_height()
- instance = None
+ dest.fill(
+ (16, 24, 32, 255),
+ pygame.Rect((0, y_offset), (text_surface.get_width(), dest.get_width())),
+ )
+ dest.blit(text_surface, (16, y_offset))
- def __new__(cls, *args, **kwds):
- """Return an open Pygame window"""
- if Window.instance is not None:
- return Window.instance
- self = object.__new__(cls)
- pygame.display.init()
- self.screen = pygame.display.set_mode((600, 400))
- Window.instance = self
- return self
+def show_usage_message():
+ print(
+ "Usage: python playmus.py \n"
+ " python -m pygame.examples.playmus "
+ )
- def __init__(self, title):
- pygame.display.set_caption(title)
- self.text_color = (254, 231, 21, 255)
- self.background_color = (16, 24, 32, 255)
- self.screen.fill(self.background_color)
- pygame.display.flip()
- pygame.freetype.init()
- self.font = pygame.freetype.Font(None, 20)
- self.font.origin = True
- self.ascender = int(self.font.get_sized_ascender() * 1.5)
- self.descender = int(self.font.get_sized_descender() * 1.5)
- self.line_height = self.ascender - self.descender
-
- self.write_lines(
- "\nPress 'q' or 'ESCAPE' or close this window to quit\n"
- "Press 'SPACE' to play / pause\n"
- "Press 'r' to rewind to the beginning (restart)\n"
- "Press 'f' to fade music out over 5 seconds\n\n"
- "Window will quit automatically when music ends\n",
- 0,
- )
-
- def __enter__(self):
- return self
-
- def __exit__(self, exc_type, exc_val, exc_tb):
- self.close()
- return False
-
- def close(self):
- pygame.display.quit()
- Window.instance = None
-
- def write_lines(self, text, line=0):
- w, h = self.screen.get_size()
- line_height = self.line_height
- nlines = h // line_height
- if line < 0:
- line = nlines + line
- for i, text_line in enumerate(text.split("\n"), line):
- y = i * line_height + self.ascender
- # Clear the line first.
- self.screen.fill(
- self.background_color, (0, i * line_height, w, line_height)
- )
- # Write new text.
- self.font.render_to(self.screen, (15, y), text_line, self.text_color)
- pygame.display.flip()
+def main(file_path):
+ """Play an audio file with pygame.mixer.music"""
+ screen = pygame.display.set_mode((600, 400))
+ screen.fill((16, 24, 32, 255))
-def show_usage_message():
- print("Usage: python playmus.py ")
- print(" python -m pygame.examples.playmus ")
+ write_lines(screen, "Loading ...", starting_line=-1)
+ pygame.mixer.init(frequency=44100)
+ pygame.mixer.music.load(file_path)
+ pygame.display.set_caption(f"File : {file_path}")
-def main(file_path):
- """Play an audio file with pygame.mixer.music"""
+ write_lines(
+ screen,
+ "Press 'q' or 'ESCAPE' or close this window to quit\n"
+ "Press 'SPACE' to play / pause\n"
+ "Press 'r' to rewind to the beginning (restart)\n"
+ "Press 'f' to fade music out over 5 seconds",
+ starting_line=1,
+ )
+
+ pygame.mixer.music.play()
+ write_lines(screen, "Playing ...\n", starting_line=-1)
- EVENT_LOOP_TICK = pygame.event.custom_type()
-
- with Window(file_path) as win:
- win.write_lines("Loading ...", -1)
- pygame.mixer.init(frequency=44100)
- try:
- paused = False
- pygame.mixer.music.load(file_path)
-
- # Make sure the event loop ticks over at least every 0.5 seconds.
- pygame.time.set_timer(EVENT_LOOP_TICK, 500)
-
- pygame.mixer.music.play()
- win.write_lines("Playing ...\n", -1)
-
- while pygame.mixer.music.get_busy() or paused:
- e = pygame.event.wait()
- if e.type == pygame.KEYDOWN:
- key = e.key
- if key == pygame.K_SPACE:
- if paused:
- pygame.mixer.music.unpause()
- paused = False
- win.write_lines("Playing ...\n", -1)
- else:
- pygame.mixer.music.pause()
- paused = True
- win.write_lines("Paused ...\n", -1)
- elif key == pygame.K_r:
- if file_path[-3:].lower() in ("ogg", "mp3", "mod"):
- status = "Rewound."
- pygame.mixer.music.rewind()
- else:
- status = "Restarted."
- pygame.mixer.music.play()
- if paused:
- pygame.mixer.music.pause()
- win.write_lines(status, -1)
- elif key == pygame.K_f:
- win.write_lines("Fading out ...\n", -1)
- pygame.mixer.music.fadeout(5000)
- # when finished get_busy() will return False.
- elif key in [pygame.K_q, pygame.K_ESCAPE]:
+ running = True
+ paused = False
+
+ while running:
+ pygame.display.flip()
+
+ for event in pygame.event.get():
+ if event.type == pygame.KEYDOWN:
+ key = event.key
+ if key == pygame.K_SPACE:
+ if paused:
+ pygame.mixer.music.unpause()
paused = False
- pygame.mixer.music.stop()
- # get_busy() will now return False.
- elif e.type == pygame.QUIT:
+ write_lines(screen, "Playing ...", starting_line=-1)
+ else:
+ pygame.mixer.music.pause()
+ paused = True
+ write_lines(screen, "Paused ...", starting_line=-1)
+ elif key == pygame.K_r:
+ if file_path[-3:].lower() in ("ogg", "mp3", "mod"):
+ status = "Rewound."
+ pygame.mixer.music.rewind()
+ else:
+ status = "Restarted."
+ pygame.mixer.music.play()
+ if paused:
+ pygame.mixer.music.pause()
+ write_lines(screen, status, starting_line=-1)
+ elif key == pygame.K_f:
+ write_lines(screen, "Fading out ...", starting_line=-1)
+ pygame.mixer.music.fadeout(5000)
+ # when finished get_busy() will return False.
+ elif key in [pygame.K_q, pygame.K_ESCAPE]:
paused = False
pygame.mixer.music.stop()
# get_busy() will now return False.
- pygame.time.set_timer(EVENT_LOOP_TICK, 0)
- finally:
- pygame.mixer.quit()
+ elif event.type == pygame.QUIT:
+ running = False
+
+ if not pygame.mixer.music.get_busy() and not paused:
+ running = False
+
pygame.quit()
diff --git a/examples/resizing_new.py b/examples/resizing_new.py
index 132c464183..cccde5badd 100644
--- a/examples/resizing_new.py
+++ b/examples/resizing_new.py
@@ -2,12 +2,11 @@
import pygame
pygame.init()
-
-RES = (160, 120)
+RESOLUTION = (160, 120)
FPS = 30
clock = pygame.Clock()
-screen = pygame.display.set_mode(RES, pygame.RESIZABLE)
+screen = pygame.display.set_mode(RESOLUTION, pygame.RESIZABLE)
pygame.display._set_autoresize(False)
# MAIN LOOP
@@ -21,12 +20,10 @@
for event in pygame.event.get():
if event.type == pygame.KEYDOWN and event.key == pygame.K_q:
done = True
- if event.type == pygame.QUIT:
- done = True
- # if event.type==pygame.WINDOWRESIZED:
- # screen=pygame.display.get_surface()
- if event.type == pygame.VIDEORESIZE:
+ elif event.type == pygame.VIDEORESIZE:
screen = pygame.display.get_surface()
+ elif event.type == pygame.QUIT:
+ done = True
i += 1
i = i % screen.get_width()
j += i % 2
diff --git a/examples/scaletest.py b/examples/scaletest.py
index e98e6becbb..5f83a69844 100644
--- a/examples/scaletest.py
+++ b/examples/scaletest.py
@@ -7,7 +7,7 @@
import sys
import time
-import pygame as pygame
+import pygame
def main(imagefile, convert_alpha=False, run_speed_test=False):
@@ -30,68 +30,59 @@ def main(imagefile, convert_alpha=False, run_speed_test=False):
pygame.display.set_mode((1, 1))
background = background.convert_alpha()
- SpeedTest(background)
+ speed_test(background)
return
- # start fullscreen mode
- screen = pygame.display.set_mode((1024, 768), pygame.FULLSCREEN)
+ # start FULLSCREEN mode
+ # On Windows, the fullscreen mode doesn't work properly, to fix the problem,
+ # add with it pygame.SCALED flag
+ screen = pygame.display.set_mode((1024, 768), pygame.FULLSCREEN | pygame.SCALED)
if convert_alpha:
background = background.convert_alpha()
# turn off the mouse pointer
- pygame.mouse.set_visible(0)
- # main loop
- bRunning = True
- bUp = False
- bDown = False
- bLeft = False
- bRight = False
+ pygame.mouse.set_visible(False)
+
+ running = True
cursize = [background.get_width(), background.get_height()]
- while bRunning:
+
+ clock = pygame.Clock()
+
+ # main loop
+ while running:
+ clock.tick(60)
+
image = pygame.transform.smoothscale(background, cursize)
imgpos = image.get_rect(centerx=512, centery=384)
screen.fill((255, 255, 255))
screen.blit(image, imgpos)
+
pygame.display.flip()
+
for event in pygame.event.get():
if event.type == pygame.QUIT or (
event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE
):
- bRunning = False
- if event.type == pygame.KEYDOWN:
- if event.key == pygame.K_UP:
- bUp = True
- if event.key == pygame.K_DOWN:
- bDown = True
- if event.key == pygame.K_LEFT:
- bLeft = True
- if event.key == pygame.K_RIGHT:
- bRight = True
- if event.type == pygame.KEYUP:
- if event.key == pygame.K_UP:
- bUp = False
- if event.key == pygame.K_DOWN:
- bDown = False
- if event.key == pygame.K_LEFT:
- bLeft = False
- if event.key == pygame.K_RIGHT:
- bRight = False
- if bUp:
+ running = False
+
+ pressed_keys = pygame.key.get_pressed()
+
+ if pressed_keys[pygame.K_UP]:
cursize[1] -= 2
if cursize[1] < 1:
cursize[1] = 1
- if bDown:
+ if pressed_keys[pygame.K_DOWN]:
cursize[1] += 2
- if bLeft:
+ if pressed_keys[pygame.K_LEFT]:
cursize[0] -= 2
if cursize[0] < 1:
cursize[0] = 1
- if bRight:
+ if pressed_keys[pygame.K_RIGHT]:
cursize[0] += 2
pygame.quit()
-def SpeedTest(image):
+def speed_test(image):
print(f"\nImage Scaling Speed Test - Image Size {str(image.get_size())}\n")
imgsize = [image.get_width(), image.get_height()]
diff --git a/meson.build b/meson.build
index a590403cb9..10f2f2d3ac 100644
--- a/meson.build
+++ b/meson.build
@@ -63,6 +63,19 @@ if not cc.has_header('Python.h', dependencies: py_dep)
)
endif
+if get_option('coverage')
+ if cc.has_argument('--coverage')
+ add_global_arguments('--coverage', language: 'c')
+ else
+ error('Requested coverage but compiler doesn\'t seem to support it')
+ endif
+ if cc.has_link_argument('--coverage')
+ add_global_link_arguments('--coverage', language: 'c')
+ else
+ error('Requested coverage but compiler doesn\'t seem to support it')
+ endif
+endif
+
pg_dir = py.get_install_dir() / pg
pg_inc_dirs = []
@@ -86,7 +99,7 @@ if plat == 'win' and host_machine.cpu_family().startswith('x86')
)
endif
- sdl_ver = '2.30.3'
+ sdl_ver = '2.30.6'
sdl_image_ver = '2.8.2'
sdl_mixer_ver = '2.8.0'
sdl_ttf_ver = '2.22.0'
diff --git a/meson_options.txt b/meson_options.txt
index c34007090a..3152e69736 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -33,3 +33,7 @@ option('error_on_warns', type: 'boolean', value: 'false')
# Controls whether to error on build if generated docs are missing. Defaults to
# false.
option('error_docs_missing', type: 'boolean', value: 'false')
+
+# Controls whether to do a coverage build.
+# This argument must be used together with the editable install.
+option('coverage', type: 'boolean', value: false)
diff --git a/pyproject.toml b/pyproject.toml
index c33803b64c..697ca14806 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,10 +1,10 @@
[project]
name = "pygame-ce"
-version = "2.5.1.dev1"
+version = "2.5.2.dev1"
description = "Python Game Development"
readme = "README.rst" # for long description
requires-python = ">=3.8"
-license = {file = "docs/LGPL.txt"} # path to LGPL license
+license = {text = "LGPL v2.1"}
authors = [{name = "A community project"}]
classifiers = [
"Development Status :: 5 - Production/Stable",
@@ -19,6 +19,7 @@ classifiers = [
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
+ "Programming Language :: Python :: 3.13",
"Programming Language :: Python :: Implementation :: CPython",
"Programming Language :: Python :: Implementation :: PyPy",
"Topic :: Games/Entertainment",
@@ -39,10 +40,11 @@ classifiers = [
]
[project.urls]
-"Homepage" = "https://github.com/pygame-community/pygame-ce"
+homepage = "https://pyga.me"
"Bug Reports" = "https://github.com/pygame-community/pygame-ce/issues"
-"Source" = "https://github.com/pygame-community/pygame-ce"
"Documentation" = "https://pyga.me/docs"
+"Release Notes" = "https://github.com/pygame-community/pygame-ce/releases"
+"Source" = "https://github.com/pygame-community/pygame-ce"
[project.entry-points.pyinstaller40]
hook-dirs = 'pygame.__pyinstaller:get_hook_dirs'
@@ -51,7 +53,13 @@ hook-dirs = 'pygame.__pyinstaller:get_hook_dirs'
pygame_ce = 'pygame.__briefcase.pygame_ce:PygameCEGuiBootstrap'
[build-system]
-requires = ["meson-python", "ninja", "cython", "sphinx<=7.2.6"]
+requires = [
+ "meson-python<=0.16.0",
+ "meson<=1.5.0",
+ "ninja<=1.11.1.1",
+ "cython<=3.0.10",
+ "sphinx<=7.2.6",
+]
build-backend = 'mesonpy'
[tool.meson-python.args]
@@ -59,6 +67,22 @@ install = ['--tags=runtime,python-runtime,pg-tag']
# uncomment to get werror locally
# setup = ["-Derror_on_warns=true"]
+[tool.cibuildwheel]
+# The default build-frontend is "pip", but we use the recommended "build" frontend.
+# build (AKA pypa/build) is a simple tool that just focusses on one thing: building
+# wheels. build still needs to interact with a pip-like tool to handle build-time
+# dependencies. Here is where uv comes into the picture. It is an "installer" like pip,
+# but faster. It has been observed to save a couple of minutes of CI time.
+build-frontend = "build[uv]"
+build = "cp3{8,9,10,11,12,13}-* pp3{8,9,10}-*"
+skip = "*-musllinux_*"
+# build[uv] is verbose by default, so below flag is not needed here
+# build-verbosity = 3
+
+environment = { SDL_VIDEODRIVER="dummy", SDL_AUDIODRIVER="disk" }
+test-command = "python -m pygame.tests -v --exclude opengl,music,timing --time_out 300"
+test-requires = ["numpy"]
+
[tool.cibuildwheel.config-settings]
setup-args = [
"-Derror_on_warns=true",
@@ -75,3 +99,18 @@ exclude = [
[tool.ruff.format]
quote-style = "preserve"
+
+# numpy is a test dependency, but we build for systems that numpy doesn't have
+# binary wheels for. In such cases, we do not want to waste CI time building
+# numpy from source. So, we are gonna force numpy to be "only-binary" and skip
+# numpy on platforms that it doesn't have wheels for
+[tool.uv.pip]
+only-binary = ["numpy"]
+
+# 1. skip all 32-bit manylinux (i686)
+# 2. skip all pypy+arm combinations
+# 3. skip all pypy 310 because it is so new numpy does not have wheels for it
+# 4. skip all python 313 because numpy doesn't have wheels for it yet
+[[tool.cibuildwheel.overrides]]
+select = "{*-manylinux_i686,pp*-*{arm64,aarch64},pp310-*,*p313-*}"
+test-requires = []
diff --git a/setup.cfg b/setup.cfg
index f7d0bed4e4..1c71cfb797 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,5 +1,5 @@
[tox:tox]
-envlist = py{38,39,310,311,312}
+envlist = py{38,39,310,311,312,313}
skip_missing_interpreters = True
skipsdist = True
diff --git a/setup.py b/setup.py
index 9aa83f0b96..47648be978 100644
--- a/setup.py
+++ b/setup.py
@@ -19,7 +19,7 @@
METADATA = {
"name": "pygame-ce",
"version": pg_ver.version,
- "license": "LGPL",
+ "license": "LGPL v2.1",
"url": "https://pyga.me",
"author": "A community project.",
"description": "Python Game Development",
@@ -29,6 +29,7 @@
"Documentation": "https://pyga.me/docs",
"Bug Tracker": "https://github.com/pygame-community/pygame-ce/issues",
"Source": "https://github.com/pygame-community/pygame-ce",
+ "Release Notes": "https://github.com/pygame-community/pygame-ce/releases",
},
"classifiers": [
"Development Status :: 5 - Production/Stable",
@@ -43,6 +44,7 @@
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
+ "Programming Language :: Python :: 3.13",
"Programming Language :: Python :: Implementation :: CPython",
"Programming Language :: Python :: Implementation :: PyPy",
"Topic :: Games/Entertainment",
@@ -124,7 +126,7 @@ def spawn(self, cmd, **kwargs):
distutils.ccompiler.CCompiler.spawn = spawn
# A (bit hacky) fix for https://github.com/pygame-community/pygame-ce/issues/1346
-# This is due to the fact that distutils uses command line args to
+# This is due to the fact that distutils uses command line args to
# export PyInit_* functions on windows, but those functions are already exported
# and that is why compiler gives warnings
from distutils.command.build_ext import build_ext
@@ -264,7 +266,7 @@ def consume_arg(name):
for i, kwargs in enumerate(queue):
kwargs['progress'] = f'[{i + 1}/{count}] '
cythonize_one(**kwargs)
-
+
if cython_only:
sys.exit(0)
@@ -420,7 +422,7 @@ def run_install_headers(self):
if "freetype" in e.name and sys.platform not in ("darwin", "win32"):
# TODO: fix freetype issues here
- if sysconfig.get_config_var("MAINCC") != "clang":
+ if sysconfig.get_config_var("MAINCC") != "clang":
e.extra_compile_args.append("-Wno-error=unused-but-set-variable")
if "mask" in e.name and sys.platform == "win32":
@@ -806,13 +808,13 @@ def run(self):
'''
import subprocess
command_line = [
- sys.executable, os.path.join('buildconfig', 'make_docs.py')
+ sys.executable, "-m", "buildconfig", "docs"
]
if self.fullgeneration:
command_line.append('full_generation')
print("WARNING: This command is deprecated and will be removed in the future.")
- print(f"Please use the following replacement: `{' '.join(command_line)}`\n")
+ print(f"Please use the following replacement: `{' '.join(command_line)}`\n")
if subprocess.call(command_line) != 0:
raise SystemExit("Failed to build documentation")
@@ -823,7 +825,7 @@ class StubcheckCommand(Command):
user_options = []
def initialize_options(self):
pass
-
+
def finalize_options(self):
pass
@@ -836,7 +838,7 @@ def run(self):
sys.executable, os.path.join("buildconfig", "stubs", "stubcheck.py")
]
print("WARNING: This command is deprecated and will be removed in the future.")
- print(f"Please use the following replacement: `{' '.join(command_line)}`\n")
+ print(f"Please use the following replacement: `{' '.join(command_line)}`\n")
result = subprocess.run(command_line)
if result.returncode != 0:
raise SystemExit("Stubcheck failed.")
diff --git a/src_c/_camera.c b/src_c/_camera.c
index 3897039cb0..8bf528b559 100644
--- a/src_c/_camera.c
+++ b/src_c/_camera.c
@@ -1951,7 +1951,6 @@ MODINIT_DEFINE(_camera)
}
/* type preparation */
- // PyType_Init(pgCamera_Type);
pgCamera_Type.tp_new = PyType_GenericNew;
if (PyType_Ready(&pgCamera_Type) < 0) {
return NULL;
diff --git a/src_c/_freetype.c b/src_c/_freetype.c
index 5ba7c6f1bb..f4da7757fd 100644
--- a/src_c/_freetype.c
+++ b/src_c/_freetype.c
@@ -51,6 +51,8 @@ _ft_get_error(PyObject *, PyObject *);
static PyObject *
_ft_get_init(PyObject *, PyObject *);
static PyObject *
+_ft_was_init(PyObject *, PyObject *);
+static PyObject *
_ft_autoinit(PyObject *, PyObject *);
static PyObject *
_ft_get_cache_size(PyObject *, PyObject *);
@@ -231,7 +233,6 @@ load_font_res(const char *filename)
{
PyObject *load_basicfunc = 0;
PyObject *pkgdatamodule = 0;
- PyObject *resourcefunc = 0;
PyObject *result = 0;
PyObject *tmp;
@@ -240,13 +241,9 @@ load_font_res(const char *filename)
goto font_resource_end;
}
- resourcefunc = PyObject_GetAttrString(pkgdatamodule, RESOURCE_FUNC_NAME);
- if (!resourcefunc) {
- goto font_resource_end;
- }
-
- result = PyObject_CallFunction(resourcefunc, "s", filename);
- if (!result) {
+ result =
+ PyObject_CallMethod(pkgdatamodule, RESOURCE_FUNC_NAME, "s", filename);
+ if (result == NULL) {
goto font_resource_end;
}
@@ -270,7 +267,6 @@ load_font_res(const char *filename)
font_resource_end:
Py_XDECREF(pkgdatamodule);
- Py_XDECREF(resourcefunc);
Py_XDECREF(load_basicfunc);
return result;
}
@@ -501,7 +497,7 @@ static PyMethodDef _ft_methods[] = {
DOC_FREETYPE_INIT},
{"quit", (PyCFunction)_ft_quit, METH_NOARGS, DOC_FREETYPE_QUIT},
{"get_init", _ft_get_init, METH_NOARGS, DOC_FREETYPE_GETINIT},
- {"was_init", _ft_get_init, METH_NOARGS,
+ {"was_init", _ft_was_init, METH_NOARGS,
DOC_FREETYPE_WASINIT}, // DEPRECATED
{"get_error", _ft_get_error, METH_NOARGS, DOC_FREETYPE_GETERROR},
{"get_version", (PyCFunction)_ft_get_version, METH_VARARGS | METH_KEYWORDS,
@@ -2236,6 +2232,19 @@ _ft_get_init(PyObject *self, PyObject *_null)
return PyBool_FromLong(FREETYPE_MOD_STATE(self)->freetype ? 1 : 0);
}
+static PyObject *
+_ft_was_init(PyObject *self, PyObject *_null)
+{
+ if (PyErr_WarnEx(
+ PyExc_DeprecationWarning,
+ "was_init has been deprecated and may be removed in a future "
+ "version. Use the equivalent get_init function instead",
+ 1) == -1) {
+ return NULL;
+ }
+ return _ft_get_init(self, _null);
+}
+
static PyObject *
_ft_get_default_font(PyObject *self, PyObject *_null)
{
diff --git a/src_c/_pygame.h b/src_c/_pygame.h
index f1a7e7487f..e87986d776 100644
--- a/src_c/_pygame.h
+++ b/src_c/_pygame.h
@@ -33,6 +33,7 @@
*/
#define PY_SSIZE_T_CLEAN
#include
+#include "include/pythoncapi_compat.h"
/* Ensure PyPy-specific code is not in use when running on GraalPython (PR
* #2580) */
@@ -101,6 +102,15 @@ PG_UnlockMutex(SDL_mutex *mutex)
/* Mask to test if surface flags are in a fullscreen window. */
#define PG_WINDOW_FULLSCREEN_INCLUSIVE SDL_WINDOW_FULLSCREEN
+#define PG_SetEventEnabled(type, enabled) SDL_SetEventEnabled(type, enabled)
+#define PG_EventEnabled(type) SDL_EventEnabled(type)
+#define PG_SetJoystickEventsEnabled(enabled) \
+ SDL_SetJoystickEventsEnabled(enabled)
+
+#define PG_FIND_VNUM_MAJOR(ver) SDL_VERSIONNUM_MAJOR(ver)
+#define PG_FIND_VNUM_MINOR(ver) SDL_VERSIONNUM_MINOR(ver)
+#define PG_FIND_VNUM_MICRO(ver) SDL_VERSIONNUM_MICRO(ver)
+
#else /* ~SDL_VERSION_ATLEAST(3, 0, 0)*/
#define PG_ShowCursor() SDL_ShowCursor(SDL_ENABLE)
#define PG_HideCursor() SDL_ShowCursor(SDL_DISABLE)
@@ -155,6 +165,17 @@ PG_UnlockMutex(SDL_mutex *mutex)
* SDL_WINDOW_FULLSCREEN. */
#define PG_WINDOW_FULLSCREEN_INCLUSIVE SDL_WINDOW_FULLSCREEN_DESKTOP
+/* SDL_EventState is meant to take SDL_IGNORE or SDL_ENABLE, but it also
+ * works identically with SDL_FALSE and SDL_TRUE, because they evaluate to
+ * the same values, respectively. */
+#define PG_SetEventEnabled(type, enabled) SDL_EventState(type, enabled)
+#define PG_EventEnabled(type) SDL_EventState(type, SDL_QUERY)
+#define PG_SetJoystickEventsEnabled(enabled) SDL_JoystickEventState(enabled)
+
+#define PG_FIND_VNUM_MAJOR(ver) ver.major
+#define PG_FIND_VNUM_MINOR(ver) ver.minor
+#define PG_FIND_VNUM_MICRO(ver) ver.patch
+
#if SDL_VERSION_ATLEAST(2, 0, 14)
#define PG_SurfaceHasRLE SDL_HasSurfaceRLE
#else
@@ -446,19 +467,6 @@ typedef enum {
(RAISE(PyExc_NotImplementedError, "Python built without thread support"))
#endif /* ~WITH_THREAD */
-#define PyType_Init(x) (((x).ob_type) = &PyType_Type)
-
-/* Python macro for comparing to Py_None
- * Py_IsNone is naturally supported by
- * Python 3.10 or higher
- * so this macro can be removed after the minimum
- * supported
- * Python version reaches 3.10
- */
-#ifndef Py_IsNone
-#define Py_IsNone(x) (x == Py_None)
-#endif
-
/* Update this function if new sequences are added to the fast sequence
* type. */
#ifndef pgSequenceFast_Check
@@ -473,21 +481,11 @@ struct pgEventObject {
PyObject *dict;
};
-/*
- * surflock module internals
- */
-typedef struct {
- PyObject_HEAD PyObject *surface;
- PyObject *lockobj;
- PyObject *weakrefs;
-} pgLifetimeLockObject;
-
/*
* surface module internals
*/
struct pgSubSurface_Data {
PyObject *owner;
- int pixeloffset;
int offsetx, offsety;
};
@@ -530,7 +528,7 @@ typedef enum {
#define PYGAMEAPI_JOYSTICK_NUMSLOTS 3
#define PYGAMEAPI_DISPLAY_NUMSLOTS 2
#define PYGAMEAPI_SURFACE_NUMSLOTS 4
-#define PYGAMEAPI_SURFLOCK_NUMSLOTS 8
+#define PYGAMEAPI_SURFLOCK_NUMSLOTS 6
#define PYGAMEAPI_RWOBJECT_NUMSLOTS 5
#define PYGAMEAPI_PIXELARRAY_NUMSLOTS 2
#define PYGAMEAPI_COLOR_NUMSLOTS 5
diff --git a/src_c/base.c b/src_c/base.c
index 2a609e6601..7cbbdae917 100644
--- a/src_c/base.c
+++ b/src_c/base.c
@@ -177,36 +177,45 @@ pg_EnvShouldBlendAlphaSDL2(void);
static int
pg_CheckSDLVersions(void)
{
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+ int compiled = SDL_VERSION;
+ int linked = SDL_GetVersion();
+#else
SDL_version compiled;
SDL_version linked;
-
SDL_VERSION(&compiled);
SDL_GetVersion(&linked);
+#endif
/* only check the major version, in general major version is bumped for ABI
* incompatible changes */
- if (compiled.major != linked.major) {
+ if (PG_FIND_VNUM_MAJOR(compiled) != PG_FIND_VNUM_MAJOR(linked)) {
PyErr_Format(PyExc_RuntimeError,
"ABI incompatibility detected! SDL compiled with "
"%d.%d.%d, linked to %d.%d.%d (major versions should "
"have matched)",
- compiled.major, compiled.minor, compiled.patch,
- linked.major, linked.minor, linked.patch);
+ PG_FIND_VNUM_MAJOR(compiled),
+ PG_FIND_VNUM_MINOR(compiled),
+ PG_FIND_VNUM_MICRO(compiled), PG_FIND_VNUM_MAJOR(linked),
+ PG_FIND_VNUM_MINOR(linked), PG_FIND_VNUM_MICRO(linked));
return 0;
}
/* Basically, this is compiled_version > linked_version case, which we
* don't allow */
- if ((linked.minor == compiled.minor && linked.patch < compiled.patch) ||
- linked.minor < compiled.minor) {
+ if ((PG_FIND_VNUM_MINOR(linked) == PG_FIND_VNUM_MINOR(compiled) &&
+ PG_FIND_VNUM_MICRO(linked) < PG_FIND_VNUM_MICRO(compiled)) ||
+ PG_FIND_VNUM_MINOR(linked) < PG_FIND_VNUM_MINOR(compiled)) {
/* We do some ifdefs to support different SDL versions at compile time.
We use newer API only when available.
Downgrading via dynamic API probably breaks this.*/
PyErr_Format(PyExc_RuntimeError,
"Dynamic linking causes SDL downgrade! (compiled with "
"version %d.%d.%d, linked to %d.%d.%d)",
- compiled.major, compiled.minor, compiled.patch,
- linked.major, linked.minor, linked.patch);
+ PG_FIND_VNUM_MAJOR(compiled),
+ PG_FIND_VNUM_MINOR(compiled),
+ PG_FIND_VNUM_MICRO(compiled), PG_FIND_VNUM_MAJOR(linked),
+ PG_FIND_VNUM_MINOR(linked), PG_FIND_VNUM_MICRO(linked));
return 0;
}
@@ -270,7 +279,7 @@ pg_mod_autoinit(const char *modname)
}
if (funcobj) {
- temp = PyObject_CallObject(funcobj, NULL);
+ temp = PyObject_CallNoArgs(funcobj);
if (temp) {
Py_DECREF(temp);
ret = 1;
@@ -305,7 +314,7 @@ pg_mod_autoquit(const char *modname)
PyErr_Clear();
if (funcobj) {
- temp = PyObject_CallObject(funcobj, NULL);
+ temp = PyObject_CallNoArgs(funcobj);
Py_XDECREF(temp);
}
@@ -373,7 +382,12 @@ static PyObject *
pg_get_sdl_version(PyObject *self, PyObject *args, PyObject *kwargs)
{
int linked = 1; /* Default is linked version. */
- SDL_version v;
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+ int version = SDL_VERSION;
+#else
+ SDL_version version;
+ SDL_VERSION(&version);
+#endif
static char *keywords[] = {"linked", NULL};
@@ -382,12 +396,16 @@ pg_get_sdl_version(PyObject *self, PyObject *args, PyObject *kwargs)
}
if (linked) {
- SDL_GetVersion(&v);
- }
- else {
- SDL_VERSION(&v);
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+ version = SDL_GetVersion();
+#else
+ SDL_GetVersion(&version);
+#endif
}
- return Py_BuildValue("iii", v.major, v.minor, v.patch);
+
+ return Py_BuildValue("iii", PG_FIND_VNUM_MAJOR(version),
+ PG_FIND_VNUM_MINOR(version),
+ PG_FIND_VNUM_MICRO(version));
}
static PyObject *
@@ -429,7 +447,7 @@ _pg_quit(void)
}
if (PyCallable_Check(quit)) {
- temp = PyObject_CallObject(quit, NULL);
+ temp = PyObject_CallNoArgs(quit);
if (temp)
Py_DECREF(temp);
else
diff --git a/src_c/circle.c b/src_c/circle.c
index 63df177084..beaee31741 100644
--- a/src_c/circle.c
+++ b/src_c/circle.c
@@ -47,7 +47,8 @@ pg_circle_init(pgCircleObject *self, PyObject *args, PyObject *kwds)
PyErr_SetString(
PyExc_TypeError,
"Arguments must be a Circle, a sequence of length 3 or 2, or an "
- "object with an attribute called 'circle'");
+ "object with an attribute called 'circle', all with corresponding "
+ "nonnegative radius argument");
return -1;
}
return 0;
@@ -494,8 +495,8 @@ pg_circle_setr(pgCircleObject *self, PyObject *value, void *closure)
return -1;
}
- if (radius <= 0) {
- PyErr_SetString(PyExc_ValueError, "Radius must be positive");
+ if (radius < 0) {
+ PyErr_SetString(PyExc_ValueError, "Radius must be nonnegative");
return -1;
}
@@ -523,9 +524,9 @@ pg_circle_setr_sqr(pgCircleObject *self, PyObject *value, void *closure)
return -1;
}
- if (radius_squared <= 0) {
+ if (radius_squared < 0) {
PyErr_SetString(PyExc_ValueError,
- "Invalid radius squared value, must be > 0");
+ "Invalid radius squared value, must be nonnegative");
return -1;
}
@@ -570,8 +571,9 @@ pg_circle_setarea(pgCircleObject *self, PyObject *value, void *closure)
return -1;
}
- if (area <= 0) {
- PyErr_SetString(PyExc_ValueError, "Invalid area value, must be > 0");
+ if (area < 0) {
+ PyErr_SetString(PyExc_ValueError,
+ "Invalid area value, must be nonnegative");
return -1;
}
@@ -600,9 +602,9 @@ pg_circle_setcircumference(pgCircleObject *self, PyObject *value,
return -1;
}
- if (circumference <= 0) {
+ if (circumference < 0) {
PyErr_SetString(PyExc_ValueError,
- "Invalid circumference value, must be > 0");
+ "Invalid circumference value, must be nonnegative");
return -1;
}
@@ -630,9 +632,9 @@ pg_circle_setdiameter(pgCircleObject *self, PyObject *value, void *closure)
return -1;
}
- if (diameter <= 0) {
+ if (diameter < 0) {
PyErr_SetString(PyExc_ValueError,
- "Invalid diameter value, must be > 0");
+ "Invalid diameter value, must be nonnegative");
return -1;
}
diff --git a/src_c/constants.c b/src_c/constants.c
index 7464c93d9f..6856db5568 100644
--- a/src_c/constants.c
+++ b/src_c/constants.c
@@ -642,6 +642,16 @@ MODINIT_DEFINE(constants)
DEC_CONSTS(WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED);
DEC_CONSTS(WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED);
+#if SDL_VERSION_ATLEAST(2, 0, 16)
+ DEC_CONSTS(FLASH_CANCEL, SDL_FLASH_CANCEL);
+ DEC_CONSTS(FLASH_BRIEFLY, SDL_FLASH_BRIEFLY);
+ DEC_CONSTS(FLASH_UNTIL_FOCUSED, SDL_FLASH_UNTIL_FOCUSED);
+#else
+ DEC_CONSTS(FLASH_CANCEL, -1);
+ DEC_CONSTS(FLASH_BRIEFLY, -1);
+ DEC_CONSTS(FLASH_UNTIL_FOCUSED, -1);
+#endif
+
if (PyModule_AddObject(module, "__all__", all_list)) {
Py_DECREF(all_list);
Py_DECREF(module);
diff --git a/src_c/display.c b/src_c/display.c
index fc07bd0c33..d6f557fb95 100644
--- a/src_c/display.c
+++ b/src_c/display.c
@@ -126,9 +126,7 @@ static PyObject *
pg_display_resource(char *filename)
{
PyObject *imagemodule = NULL;
- PyObject *load_basicfunc = NULL;
PyObject *pkgdatamodule = NULL;
- PyObject *resourcefunc = NULL;
PyObject *fresult = NULL;
PyObject *result = NULL;
PyObject *name = NULL;
@@ -137,19 +135,12 @@ pg_display_resource(char *filename)
if (!pkgdatamodule)
goto display_resource_end;
- resourcefunc = PyObject_GetAttrString(pkgdatamodule, resourcefunc_name);
- if (!resourcefunc)
- goto display_resource_end;
-
imagemodule = PyImport_ImportModule(imagemodule_name);
if (!imagemodule)
goto display_resource_end;
- load_basicfunc = PyObject_GetAttrString(imagemodule, load_basicfunc_name);
- if (!load_basicfunc)
- goto display_resource_end;
-
- fresult = PyObject_CallFunction(resourcefunc, "s", filename);
+ fresult =
+ PyObject_CallMethod(pkgdatamodule, resourcefunc_name, "s", filename);
if (!fresult)
goto display_resource_end;
@@ -166,15 +157,14 @@ pg_display_resource(char *filename)
PyErr_Clear();
}
- result = PyObject_CallFunction(load_basicfunc, "O", fresult);
+ result =
+ PyObject_CallMethod(imagemodule, load_basicfunc_name, "O", fresult);
if (!result)
goto display_resource_end;
display_resource_end:
Py_XDECREF(pkgdatamodule);
- Py_XDECREF(resourcefunc);
Py_XDECREF(imagemodule);
- Py_XDECREF(load_basicfunc);
Py_XDECREF(fresult);
Py_XDECREF(name);
return result;
@@ -1215,6 +1205,15 @@ pg_set_mode(PyObject *self, PyObject *arg, PyObject *kwds)
SDL_SetHintWithPriority(SDL_HINT_RENDER_SCALE_QUALITY,
"nearest", SDL_HINT_DEFAULT);
+#if SDL_VERSION_ATLEAST(2, 28, 0)
+ /* If the window has a surface associated with it already,
+ * we need to destroy it (if possible) because now we are
+ * associating a renderer with it. */
+ if (SDL_HasWindowSurface(win)) {
+ SDL_DestroyWindowSurface(win);
+ }
+#endif
+
if (vsync) {
pg_renderer = SDL_CreateRenderer(
win, -1, SDL_RENDERER_PRESENTVSYNC);
@@ -1224,8 +1223,7 @@ pg_set_mode(PyObject *self, PyObject *arg, PyObject *kwds)
}
if (pg_renderer == NULL) {
- return RAISE(pgExc_SDLError,
- "failed to create renderer");
+ return RAISE(pgExc_SDLError, SDL_GetError());
}
if (flags & PGS_SCALED) {
@@ -1696,31 +1694,48 @@ pg_update(PyObject *self, PyObject *arg)
SDL_UpdateWindowSurfaceRects(win, &sdlr, 1);
}
else {
- PyObject *seq;
- PyObject *r;
- Py_ssize_t loop, num;
+ PyObject *iterable, *single_arg, *r;
+ Py_ssize_t num;
int count;
SDL_Rect *rects;
if (PyTuple_Size(arg) != 1)
return RAISE(
PyExc_ValueError,
- "update requires a rectstyle or sequence of rectstyles");
- seq = PyTuple_GET_ITEM(arg, 0);
- if (!seq || !PySequence_Check(seq))
+ "update requires a rectstyle or an iterable of rectstyles");
+
+ single_arg = PyTuple_GET_ITEM(arg, 0);
+ num = PyObject_Size(single_arg);
+ if (num == -1) {
+ /* Either __len__ errored, or object doesn't have __len__.
+ * In this case we can assume a length arbitrarily, and keep
+ * scaling it as needed. */
+ PyErr_Clear();
+ num = 8;
+ }
+ iterable = PyObject_GetIter(single_arg);
+ if (!iterable)
return RAISE(
PyExc_ValueError,
- "update requires a rectstyle or sequence of rectstyles");
+ "update requires a rectstyle or an iterable of rectstyles");
- num = PySequence_Length(seq);
rects = PyMem_New(SDL_Rect, num);
- if (!rects)
+ if (!rects) {
+ Py_DECREF(iterable);
return NULL;
+ }
count = 0;
- for (loop = 0; loop < num; ++loop) {
- SDL_Rect *cur_rect = (rects + count);
-
- /*get rect from the sequence*/
- r = PySequence_GetItem(seq, loop);
+ while (1) {
+ r = PyIter_Next(iterable);
+ if (!r) {
+ if (PyErr_Occurred()) {
+ /* forward error */
+ Py_DECREF(iterable);
+ PyMem_Free((void *)rects);
+ return NULL;
+ }
+ /* End of sequence, break loop */
+ break;
+ }
if (r == Py_None) {
Py_DECREF(r);
continue;
@@ -1728,7 +1743,8 @@ pg_update(PyObject *self, PyObject *arg)
gr = pgRect_FromObject(r, &temp);
Py_XDECREF(r);
if (!gr) {
- PyMem_Free((char *)rects);
+ Py_DECREF(iterable);
+ PyMem_Free((void *)rects);
return RAISE(PyExc_ValueError,
"update_rects requires a single list of rects");
}
@@ -1736,8 +1752,20 @@ pg_update(PyObject *self, PyObject *arg)
if (gr->w < 1 && gr->h < 1)
continue;
- /*bail out if rect not onscreen*/
- if (!pg_screencroprect(gr, wide, high, cur_rect))
+ if (count >= num) {
+ /* About to overstep boundary, need reallocing */
+ num *= 2;
+ SDL_Rect *new_rects = PyMem_Resize(rects, SDL_Rect, num);
+ if (!new_rects) {
+ Py_DECREF(iterable);
+ PyMem_Free((void *)rects);
+ return NULL;
+ }
+ rects = new_rects;
+ }
+
+ /* bail out if rect not onscreen */
+ if (!pg_screencroprect(gr, wide, high, &rects[count]))
continue;
++count;
@@ -1749,7 +1777,8 @@ pg_update(PyObject *self, PyObject *arg)
Py_END_ALLOW_THREADS;
}
- PyMem_Free((char *)rects);
+ Py_DECREF(iterable);
+ PyMem_Free((void *)rects);
}
Py_RETURN_NONE;
}
@@ -2704,10 +2733,10 @@ pg_message_box(PyObject *self, PyObject *arg, PyObject *kwargs)
"parent_window", "buttons", "return_button",
"escape_button", NULL};
- if (!PyArg_ParseTupleAndKeywords(
- arg, kwargs, "s|OsO!OiO", keywords, &title, &message, &msgbox_type,
- &pgWindow_Type, &parent_window, &buttons, &return_button_index,
- &escape_button_index_obj)) {
+ if (!PyArg_ParseTupleAndKeywords(arg, kwargs, "s|OsOOiO", keywords, &title,
+ &message, &msgbox_type, &parent_window,
+ &buttons, &return_button_index,
+ &escape_button_index_obj)) {
return NULL;
}
@@ -2744,10 +2773,15 @@ pg_message_box(PyObject *self, PyObject *arg, PyObject *kwargs)
msgbox_data.flags |= SDL_MESSAGEBOX_BUTTONS_LEFT_TO_RIGHT;
#endif
- if (parent_window == Py_None)
+ if (parent_window == Py_None) {
msgbox_data.window = NULL;
- else
+ }
+ else {
+ if (!pgWindow_Check(parent_window)) {
+ return RAISE(PyExc_TypeError, "'parent_window' must be a Window");
+ }
msgbox_data.window = ((pgWindowObject *)parent_window)->_win;
+ }
msgbox_data.colorScheme = NULL; // use system color scheme settings
diff --git a/src_c/doc/display_doc.h b/src_c/doc/display_doc.h
index a0d81d902a..17b881bd49 100644
--- a/src_c/doc/display_doc.h
+++ b/src_c/doc/display_doc.h
@@ -6,7 +6,7 @@
#define DOC_DISPLAY_SETMODE "set_mode(size=(0, 0), flags=0, depth=0, display=0, vsync=0) -> Surface\nInitialize a window or screen for display"
#define DOC_DISPLAY_GETSURFACE "get_surface() -> Surface\nGet a reference to the currently set display surface"
#define DOC_DISPLAY_FLIP "flip() -> None\nUpdate the full display Surface to the screen"
-#define DOC_DISPLAY_UPDATE "update(rectangle=None, /) -> None\nupdate(rectangle_list, /) -> None\nUpdate all, or a portion, of the display. For non-OpenGL displays."
+#define DOC_DISPLAY_UPDATE "update(rectangle=None, /) -> None\nupdate(rectangle_iterable, /) -> None\nUpdate all, or a portion, of the display. For non-OpenGL displays."
#define DOC_DISPLAY_GETDRIVER "get_driver() -> name\nGet the name of the pygame display backend"
#define DOC_DISPLAY_INFO "Info() -> VideoInfo\nCreate a video display information object"
#define DOC_DISPLAY_GETWMINFO "get_wm_info() -> dict\nGet information about the current windowing system"
diff --git a/src_c/doc/geometry_doc.h b/src_c/doc/geometry_doc.h
index 960714ff73..e179cbf874 100644
--- a/src_c/doc/geometry_doc.h
+++ b/src_c/doc/geometry_doc.h
@@ -9,16 +9,16 @@
#define DOC_CIRCLE_DIAMETER "diameter -> float\ndiameter of the circle"
#define DOC_CIRCLE_AREA "area -> float\narea of the circle"
#define DOC_CIRCLE_CIRCUMFERENCE "circumference -> float\ncircumference of the circle"
-#define DOC_CIRCLE_COLLIDEPOINT "collidepoint((x, y), /) -> bool\ncollidepoint(x, y, /) -> bool\ncollidepoint(vector2, /) -> bool\ntest if a point is inside the circle"
-#define DOC_CIRCLE_COLLIDECIRCLE "collidecircle(circle, /) -> bool\ncollidecircle(x, y, radius, /) -> bool\ncollidecircle((x, y), radius, /) -> bool\ntest if two circles collide"
+#define DOC_CIRCLE_COLLIDEPOINT "collidepoint((x, y), /) -> bool\ncollidepoint(x, y, /) -> bool\ncollidepoint(vector2, /) -> bool\ntests if a point is inside the circle"
+#define DOC_CIRCLE_COLLIDECIRCLE "collidecircle(circle, /) -> bool\ncollidecircle(x, y, radius, /) -> bool\ncollidecircle((x, y), radius, /) -> bool\ncollidecircle(vector2, radius, /) -> bool\ntests if a circle collides with this circle"
+#define DOC_CIRCLE_COLLIDERECT "colliderect(rect, /) -> bool\ncolliderect((x, y, width, height), /) -> bool\ncolliderect(x, y, width, height, /) -> bool\ncolliderect((x, y), (width, height), /) -> bool\ncolliderect(vector2, (width, height), /) -> bool\ntests if a rectangle collides with this circle"
+#define DOC_CIRCLE_COLLIDESWITH "collideswith(circle, /) -> bool\ncollideswith(rect, /) -> bool\ncollideswith((x, y), /) -> bool\ncollideswith(vector2, /) -> bool\ntests if a shape or point collides with this circle"
+#define DOC_CIRCLE_CONTAINS "contains(circle, /) -> bool\ncontains(rect, /) -> bool\ncontains((x, y), /) -> bool\ncontains(vector2, /) -> bool\ntests if a shape or point is inside the circle"
#define DOC_CIRCLE_MOVE "move((x, y), /) -> Circle\nmove(x, y, /) -> Circle\nmove(vector2, /) -> Circle\nmoves the circle by a given amount"
#define DOC_CIRCLE_MOVEIP "move_ip((x, y), /) -> None\nmove_ip(x, y, /) -> None\nmove_ip(vector2, /) -> None\nmoves the circle by a given amount, in place"
-#define DOC_CIRCLE_COLLIDERECT "colliderect(rect, /) -> bool\ncolliderect((x, y, width, height), /) -> bool\ncolliderect(x, y, width, height, /) -> bool\ncolliderect((x, y), (width, height), /) -> bool\nchecks if a rectangle intersects the circle"
-#define DOC_CIRCLE_COLLIDESWITH "collideswith(circle, /) -> bool\ncollideswith(rect, /) -> bool\ncollideswith((x, y), /) -> bool\ncollideswith(vector2, /) -> bool\ncheck if a shape or point collides with the circle"
-#define DOC_CIRCLE_CONTAINS "contains(circle, /) -> bool\ncontains(rect, /) -> bool\ncontains((x, y), /) -> bool\ncontains(vector2, /) -> bool\ncheck if a shape or point is inside the circle"
-#define DOC_CIRCLE_UPDATE "update((x, y), radius, /) -> None\nupdate(x, y, radius, /) -> None\nupdates the circle position and radius"
+#define DOC_CIRCLE_UPDATE "update((x, y), radius, /) -> None\nupdate(x, y, radius, /) -> None\nupdate(vector2, radius, /) -> None\nupdates the circle position and radius"
#define DOC_CIRCLE_ROTATE "rotate(angle, rotation_point=Circle.center, /) -> Circle\nrotate(angle, /) -> Circle\nrotates the circle"
#define DOC_CIRCLE_ROTATEIP "rotate_ip(angle, rotation_point=Circle.center, /) -> None\nrotate_ip(angle, /) -> None\nrotates the circle in place"
-#define DOC_CIRCLE_ASRECT "as_rect() -> Rect\nreturns the smallest pygame.Rect object that contains the circle"
-#define DOC_CIRCLE_ASFRECT "as_frect() -> FRect\nreturns the smallest pygame.FRect object that contains the circle"
-#define DOC_CIRCLE_COPY "copy() -> Circle\nreturns a copy of the circle"
+#define DOC_CIRCLE_ASRECT "as_rect() -> Rect\nreturns the smallest Rect containing the circle"
+#define DOC_CIRCLE_ASFRECT "as_frect() -> FRect\nreturns the smallest FRect containing the circle"
+#define DOC_CIRCLE_COPY "copy() -> Circle\ncopies the circle"
diff --git a/src_c/doc/surface_doc.h b/src_c/doc/surface_doc.h
index b9c259dd78..38a6fb7120 100644
--- a/src_c/doc/surface_doc.h
+++ b/src_c/doc/surface_doc.h
@@ -1,6 +1,6 @@
/* Auto generated file: with make_docs.py . Docs go in docs/reST/ref/ . */
#define DOC_SURFACE "Surface((width, height), flags=0, depth=0, masks=None) -> Surface\nSurface((width, height), flags=0, Surface) -> Surface\npygame object for representing images"
-#define DOC_SURFACE_BLIT "blit(source, dest, area=None, special_flags=0) -> Rect\ndraw another surface onto this one"
+#define DOC_SURFACE_BLIT "blit(source, dest=(0, 0), area=None, special_flags=0) -> Rect\ndraw another surface onto this one"
#define DOC_SURFACE_BLITS "blits(blit_sequence=((source, dest), ...), doreturn=True) -> [Rect, ...] or None\nblits(((source, dest, area), ...)) -> [Rect, ...]\nblits(((source, dest, area, special_flags), ...)) -> [Rect, ...]\ndraw many surfaces onto this surface at their corresponding location"
#define DOC_SURFACE_FBLITS "fblits(blit_sequence=((source, dest), ...), special_flags=0, /) -> None\ndraw many surfaces onto this surface at their corresponding location and with the same special_flags"
#define DOC_SURFACE_CONVERT "convert(surface, /) -> Surface\nconvert(depth, flags=0, /) -> Surface\nconvert(masks, flags=0, /) -> Surface\nconvert() -> Surface\nchange the pixel format of a surface"
@@ -52,6 +52,7 @@
#define DOC_SURFACE_GETBUFFER "get_buffer() -> BufferProxy\nacquires a buffer object for the pixels of the Surface."
#define DOC_SURFACE_PIXELSADDRESS "_pixels_address -> int\npixel buffer address"
#define DOC_SURFACE_PREMULALPHA "premul_alpha() -> Surface\nreturns a copy of the surface with the RGB channels pre-multiplied by the alpha channel."
+#define DOC_SURFACE_PREMULALPHAIP "premul_alpha_ip() -> Surface\nmultiplies the RGB channels by the surface alpha channel."
#define DOC_SURFACE_WIDTH "width -> int\nSurface width in pixels (read-only)"
#define DOC_SURFACE_HEIGHT "height -> int\nSurface height in pixels (read-only)"
#define DOC_SURFACE_SIZE "height -> tuple[int, int]\nSurface size in pixels (read-only)"
diff --git a/src_c/doc/typing_doc.h b/src_c/doc/typing_doc.h
new file mode 100644
index 0000000000..ed354c8439
--- /dev/null
+++ b/src_c/doc/typing_doc.h
@@ -0,0 +1,10 @@
+/* Auto generated file: with make_docs.py . Docs go in docs/reST/ref/ . */
+#define DOC_TYPING "pygame module providing common typehints"
+#define DOC_TYPING_PATHLIKE ""
+#define DOC_TYPING_FILELIKE ""
+#define DOC_TYPING_SEQUENCELIKE ""
+#define DOC_TYPING_COORDINATE ""
+#define DOC_TYPING_INTCOORDINATE ""
+#define DOC_TYPING_RGBATUPLE ""
+#define DOC_TYPING_COLORLIKE ""
+#define DOC_TYPING_RECTLIKE ""
diff --git a/src_c/doc/window_doc.h b/src_c/doc/window_doc.h
index 323a67f6bc..ebcaac2e69 100644
--- a/src_c/doc/window_doc.h
+++ b/src_c/doc/window_doc.h
@@ -30,3 +30,4 @@
#define DOC_WINDOW_MINIMIZE "maximize() -> None\nMinimize the window"
#define DOC_WINDOW_SETICON "set_icon(surface, /) -> None\nSet the window icon"
#define DOC_WINDOW_SETMODALFOR "set_modal_for(parent, /) -> None\nSet the window as a modal for a parent window"
+#define DOC_WINDOW_FLASH "flash(operation, /) -> None\nFlash a window to demand attention from the user"
diff --git a/src_c/draw.c b/src_c/draw.c
index c7750664d6..edd2e55c00 100644
--- a/src_c/draw.c
+++ b/src_c/draw.c
@@ -58,13 +58,13 @@ static void
draw_circle_bresenham_thin(SDL_Surface *surf, int x0, int y0, int radius,
Uint32 color, int *drawn_area);
static void
-draw_circle_xaolinwu(SDL_Surface *surf, int x0, int y0, int radius,
- int thickness, Uint32 color, int top_right, int top_left,
- int bottom_left, int bottom_right, int *drawn_area);
+draw_circle_xiaolinwu(SDL_Surface *surf, int x0, int y0, int radius,
+ int thickness, Uint32 color, int top_right, int top_left,
+ int bottom_left, int bottom_right, int *drawn_area);
static void
-draw_circle_xaolinwu_thin(SDL_Surface *surf, int x0, int y0, int radius,
- Uint32 color, int top_right, int top_left,
- int bottom_left, int bottom_right, int *drawn_area);
+draw_circle_xiaolinwu_thin(SDL_Surface *surf, int x0, int y0, int radius,
+ Uint32 color, int top_right, int top_left,
+ int bottom_left, int bottom_right, int *drawn_area);
static void
draw_circle_filled(SDL_Surface *surf, int x0, int y0, int radius, Uint32 color,
int *drawn_area);
@@ -820,33 +820,33 @@ aacircle(PyObject *self, PyObject *args, PyObject *kwargs)
if (!width || width == radius) {
draw_circle_filled(surf, posx, posy, radius - 1, color,
drawn_area);
- draw_circle_xaolinwu(surf, posx, posy, radius, 2, color, 1, 1, 1,
- 1, drawn_area);
+ draw_circle_xiaolinwu(surf, posx, posy, radius, 2, color, 1, 1, 1,
+ 1, drawn_area);
}
else if (width == 1) {
- draw_circle_xaolinwu_thin(surf, posx, posy, radius, color, 1, 1, 1,
- 1, drawn_area);
+ draw_circle_xiaolinwu_thin(surf, posx, posy, radius, color, 1, 1,
+ 1, 1, drawn_area);
}
else {
- draw_circle_xaolinwu(surf, posx, posy, radius, width, color, 1, 1,
- 1, 1, drawn_area);
+ draw_circle_xiaolinwu(surf, posx, posy, radius, width, color, 1, 1,
+ 1, 1, drawn_area);
}
}
else {
if (!width || width == radius) {
- draw_circle_xaolinwu(surf, posx, posy, radius, radius, color,
- top_right, top_left, bottom_left,
- bottom_right, drawn_area);
+ draw_circle_xiaolinwu(surf, posx, posy, radius, radius, color,
+ top_right, top_left, bottom_left,
+ bottom_right, drawn_area);
}
else if (width == 1) {
- draw_circle_xaolinwu_thin(surf, posx, posy, radius, color,
- top_right, top_left, bottom_left,
- bottom_right, drawn_area);
+ draw_circle_xiaolinwu_thin(surf, posx, posy, radius, color,
+ top_right, top_left, bottom_left,
+ bottom_right, drawn_area);
}
else {
- draw_circle_xaolinwu(surf, posx, posy, radius, width, color,
- top_right, top_left, bottom_left,
- bottom_right, drawn_area);
+ draw_circle_xiaolinwu(surf, posx, posy, radius, width, color,
+ top_right, top_left, bottom_left,
+ bottom_right, drawn_area);
}
}
@@ -1213,7 +1213,6 @@ set_at(SDL_Surface *surf, int x, int y, Uint32 color)
{
SDL_PixelFormat *format = surf->format;
Uint8 *pixels = (Uint8 *)surf->pixels;
- Uint8 *byte_buf, rgb[4];
if (x < surf->clip_rect.x || x >= surf->clip_rect.x + surf->clip_rect.w ||
y < surf->clip_rect.y || y >= surf->clip_rect.y + surf->clip_rect.h)
@@ -1230,17 +1229,11 @@ set_at(SDL_Surface *surf, int x, int y, Uint32 color)
*((Uint32 *)(pixels + y * surf->pitch) + x) = color;
break;
default: /*case 3:*/
- SDL_GetRGB(color, format, rgb, rgb + 1, rgb + 2);
- byte_buf = (Uint8 *)(pixels + y * surf->pitch) + x * 3;
-#if (SDL_BYTEORDER == SDL_LIL_ENDIAN)
- *(byte_buf + (format->Rshift >> 3)) = rgb[0];
- *(byte_buf + (format->Gshift >> 3)) = rgb[1];
- *(byte_buf + (format->Bshift >> 3)) = rgb[2];
-#else
- *(byte_buf + 2 - (format->Rshift >> 3)) = rgb[0];
- *(byte_buf + 2 - (format->Gshift >> 3)) = rgb[1];
- *(byte_buf + 2 - (format->Bshift >> 3)) = rgb[2];
+#if SDL_BYTEORDER == SDL_BIG_ENDIAN
+ color <<= 8;
#endif
+ memcpy((pixels + y * surf->pitch) + x * 3, &color,
+ 3 * sizeof(Uint8));
break;
}
return 1;
@@ -1805,7 +1798,6 @@ unsafe_set_at(SDL_Surface *surf, int x, int y, Uint32 color)
{
SDL_PixelFormat *format = surf->format;
Uint8 *pixels = (Uint8 *)surf->pixels;
- Uint8 *byte_buf, rgb[4];
switch (PG_FORMAT_BytesPerPixel(format)) {
case 1:
@@ -1818,17 +1810,11 @@ unsafe_set_at(SDL_Surface *surf, int x, int y, Uint32 color)
*((Uint32 *)(pixels + y * surf->pitch) + x) = color;
break;
default: /*case 3:*/
- SDL_GetRGB(color, format, rgb, rgb + 1, rgb + 2);
- byte_buf = (Uint8 *)(pixels + y * surf->pitch) + x * 3;
-#if (SDL_BYTEORDER == SDL_LIL_ENDIAN)
- *(byte_buf + (format->Rshift >> 3)) = rgb[0];
- *(byte_buf + (format->Gshift >> 3)) = rgb[1];
- *(byte_buf + (format->Bshift >> 3)) = rgb[2];
-#else
- *(byte_buf + 2 - (format->Rshift >> 3)) = rgb[0];
- *(byte_buf + 2 - (format->Gshift >> 3)) = rgb[1];
- *(byte_buf + 2 - (format->Bshift >> 3)) = rgb[2];
+#if SDL_BYTEORDER == SDL_BIG_ENDIAN
+ color <<= 8;
#endif
+ memcpy((pixels + y * surf->pitch) + x * 3, &color,
+ 3 * sizeof(Uint8));
break;
}
}
@@ -2514,23 +2500,24 @@ draw_eight_symetric_pixels(SDL_Surface *surf, int x0, int y0, Uint32 color,
}
}
-/* Xaolin Wu Circle Algorithm
+/* Xiaolin Wu Circle Algorithm
* adapted from: https://cgg.mff.cuni.cz/~pepca/ref/WU.pdf
* with additional line width parameter and quadrants option
*/
static void
-draw_circle_xaolinwu(SDL_Surface *surf, int x0, int y0, int radius,
- int thickness, Uint32 color, int top_right, int top_left,
- int bottom_left, int bottom_right, int *drawn_area)
+draw_circle_xiaolinwu(SDL_Surface *surf, int x0, int y0, int radius,
+ int thickness, Uint32 color, int top_right, int top_left,
+ int bottom_left, int bottom_right, int *drawn_area)
{
for (int layer_radius = radius - thickness; layer_radius <= radius;
layer_radius++) {
int x = 0;
int y = layer_radius;
+ double pow_layer_r = pow(layer_radius, 2);
double prev_opacity = 0.0;
if (layer_radius == radius - thickness) {
while (x < y) {
- double height = sqrt(pow(layer_radius, 2) - pow(x, 2));
+ double height = sqrt(pow_layer_r - pow(x, 2));
double opacity = 255.0 * (ceil(height) - height);
if (opacity < prev_opacity) {
--y;
@@ -2547,7 +2534,7 @@ draw_circle_xaolinwu(SDL_Surface *surf, int x0, int y0, int radius,
}
else if (layer_radius == radius) {
while (x < y) {
- double height = sqrt(pow(layer_radius, 2) - pow(x, 2));
+ double height = sqrt(pow_layer_r - pow(x, 2));
double opacity = 255.0 * (ceil(height) - height);
if (opacity < prev_opacity) {
--y;
@@ -2565,7 +2552,7 @@ draw_circle_xaolinwu(SDL_Surface *surf, int x0, int y0, int radius,
}
else {
while (x < y) {
- double height = sqrt(pow(layer_radius, 2) - pow(x, 2));
+ double height = sqrt(pow_layer_r - pow(x, 2));
double opacity = 255.0 * (ceil(height) - height);
if (opacity < prev_opacity) {
--y;
@@ -2584,15 +2571,16 @@ draw_circle_xaolinwu(SDL_Surface *surf, int x0, int y0, int radius,
}
static void
-draw_circle_xaolinwu_thin(SDL_Surface *surf, int x0, int y0, int radius,
- Uint32 color, int top_right, int top_left,
- int bottom_left, int bottom_right, int *drawn_area)
+draw_circle_xiaolinwu_thin(SDL_Surface *surf, int x0, int y0, int radius,
+ Uint32 color, int top_right, int top_left,
+ int bottom_left, int bottom_right, int *drawn_area)
{
int x = 0;
int y = radius;
+ double pow_r = pow(radius, 2);
double prev_opacity = 0.0;
while (x < y) {
- double height = sqrt(pow(radius, 2) - pow(x, 2));
+ double height = sqrt(pow_r - pow(x, 2));
double opacity = 255.0 * (ceil(height) - height);
if (opacity < prev_opacity) {
--y;
diff --git a/src_c/event.c b/src_c/event.c
index aa0c95b39e..5a83df7adc 100644
--- a/src_c/event.c
+++ b/src_c/event.c
@@ -426,7 +426,7 @@ _pg_translate_windowevent(void *_, SDL_Event *event)
{
if (event->type == SDL_WINDOWEVENT) {
event->type = PGE_WINDOWSHOWN + event->window.event - 1;
- return SDL_EventState(_pg_pgevent_proxify(event->type), SDL_QUERY);
+ return PG_EventEnabled(_pg_pgevent_proxify(event->type));
}
return 1;
}
@@ -599,7 +599,7 @@ pg_event_filter(void *_, SDL_Event *event)
return RAISE(pgExc_SDLError, SDL_GetError()), 0;
*/
}
- return SDL_EventState(_pg_pgevent_proxify(event->type), SDL_QUERY);
+ return PG_EventEnabled(_pg_pgevent_proxify(event->type));
}
/* The two keyrepeat functions below modify state accessed by the event filter,
@@ -2142,7 +2142,7 @@ pg_event_set_allowed(PyObject *self, PyObject *obj)
if (obj == Py_None) {
int i;
for (i = SDL_FIRSTEVENT; i < SDL_LASTEVENT; i++) {
- SDL_EventState(i, SDL_ENABLE);
+ PG_SetEventEnabled(i, SDL_TRUE);
}
}
else {
@@ -2156,7 +2156,7 @@ pg_event_set_allowed(PyObject *self, PyObject *obj)
Py_DECREF(seq);
return NULL;
}
- SDL_EventState(_pg_pgevent_proxify(type), SDL_ENABLE);
+ PG_SetEventEnabled(_pg_pgevent_proxify(type), SDL_TRUE);
}
Py_DECREF(seq);
}
@@ -2175,7 +2175,7 @@ pg_event_set_blocked(PyObject *self, PyObject *obj)
int i;
/* Start at PGPOST_EVENTBEGIN */
for (i = PGPOST_EVENTBEGIN; i < SDL_LASTEVENT; i++) {
- SDL_EventState(i, SDL_IGNORE);
+ PG_SetEventEnabled(i, SDL_FALSE);
}
}
else {
@@ -2189,14 +2189,14 @@ pg_event_set_blocked(PyObject *self, PyObject *obj)
Py_DECREF(seq);
return NULL;
}
- SDL_EventState(_pg_pgevent_proxify(type), SDL_IGNORE);
+ PG_SetEventEnabled(_pg_pgevent_proxify(type), SDL_FALSE);
}
Py_DECREF(seq);
}
/* Never block SDL_WINDOWEVENT, we need them for translation */
- SDL_EventState(SDL_WINDOWEVENT, SDL_ENABLE);
+ PG_SetEventEnabled(SDL_WINDOWEVENT, SDL_TRUE);
/* Never block PGE_KEYREPEAT too, its needed for pygame internal use */
- SDL_EventState(PGE_KEYREPEAT, SDL_ENABLE);
+ PG_SetEventEnabled(PGE_KEYREPEAT, SDL_TRUE);
Py_RETURN_NONE;
}
@@ -2219,8 +2219,7 @@ pg_event_get_blocked(PyObject *self, PyObject *obj)
Py_DECREF(seq);
return NULL;
}
- if (SDL_EventState(_pg_pgevent_proxify(type), SDL_QUERY) ==
- SDL_IGNORE) {
+ if (PG_EventEnabled(_pg_pgevent_proxify(type)) == SDL_FALSE) {
isblocked = 1;
break;
}
diff --git a/src_c/font.c b/src_c/font.c
index 1f2f4b0db3..cae985f1cc 100644
--- a/src_c/font.c
+++ b/src_c/font.c
@@ -84,7 +84,6 @@ static PyObject *
font_resource(const char *filename)
{
PyObject *pkgdatamodule = NULL;
- PyObject *resourcefunc = NULL;
PyObject *result = NULL;
PyObject *tmp;
@@ -93,14 +92,9 @@ font_resource(const char *filename)
return NULL;
}
- resourcefunc = PyObject_GetAttrString(pkgdatamodule, resourcefunc_name);
+ result =
+ PyObject_CallMethod(pkgdatamodule, resourcefunc_name, "s", filename);
Py_DECREF(pkgdatamodule);
- if (resourcefunc == NULL) {
- return NULL;
- }
-
- result = PyObject_CallFunction(resourcefunc, "s", filename);
- Py_DECREF(resourcefunc);
if (result == NULL) {
return NULL;
}
@@ -925,10 +919,7 @@ font_set_script(PyObject *self, PyObject *arg)
return RAISE_FONT_QUIT_ERROR();
}
-/*Sadly, SDL_TTF_VERSION_ATLEAST is new in SDL_ttf 2.0.15, still too
- * new to use */
-#if SDL_VERSIONNUM(SDL_TTF_MAJOR_VERSION, SDL_TTF_MINOR_VERSION, \
- SDL_TTF_PATCHLEVEL) >= SDL_VERSIONNUM(2, 20, 0)
+#if SDL_TTF_VERSION_ATLEAST(2, 20, 0)
TTF_Font *font = PyFont_AsFont(self);
Py_ssize_t size;
const char *script_code;
@@ -962,10 +953,7 @@ font_set_direction(PyObject *self, PyObject *arg, PyObject *kwarg)
return RAISE_FONT_QUIT_ERROR();
}
-/* Can't use SDL_TTF_VERSION_ATLEAST until SDL_ttf 2.0.15 is minimum supported
- */
-#if SDL_VERSIONNUM(SDL_TTF_MAJOR_VERSION, SDL_TTF_MINOR_VERSION, \
- SDL_TTF_PATCHLEVEL) >= SDL_VERSIONNUM(2, 20, 0)
+#if SDL_TTF_VERSION_ATLEAST(2, 20, 0)
TTF_Font *font = PyFont_AsFont(self);
int direction;
char *kwds[] = {"direction", NULL};
@@ -995,8 +983,7 @@ font_set_direction(PyObject *self, PyObject *arg, PyObject *kwarg)
writing this) This bug flips the top-to-bottom and bottom-to-top rendering.
So, this is a compat patch for that behavior
*/
-#if SDL_VERSIONNUM(SDL_TTF_MAJOR_VERSION, SDL_TTF_MINOR_VERSION, \
- SDL_TTF_PATCHLEVEL) < SDL_VERSIONNUM(2, 22, 0)
+#if !SDL_TTF_VERSION_ATLEAST(2, 22, 0)
case 2: {
dir = TTF_DIRECTION_BTT;
break;
@@ -1237,6 +1224,12 @@ static PyObject *
get_ttf_version(PyObject *self, PyObject *args, PyObject *kwargs)
{
int linked = 1; /* Default is linked version. */
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+ int version = SDL_TTF_VERSION;
+#else
+ SDL_version version;
+ TTF_VERSION(&version);
+#endif
static char *keywords[] = {"linked", NULL};
@@ -1245,15 +1238,16 @@ get_ttf_version(PyObject *self, PyObject *args, PyObject *kwargs)
}
if (linked) {
- const SDL_version *v = TTF_Linked_Version();
- return Py_BuildValue("iii", v->major, v->minor, v->patch);
- }
- else {
- /* compiled version */
- SDL_version v;
- TTF_VERSION(&v);
- return Py_BuildValue("iii", v.major, v.minor, v.patch);
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+ version = TTF_Version();
+#else
+ version = *TTF_Linked_Version();
+#endif
}
+
+ return Py_BuildValue("iii", PG_FIND_VNUM_MAJOR(version),
+ PG_FIND_VNUM_MINOR(version),
+ PG_FIND_VNUM_MICRO(version));
}
static PyMethodDef _font_methods[] = {
diff --git a/src_c/geometry_common.c b/src_c/geometry_common.c
index 5116cc6b0d..57f4b1db37 100644
--- a/src_c/geometry_common.c
+++ b/src_c/geometry_common.c
@@ -4,7 +4,7 @@ int
_pg_circle_set_radius(PyObject *value, pgCircleBase *circle)
{
double radius = 0.0;
- if (!pg_DoubleFromObj(value, &radius) || radius <= 0.0) {
+ if (!pg_DoubleFromObj(value, &radius) || radius < 0.0) {
return 0;
}
circle->r = radius;
@@ -108,7 +108,7 @@ pgCircle_FromObject(PyObject *obj, pgCircleBase *out)
if (PyCallable_Check(circleattr)) /*call if it's a method*/
{
- PyObject *circleresult = PyObject_CallObject(circleattr, NULL);
+ PyObject *circleresult = PyObject_CallNoArgs(circleattr);
Py_DECREF(circleattr);
if (!circleresult) {
PyErr_Clear();
@@ -146,39 +146,3 @@ pgCircle_FromObjectFastcall(PyObject *const *args, Py_ssize_t nargs,
return 0;
}
}
-
-/* === Collision Functions === */
-
-inline int
-pgCollision_CirclePoint(pgCircleBase *circle, double Cx, double Cy)
-{
- double dx = circle->x - Cx;
- double dy = circle->y - Cy;
- return dx * dx + dy * dy <= circle->r * circle->r;
-}
-
-inline int
-pgCollision_CircleCircle(pgCircleBase *A, pgCircleBase *B)
-{
- double dx, dy;
- double sum_radii;
-
- dx = A->x - B->x;
- dy = A->y - B->y;
- sum_radii = A->r + B->r;
-
- return dx * dx + dy * dy <= sum_radii * sum_radii;
-}
-
-inline int
-pgCollision_RectCircle(double rx, double ry, double rw, double rh,
- pgCircleBase *circle)
-{
- const double cx = circle->x, cy = circle->y;
- const double r_bottom = ry + rh, r_right = rx + rw;
-
- const double test_x = (cx < rx) ? rx : ((cx > r_right) ? r_right : cx);
- const double test_y = (cy < ry) ? ry : ((cy > r_bottom) ? r_bottom : cy);
-
- return pgCollision_CirclePoint(circle, test_x, test_y);
-}
diff --git a/src_c/geometry_common.h b/src_c/geometry_common.h
index 124abca450..66628de282 100644
--- a/src_c/geometry_common.h
+++ b/src_c/geometry_common.h
@@ -15,14 +15,38 @@ pgCircle_FromObjectFastcall(PyObject *const *args, Py_ssize_t nargs,
/* === Collision Functions === */
-inline int
-pgCollision_CirclePoint(pgCircleBase *circle, double Cx, double Cy);
+static inline int
+pgCollision_CirclePoint(pgCircleBase *circle, double Cx, double Cy)
+{
+ double dx = circle->x - Cx;
+ double dy = circle->y - Cy;
+ return dx * dx + dy * dy <= circle->r * circle->r;
+}
+
+static inline int
+pgCollision_CircleCircle(pgCircleBase *A, pgCircleBase *B)
+{
+ double dx, dy;
+ double sum_radii;
+
+ dx = A->x - B->x;
+ dy = A->y - B->y;
+ sum_radii = A->r + B->r;
+
+ return dx * dx + dy * dy <= sum_radii * sum_radii;
+}
+
+static inline int
+pgCollision_RectCircle(double rx, double ry, double rw, double rh,
+ pgCircleBase *circle)
+{
+ const double cx = circle->x, cy = circle->y;
+ const double r_bottom = ry + rh, r_right = rx + rw;
-inline int
-pgCollision_CircleCircle(pgCircleBase *A, pgCircleBase *B);
+ const double test_x = (cx < rx) ? rx : ((cx > r_right) ? r_right : cx);
+ const double test_y = (cy < ry) ? ry : ((cy > r_bottom) ? r_bottom : cy);
-inline int
-pgCollision_RectCircle(double rx, double ry, double rw, double rh,
- pgCircleBase *circle);
+ return pgCollision_CirclePoint(circle, test_x, test_y);
+}
#endif // PYGAME_CE_GEOMETRY_COMMON_H
diff --git a/src_c/image.c b/src_c/image.c
index 481b6a99fa..90f52f0988 100644
--- a/src_c/image.c
+++ b/src_c/image.c
@@ -495,6 +495,8 @@ tobytes_surf_32bpp(SDL_Surface *surf, int flipped, int hascolorkey,
}
}
+#define PREMUL_PIXEL_ALPHA(pixel, alpha) (char)((((pixel) + 1) * (alpha)) >> 8)
+
PyObject *
image_tobytes(PyObject *self, PyObject *arg, PyObject *kwarg)
{
@@ -554,7 +556,7 @@ image_tobytes(PyObject *self, PyObject *arg, PyObject *kwarg)
byte_width = surf->w * 4;
}
else if (!strcmp(format, "RGBX") || !strcmp(format, "ARGB") ||
- !strcmp(format, "BGRA")) {
+ !strcmp(format, "BGRA") || !strcmp(format, "ABGR")) {
byte_width = surf->w * 4;
}
else if (!strcmp(format, "RGBA_PREMULT") ||
@@ -878,6 +880,83 @@ image_tobytes(PyObject *self, PyObject *arg, PyObject *kwarg)
}
pgSurface_Unlock(surfobj);
}
+ else if (!strcmp(format, "ABGR")) {
+ pgSurface_Lock(surfobj);
+ switch (PG_SURF_BytesPerPixel(surf)) {
+ case 1:
+ for (h = 0; h < surf->h; ++h) {
+ Uint8 *ptr = (Uint8 *)DATAROW(surf->pixels, h, surf->pitch,
+ surf->h, flipped);
+ for (w = 0; w < surf->w; ++w) {
+ color = *ptr++;
+ data[3] = (char)surf->format->palette->colors[color].r;
+ data[2] = (char)surf->format->palette->colors[color].g;
+ data[1] = (char)surf->format->palette->colors[color].b;
+ data[0] = (char)255;
+ data += 4;
+ }
+ pad(&data, padding);
+ }
+ break;
+ case 2:
+ for (h = 0; h < surf->h; ++h) {
+ Uint16 *ptr = (Uint16 *)DATAROW(
+ surf->pixels, h, surf->pitch, surf->h, flipped);
+ for (w = 0; w < surf->w; ++w) {
+ color = *ptr++;
+ data[3] = (char)(((color & Rmask) >> Rshift) << Rloss);
+ data[2] = (char)(((color & Gmask) >> Gshift) << Gloss);
+ data[1] = (char)(((color & Bmask) >> Bshift) << Bloss);
+ data[0] = (char)(Amask ? (((color & Amask) >> Ashift)
+ << Aloss)
+ : 255);
+ data += 4;
+ }
+ pad(&data, padding);
+ }
+ break;
+ case 3:
+ for (h = 0; h < surf->h; ++h) {
+ Uint8 *ptr = (Uint8 *)DATAROW(surf->pixels, h, surf->pitch,
+ surf->h, flipped);
+ for (w = 0; w < surf->w; ++w) {
+#if SDL_BYTEORDER == SDL_LIL_ENDIAN
+ color = ptr[0] + (ptr[1] << 8) + (ptr[2] << 16);
+#else
+ color = ptr[2] + (ptr[1] << 8) + (ptr[0] << 16);
+#endif
+ ptr += 3;
+ data[3] = (char)(((color & Rmask) >> Rshift) << Rloss);
+ data[2] = (char)(((color & Gmask) >> Gshift) << Gloss);
+ data[1] = (char)(((color & Bmask) >> Bshift) << Bloss);
+ data[0] = (char)(Amask ? (((color & Amask) >> Ashift)
+ << Aloss)
+ : 255);
+ data += 4;
+ }
+ pad(&data, padding);
+ }
+ break;
+ case 4:
+ for (h = 0; h < surf->h; ++h) {
+ Uint32 *ptr = (Uint32 *)DATAROW(
+ surf->pixels, h, surf->pitch, surf->h, flipped);
+ for (w = 0; w < surf->w; ++w) {
+ color = *ptr++;
+ data[3] = (char)(((color & Rmask) >> Rshift) << Rloss);
+ data[2] = (char)(((color & Gmask) >> Gshift) << Gloss);
+ data[1] = (char)(((color & Bmask) >> Bshift) << Bloss);
+ data[0] = (char)(Amask ? (((color & Amask) >> Ashift)
+ << Aloss)
+ : 255);
+ data += 4;
+ }
+ pad(&data, padding);
+ }
+ break;
+ }
+ pgSurface_Unlock(surfobj);
+ }
else if (!strcmp(format, "RGBA_PREMULT")) {
pgSurface_Lock(surfobj);
switch (PG_SURF_BytesPerPixel(surf)) {
@@ -888,15 +967,12 @@ image_tobytes(PyObject *self, PyObject *arg, PyObject *kwarg)
for (w = 0; w < surf->w; ++w) {
color = *ptr++;
alpha = ((color & Amask) >> Ashift) << Aloss;
- data[0] =
- (char)((((color & Rmask) >> Rshift) << Rloss) *
- alpha / 255);
- data[1] =
- (char)((((color & Gmask) >> Gshift) << Gloss) *
- alpha / 255);
- data[2] =
- (char)((((color & Bmask) >> Bshift) << Bloss) *
- alpha / 255);
+ data[0] = PREMUL_PIXEL_ALPHA(
+ ((color & Rmask) >> Rshift) << Rloss, alpha);
+ data[1] = PREMUL_PIXEL_ALPHA(
+ ((color & Gmask) >> Gshift) << Gloss, alpha);
+ data[2] = PREMUL_PIXEL_ALPHA(
+ ((color & Bmask) >> Bshift) << Bloss, alpha);
data[3] = (char)alpha;
data += 4;
}
@@ -915,15 +991,12 @@ image_tobytes(PyObject *self, PyObject *arg, PyObject *kwarg)
#endif
ptr += 3;
alpha = ((color & Amask) >> Ashift) << Aloss;
- data[0] =
- (char)((((color & Rmask) >> Rshift) << Rloss) *
- alpha / 255);
- data[1] =
- (char)((((color & Gmask) >> Gshift) << Gloss) *
- alpha / 255);
- data[2] =
- (char)((((color & Bmask) >> Bshift) << Bloss) *
- alpha / 255);
+ data[0] = PREMUL_PIXEL_ALPHA(
+ ((color & Rmask) >> Rshift) << Rloss, alpha);
+ data[1] = PREMUL_PIXEL_ALPHA(
+ ((color & Gmask) >> Gshift) << Gloss, alpha);
+ data[2] = PREMUL_PIXEL_ALPHA(
+ ((color & Bmask) >> Bshift) << Bloss, alpha);
data[3] = (char)alpha;
data += 4;
}
@@ -941,15 +1014,12 @@ image_tobytes(PyObject *self, PyObject *arg, PyObject *kwarg)
data[0] = data[1] = data[2] = 0;
}
else {
- data[0] =
- (char)((((color & Rmask) >> Rshift) << Rloss) *
- alpha / 255);
- data[1] =
- (char)((((color & Gmask) >> Gshift) << Gloss) *
- alpha / 255);
- data[2] =
- (char)((((color & Bmask) >> Bshift) << Bloss) *
- alpha / 255);
+ data[0] = PREMUL_PIXEL_ALPHA(
+ ((color & Rmask) >> Rshift) << Rloss, alpha);
+ data[1] = PREMUL_PIXEL_ALPHA(
+ ((color & Gmask) >> Gshift) << Gloss, alpha);
+ data[2] = PREMUL_PIXEL_ALPHA(
+ ((color & Bmask) >> Bshift) << Bloss, alpha);
}
data[3] = (char)alpha;
data += 4;
@@ -970,15 +1040,12 @@ image_tobytes(PyObject *self, PyObject *arg, PyObject *kwarg)
for (w = 0; w < surf->w; ++w) {
color = *ptr++;
alpha = ((color & Amask) >> Ashift) << Aloss;
- data[1] =
- (char)((((color & Rmask) >> Rshift) << Rloss) *
- alpha / 255);
- data[2] =
- (char)((((color & Gmask) >> Gshift) << Gloss) *
- alpha / 255);
- data[3] =
- (char)((((color & Bmask) >> Bshift) << Bloss) *
- alpha / 255);
+ data[1] = PREMUL_PIXEL_ALPHA(
+ ((color & Rmask) >> Rshift) << Rloss, alpha);
+ data[2] = PREMUL_PIXEL_ALPHA(
+ ((color & Gmask) >> Gshift) << Gloss, alpha);
+ data[3] = PREMUL_PIXEL_ALPHA(
+ ((color & Bmask) >> Bshift) << Bloss, alpha);
data[0] = (char)alpha;
data += 4;
}
@@ -997,15 +1064,12 @@ image_tobytes(PyObject *self, PyObject *arg, PyObject *kwarg)
#endif
ptr += 3;
alpha = ((color & Amask) >> Ashift) << Aloss;
- data[1] =
- (char)((((color & Rmask) >> Rshift) << Rloss) *
- alpha / 255);
- data[2] =
- (char)((((color & Gmask) >> Gshift) << Gloss) *
- alpha / 255);
- data[3] =
- (char)((((color & Bmask) >> Bshift) << Bloss) *
- alpha / 255);
+ data[1] = PREMUL_PIXEL_ALPHA(
+ ((color & Rmask) >> Rshift) << Rloss, alpha);
+ data[2] = PREMUL_PIXEL_ALPHA(
+ ((color & Gmask) >> Gshift) << Gloss, alpha);
+ data[3] = PREMUL_PIXEL_ALPHA(
+ ((color & Bmask) >> Bshift) << Bloss, alpha);
data[0] = (char)alpha;
data += 4;
}
@@ -1023,15 +1087,12 @@ image_tobytes(PyObject *self, PyObject *arg, PyObject *kwarg)
data[1] = data[2] = data[3] = 0;
}
else {
- data[1] =
- (char)((((color & Rmask) >> Rshift) << Rloss) *
- alpha / 255);
- data[2] =
- (char)((((color & Gmask) >> Gshift) << Gloss) *
- alpha / 255);
- data[3] =
- (char)((((color & Bmask) >> Bshift) << Bloss) *
- alpha / 255);
+ data[1] = PREMUL_PIXEL_ALPHA(
+ ((color & Rmask) >> Rshift) << Rloss, alpha);
+ data[2] = PREMUL_PIXEL_ALPHA(
+ ((color & Gmask) >> Gshift) << Gloss, alpha);
+ data[3] = PREMUL_PIXEL_ALPHA(
+ ((color & Bmask) >> Bshift) << Bloss, alpha);
}
data[0] = (char)alpha;
data += 4;
@@ -1224,6 +1285,32 @@ image_frombytes(PyObject *self, PyObject *arg, PyObject *kwds)
}
SDL_UnlockSurface(surf);
}
+ else if (!strcmp(format, "ABGR")) {
+ if (pitch == -1) {
+ pitch = w * 4;
+ }
+ else if (pitch < w * 4) {
+ return RAISE(PyExc_ValueError,
+ "Pitch must be greater than or equal to the width * "
+ "4 as per the format");
+ }
+
+ if (len != (Py_ssize_t)pitch * h)
+ return RAISE(
+ PyExc_ValueError,
+ "Bytes length does not equal format and resolution size");
+ surf = PG_CreateSurface(w, h, SDL_PIXELFORMAT_ABGR32);
+ if (!surf)
+ return RAISE(pgExc_SDLError, SDL_GetError());
+ SDL_LockSurface(surf);
+ for (looph = 0; looph < h; ++looph) {
+ Uint32 *pix = (Uint32 *)DATAROW(surf->pixels, looph, surf->pitch,
+ h, flipped);
+ memcpy(pix, data, w * sizeof(Uint32));
+ data += pitch;
+ }
+ SDL_UnlockSurface(surf);
+ }
else
return RAISE(PyExc_ValueError, "Unrecognized type of format");
diff --git a/src_c/imageext.c b/src_c/imageext.c
index b3bcc33209..d2823fcb00 100644
--- a/src_c/imageext.c
+++ b/src_c/imageext.c
@@ -292,6 +292,12 @@ imageext_get_sdl_image_version(PyObject *self, PyObject *args,
PyObject *kwargs)
{
int linked = 1;
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+ int version = SDL_IMAGE_VERSION;
+#else
+ SDL_version version;
+ SDL_IMAGE_VERSION(&version);
+#endif
static char *keywords[] = {"linked", NULL};
@@ -300,14 +306,16 @@ imageext_get_sdl_image_version(PyObject *self, PyObject *args,
}
if (linked) {
- const SDL_version *v = IMG_Linked_Version();
- return Py_BuildValue("iii", v->major, v->minor, v->patch);
- }
- else {
- SDL_version v;
- SDL_IMAGE_VERSION(&v);
- return Py_BuildValue("iii", v.major, v.minor, v.patch);
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+ version = IMG_Version();
+#else
+ version = *IMG_Linked_Version();
+#endif
}
+
+ return Py_BuildValue("iii", PG_FIND_VNUM_MAJOR(version),
+ PG_FIND_VNUM_MINOR(version),
+ PG_FIND_VNUM_MICRO(version));
}
/*
diff --git a/src_c/include/_pygame.h b/src_c/include/_pygame.h
index 1477fe24cc..e1510b7756 100644
--- a/src_c/include/_pygame.h
+++ b/src_c/include/_pygame.h
@@ -354,32 +354,25 @@ typedef struct {
* auto imported/initialized by surface
*/
#ifndef PYGAMEAPI_SURFLOCK_INTERNAL
-#define pgLifetimeLock_Type (*(PyTypeObject *)PYGAMEAPI_GET_SLOT(surflock, 0))
-
-#define pgLifetimeLock_Check(x) ((x)->ob_type == &pgLifetimeLock_Type)
-
#define pgSurface_Prep(x) \
if ((x)->subsurface) \
- (*(*(void (*)(pgSurfaceObject *))PYGAMEAPI_GET_SLOT(surflock, 1)))(x)
+ (*(*(void (*)(pgSurfaceObject *))PYGAMEAPI_GET_SLOT(surflock, 0)))(x)
#define pgSurface_Unprep(x) \
if ((x)->subsurface) \
- (*(*(void (*)(pgSurfaceObject *))PYGAMEAPI_GET_SLOT(surflock, 2)))(x)
+ (*(*(void (*)(pgSurfaceObject *))PYGAMEAPI_GET_SLOT(surflock, 1)))(x)
#define pgSurface_Lock \
- (*(int (*)(pgSurfaceObject *))PYGAMEAPI_GET_SLOT(surflock, 3))
+ (*(int (*)(pgSurfaceObject *))PYGAMEAPI_GET_SLOT(surflock, 2))
#define pgSurface_Unlock \
- (*(int (*)(pgSurfaceObject *))PYGAMEAPI_GET_SLOT(surflock, 4))
+ (*(int (*)(pgSurfaceObject *))PYGAMEAPI_GET_SLOT(surflock, 3))
#define pgSurface_LockBy \
- (*(int (*)(pgSurfaceObject *, PyObject *))PYGAMEAPI_GET_SLOT(surflock, 5))
+ (*(int (*)(pgSurfaceObject *, PyObject *))PYGAMEAPI_GET_SLOT(surflock, 4))
#define pgSurface_UnlockBy \
- (*(int (*)(pgSurfaceObject *, PyObject *))PYGAMEAPI_GET_SLOT(surflock, 6))
-
-#define pgSurface_LockLifetime \
- (*(PyObject * (*)(PyObject *, PyObject *)) PYGAMEAPI_GET_SLOT(surflock, 7))
+ (*(int (*)(pgSurfaceObject *, PyObject *))PYGAMEAPI_GET_SLOT(surflock, 5))
#endif
/*
diff --git a/src_c/include/pythoncapi_compat.h b/src_c/include/pythoncapi_compat.h
new file mode 100644
index 0000000000..51e8c0de75
--- /dev/null
+++ b/src_c/include/pythoncapi_compat.h
@@ -0,0 +1,1360 @@
+// Header file providing new C API functions to old Python versions.
+//
+// File distributed under the Zero Clause BSD (0BSD) license.
+// Copyright Contributors to the pythoncapi_compat project.
+//
+// Homepage:
+// https://github.com/python/pythoncapi_compat
+//
+// Latest version:
+// https://raw.githubusercontent.com/python/pythoncapi_compat/master/pythoncapi_compat.h
+//
+// SPDX-License-Identifier: 0BSD
+
+#ifndef PYTHONCAPI_COMPAT
+#define PYTHONCAPI_COMPAT
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include
+
+// Python 3.11.0b4 added PyFrame_Back() to Python.h
+#if PY_VERSION_HEX < 0x030b00B4 && !defined(PYPY_VERSION)
+# include "frameobject.h" // PyFrameObject, PyFrame_GetBack()
+#endif
+
+
+#ifndef _Py_CAST
+# define _Py_CAST(type, expr) ((type)(expr))
+#endif
+
+// Static inline functions should use _Py_NULL rather than using directly NULL
+// to prevent C++ compiler warnings. On C23 and newer and on C++11 and newer,
+// _Py_NULL is defined as nullptr.
+#if (defined (__STDC_VERSION__) && __STDC_VERSION__ > 201710L) \
+ || (defined(__cplusplus) && __cplusplus >= 201103)
+# define _Py_NULL nullptr
+#else
+# define _Py_NULL NULL
+#endif
+
+// Cast argument to PyObject* type.
+#ifndef _PyObject_CAST
+# define _PyObject_CAST(op) _Py_CAST(PyObject*, op)
+#endif
+
+
+// bpo-42262 added Py_NewRef() to Python 3.10.0a3
+#if PY_VERSION_HEX < 0x030A00A3 && !defined(Py_NewRef)
+static inline PyObject* _Py_NewRef(PyObject *obj)
+{
+ Py_INCREF(obj);
+ return obj;
+}
+#define Py_NewRef(obj) _Py_NewRef(_PyObject_CAST(obj))
+#endif
+
+
+// bpo-42262 added Py_XNewRef() to Python 3.10.0a3
+#if PY_VERSION_HEX < 0x030A00A3 && !defined(Py_XNewRef)
+static inline PyObject* _Py_XNewRef(PyObject *obj)
+{
+ Py_XINCREF(obj);
+ return obj;
+}
+#define Py_XNewRef(obj) _Py_XNewRef(_PyObject_CAST(obj))
+#endif
+
+
+// bpo-39573 added Py_SET_REFCNT() to Python 3.9.0a4
+#if PY_VERSION_HEX < 0x030900A4 && !defined(Py_SET_REFCNT)
+static inline void _Py_SET_REFCNT(PyObject *ob, Py_ssize_t refcnt)
+{
+ ob->ob_refcnt = refcnt;
+}
+#define Py_SET_REFCNT(ob, refcnt) _Py_SET_REFCNT(_PyObject_CAST(ob), refcnt)
+#endif
+
+
+// Py_SETREF() and Py_XSETREF() were added to Python 3.5.2.
+// It is excluded from the limited C API.
+#if (PY_VERSION_HEX < 0x03050200 && !defined(Py_SETREF)) && !defined(Py_LIMITED_API)
+#define Py_SETREF(dst, src) \
+ do { \
+ PyObject **_tmp_dst_ptr = _Py_CAST(PyObject**, &(dst)); \
+ PyObject *_tmp_dst = (*_tmp_dst_ptr); \
+ *_tmp_dst_ptr = _PyObject_CAST(src); \
+ Py_DECREF(_tmp_dst); \
+ } while (0)
+
+#define Py_XSETREF(dst, src) \
+ do { \
+ PyObject **_tmp_dst_ptr = _Py_CAST(PyObject**, &(dst)); \
+ PyObject *_tmp_dst = (*_tmp_dst_ptr); \
+ *_tmp_dst_ptr = _PyObject_CAST(src); \
+ Py_XDECREF(_tmp_dst); \
+ } while (0)
+#endif
+
+
+// bpo-43753 added Py_Is(), Py_IsNone(), Py_IsTrue() and Py_IsFalse()
+// to Python 3.10.0b1.
+#if PY_VERSION_HEX < 0x030A00B1 && !defined(Py_Is)
+# define Py_Is(x, y) ((x) == (y))
+#endif
+#if PY_VERSION_HEX < 0x030A00B1 && !defined(Py_IsNone)
+# define Py_IsNone(x) Py_Is(x, Py_None)
+#endif
+#if (PY_VERSION_HEX < 0x030A00B1 || defined(PYPY_VERSION)) && !defined(Py_IsTrue)
+# define Py_IsTrue(x) Py_Is(x, Py_True)
+#endif
+#if (PY_VERSION_HEX < 0x030A00B1 || defined(PYPY_VERSION)) && !defined(Py_IsFalse)
+# define Py_IsFalse(x) Py_Is(x, Py_False)
+#endif
+
+
+// bpo-39573 added Py_SET_TYPE() to Python 3.9.0a4
+#if PY_VERSION_HEX < 0x030900A4 && !defined(Py_SET_TYPE)
+static inline void _Py_SET_TYPE(PyObject *ob, PyTypeObject *type)
+{
+ ob->ob_type = type;
+}
+#define Py_SET_TYPE(ob, type) _Py_SET_TYPE(_PyObject_CAST(ob), type)
+#endif
+
+
+// bpo-39573 added Py_SET_SIZE() to Python 3.9.0a4
+#if PY_VERSION_HEX < 0x030900A4 && !defined(Py_SET_SIZE)
+static inline void _Py_SET_SIZE(PyVarObject *ob, Py_ssize_t size)
+{
+ ob->ob_size = size;
+}
+#define Py_SET_SIZE(ob, size) _Py_SET_SIZE((PyVarObject*)(ob), size)
+#endif
+
+
+// bpo-40421 added PyFrame_GetCode() to Python 3.9.0b1
+#if PY_VERSION_HEX < 0x030900B1 || defined(PYPY_VERSION)
+static inline PyCodeObject* PyFrame_GetCode(PyFrameObject *frame)
+{
+ assert(frame != _Py_NULL);
+ assert(frame->f_code != _Py_NULL);
+ return _Py_CAST(PyCodeObject*, Py_NewRef(frame->f_code));
+}
+#endif
+
+static inline PyCodeObject* _PyFrame_GetCodeBorrow(PyFrameObject *frame)
+{
+ PyCodeObject *code = PyFrame_GetCode(frame);
+ Py_DECREF(code);
+ return code;
+}
+
+
+// bpo-40421 added PyFrame_GetBack() to Python 3.9.0b1
+#if PY_VERSION_HEX < 0x030900B1 && !defined(PYPY_VERSION)
+static inline PyFrameObject* PyFrame_GetBack(PyFrameObject *frame)
+{
+ assert(frame != _Py_NULL);
+ return _Py_CAST(PyFrameObject*, Py_XNewRef(frame->f_back));
+}
+#endif
+
+#if !defined(PYPY_VERSION)
+static inline PyFrameObject* _PyFrame_GetBackBorrow(PyFrameObject *frame)
+{
+ PyFrameObject *back = PyFrame_GetBack(frame);
+ Py_XDECREF(back);
+ return back;
+}
+#endif
+
+
+// bpo-40421 added PyFrame_GetLocals() to Python 3.11.0a7
+#if PY_VERSION_HEX < 0x030B00A7 && !defined(PYPY_VERSION)
+static inline PyObject* PyFrame_GetLocals(PyFrameObject *frame)
+{
+#if PY_VERSION_HEX >= 0x030400B1
+ if (PyFrame_FastToLocalsWithError(frame) < 0) {
+ return NULL;
+ }
+#else
+ PyFrame_FastToLocals(frame);
+#endif
+ return Py_NewRef(frame->f_locals);
+}
+#endif
+
+
+// bpo-40421 added PyFrame_GetGlobals() to Python 3.11.0a7
+#if PY_VERSION_HEX < 0x030B00A7 && !defined(PYPY_VERSION)
+static inline PyObject* PyFrame_GetGlobals(PyFrameObject *frame)
+{
+ return Py_NewRef(frame->f_globals);
+}
+#endif
+
+
+// bpo-40421 added PyFrame_GetBuiltins() to Python 3.11.0a7
+#if PY_VERSION_HEX < 0x030B00A7 && !defined(PYPY_VERSION)
+static inline PyObject* PyFrame_GetBuiltins(PyFrameObject *frame)
+{
+ return Py_NewRef(frame->f_builtins);
+}
+#endif
+
+
+// bpo-40421 added PyFrame_GetLasti() to Python 3.11.0b1
+#if PY_VERSION_HEX < 0x030B00B1 && !defined(PYPY_VERSION)
+static inline int PyFrame_GetLasti(PyFrameObject *frame)
+{
+#if PY_VERSION_HEX >= 0x030A00A7
+ // bpo-27129: Since Python 3.10.0a7, f_lasti is an instruction offset,
+ // not a bytes offset anymore. Python uses 16-bit "wordcode" (2 bytes)
+ // instructions.
+ if (frame->f_lasti < 0) {
+ return -1;
+ }
+ return frame->f_lasti * 2;
+#else
+ return frame->f_lasti;
+#endif
+}
+#endif
+
+
+// gh-91248 added PyFrame_GetVar() to Python 3.12.0a2
+#if PY_VERSION_HEX < 0x030C00A2 && !defined(PYPY_VERSION)
+static inline PyObject* PyFrame_GetVar(PyFrameObject *frame, PyObject *name)
+{
+ PyObject *locals, *value;
+
+ locals = PyFrame_GetLocals(frame);
+ if (locals == NULL) {
+ return NULL;
+ }
+#if PY_VERSION_HEX >= 0x03000000
+ value = PyDict_GetItemWithError(locals, name);
+#else
+ value = _PyDict_GetItemWithError(locals, name);
+#endif
+ Py_DECREF(locals);
+
+ if (value == NULL) {
+ if (PyErr_Occurred()) {
+ return NULL;
+ }
+#if PY_VERSION_HEX >= 0x03000000
+ PyErr_Format(PyExc_NameError, "variable %R does not exist", name);
+#else
+ PyErr_SetString(PyExc_NameError, "variable does not exist");
+#endif
+ return NULL;
+ }
+ return Py_NewRef(value);
+}
+#endif
+
+
+// gh-91248 added PyFrame_GetVarString() to Python 3.12.0a2
+#if PY_VERSION_HEX < 0x030C00A2 && !defined(PYPY_VERSION)
+static inline PyObject*
+PyFrame_GetVarString(PyFrameObject *frame, const char *name)
+{
+ PyObject *name_obj, *value;
+#if PY_VERSION_HEX >= 0x03000000
+ name_obj = PyUnicode_FromString(name);
+#else
+ name_obj = PyString_FromString(name);
+#endif
+ if (name_obj == NULL) {
+ return NULL;
+ }
+ value = PyFrame_GetVar(frame, name_obj);
+ Py_DECREF(name_obj);
+ return value;
+}
+#endif
+
+
+// bpo-39947 added PyThreadState_GetInterpreter() to Python 3.9.0a5
+#if PY_VERSION_HEX < 0x030900A5 || defined(PYPY_VERSION)
+static inline PyInterpreterState *
+PyThreadState_GetInterpreter(PyThreadState *tstate)
+{
+ assert(tstate != _Py_NULL);
+ return tstate->interp;
+}
+#endif
+
+
+// bpo-40429 added PyThreadState_GetFrame() to Python 3.9.0b1
+#if PY_VERSION_HEX < 0x030900B1 && !defined(PYPY_VERSION)
+static inline PyFrameObject* PyThreadState_GetFrame(PyThreadState *tstate)
+{
+ assert(tstate != _Py_NULL);
+ return _Py_CAST(PyFrameObject *, Py_XNewRef(tstate->frame));
+}
+#endif
+
+#if !defined(PYPY_VERSION)
+static inline PyFrameObject*
+_PyThreadState_GetFrameBorrow(PyThreadState *tstate)
+{
+ PyFrameObject *frame = PyThreadState_GetFrame(tstate);
+ Py_XDECREF(frame);
+ return frame;
+}
+#endif
+
+
+// bpo-39947 added PyInterpreterState_Get() to Python 3.9.0a5
+#if PY_VERSION_HEX < 0x030900A5 || defined(PYPY_VERSION)
+static inline PyInterpreterState* PyInterpreterState_Get(void)
+{
+ PyThreadState *tstate;
+ PyInterpreterState *interp;
+
+ tstate = PyThreadState_GET();
+ if (tstate == _Py_NULL) {
+ Py_FatalError("GIL released (tstate is NULL)");
+ }
+ interp = tstate->interp;
+ if (interp == _Py_NULL) {
+ Py_FatalError("no current interpreter");
+ }
+ return interp;
+}
+#endif
+
+
+// bpo-39947 added PyInterpreterState_Get() to Python 3.9.0a6
+#if 0x030700A1 <= PY_VERSION_HEX && PY_VERSION_HEX < 0x030900A6 && !defined(PYPY_VERSION)
+static inline uint64_t PyThreadState_GetID(PyThreadState *tstate)
+{
+ assert(tstate != _Py_NULL);
+ return tstate->id;
+}
+#endif
+
+// bpo-43760 added PyThreadState_EnterTracing() to Python 3.11.0a2
+#if PY_VERSION_HEX < 0x030B00A2 && !defined(PYPY_VERSION)
+static inline void PyThreadState_EnterTracing(PyThreadState *tstate)
+{
+ tstate->tracing++;
+#if PY_VERSION_HEX >= 0x030A00A1
+ tstate->cframe->use_tracing = 0;
+#else
+ tstate->use_tracing = 0;
+#endif
+}
+#endif
+
+// bpo-43760 added PyThreadState_LeaveTracing() to Python 3.11.0a2
+#if PY_VERSION_HEX < 0x030B00A2 && !defined(PYPY_VERSION)
+static inline void PyThreadState_LeaveTracing(PyThreadState *tstate)
+{
+ int use_tracing = (tstate->c_tracefunc != _Py_NULL
+ || tstate->c_profilefunc != _Py_NULL);
+ tstate->tracing--;
+#if PY_VERSION_HEX >= 0x030A00A1
+ tstate->cframe->use_tracing = use_tracing;
+#else
+ tstate->use_tracing = use_tracing;
+#endif
+}
+#endif
+
+
+// bpo-37194 added PyObject_CallNoArgs() to Python 3.9.0a1
+// PyObject_CallNoArgs() added to PyPy 3.9.16-v7.3.11
+#if !defined(PyObject_CallNoArgs) && PY_VERSION_HEX < 0x030900A1
+static inline PyObject* PyObject_CallNoArgs(PyObject *func)
+{
+ return PyObject_CallFunctionObjArgs(func, NULL);
+}
+#endif
+
+
+// bpo-39245 made PyObject_CallOneArg() public (previously called
+// _PyObject_CallOneArg) in Python 3.9.0a4
+// PyObject_CallOneArg() added to PyPy 3.9.16-v7.3.11
+#if !defined(PyObject_CallOneArg) && PY_VERSION_HEX < 0x030900A4
+static inline PyObject* PyObject_CallOneArg(PyObject *func, PyObject *arg)
+{
+ return PyObject_CallFunctionObjArgs(func, arg, NULL);
+}
+#endif
+
+
+// bpo-1635741 added PyModule_AddObjectRef() to Python 3.10.0a3
+#if PY_VERSION_HEX < 0x030A00A3
+static inline int
+PyModule_AddObjectRef(PyObject *module, const char *name, PyObject *value)
+{
+ int res;
+
+ if (!value && !PyErr_Occurred()) {
+ // PyModule_AddObject() raises TypeError in this case
+ PyErr_SetString(PyExc_SystemError,
+ "PyModule_AddObjectRef() must be called "
+ "with an exception raised if value is NULL");
+ return -1;
+ }
+
+ Py_XINCREF(value);
+ res = PyModule_AddObject(module, name, value);
+ if (res < 0) {
+ Py_XDECREF(value);
+ }
+ return res;
+}
+#endif
+
+
+// bpo-40024 added PyModule_AddType() to Python 3.9.0a5
+#if PY_VERSION_HEX < 0x030900A5
+static inline int PyModule_AddType(PyObject *module, PyTypeObject *type)
+{
+ const char *name, *dot;
+
+ if (PyType_Ready(type) < 0) {
+ return -1;
+ }
+
+ // inline _PyType_Name()
+ name = type->tp_name;
+ assert(name != _Py_NULL);
+ dot = strrchr(name, '.');
+ if (dot != _Py_NULL) {
+ name = dot + 1;
+ }
+
+ return PyModule_AddObjectRef(module, name, _PyObject_CAST(type));
+}
+#endif
+
+
+// bpo-40241 added PyObject_GC_IsTracked() to Python 3.9.0a6.
+// bpo-4688 added _PyObject_GC_IS_TRACKED() to Python 2.7.0a2.
+#if PY_VERSION_HEX < 0x030900A6 && !defined(PYPY_VERSION)
+static inline int PyObject_GC_IsTracked(PyObject* obj)
+{
+ return (PyObject_IS_GC(obj) && _PyObject_GC_IS_TRACKED(obj));
+}
+#endif
+
+// bpo-40241 added PyObject_GC_IsFinalized() to Python 3.9.0a6.
+// bpo-18112 added _PyGCHead_FINALIZED() to Python 3.4.0 final.
+#if PY_VERSION_HEX < 0x030900A6 && PY_VERSION_HEX >= 0x030400F0 && !defined(PYPY_VERSION)
+static inline int PyObject_GC_IsFinalized(PyObject *obj)
+{
+ PyGC_Head *gc = _Py_CAST(PyGC_Head*, obj) - 1;
+ return (PyObject_IS_GC(obj) && _PyGCHead_FINALIZED(gc));
+}
+#endif
+
+
+// bpo-39573 added Py_IS_TYPE() to Python 3.9.0a4
+#if PY_VERSION_HEX < 0x030900A4 && !defined(Py_IS_TYPE)
+static inline int _Py_IS_TYPE(PyObject *ob, PyTypeObject *type) {
+ return Py_TYPE(ob) == type;
+}
+#define Py_IS_TYPE(ob, type) _Py_IS_TYPE(_PyObject_CAST(ob), type)
+#endif
+
+
+// bpo-46906 added PyFloat_Pack2() and PyFloat_Unpack2() to Python 3.11a7.
+// bpo-11734 added _PyFloat_Pack2() and _PyFloat_Unpack2() to Python 3.6.0b1.
+// Python 3.11a2 moved _PyFloat_Pack2() and _PyFloat_Unpack2() to the internal
+// C API: Python 3.11a2-3.11a6 versions are not supported.
+#if 0x030600B1 <= PY_VERSION_HEX && PY_VERSION_HEX <= 0x030B00A1 && !defined(PYPY_VERSION)
+static inline int PyFloat_Pack2(double x, char *p, int le)
+{ return _PyFloat_Pack2(x, (unsigned char*)p, le); }
+
+static inline double PyFloat_Unpack2(const char *p, int le)
+{ return _PyFloat_Unpack2((const unsigned char *)p, le); }
+#endif
+
+
+// bpo-46906 added PyFloat_Pack4(), PyFloat_Pack8(), PyFloat_Unpack4() and
+// PyFloat_Unpack8() to Python 3.11a7.
+// Python 3.11a2 moved _PyFloat_Pack4(), _PyFloat_Pack8(), _PyFloat_Unpack4()
+// and _PyFloat_Unpack8() to the internal C API: Python 3.11a2-3.11a6 versions
+// are not supported.
+#if PY_VERSION_HEX <= 0x030B00A1 && !defined(PYPY_VERSION)
+static inline int PyFloat_Pack4(double x, char *p, int le)
+{ return _PyFloat_Pack4(x, (unsigned char*)p, le); }
+
+static inline int PyFloat_Pack8(double x, char *p, int le)
+{ return _PyFloat_Pack8(x, (unsigned char*)p, le); }
+
+static inline double PyFloat_Unpack4(const char *p, int le)
+{ return _PyFloat_Unpack4((const unsigned char *)p, le); }
+
+static inline double PyFloat_Unpack8(const char *p, int le)
+{ return _PyFloat_Unpack8((const unsigned char *)p, le); }
+#endif
+
+
+// gh-92154 added PyCode_GetCode() to Python 3.11.0b1
+#if PY_VERSION_HEX < 0x030B00B1 && !defined(PYPY_VERSION)
+static inline PyObject* PyCode_GetCode(PyCodeObject *code)
+{
+ return Py_NewRef(code->co_code);
+}
+#endif
+
+
+// gh-95008 added PyCode_GetVarnames() to Python 3.11.0rc1
+#if PY_VERSION_HEX < 0x030B00C1 && !defined(PYPY_VERSION)
+static inline PyObject* PyCode_GetVarnames(PyCodeObject *code)
+{
+ return Py_NewRef(code->co_varnames);
+}
+#endif
+
+// gh-95008 added PyCode_GetFreevars() to Python 3.11.0rc1
+#if PY_VERSION_HEX < 0x030B00C1 && !defined(PYPY_VERSION)
+static inline PyObject* PyCode_GetFreevars(PyCodeObject *code)
+{
+ return Py_NewRef(code->co_freevars);
+}
+#endif
+
+// gh-95008 added PyCode_GetCellvars() to Python 3.11.0rc1
+#if PY_VERSION_HEX < 0x030B00C1 && !defined(PYPY_VERSION)
+static inline PyObject* PyCode_GetCellvars(PyCodeObject *code)
+{
+ return Py_NewRef(code->co_cellvars);
+}
+#endif
+
+
+// Py_UNUSED() was added to Python 3.4.0b2.
+#if PY_VERSION_HEX < 0x030400B2 && !defined(Py_UNUSED)
+# if defined(__GNUC__) || defined(__clang__)
+# define Py_UNUSED(name) _unused_ ## name __attribute__((unused))
+# else
+# define Py_UNUSED(name) _unused_ ## name
+# endif
+#endif
+
+
+// gh-105922 added PyImport_AddModuleRef() to Python 3.13.0a1
+#if PY_VERSION_HEX < 0x030D00A0
+static inline PyObject* PyImport_AddModuleRef(const char *name)
+{
+ return Py_XNewRef(PyImport_AddModule(name));
+}
+#endif
+
+
+// gh-105927 added PyWeakref_GetRef() to Python 3.13.0a1
+#if PY_VERSION_HEX < 0x030D0000
+static inline int PyWeakref_GetRef(PyObject *ref, PyObject **pobj)
+{
+ PyObject *obj;
+ if (ref != NULL && !PyWeakref_Check(ref)) {
+ *pobj = NULL;
+ PyErr_SetString(PyExc_TypeError, "expected a weakref");
+ return -1;
+ }
+ obj = PyWeakref_GetObject(ref);
+ if (obj == NULL) {
+ // SystemError if ref is NULL
+ *pobj = NULL;
+ return -1;
+ }
+ if (obj == Py_None) {
+ *pobj = NULL;
+ return 0;
+ }
+ *pobj = Py_NewRef(obj);
+ return (*pobj != NULL);
+}
+#endif
+
+
+// bpo-36974 added PY_VECTORCALL_ARGUMENTS_OFFSET to Python 3.8b1
+#ifndef PY_VECTORCALL_ARGUMENTS_OFFSET
+# define PY_VECTORCALL_ARGUMENTS_OFFSET (_Py_CAST(size_t, 1) << (8 * sizeof(size_t) - 1))
+#endif
+
+// bpo-36974 added PyVectorcall_NARGS() to Python 3.8b1
+#if PY_VERSION_HEX < 0x030800B1
+static inline Py_ssize_t PyVectorcall_NARGS(size_t n)
+{
+ return n & ~PY_VECTORCALL_ARGUMENTS_OFFSET;
+}
+#endif
+
+
+// gh-105922 added PyObject_Vectorcall() to Python 3.9.0a4
+#if PY_VERSION_HEX < 0x030900A4
+static inline PyObject*
+PyObject_Vectorcall(PyObject *callable, PyObject *const *args,
+ size_t nargsf, PyObject *kwnames)
+{
+#if PY_VERSION_HEX >= 0x030800B1 && !defined(PYPY_VERSION)
+ // bpo-36974 added _PyObject_Vectorcall() to Python 3.8.0b1
+ return _PyObject_Vectorcall(callable, args, nargsf, kwnames);
+#else
+ PyObject *posargs = NULL, *kwargs = NULL;
+ PyObject *res;
+ Py_ssize_t nposargs, nkwargs, i;
+
+ if (nargsf != 0 && args == NULL) {
+ PyErr_BadInternalCall();
+ goto error;
+ }
+ if (kwnames != NULL && !PyTuple_Check(kwnames)) {
+ PyErr_BadInternalCall();
+ goto error;
+ }
+
+ nposargs = (Py_ssize_t)PyVectorcall_NARGS(nargsf);
+ if (kwnames) {
+ nkwargs = PyTuple_GET_SIZE(kwnames);
+ }
+ else {
+ nkwargs = 0;
+ }
+
+ posargs = PyTuple_New(nposargs);
+ if (posargs == NULL) {
+ goto error;
+ }
+ if (nposargs) {
+ for (i=0; i < nposargs; i++) {
+ PyTuple_SET_ITEM(posargs, i, Py_NewRef(*args));
+ args++;
+ }
+ }
+
+ if (nkwargs) {
+ kwargs = PyDict_New();
+ if (kwargs == NULL) {
+ goto error;
+ }
+
+ for (i = 0; i < nkwargs; i++) {
+ PyObject *key = PyTuple_GET_ITEM(kwnames, i);
+ PyObject *value = *args;
+ args++;
+ if (PyDict_SetItem(kwargs, key, value) < 0) {
+ goto error;
+ }
+ }
+ }
+ else {
+ kwargs = NULL;
+ }
+
+ res = PyObject_Call(callable, posargs, kwargs);
+ Py_DECREF(posargs);
+ Py_XDECREF(kwargs);
+ return res;
+
+error:
+ Py_DECREF(posargs);
+ Py_XDECREF(kwargs);
+ return NULL;
+#endif
+}
+#endif
+
+
+// gh-106521 added PyObject_GetOptionalAttr() and
+// PyObject_GetOptionalAttrString() to Python 3.13.0a1
+#if PY_VERSION_HEX < 0x030D00A1
+static inline int
+PyObject_GetOptionalAttr(PyObject *obj, PyObject *attr_name, PyObject **result)
+{
+ // bpo-32571 added _PyObject_LookupAttr() to Python 3.7.0b1
+#if PY_VERSION_HEX >= 0x030700B1 && !defined(PYPY_VERSION)
+ return _PyObject_LookupAttr(obj, attr_name, result);
+#else
+ *result = PyObject_GetAttr(obj, attr_name);
+ if (*result != NULL) {
+ return 1;
+ }
+ if (!PyErr_Occurred()) {
+ return 0;
+ }
+ if (PyErr_ExceptionMatches(PyExc_AttributeError)) {
+ PyErr_Clear();
+ return 0;
+ }
+ return -1;
+#endif
+}
+
+static inline int
+PyObject_GetOptionalAttrString(PyObject *obj, const char *attr_name, PyObject **result)
+{
+ PyObject *name_obj;
+ int rc;
+#if PY_VERSION_HEX >= 0x03000000
+ name_obj = PyUnicode_FromString(attr_name);
+#else
+ name_obj = PyString_FromString(attr_name);
+#endif
+ if (name_obj == NULL) {
+ *result = NULL;
+ return -1;
+ }
+ rc = PyObject_GetOptionalAttr(obj, name_obj, result);
+ Py_DECREF(name_obj);
+ return rc;
+}
+#endif
+
+
+// gh-106307 added PyObject_GetOptionalAttr() and
+// PyMapping_GetOptionalItemString() to Python 3.13.0a1
+#if PY_VERSION_HEX < 0x030D00A1
+static inline int
+PyMapping_GetOptionalItem(PyObject *obj, PyObject *key, PyObject **result)
+{
+ *result = PyObject_GetItem(obj, key);
+ if (*result) {
+ return 1;
+ }
+ if (!PyErr_ExceptionMatches(PyExc_KeyError)) {
+ return -1;
+ }
+ PyErr_Clear();
+ return 0;
+}
+
+static inline int
+PyMapping_GetOptionalItemString(PyObject *obj, const char *key, PyObject **result)
+{
+ PyObject *key_obj;
+ int rc;
+#if PY_VERSION_HEX >= 0x03000000
+ key_obj = PyUnicode_FromString(key);
+#else
+ key_obj = PyString_FromString(key);
+#endif
+ if (key_obj == NULL) {
+ *result = NULL;
+ return -1;
+ }
+ rc = PyMapping_GetOptionalItem(obj, key_obj, result);
+ Py_DECREF(key_obj);
+ return rc;
+}
+#endif
+
+// gh-108511 added PyMapping_HasKeyWithError() and
+// PyMapping_HasKeyStringWithError() to Python 3.13.0a1
+#if PY_VERSION_HEX < 0x030D00A1
+static inline int
+PyMapping_HasKeyWithError(PyObject *obj, PyObject *key)
+{
+ PyObject *res;
+ int rc = PyMapping_GetOptionalItem(obj, key, &res);
+ Py_XDECREF(res);
+ return rc;
+}
+
+static inline int
+PyMapping_HasKeyStringWithError(PyObject *obj, const char *key)
+{
+ PyObject *res;
+ int rc = PyMapping_GetOptionalItemString(obj, key, &res);
+ Py_XDECREF(res);
+ return rc;
+}
+#endif
+
+
+// gh-108511 added PyObject_HasAttrWithError() and
+// PyObject_HasAttrStringWithError() to Python 3.13.0a1
+#if PY_VERSION_HEX < 0x030D00A1
+static inline int
+PyObject_HasAttrWithError(PyObject *obj, PyObject *attr)
+{
+ PyObject *res;
+ int rc = PyObject_GetOptionalAttr(obj, attr, &res);
+ Py_XDECREF(res);
+ return rc;
+}
+
+static inline int
+PyObject_HasAttrStringWithError(PyObject *obj, const char *attr)
+{
+ PyObject *res;
+ int rc = PyObject_GetOptionalAttrString(obj, attr, &res);
+ Py_XDECREF(res);
+ return rc;
+}
+#endif
+
+
+// gh-106004 added PyDict_GetItemRef() and PyDict_GetItemStringRef()
+// to Python 3.13.0a1
+#if PY_VERSION_HEX < 0x030D00A1
+static inline int
+PyDict_GetItemRef(PyObject *mp, PyObject *key, PyObject **result)
+{
+#if PY_VERSION_HEX >= 0x03000000
+ PyObject *item = PyDict_GetItemWithError(mp, key);
+#else
+ PyObject *item = _PyDict_GetItemWithError(mp, key);
+#endif
+ if (item != NULL) {
+ *result = Py_NewRef(item);
+ return 1; // found
+ }
+ if (!PyErr_Occurred()) {
+ *result = NULL;
+ return 0; // not found
+ }
+ *result = NULL;
+ return -1;
+}
+
+static inline int
+PyDict_GetItemStringRef(PyObject *mp, const char *key, PyObject **result)
+{
+ int res;
+#if PY_VERSION_HEX >= 0x03000000
+ PyObject *key_obj = PyUnicode_FromString(key);
+#else
+ PyObject *key_obj = PyString_FromString(key);
+#endif
+ if (key_obj == NULL) {
+ *result = NULL;
+ return -1;
+ }
+ res = PyDict_GetItemRef(mp, key_obj, result);
+ Py_DECREF(key_obj);
+ return res;
+}
+#endif
+
+
+// gh-106307 added PyModule_Add() to Python 3.13.0a1
+#if PY_VERSION_HEX < 0x030D00A1
+static inline int
+PyModule_Add(PyObject *mod, const char *name, PyObject *value)
+{
+ int res = PyModule_AddObjectRef(mod, name, value);
+ Py_XDECREF(value);
+ return res;
+}
+#endif
+
+
+// gh-108014 added Py_IsFinalizing() to Python 3.13.0a1
+// bpo-1856 added _Py_Finalizing to Python 3.2.1b1.
+// _Py_IsFinalizing() was added to PyPy 7.3.0.
+#if (0x030201B1 <= PY_VERSION_HEX && PY_VERSION_HEX < 0x030D00A1) \
+ && (!defined(PYPY_VERSION_NUM) || PYPY_VERSION_NUM >= 0x7030000)
+static inline int Py_IsFinalizing(void)
+{
+#if PY_VERSION_HEX >= 0x030700A1
+ // _Py_IsFinalizing() was added to Python 3.7.0a1.
+ return _Py_IsFinalizing();
+#else
+ return (_Py_Finalizing != NULL);
+#endif
+}
+#endif
+
+
+// gh-108323 added PyDict_ContainsString() to Python 3.13.0a1
+#if PY_VERSION_HEX < 0x030D00A1
+static inline int PyDict_ContainsString(PyObject *op, const char *key)
+{
+ PyObject *key_obj = PyUnicode_FromString(key);
+ if (key_obj == NULL) {
+ return -1;
+ }
+ int res = PyDict_Contains(op, key_obj);
+ Py_DECREF(key_obj);
+ return res;
+}
+#endif
+
+
+// gh-108445 added PyLong_AsInt() to Python 3.13.0a1
+#if PY_VERSION_HEX < 0x030D00A1
+static inline int PyLong_AsInt(PyObject *obj)
+{
+#ifdef PYPY_VERSION
+ long value = PyLong_AsLong(obj);
+ if (value == -1 && PyErr_Occurred()) {
+ return -1;
+ }
+ if (value < (long)INT_MIN || (long)INT_MAX < value) {
+ PyErr_SetString(PyExc_OverflowError,
+ "Python int too large to convert to C int");
+ return -1;
+ }
+ return (int)value;
+#else
+ return _PyLong_AsInt(obj);
+#endif
+}
+#endif
+
+
+// gh-107073 added PyObject_VisitManagedDict() to Python 3.13.0a1
+#if PY_VERSION_HEX < 0x030D00A1
+static inline int
+PyObject_VisitManagedDict(PyObject *obj, visitproc visit, void *arg)
+{
+ PyObject **dict = _PyObject_GetDictPtr(obj);
+ if (*dict == NULL) {
+ return -1;
+ }
+ Py_VISIT(*dict);
+ return 0;
+}
+
+static inline void
+PyObject_ClearManagedDict(PyObject *obj)
+{
+ PyObject **dict = _PyObject_GetDictPtr(obj);
+ if (*dict == NULL) {
+ return;
+ }
+ Py_CLEAR(*dict);
+}
+#endif
+
+// gh-108867 added PyThreadState_GetUnchecked() to Python 3.13.0a1
+// Python 3.5.2 added _PyThreadState_UncheckedGet().
+#if PY_VERSION_HEX >= 0x03050200 && PY_VERSION_HEX < 0x030D00A1
+static inline PyThreadState*
+PyThreadState_GetUnchecked(void)
+{
+ return _PyThreadState_UncheckedGet();
+}
+#endif
+
+// gh-110289 added PyUnicode_EqualToUTF8() and PyUnicode_EqualToUTF8AndSize()
+// to Python 3.13.0a1
+#if PY_VERSION_HEX < 0x030D00A1
+static inline int
+PyUnicode_EqualToUTF8AndSize(PyObject *unicode, const char *str, Py_ssize_t str_len)
+{
+ Py_ssize_t len;
+ const void *utf8;
+ PyObject *exc_type, *exc_value, *exc_tb;
+ int res;
+
+ // API cannot report errors so save/restore the exception
+ PyErr_Fetch(&exc_type, &exc_value, &exc_tb);
+
+ // Python 3.3.0a1 added PyUnicode_AsUTF8AndSize()
+#if PY_VERSION_HEX >= 0x030300A1
+ if (PyUnicode_IS_ASCII(unicode)) {
+ utf8 = PyUnicode_DATA(unicode);
+ len = PyUnicode_GET_LENGTH(unicode);
+ }
+ else {
+ utf8 = PyUnicode_AsUTF8AndSize(unicode, &len);
+ if (utf8 == NULL) {
+ // Memory allocation failure. The API cannot report error,
+ // so ignore the exception and return 0.
+ res = 0;
+ goto done;
+ }
+ }
+
+ if (len != str_len) {
+ res = 0;
+ goto done;
+ }
+ res = (memcmp(utf8, str, (size_t)len) == 0);
+#else
+ PyObject *bytes = PyUnicode_AsUTF8String(unicode);
+ if (bytes == NULL) {
+ // Memory allocation failure. The API cannot report error,
+ // so ignore the exception and return 0.
+ res = 0;
+ goto done;
+ }
+
+#if PY_VERSION_HEX >= 0x03000000
+ len = PyBytes_GET_SIZE(bytes);
+ utf8 = PyBytes_AS_STRING(bytes);
+#else
+ len = PyString_GET_SIZE(bytes);
+ utf8 = PyString_AS_STRING(bytes);
+#endif
+ if (len != str_len) {
+ Py_DECREF(bytes);
+ res = 0;
+ goto done;
+ }
+
+ res = (memcmp(utf8, str, (size_t)len) == 0);
+ Py_DECREF(bytes);
+#endif
+
+done:
+ PyErr_Restore(exc_type, exc_value, exc_tb);
+ return res;
+}
+
+static inline int
+PyUnicode_EqualToUTF8(PyObject *unicode, const char *str)
+{
+ return PyUnicode_EqualToUTF8AndSize(unicode, str, (Py_ssize_t)strlen(str));
+}
+#endif
+
+
+// gh-111138 added PyList_Extend() and PyList_Clear() to Python 3.13.0a2
+#if PY_VERSION_HEX < 0x030D00A2
+static inline int
+PyList_Extend(PyObject *list, PyObject *iterable)
+{
+ return PyList_SetSlice(list, PY_SSIZE_T_MAX, PY_SSIZE_T_MAX, iterable);
+}
+
+static inline int
+PyList_Clear(PyObject *list)
+{
+ return PyList_SetSlice(list, 0, PY_SSIZE_T_MAX, NULL);
+}
+#endif
+
+// gh-111262 added PyDict_Pop() and PyDict_PopString() to Python 3.13.0a2
+#if PY_VERSION_HEX < 0x030D00A2
+static inline int
+PyDict_Pop(PyObject *dict, PyObject *key, PyObject **result)
+{
+ PyObject *value;
+
+ if (!PyDict_Check(dict)) {
+ PyErr_BadInternalCall();
+ if (result) {
+ *result = NULL;
+ }
+ return -1;
+ }
+
+ // bpo-16991 added _PyDict_Pop() to Python 3.5.0b2.
+ // Python 3.6.0b3 changed _PyDict_Pop() first argument type to PyObject*.
+ // Python 3.13.0a1 removed _PyDict_Pop().
+#if defined(PYPY_VERSION) || PY_VERSION_HEX < 0x030500b2 || PY_VERSION_HEX >= 0x030D0000
+ value = PyObject_CallMethod(dict, "pop", "O", key);
+#elif PY_VERSION_HEX < 0x030600b3
+ value = _PyDict_Pop(_Py_CAST(PyDictObject*, dict), key, NULL);
+#else
+ value = _PyDict_Pop(dict, key, NULL);
+#endif
+ if (value == NULL) {
+ if (result) {
+ *result = NULL;
+ }
+ if (PyErr_Occurred() && !PyErr_ExceptionMatches(PyExc_KeyError)) {
+ return -1;
+ }
+ PyErr_Clear();
+ return 0;
+ }
+ if (result) {
+ *result = value;
+ }
+ else {
+ Py_DECREF(value);
+ }
+ return 1;
+}
+
+static inline int
+PyDict_PopString(PyObject *dict, const char *key, PyObject **result)
+{
+ PyObject *key_obj = PyUnicode_FromString(key);
+ if (key_obj == NULL) {
+ if (result != NULL) {
+ *result = NULL;
+ }
+ return -1;
+ }
+
+ int res = PyDict_Pop(dict, key_obj, result);
+ Py_DECREF(key_obj);
+ return res;
+}
+#endif
+
+
+#if PY_VERSION_HEX < 0x030200A4
+// Python 3.2.0a4 added Py_hash_t type
+typedef Py_ssize_t Py_hash_t;
+#endif
+
+
+// gh-111545 added Py_HashPointer() to Python 3.13.0a3
+#if PY_VERSION_HEX < 0x030D00A3
+static inline Py_hash_t Py_HashPointer(const void *ptr)
+{
+#if PY_VERSION_HEX >= 0x030900A4 && !defined(PYPY_VERSION)
+ return _Py_HashPointer(ptr);
+#else
+ return _Py_HashPointer(_Py_CAST(void*, ptr));
+#endif
+}
+#endif
+
+
+// Python 3.13a4 added a PyTime API.
+// Use the private API added to Python 3.5.
+#if PY_VERSION_HEX < 0x030D00A4 && PY_VERSION_HEX >= 0x03050000
+typedef _PyTime_t PyTime_t;
+#define PyTime_MIN _PyTime_MIN
+#define PyTime_MAX _PyTime_MAX
+
+static inline double PyTime_AsSecondsDouble(PyTime_t t)
+{ return _PyTime_AsSecondsDouble(t); }
+
+static inline int PyTime_Monotonic(PyTime_t *result)
+{ return _PyTime_GetMonotonicClockWithInfo(result, NULL); }
+
+static inline int PyTime_Time(PyTime_t *result)
+{ return _PyTime_GetSystemClockWithInfo(result, NULL); }
+
+static inline int PyTime_PerfCounter(PyTime_t *result)
+{
+#if PY_VERSION_HEX >= 0x03070000 && !defined(PYPY_VERSION)
+ return _PyTime_GetPerfCounterWithInfo(result, NULL);
+#elif PY_VERSION_HEX >= 0x03070000
+ // Call time.perf_counter_ns() and convert Python int object to PyTime_t.
+ // Cache time.perf_counter_ns() function for best performance.
+ static PyObject *func = NULL;
+ if (func == NULL) {
+ PyObject *mod = PyImport_ImportModule("time");
+ if (mod == NULL) {
+ return -1;
+ }
+
+ func = PyObject_GetAttrString(mod, "perf_counter_ns");
+ Py_DECREF(mod);
+ if (func == NULL) {
+ return -1;
+ }
+ }
+
+ PyObject *res = PyObject_CallNoArgs(func);
+ if (res == NULL) {
+ return -1;
+ }
+ long long value = PyLong_AsLongLong(res);
+ Py_DECREF(res);
+
+ if (value == -1 && PyErr_Occurred()) {
+ return -1;
+ }
+
+ Py_BUILD_ASSERT(sizeof(value) >= sizeof(PyTime_t));
+ *result = (PyTime_t)value;
+ return 0;
+#else
+ // Call time.perf_counter() and convert C double to PyTime_t.
+ // Cache time.perf_counter() function for best performance.
+ static PyObject *func = NULL;
+ if (func == NULL) {
+ PyObject *mod = PyImport_ImportModule("time");
+ if (mod == NULL) {
+ return -1;
+ }
+
+ func = PyObject_GetAttrString(mod, "perf_counter");
+ Py_DECREF(mod);
+ if (func == NULL) {
+ return -1;
+ }
+ }
+
+ PyObject *res = PyObject_CallNoArgs(func);
+ if (res == NULL) {
+ return -1;
+ }
+ double d = PyFloat_AsDouble(res);
+ Py_DECREF(res);
+
+ if (d == -1.0 && PyErr_Occurred()) {
+ return -1;
+ }
+
+ // Avoid floor() to avoid having to link to libm
+ *result = (PyTime_t)(d * 1e9);
+ return 0;
+#endif
+}
+
+#endif
+
+// gh-111389 added hash constants to Python 3.13.0a5. These constants were
+// added first as private macros to Python 3.4.0b1 and PyPy 7.3.9.
+#if (!defined(PyHASH_BITS) \
+ && ((!defined(PYPY_VERSION) && PY_VERSION_HEX >= 0x030400B1) \
+ || (defined(PYPY_VERSION) && PY_VERSION_HEX >= 0x03070000 \
+ && PYPY_VERSION_NUM >= 0x07090000)))
+# define PyHASH_BITS _PyHASH_BITS
+# define PyHASH_MODULUS _PyHASH_MODULUS
+# define PyHASH_INF _PyHASH_INF
+# define PyHASH_IMAG _PyHASH_IMAG
+#endif
+
+
+// gh-111545 added Py_GetConstant() and Py_GetConstantBorrowed()
+// to Python 3.13.0a6
+#if PY_VERSION_HEX < 0x030D00A6 && !defined(Py_CONSTANT_NONE)
+
+#define Py_CONSTANT_NONE 0
+#define Py_CONSTANT_FALSE 1
+#define Py_CONSTANT_TRUE 2
+#define Py_CONSTANT_ELLIPSIS 3
+#define Py_CONSTANT_NOT_IMPLEMENTED 4
+#define Py_CONSTANT_ZERO 5
+#define Py_CONSTANT_ONE 6
+#define Py_CONSTANT_EMPTY_STR 7
+#define Py_CONSTANT_EMPTY_BYTES 8
+#define Py_CONSTANT_EMPTY_TUPLE 9
+
+static inline PyObject* Py_GetConstant(unsigned int constant_id)
+{
+ static PyObject* constants[Py_CONSTANT_EMPTY_TUPLE + 1] = {NULL};
+
+ if (constants[Py_CONSTANT_NONE] == NULL) {
+ constants[Py_CONSTANT_NONE] = Py_None;
+ constants[Py_CONSTANT_FALSE] = Py_False;
+ constants[Py_CONSTANT_TRUE] = Py_True;
+ constants[Py_CONSTANT_ELLIPSIS] = Py_Ellipsis;
+ constants[Py_CONSTANT_NOT_IMPLEMENTED] = Py_NotImplemented;
+
+ constants[Py_CONSTANT_ZERO] = PyLong_FromLong(0);
+ if (constants[Py_CONSTANT_ZERO] == NULL) {
+ goto fatal_error;
+ }
+
+ constants[Py_CONSTANT_ONE] = PyLong_FromLong(1);
+ if (constants[Py_CONSTANT_ONE] == NULL) {
+ goto fatal_error;
+ }
+
+ constants[Py_CONSTANT_EMPTY_STR] = PyUnicode_FromStringAndSize("", 0);
+ if (constants[Py_CONSTANT_EMPTY_STR] == NULL) {
+ goto fatal_error;
+ }
+
+ constants[Py_CONSTANT_EMPTY_BYTES] = PyBytes_FromStringAndSize("", 0);
+ if (constants[Py_CONSTANT_EMPTY_BYTES] == NULL) {
+ goto fatal_error;
+ }
+
+ constants[Py_CONSTANT_EMPTY_TUPLE] = PyTuple_New(0);
+ if (constants[Py_CONSTANT_EMPTY_TUPLE] == NULL) {
+ goto fatal_error;
+ }
+ // goto dance to avoid compiler warnings about Py_FatalError()
+ goto init_done;
+
+fatal_error:
+ // This case should never happen
+ Py_FatalError("Py_GetConstant() failed to get constants");
+ }
+
+init_done:
+ if (constant_id <= Py_CONSTANT_EMPTY_TUPLE) {
+ return Py_NewRef(constants[constant_id]);
+ }
+ else {
+ PyErr_BadInternalCall();
+ return NULL;
+ }
+}
+
+static inline PyObject* Py_GetConstantBorrowed(unsigned int constant_id)
+{
+ PyObject *obj = Py_GetConstant(constant_id);
+ Py_XDECREF(obj);
+ return obj;
+}
+#endif
+
+
+// gh-114329 added PyList_GetItemRef() to Python 3.13.0a4
+#if PY_VERSION_HEX < 0x030D00A4
+static inline PyObject *
+PyList_GetItemRef(PyObject *op, Py_ssize_t index)
+{
+ PyObject *item = PyList_GetItem(op, index);
+ Py_XINCREF(item);
+ return item;
+}
+#endif
+
+
+// gh-114329 added PyList_GetItemRef() to Python 3.13.0a4
+#if PY_VERSION_HEX < 0x030D00A4
+static inline int
+PyDict_SetDefaultRef(PyObject *d, PyObject *key, PyObject *default_value,
+ PyObject **result)
+{
+ PyObject *value;
+ if (PyDict_GetItemRef(d, key, &value) < 0) {
+ // get error
+ if (result) {
+ *result = NULL;
+ }
+ return -1;
+ }
+ if (value != NULL) {
+ // present
+ if (result) {
+ *result = value;
+ }
+ else {
+ Py_DECREF(value);
+ }
+ return 1;
+ }
+
+ // missing: set the item
+ if (PyDict_SetItem(d, key, default_value) < 0) {
+ // set error
+ if (result) {
+ *result = NULL;
+ }
+ return -1;
+ }
+ if (result) {
+ *result = Py_NewRef(default_value);
+ }
+ return 0;
+}
+#endif
+
+
+// gh-116560 added PyLong_GetSign() to Python 3.14.0a0
+#if PY_VERSION_HEX < 0x030E00A0
+static inline int PyLong_GetSign(PyObject *obj, int *sign)
+{
+ if (!PyLong_Check(obj)) {
+ PyErr_Format(PyExc_TypeError, "expect int, got %s", Py_TYPE(obj)->tp_name);
+ return -1;
+ }
+
+ *sign = _PyLong_Sign(obj);
+ return 0;
+}
+#endif
+
+
+#ifdef __cplusplus
+}
+#endif
+#endif // PYTHONCAPI_COMPAT
diff --git a/src_c/joystick.c b/src_c/joystick.c
index 047e8b7eff..806dfd0b77 100644
--- a/src_c/joystick.c
+++ b/src_c/joystick.c
@@ -41,7 +41,7 @@ init(PyObject *self, PyObject *_null)
if (!SDL_WasInit(SDL_INIT_JOYSTICK)) {
if (SDL_InitSubSystem(SDL_INIT_JOYSTICK))
return RAISE(pgExc_SDLError, SDL_GetError());
- SDL_JoystickEventState(SDL_ENABLE);
+ PG_SetJoystickEventsEnabled(SDL_TRUE);
}
Py_RETURN_NONE;
}
@@ -60,7 +60,7 @@ quit(PyObject *self, PyObject *_null)
}
if (SDL_WasInit(SDL_INIT_JOYSTICK)) {
- SDL_JoystickEventState(SDL_ENABLE);
+ PG_SetJoystickEventsEnabled(SDL_TRUE);
SDL_QuitSubSystem(SDL_INIT_JOYSTICK);
}
Py_RETURN_NONE;
diff --git a/src_c/mask.c b/src_c/mask.c
index 052322a9e1..020291edc2 100644
--- a/src_c/mask.c
+++ b/src_c/mask.c
@@ -43,6 +43,24 @@
#define M_PI 3.14159265358979323846
#endif
+/* Pretty good idea from Tom Duff :-). */
+#ifndef LOOP_UNROLLED4
+#define LOOP_UNROLLED4(code, n, width) \
+ n = (width + 3) / 4; \
+ switch (width & 3) { \
+ case 0: \
+ do { \
+ code; \
+ case 3: \
+ code; \
+ case 2: \
+ code; \
+ case 1: \
+ code; \
+ } while (--n > 0); \
+ }
+#endif
+
/* Macro to create mask objects. This will call the type's tp_new and tp_init.
* Params:
* w: width of mask
@@ -770,22 +788,61 @@ set_pixel_color(Uint8 *pixel, Uint8 bpp, Uint32 color)
static void
set_from_threshold(SDL_Surface *surf, bitmask_t *bitmask, int threshold)
{
- SDL_PixelFormat *format = surf->format;
- Uint8 bpp = PG_FORMAT_BytesPerPixel(format);
- Uint8 *pixel = NULL;
- Uint8 rgba[4];
- int x, y;
-
- for (y = 0; y < surf->h; ++y) {
- pixel = (Uint8 *)surf->pixels + y * surf->pitch;
+ /* This function expects surf to be non-zero sized. */
+ SDL_PixelFormat *fmt = surf->format;
+ const Uint8 bpp = PG_FORMAT_BytesPerPixel(fmt);
+ int x, y, n;
+ Uint8 *srcp;
+ const int src_skip = surf->pitch - surf->w * bpp;
+
+ if (threshold >= 255) {
+ return;
+ }
- for (x = 0; x < surf->w; ++x, pixel += bpp) {
- SDL_GetRGBA(get_pixel_color(pixel, bpp), format, rgba, rgba + 1,
- rgba + 2, rgba + 3);
- if (rgba[3] > threshold) {
- bitmask_setbit(bitmask, x, y);
+ if (threshold < 0 || !SDL_ISPIXELFORMAT_ALPHA(fmt->format)) {
+ bitmask_fill(bitmask);
+ return;
+ }
+ else if (bpp < 3) {
+ Uint8 r, g, b, a;
+ srcp = (Uint8 *)surf->pixels;
+ for (y = 0; y < surf->h; ++y) {
+ for (x = 0; x < surf->w; ++x, srcp += bpp) {
+ SDL_GetRGBA(bpp == 1 ? *srcp : *((Uint16 *)srcp), fmt, &r, &g,
+ &b, &a);
+ if (a > threshold)
+ bitmask_setbit(bitmask, x, y);
}
+ srcp += src_skip;
}
+ return;
+ }
+
+ /* With this strategy we avoid to get the rgb channels that we don't need
+ * and instead we just jump from alpha channel to alpha channel, comparing
+ * it with the threshold. */
+
+#if SDL_BYTEORDER == SDL_LIL_ENDIAN
+ const char _a_off = fmt->Ashift >> 3;
+#else
+ const char _a_off = 3 - (fmt->Ashift >> 3);
+#endif
+
+ srcp = (Uint8 *)surf->pixels + _a_off;
+ const Uint8 u_threshold = (Uint8)threshold;
+
+ for (y = 0; y < surf->h; ++y) {
+ x = 0;
+ LOOP_UNROLLED4(
+ {
+ if ((*srcp) > u_threshold)
+ bitmask_setbit(bitmask, x, y);
+ srcp += bpp;
+ x++;
+ },
+ n, surf->w);
+
+ srcp += src_skip;
}
}
diff --git a/src_c/math.c b/src_c/math.c
index 8ed8ccae5e..51c5fd3641 100644
--- a/src_c/math.c
+++ b/src_c/math.c
@@ -4192,13 +4192,13 @@ vector_elementwise(pgVector *vec, PyObject *_null)
return (PyObject *)proxy;
}
-inline double
+static inline double
lerp(double a, double b, double v)
{
return a + (b - a) * v;
}
-inline double
+static inline double
invlerp(double a, double b, double v)
{
return (v - a) / (b - a);
diff --git a/src_c/mixer.c b/src_c/mixer.c
index 9bd29aa63a..f342e18216 100644
--- a/src_c/mixer.c
+++ b/src_c/mixer.c
@@ -1583,6 +1583,12 @@ static PyObject *
mixer_get_sdl_mixer_version(PyObject *self, PyObject *args, PyObject *kwargs)
{
int linked = 1; /* Default is linked version. */
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+ int version = SDL_MIXER_VERSION;
+#else
+ SDL_version version;
+ SDL_MIXER_VERSION(&version);
+#endif
static char *keywords[] = {"linked", NULL};
@@ -1593,16 +1599,16 @@ mixer_get_sdl_mixer_version(PyObject *self, PyObject *args, PyObject *kwargs)
/* MIXER_INIT_CHECK() is not required for these methods. */
if (linked) {
- /* linked version */
- const SDL_version *v = Mix_Linked_Version();
- return Py_BuildValue("iii", v->major, v->minor, v->patch);
- }
- else {
- /* compiled version */
- SDL_version v;
- SDL_MIXER_VERSION(&v);
- return Py_BuildValue("iii", v.major, v.minor, v.patch);
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+ version = Mix_Version();
+#else
+ version = *Mix_Linked_Version();
+#endif
}
+
+ return Py_BuildValue("iii", PG_FIND_VNUM_MAJOR(version),
+ PG_FIND_VNUM_MINOR(version),
+ PG_FIND_VNUM_MICRO(version));
}
static int
diff --git a/src_c/pixelarray_methods.c b/src_c/pixelarray_methods.c
index dac65170da..235a5b4b64 100644
--- a/src_c/pixelarray_methods.c
+++ b/src_c/pixelarray_methods.c
@@ -125,17 +125,22 @@ _make_surface(pgPixelArrayObject *array, PyObject *args)
surf = pgSurface_AsSurface(array->surface);
bpp = PG_SURF_BytesPerPixel(surf);
-
- /* Create the second surface. */
-
- temp_surf = PG_CreateSurface((int)dim0, (int)dim1, surf->format->format);
- if (!temp_surf) {
- return RAISE(pgExc_SDLError, SDL_GetError());
+ temp_surf = surf;
+ const int same_dims = (dim0 == surf->w && dim1 == surf->h);
+
+ /* If the array dimensions are different from the surface dimensions,
+ * create a new surface with the array dimensions */
+ if (!same_dims) {
+ if (!(temp_surf = PG_CreateSurface((int)dim0, (int)dim1,
+ surf->format->format)))
+ return RAISE(pgExc_SDLError, SDL_GetError());
}
- /* Guarantee an identical format. */
+ /* Ensure the new surface has the same format as the original */
new_surf = PG_ConvertSurface(temp_surf, surf->format);
- SDL_FreeSurface(temp_surf);
+ if (temp_surf != surf)
+ SDL_FreeSurface(temp_surf);
+
if (!new_surf) {
return RAISE(pgExc_SDLError, SDL_GetError());
}
@@ -146,6 +151,10 @@ _make_surface(pgPixelArrayObject *array, PyObject *args)
return 0;
}
+ /* if the surf and array dims match just return a copy */
+ if (same_dims)
+ return (PyObject *)new_surface;
+
/* Acquire a temporary lock. */
if (SDL_MUSTLOCK(new_surf) == 0) {
SDL_LockSurface(new_surf);
@@ -158,58 +167,71 @@ _make_surface(pgPixelArrayObject *array, PyObject *args)
new_pixelrow = new_pixels;
Py_BEGIN_ALLOW_THREADS;
- switch (bpp) {
- case 1:
- for (y = 0; y < dim1; ++y) {
- pixel_p = pixelrow;
- new_pixel_p = new_pixelrow;
- for (x = 0; x < dim0; ++x) {
- *new_pixel_p = *pixel_p;
- pixel_p += stride0;
- new_pixel_p += new_stride0;
+
+ if (stride0 == new_stride0) {
+ /* if src and dest have the same bpp, so we can copy the whole
+ * rows at once */
+ y = dim1;
+ while (y--) {
+ memcpy(new_pixelrow, pixelrow, stride0 * dim0);
+ pixelrow += stride1;
+ new_pixelrow += new_stride1;
+ }
+ }
+ else {
+ switch (bpp) {
+ case 1:
+ for (y = 0; y < dim1; ++y) {
+ pixel_p = pixelrow;
+ new_pixel_p = new_pixelrow;
+ for (x = 0; x < dim0; ++x) {
+ *new_pixel_p = *pixel_p;
+ pixel_p += stride0;
+ new_pixel_p += new_stride0;
+ }
+ pixelrow += stride1;
+ new_pixelrow += new_stride1;
}
- pixelrow += stride1;
- new_pixelrow += new_stride1;
- }
- break;
- case 2:
- for (y = 0; y < dim1; ++y) {
- pixel_p = pixelrow;
- new_pixel_p = new_pixelrow;
- for (x = 0; x < dim0; ++x) {
- *((Uint16 *)new_pixel_p) = *((Uint16 *)pixel_p);
- pixel_p += stride0;
- new_pixel_p += new_stride0;
+ break;
+ case 2:
+ for (y = 0; y < dim1; ++y) {
+ pixel_p = pixelrow;
+ new_pixel_p = new_pixelrow;
+ for (x = 0; x < dim0; ++x) {
+ *((Uint16 *)new_pixel_p) = *((Uint16 *)pixel_p);
+ pixel_p += stride0;
+ new_pixel_p += new_stride0;
+ }
+ pixelrow += stride1;
+ new_pixelrow += new_stride1;
}
- pixelrow += stride1;
- new_pixelrow += new_stride1;
- }
- break;
- case 3:
- for (y = 0; y < dim1; ++y) {
- pixel_p = pixelrow;
- new_pixel_p = new_pixelrow;
- for (x = 0; x < dim0; ++x) {
- memcpy(new_pixel_p, pixel_p, 3);
- pixel_p += stride0;
- new_pixel_p += new_stride0;
+ break;
+ case 3:
+ for (y = 0; y < dim1; ++y) {
+ pixel_p = pixelrow;
+ new_pixel_p = new_pixelrow;
+ for (x = 0; x < dim0; ++x) {
+ memcpy(new_pixel_p, pixel_p, 3);
+ pixel_p += stride0;
+ new_pixel_p += new_stride0;
+ }
+ pixelrow += stride1;
+ new_pixelrow += new_stride1;
}
- pixelrow += stride1;
- new_pixelrow += new_stride1;
- }
- break;
- default: /* case: 4 */
- for (y = 0; y < dim1; ++y) {
- pixel_p = pixelrow;
- new_pixel_p = new_pixelrow;
- for (x = 0; x < dim0; ++x) {
- *((Uint32 *)new_pixel_p) = *((Uint32 *)pixel_p);
- pixel_p += stride0;
- new_pixel_p += new_stride0;
+ break;
+ default: /* case: 4 */
+ for (y = 0; y < dim1; ++y) {
+ pixel_p = pixelrow;
+ new_pixel_p = new_pixelrow;
+ for (x = 0; x < dim0; ++x) {
+ *((Uint32 *)new_pixel_p) = *((Uint32 *)pixel_p);
+ pixel_p += stride0;
+ new_pixel_p += new_stride0;
+ }
+ pixelrow += stride1;
+ new_pixelrow += new_stride1;
}
- pixelrow += stride1;
- new_pixelrow += new_stride1;
- }
+ }
}
Py_END_ALLOW_THREADS;
diff --git a/src_c/rect.c b/src_c/rect.c
index b300c2f97f..f3e22f69d8 100644
--- a/src_c/rect.c
+++ b/src_c/rect.c
@@ -138,6 +138,7 @@ four_floats_from_obj(PyObject *obj, float *val1, float *val2, float *val3,
#define RectImport_primitiveType int
#define RectImport_RectCheck pgRect_Check
#define RectImport_OtherRectCheck pgFRect_Check
+#define RectImport_OtherRectCheckExact pgFRect_CheckExact
#define RectImport_RectCheckExact pgRect_CheckExact
#define RectImport_innerRectStruct SDL_Rect
#define RectImport_otherInnerRectStruct SDL_FRect
@@ -151,6 +152,7 @@ four_floats_from_obj(PyObject *obj, float *val1, float *val2, float *val3,
#define RectImport_TypeObject pgRect_Type
#define RectImport_IntersectRectAndLine SDL_IntersectRectAndLine
#define RectImport_PyBuildValueFormat "i"
+#define RectImport_TupleFromTwoPrimitives pg_tuple_couple_from_values_int
#define RectImport_ObjectName "pygame.rect.Rect"
#define RectImport_PythonNumberCheck PyLong_Check
#define RectImport_PythonNumberAsPrimitiveType PyLong_AsLong
@@ -253,6 +255,7 @@ four_floats_from_obj(PyObject *obj, float *val1, float *val2, float *val3,
#define RectImport_primitiveType float
#define RectImport_RectCheck pgFRect_Check
#define RectImport_OtherRectCheck pgRect_Check
+#define RectImport_OtherRectCheckExact pgRect_CheckExact
#define RectImport_RectCheckExact pgFRect_CheckExact
#define RectImport_innerRectStruct SDL_FRect
#define RectImport_otherInnerRectStruct SDL_Rect
@@ -266,6 +269,7 @@ four_floats_from_obj(PyObject *obj, float *val1, float *val2, float *val3,
#define RectImport_IntersectRectAndLine PG_IntersectFRectAndLine
#define RectImport_TypeObject pgFRect_Type
#define RectImport_PyBuildValueFormat "f"
+#define RectImport_TupleFromTwoPrimitives pg_tuple_couple_from_values_double
#define RectImport_ObjectName "pygame.rect.FRect"
#define RectImport_PythonNumberCheck PyFloat_Check
#define RectImport_PythonNumberAsPrimitiveType PyFloat_AsDouble
@@ -611,11 +615,11 @@ pg_rect_repr(pgRectObject *self)
static PyObject *
pg_frect_repr(pgFRectObject *self)
{
- char str[64];
+ char str[256];
- int ret = PyOS_snprintf(str, 64, "FRect(%f, %f, %f, %f)", self->r.x,
+ int ret = PyOS_snprintf(str, 256, "FRect(%f, %f, %f, %f)", self->r.x,
self->r.y, self->r.w, self->r.h);
- if (ret < 0 || ret >= 64) {
+ if (ret < 0 || ret >= 256) {
return RAISE(PyExc_RuntimeError,
"Internal PyOS_snprintf call failed!");
}
diff --git a/src_c/rect_impl.h b/src_c/rect_impl.h
index 0187f07768..ee4f7389d8 100644
--- a/src_c/rect_impl.h
+++ b/src_c/rect_impl.h
@@ -320,6 +320,9 @@
#ifndef RectImport_RectCheckExact
#error RectImport_RectCheckExact needs to be Defined
#endif
+#ifndef RectImport_OtherRectCheckExact
+#error RectImport_OtherRectCheckExact needs to be Defined
+#endif
#ifndef RectImport_primitiveType
#error RectImport_primitiveType needs to be defined
#endif
@@ -356,6 +359,9 @@
#ifndef RectImport_PyBuildValueFormat
#error RectImport_PyBuildValueFormat needs to be defined
#endif
+#ifndef RectImport_TupleFromTwoPrimitives
+#error RectImport_TupleFromTwoPrimitives needs to be defined
+#endif
// #endregion
// #region RectOptional
@@ -396,6 +402,7 @@
#define fourPrimivitesFromObj RectImport_fourPrimiviteFromObj
#define PrimitiveFromObj RectImport_PrimitiveFromObj
#define TypeFMT RectImport_PyBuildValueFormat
+#define TupleFromTwoPrimitives RectImport_TupleFromTwoPrimitives
#define ObjectName RectImport_ObjectName
#define PythonNumberCheck RectImport_PythonNumberCheck
#define PythonNumberAsPrimitiveType RectImport_PythonNumberAsPrimitiveType
@@ -424,7 +431,7 @@ RectExport_do_rects_intresect(InnerRect *A, InnerRect *B)
#define _pg_do_rects_intersect RectExport_do_rects_intresect
-static InnerRect *
+static PG_INLINE InnerRect *
RectExport_RectFromObject(PyObject *obj, InnerRect *temp);
static InnerRect *
RectExport_RectFromFastcallArgs(PyObject *const *args, Py_ssize_t nargs,
@@ -611,16 +618,16 @@ static RectObject
int RectOptional_Freelist_Num = -1;
#endif
-static InnerRect *
+static PG_INLINE InnerRect *
RectExport_RectFromObject(PyObject *obj, InnerRect *temp)
{
Py_ssize_t length;
- if (RectCheck(obj)) {
+ /* fast path for exact Rect / FRect class */
+ if (RectImport_RectCheckExact(obj)) {
return &((RectObject *)obj)->r;
}
-
- if (OtherRectCheck(obj)) {
+ if (RectImport_OtherRectCheckExact(obj)) {
OtherInnerRect rect = ((OtherRectObject *)obj)->r;
temp->x = (PrimitiveType)rect.x;
temp->y = (PrimitiveType)rect.y;
@@ -629,6 +636,7 @@ RectExport_RectFromObject(PyObject *obj, InnerRect *temp)
return temp;
}
+ /* fast check for sequences */
if (pgSequenceFast_Check(obj)) {
length = PySequence_Fast_GET_SIZE(obj);
PyObject **items = PySequence_Fast_ITEMS(obj);
@@ -721,7 +729,20 @@ RectExport_RectFromObject(PyObject *obj, InnerRect *temp)
}
}
- /* Try to get the rect attribute */
+ /* path for possible subclasses (these are very slow checks) */
+ if (RectImport_RectCheck(obj)) {
+ return &((RectObject *)obj)->r;
+ }
+ if (RectImport_OtherRectCheck(obj)) {
+ OtherInnerRect rect = ((OtherRectObject *)obj)->r;
+ temp->x = (PrimitiveType)rect.x;
+ temp->y = (PrimitiveType)rect.y;
+ temp->w = (PrimitiveType)rect.w;
+ temp->h = (PrimitiveType)rect.h;
+ return temp;
+ }
+
+ /* path to get the 'rect' attribute if present */
PyObject *rectattr;
if (!(rectattr = PyObject_GetAttrString(obj, "rect"))) {
PyErr_Clear();
@@ -731,7 +752,7 @@ RectExport_RectFromObject(PyObject *obj, InnerRect *temp)
InnerRect *returnrect;
/*call if it's a method*/
if (PyCallable_Check(rectattr)) {
- PyObject *rectresult = PyObject_CallObject(rectattr, NULL);
+ PyObject *rectresult = PyObject_CallNoArgs(rectattr);
Py_DECREF(rectattr);
if (rectresult == NULL) {
PyErr_Clear();
@@ -1508,8 +1529,8 @@ RectExport_RectFromObjectAndKeyFunc(PyObject *obj, PyObject *keyfunc,
InnerRect *temp)
{
if (keyfunc) {
- PyObject *obj_with_rect =
- PyObject_CallFunctionObjArgs(keyfunc, obj, NULL);
+ PyObject *obj_with_rect = PyObject_Vectorcall(keyfunc, &obj, 1, NULL);
+
if (!obj_with_rect) {
return NULL;
}
@@ -1898,8 +1919,29 @@ RectExport_clipline(RectObject *self, PyObject *const *args, Py_ssize_t nargs)
}
Py_XDECREF(rect_copy);
- return Py_BuildValue("((" TypeFMT "" TypeFMT ")(" TypeFMT "" TypeFMT "))",
- x1, y1, x2, y2);
+
+ PyObject *subtup1, *subtup2;
+ subtup1 = TupleFromTwoPrimitives(x1, y1);
+ if (!subtup1)
+ return NULL;
+
+ subtup2 = TupleFromTwoPrimitives(x2, y2);
+ if (!subtup2) {
+ Py_DECREF(subtup1);
+ return NULL;
+ }
+
+ PyObject *tup = PyTuple_New(2);
+ if (!tup) {
+ Py_DECREF(subtup1);
+ Py_DECREF(subtup2);
+ return NULL;
+ }
+
+ PyTuple_SET_ITEM(tup, 0, subtup1);
+ PyTuple_SET_ITEM(tup, 1, subtup2);
+
+ return tup;
}
static int
@@ -2540,7 +2582,7 @@ RectExport_setcentery(RectObject *self, PyObject *value, void *closure)
static PyObject *
RectExport_gettopleft(RectObject *self, void *closure)
{
- return Py_BuildValue("(" TypeFMT "" TypeFMT ")", self->r.x, self->r.y);
+ return TupleFromTwoPrimitives(self->r.x, self->r.y);
}
static int
@@ -2567,8 +2609,7 @@ RectExport_settopleft(RectObject *self, PyObject *value, void *closure)
static PyObject *
RectExport_gettopright(RectObject *self, void *closure)
{
- return Py_BuildValue("(" TypeFMT "" TypeFMT ")", self->r.x + self->r.w,
- self->r.y);
+ return TupleFromTwoPrimitives(self->r.x + self->r.w, self->r.y);
}
static int
@@ -2595,8 +2636,7 @@ RectExport_settopright(RectObject *self, PyObject *value, void *closure)
static PyObject *
RectExport_getbottomleft(RectObject *self, void *closure)
{
- return Py_BuildValue("(" TypeFMT "" TypeFMT ")", self->r.x,
- self->r.y + self->r.h);
+ return TupleFromTwoPrimitives(self->r.x, self->r.y + self->r.h);
}
static int
@@ -2623,8 +2663,8 @@ RectExport_setbottomleft(RectObject *self, PyObject *value, void *closure)
static PyObject *
RectExport_getbottomright(RectObject *self, void *closure)
{
- return Py_BuildValue("(" TypeFMT "" TypeFMT ")", self->r.x + self->r.w,
- self->r.y + self->r.h);
+ return TupleFromTwoPrimitives(self->r.x + self->r.w,
+ self->r.y + self->r.h);
}
static int
@@ -2651,8 +2691,7 @@ RectExport_setbottomright(RectObject *self, PyObject *value, void *closure)
static PyObject *
RectExport_getmidtop(RectObject *self, void *closure)
{
- return Py_BuildValue("(" TypeFMT "" TypeFMT ")",
- self->r.x + (self->r.w / 2), self->r.y);
+ return TupleFromTwoPrimitives(self->r.x + (self->r.w / 2), self->r.y);
}
static int
@@ -2679,8 +2718,7 @@ RectExport_setmidtop(RectObject *self, PyObject *value, void *closure)
static PyObject *
RectExport_getmidleft(RectObject *self, void *closure)
{
- return Py_BuildValue("(" TypeFMT "" TypeFMT ")", self->r.x,
- self->r.y + (self->r.h / 2));
+ return TupleFromTwoPrimitives(self->r.x, self->r.y + (self->r.h / 2));
}
static int
@@ -2707,8 +2745,8 @@ RectExport_setmidleft(RectObject *self, PyObject *value, void *closure)
static PyObject *
RectExport_getmidbottom(RectObject *self, void *closure)
{
- return Py_BuildValue("(" TypeFMT "" TypeFMT ")",
- self->r.x + (self->r.w / 2), self->r.y + self->r.h);
+ return TupleFromTwoPrimitives(self->r.x + (self->r.w / 2),
+ self->r.y + self->r.h);
}
static int
@@ -2735,8 +2773,8 @@ RectExport_setmidbottom(RectObject *self, PyObject *value, void *closure)
static PyObject *
RectExport_getmidright(RectObject *self, void *closure)
{
- return Py_BuildValue("(" TypeFMT "" TypeFMT ")", self->r.x + self->r.w,
- self->r.y + (self->r.h / 2));
+ return TupleFromTwoPrimitives(self->r.x + self->r.w,
+ self->r.y + (self->r.h / 2));
}
static int
@@ -2763,9 +2801,8 @@ RectExport_setmidright(RectObject *self, PyObject *value, void *closure)
static PyObject *
RectExport_getcenter(RectObject *self, void *closure)
{
- return Py_BuildValue("(" TypeFMT "" TypeFMT ")",
- self->r.x + (self->r.w / 2),
- self->r.y + (self->r.h / 2));
+ return TupleFromTwoPrimitives(self->r.x + (self->r.w / 2),
+ self->r.y + (self->r.h / 2));
}
static int
@@ -2792,7 +2829,7 @@ RectExport_setcenter(RectObject *self, PyObject *value, void *closure)
static PyObject *
RectExport_getsize(RectObject *self, void *closure)
{
- return Py_BuildValue("(" TypeFMT "" TypeFMT ")", self->r.w, self->r.h);
+ return TupleFromTwoPrimitives(self->r.w, self->r.h);
}
static int
@@ -2933,6 +2970,7 @@ RectExport_iterator(RectObject *self)
#undef RectImport_RectCheck
#undef RectImport_OtherRectCheck
#undef RectImport_RectCheckExact
+#undef RectImport_OtherRectCheckExact
#undef RectImport_innerRectStruct
#undef RectImport_otherInnerRectStruct
#undef RectImport_innerPointStruct
@@ -2946,6 +2984,7 @@ RectExport_iterator(RectObject *self)
#undef RectImport_TypeObject
#undef RectImport_PrimitiveFromObj
#undef RectImport_PyBuildValueFormat
+#undef RectImport_TupleFromTwoPrimitives
#undef RectImport_ObjectName
#undef PrimitiveType
diff --git a/src_c/rwobject.c b/src_c/rwobject.c
index a76de8e806..35b2d0353e 100644
--- a/src_c/rwobject.c
+++ b/src_c/rwobject.c
@@ -341,7 +341,7 @@ _pg_rw_size(SDL_RWops *context)
/* Current file position; need to restore it later.
*/
- pos = PyObject_CallFunction(helper->tell, NULL);
+ pos = PyObject_CallNoArgs(helper->tell);
if (!pos) {
PyErr_Print();
goto end;
@@ -358,7 +358,7 @@ _pg_rw_size(SDL_RWops *context)
/* Record file size.
*/
- tmp = PyObject_CallFunction(helper->tell, NULL);
+ tmp = PyObject_CallNoArgs(helper->tell);
if (!tmp) {
PyErr_Print();
goto end;
@@ -449,7 +449,7 @@ _pg_rw_close(SDL_RWops *context)
PyGILState_STATE state = PyGILState_Ensure();
if (helper->close) {
- result = PyObject_CallFunction(helper->close, NULL);
+ result = PyObject_CallNoArgs(helper->close);
if (!result) {
PyErr_Print();
retval = -1;
@@ -571,7 +571,7 @@ _pg_rw_seek(SDL_RWops *context, Sint64 offset, int whence)
Py_DECREF(result);
}
- result = PyObject_CallFunction(helper->tell, NULL);
+ result = PyObject_CallNoArgs(helper->tell);
if (!result) {
PyErr_Print();
retval = -1;
diff --git a/src_c/scale2x.c b/src_c/scale2x.c
index a60de524f4..61a9beffa5 100644
--- a/src_c/scale2x.c
+++ b/src_c/scale2x.c
@@ -34,13 +34,19 @@
#define MAX(a, b) (((a) > (b)) ? (a) : (b))
#define MIN(a, b) (((a) < (b)) ? (a) : (b))
-#define READINT24(x) ((x)[0] << 16 | (x)[1] << 8 | (x)[2])
-#define WRITEINT24(x, i) \
- { \
- (x)[0] = i >> 16; \
- (x)[1] = (i >> 8) & 0xff; \
- x[2] = i & 0xff; \
- }
+static inline int
+read_int24(const Uint8 *x)
+{
+ return (x[0] << 16 | x[1] << 8 | x[2]);
+}
+
+static inline void
+store_int24(Uint8 *x, int i)
+{
+ x[0] = i >> 16;
+ x[1] = (i >> 8) & 0xff;
+ x[2] = i & 0xff;
+}
/*
this requires a destination surface already setup to be twice as
@@ -62,38 +68,47 @@ scale2x(SDL_Surface *src, SDL_Surface *dst)
const int height = src->h;
#if SDL_VERSION_ATLEAST(3, 0, 0)
- switch (src->format->bytes_per_pixel) {
+ const Uint8 Bpp = src->format->bytes_per_pixel;
#else
- switch (src->format->BytesPerPixel) {
+ const Uint8 Bpp = src->format->BytesPerPixel;
#endif
+
+ switch (Bpp) {
case 1: {
Uint8 E0, E1, E2, E3, B, D, E, F, H;
for (looph = 0; looph < height; ++looph) {
+ Uint8 *src_row = srcpix + looph * srcpitch;
+ Uint8 *dst_row0 = dstpix + looph * 2 * dstpitch;
+ Uint8 *dst_row1 = dstpix + (looph * 2 + 1) * dstpitch;
+
+ Uint8 *src_row_prev = srcpix + MAX(0, looph - 1) * srcpitch;
+ Uint8 *src_row_next =
+ srcpix + MIN(height - 1, looph + 1) * srcpitch;
+
for (loopw = 0; loopw < width; ++loopw) {
- B = *(Uint8 *)(srcpix + (MAX(0, looph - 1) * srcpitch) +
- (1 * loopw));
- D = *(Uint8 *)(srcpix + (looph * srcpitch) +
- (1 * MAX(0, loopw - 1)));
- E = *(Uint8 *)(srcpix + (looph * srcpitch) + (1 * loopw));
- F = *(Uint8 *)(srcpix + (looph * srcpitch) +
- (1 * MIN(width - 1, loopw + 1)));
- H = *(Uint8 *)(srcpix +
- (MIN(height - 1, looph + 1) * srcpitch) +
- (1 * loopw));
-
- E0 = D == B && B != F && D != H ? D : E;
- E1 = B == F && B != D && F != H ? F : E;
- E2 = D == H && D != B && H != F ? D : E;
- E3 = H == F && D != H && B != F ? F : E;
-
- *(Uint8 *)(dstpix + looph * 2 * dstpitch + loopw * 2 * 1) =
- E0;
- *(Uint8 *)(dstpix + looph * 2 * dstpitch +
- (loopw * 2 + 1) * 1) = E1;
- *(Uint8 *)(dstpix + (looph * 2 + 1) * dstpitch +
- loopw * 2 * 1) = E2;
- *(Uint8 *)(dstpix + (looph * 2 + 1) * dstpitch +
- (loopw * 2 + 1) * 1) = E3;
+ B = *(Uint8 *)(src_row_prev + loopw);
+ D = *(Uint8 *)(src_row + MAX(0, loopw - 1));
+ E = *(Uint8 *)(src_row + loopw);
+ F = *(Uint8 *)(src_row + MIN(width - 1, loopw + 1));
+ H = *(Uint8 *)(src_row_next + loopw);
+
+ if (B != H && D != F) {
+ E0 = (D == B) ? D : E;
+ E1 = (B == F) ? F : E;
+ E2 = (D == H) ? D : E;
+ E3 = (H == F) ? F : E;
+ }
+ else {
+ E0 = E;
+ E1 = E;
+ E2 = E;
+ E3 = E;
+ }
+
+ *(Uint8 *)(dst_row0 + loopw * 2) = E0;
+ *(Uint8 *)(dst_row0 + loopw * 2 + 1) = E1;
+ *(Uint8 *)(dst_row1 + loopw * 2) = E2;
+ *(Uint8 *)(dst_row1 + loopw * 2 + 1) = E3;
}
}
break;
@@ -101,31 +116,38 @@ scale2x(SDL_Surface *src, SDL_Surface *dst)
case 2: {
Uint16 E0, E1, E2, E3, B, D, E, F, H;
for (looph = 0; looph < height; ++looph) {
+ Uint8 *src_row = srcpix + looph * srcpitch;
+ Uint8 *dst_row0 = dstpix + looph * 2 * dstpitch;
+ Uint8 *dst_row1 = dstpix + (looph * 2 + 1) * dstpitch;
+
+ Uint8 *src_row_prev = srcpix + MAX(0, looph - 1) * srcpitch;
+ Uint8 *src_row_next =
+ srcpix + MIN(height - 1, looph + 1) * srcpitch;
+
for (loopw = 0; loopw < width; ++loopw) {
- B = *(Uint16 *)(srcpix + (MAX(0, looph - 1) * srcpitch) +
- (2 * loopw));
- D = *(Uint16 *)(srcpix + (looph * srcpitch) +
- (2 * MAX(0, loopw - 1)));
- E = *(Uint16 *)(srcpix + (looph * srcpitch) + (2 * loopw));
- F = *(Uint16 *)(srcpix + (looph * srcpitch) +
- (2 * MIN(width - 1, loopw + 1)));
- H = *(Uint16 *)(srcpix +
- (MIN(height - 1, looph + 1) * srcpitch) +
- (2 * loopw));
-
- E0 = D == B && B != F && D != H ? D : E;
- E1 = B == F && B != D && F != H ? F : E;
- E2 = D == H && D != B && H != F ? D : E;
- E3 = H == F && D != H && B != F ? F : E;
-
- *(Uint16 *)(dstpix + looph * 2 * dstpitch +
- loopw * 2 * 2) = E0;
- *(Uint16 *)(dstpix + looph * 2 * dstpitch +
- (loopw * 2 + 1) * 2) = E1;
- *(Uint16 *)(dstpix + (looph * 2 + 1) * dstpitch +
- loopw * 2 * 2) = E2;
- *(Uint16 *)(dstpix + (looph * 2 + 1) * dstpitch +
- (loopw * 2 + 1) * 2) = E3;
+ B = *(Uint16 *)(src_row_prev + 2 * loopw);
+ D = *(Uint16 *)(src_row + 2 * MAX(0, loopw - 1));
+ E = *(Uint16 *)(src_row + 2 * loopw);
+ F = *(Uint16 *)(src_row + 2 * MIN(width - 1, loopw + 1));
+ H = *(Uint16 *)(src_row_next + 2 * loopw);
+
+ if (B != H && D != F) {
+ E0 = (D == B) ? D : E;
+ E1 = (B == F) ? F : E;
+ E2 = (D == H) ? D : E;
+ E3 = (H == F) ? F : E;
+ }
+ else {
+ E0 = E;
+ E1 = E;
+ E2 = E;
+ E3 = E;
+ }
+
+ *(Uint16 *)(dst_row0 + loopw * 2 * 2) = E0;
+ *(Uint16 *)(dst_row0 + (loopw * 2 + 1) * 2) = E1;
+ *(Uint16 *)(dst_row1 + loopw * 2 * 2) = E2;
+ *(Uint16 *)(dst_row1 + (loopw * 2 + 1) * 2) = E3;
}
}
break;
@@ -133,66 +155,78 @@ scale2x(SDL_Surface *src, SDL_Surface *dst)
case 3: {
int E0, E1, E2, E3, B, D, E, F, H;
for (looph = 0; looph < height; ++looph) {
+ Uint8 *src_row = srcpix + looph * srcpitch;
+ Uint8 *dst_row0 = dstpix + looph * 2 * dstpitch;
+ Uint8 *dst_row1 = dstpix + (looph * 2 + 1) * dstpitch;
+
+ Uint8 *src_row_prev = srcpix + MAX(0, looph - 1) * srcpitch;
+ Uint8 *src_row_next =
+ srcpix + MIN(height - 1, looph + 1) * srcpitch;
+
for (loopw = 0; loopw < width; ++loopw) {
- B = READINT24(srcpix + (MAX(0, looph - 1) * srcpitch) +
- (3 * loopw));
- D = READINT24(srcpix + (looph * srcpitch) +
- (3 * MAX(0, loopw - 1)));
- E = READINT24(srcpix + (looph * srcpitch) + (3 * loopw));
- F = READINT24(srcpix + (looph * srcpitch) +
- (3 * MIN(width - 1, loopw + 1)));
- H = READINT24(srcpix +
- (MIN(height - 1, looph + 1) * srcpitch) +
- (3 * loopw));
-
- E0 = D == B && B != F && D != H ? D : E;
- E1 = B == F && B != D && F != H ? F : E;
- E2 = D == H && D != B && H != F ? D : E;
- E3 = H == F && D != H && B != F ? F : E;
-
- WRITEINT24((dstpix + looph * 2 * dstpitch + loopw * 2 * 3),
- E0);
- WRITEINT24(
- (dstpix + looph * 2 * dstpitch + (loopw * 2 + 1) * 3),
- E1);
- WRITEINT24(
- (dstpix + (looph * 2 + 1) * dstpitch + loopw * 2 * 3),
- E2);
- WRITEINT24((dstpix + (looph * 2 + 1) * dstpitch +
- (loopw * 2 + 1) * 3),
- E3);
+ B = read_int24(src_row_prev + (3 * loopw));
+ D = read_int24(src_row + (3 * MAX(0, loopw - 1)));
+ E = read_int24(src_row + (3 * loopw));
+ F = read_int24(src_row + (3 * MIN(width - 1, loopw + 1)));
+ H = read_int24(src_row_next + (3 * loopw));
+
+ if (B != H && D != F) {
+ E0 = (D == B) ? D : E;
+ E1 = (B == F) ? F : E;
+ E2 = (D == H) ? D : E;
+ E3 = (H == F) ? F : E;
+ }
+ else {
+ E0 = E;
+ E1 = E;
+ E2 = E;
+ E3 = E;
+ }
+
+ store_int24(dst_row0 + loopw * 2 * 3, E0);
+ store_int24(dst_row0 + (loopw * 2 + 1) * 3, E1);
+ store_int24(dst_row1 + loopw * 2 * 3, E2);
+ store_int24(dst_row1 + (loopw * 2 + 1) * 3, E3);
}
}
break;
}
- default: { /*case 4:*/
+ default: {
Uint32 E0, E1, E2, E3, B, D, E, F, H;
+
for (looph = 0; looph < height; ++looph) {
+ Uint8 *src_row = srcpix + looph * srcpitch;
+ Uint8 *dst_row0 = dstpix + looph * 2 * dstpitch;
+ Uint8 *dst_row1 = dstpix + (looph * 2 + 1) * dstpitch;
+
+ Uint8 *src_row_prev = srcpix + MAX(0, looph - 1) * srcpitch;
+ Uint8 *src_row_next =
+ srcpix + MIN(height - 1, looph + 1) * srcpitch;
+
for (loopw = 0; loopw < width; ++loopw) {
- B = *(Uint32 *)(srcpix + (MAX(0, looph - 1) * srcpitch) +
- (4 * loopw));
- D = *(Uint32 *)(srcpix + (looph * srcpitch) +
- (4 * MAX(0, loopw - 1)));
- E = *(Uint32 *)(srcpix + (looph * srcpitch) + (4 * loopw));
- F = *(Uint32 *)(srcpix + (looph * srcpitch) +
- (4 * MIN(width - 1, loopw + 1)));
- H = *(Uint32 *)(srcpix +
- (MIN(height - 1, looph + 1) * srcpitch) +
- (4 * loopw));
-
- E0 = D == B && B != F && D != H ? D : E;
- E1 = B == F && B != D && F != H ? F : E;
- E2 = D == H && D != B && H != F ? D : E;
- E3 = H == F && D != H && B != F ? F : E;
-
- *(Uint32 *)(dstpix + looph * 2 * dstpitch +
- loopw * 2 * 4) = E0;
- *(Uint32 *)(dstpix + looph * 2 * dstpitch +
- (loopw * 2 + 1) * 4) = E1;
- *(Uint32 *)(dstpix + (looph * 2 + 1) * dstpitch +
- loopw * 2 * 4) = E2;
- *(Uint32 *)(dstpix + (looph * 2 + 1) * dstpitch +
- (loopw * 2 + 1) * 4) = E3;
+ B = *(Uint32 *)(src_row_prev + 4 * loopw);
+ D = *(Uint32 *)(src_row + 4 * MAX(0, loopw - 1));
+ E = *(Uint32 *)(src_row + 4 * loopw);
+ F = *(Uint32 *)(src_row + 4 * MIN(width - 1, loopw + 1));
+ H = *(Uint32 *)(src_row_next + 4 * loopw);
+
+ if (B != H && D != F) {
+ E0 = (D == B) ? D : E;
+ E1 = (B == F) ? F : E;
+ E2 = (D == H) ? D : E;
+ E3 = (H == F) ? F : E;
+ }
+ else {
+ E0 = E;
+ E1 = E;
+ E2 = E;
+ E3 = E;
+ }
+
+ *(Uint32 *)(dst_row0 + loopw * 2 * 4) = E0;
+ *(Uint32 *)(dst_row0 + (loopw * 2 + 1) * 4) = E1;
+ *(Uint32 *)(dst_row1 + loopw * 2 * 4) = E2;
+ *(Uint32 *)(dst_row1 + (loopw * 2 + 1) * 4) = E3;
}
}
break;
diff --git a/src_c/scrap.c b/src_c/scrap.c
index e83671b845..b34cb6b06e 100644
--- a/src_c/scrap.c
+++ b/src_c/scrap.c
@@ -349,6 +349,11 @@ _scrap_lost_scrap(PyObject *self, PyObject *_null)
{
PYGAME_SCRAP_INIT_CHECK();
+ if (PyErr_WarnEx(PyExc_DeprecationWarning,
+ "pygame.scrap.lost deprecated since 2.2.0", 1) == -1) {
+ return NULL;
+ }
+
if (pygame_scrap_lost())
Py_RETURN_TRUE;
Py_RETURN_FALSE;
diff --git a/src_c/static.c b/src_c/static.c
index b7e8d1ec66..c8929de6e7 100644
--- a/src_c/static.c
+++ b/src_c/static.c
@@ -334,8 +334,6 @@ PyInit_pygame_static()
#undef pgSurface_UnlockBy
#undef pgSurface_Prep
#undef pgSurface_Unprep
-#undef pgLifetimeLock_Type
-#undef pgSurface_LockLifetime
#include "surflock.c"
diff --git a/src_c/surface.c b/src_c/surface.c
index 2c1575ee04..3f36377d22 100644
--- a/src_c/surface.c
+++ b/src_c/surface.c
@@ -194,6 +194,8 @@ static PyObject *
surf_get_pixels_address(PyObject *self, PyObject *closure);
static PyObject *
surf_premul_alpha(pgSurfaceObject *self, PyObject *args);
+static PyObject *
+surf_premul_alpha_ip(pgSurfaceObject *self, PyObject *args);
static int
_view_kind(PyObject *obj, void *view_kind_vptr);
static int
@@ -318,6 +320,8 @@ static struct PyMethodDef surface_methods[] = {
{"get_buffer", surf_get_buffer, METH_NOARGS, DOC_SURFACE_GETBUFFER},
{"premul_alpha", (PyCFunction)surf_premul_alpha, METH_NOARGS,
DOC_SURFACE_PREMULALPHA},
+ {"premul_alpha_ip", (PyCFunction)surf_premul_alpha_ip, METH_NOARGS,
+ DOC_SURFACE_PREMULALPHAIP},
{NULL, NULL, 0, NULL}};
@@ -989,6 +993,7 @@ surf_get_locks(PyObject *self, PyObject *_null)
{
pgSurfaceObject *surf = (pgSurfaceObject *)self;
Py_ssize_t len, i = 0;
+ int weakref_getref_result;
PyObject *tuple, *tmp;
SURF_INIT_CHECK(pgSurface_AsSurface(self))
if (!surf->locklist)
@@ -1000,8 +1005,16 @@ surf_get_locks(PyObject *self, PyObject *_null)
return NULL;
for (i = 0; i < len; i++) {
- tmp = PyWeakref_GetObject(PyList_GetItem(surf->locklist, i));
- Py_INCREF(tmp);
+ weakref_getref_result =
+ PyWeakref_GetRef(PyList_GetItem(surf->locklist, i), &tmp);
+ if (weakref_getref_result == -1) { // exception already set
+ Py_DECREF(tuple);
+ return NULL;
+ }
+ if (weakref_getref_result == 0) {
+ tmp = Py_None;
+ Py_INCREF(tmp);
+ }
PyTuple_SetItem(tuple, i, tmp);
}
return tuple;
@@ -1755,53 +1768,32 @@ surf_fill(pgSurfaceObject *self, PyObject *args, PyObject *keywds)
rect = &temp;
}
- if (rect->w < 0 || rect->h < 0 || rect->x > surf->w || rect->y > surf->h) {
- sdlrect.x = sdlrect.y = 0;
- sdlrect.w = sdlrect.h = 0;
+ // In SDL3, SDL_IntersectRect is renamed to SDL_GetRectIntersection
+ SDL_Rect surfrect = {0, 0, surf->w, surf->h};
+ if (!SDL_IntersectRect(rect, &surfrect, &sdlrect)) {
+ sdlrect.x = 0;
+ sdlrect.y = 0;
+ sdlrect.w = 0;
+ sdlrect.h = 0;
}
- else {
- sdlrect.x = rect->x;
- sdlrect.y = rect->y;
- sdlrect.w = rect->w;
- sdlrect.h = rect->h;
-
- // clip the rect to be within the surface.
- if (sdlrect.x + sdlrect.w <= 0 || sdlrect.y + sdlrect.h <= 0) {
- sdlrect.w = 0;
- sdlrect.h = 0;
- }
-
- if (sdlrect.x < 0) {
- sdlrect.x = 0;
- }
- if (sdlrect.y < 0) {
- sdlrect.y = 0;
- }
- if (sdlrect.x + sdlrect.w > surf->w) {
- sdlrect.w = sdlrect.w + (surf->w - (sdlrect.x + sdlrect.w));
- }
- if (sdlrect.y + sdlrect.h > surf->h) {
- sdlrect.h = sdlrect.h + (surf->h - (sdlrect.y + sdlrect.h));
- }
-
- if (sdlrect.w <= 0 || sdlrect.h <= 0) {
- return pgRect_New(&sdlrect);
- }
+ if (sdlrect.w <= 0 || sdlrect.h <= 0) {
+ return pgRect_New(&sdlrect);
+ }
- if (blendargs != 0) {
- result = surface_fill_blend(surf, &sdlrect, color, blendargs);
- }
- else {
- pgSurface_Prep(self);
- pgSurface_Lock((pgSurfaceObject *)self);
- result = SDL_FillRect(surf, &sdlrect, color);
- pgSurface_Unlock((pgSurfaceObject *)self);
- pgSurface_Unprep(self);
- }
- if (result == -1)
- return RAISE(pgExc_SDLError, SDL_GetError());
+ if (blendargs != 0) {
+ result = surface_fill_blend(surf, &sdlrect, color, blendargs);
+ }
+ else {
+ pgSurface_Prep(self);
+ pgSurface_Lock((pgSurfaceObject *)self);
+ result = SDL_FillRect(surf, &sdlrect, color);
+ pgSurface_Unlock((pgSurfaceObject *)self);
+ pgSurface_Unprep(self);
}
+ if (result == -1)
+ return RAISE(pgExc_SDLError, SDL_GetError());
+
return pgRect_New(&sdlrect);
}
@@ -1810,7 +1802,7 @@ surf_blit(pgSurfaceObject *self, PyObject *args, PyObject *keywds)
{
SDL_Surface *src, *dest = pgSurface_AsSurface(self);
SDL_Rect *src_rect, temp;
- PyObject *argpos, *argrect = NULL;
+ PyObject *argpos = NULL, *argrect = NULL;
pgSurfaceObject *srcobject;
int dx, dy, result;
SDL_Rect dest_rect;
@@ -1818,7 +1810,7 @@ surf_blit(pgSurfaceObject *self, PyObject *args, PyObject *keywds)
int blend_flags = 0;
static char *kwids[] = {"source", "dest", "area", "special_flags", NULL};
- if (!PyArg_ParseTupleAndKeywords(args, keywds, "O!O|Oi", kwids,
+ if (!PyArg_ParseTupleAndKeywords(args, keywds, "O!|OOi", kwids,
&pgSurface_Type, &srcobject, &argpos,
&argrect, &blend_flags))
return NULL;
@@ -1827,7 +1819,11 @@ surf_blit(pgSurfaceObject *self, PyObject *args, PyObject *keywds)
SURF_INIT_CHECK(src)
SURF_INIT_CHECK(dest)
- if ((src_rect = pgRect_FromObject(argpos, &temp))) {
+ if (argpos == NULL) { /* dest argument is absent */
+ dx = 0;
+ dy = 0;
+ }
+ else if ((src_rect = pgRect_FromObject(argpos, &temp))) {
dx = src_rect->x;
dy = src_rect->y;
}
@@ -2120,7 +2116,7 @@ surf_blits(pgSurfaceObject *self, PyObject *args, PyObject *keywds)
#define FBLITS_ERR_INCORRECT_ARGS_NUM 12
#define FBLITS_ERR_FLAG_NOT_NUMERIC 13
-int
+static int PG_FORCEINLINE
_surf_fblits_item_check_and_blit(pgSurfaceObject *self, PyObject *item,
int blend_flags)
{
@@ -2561,8 +2557,6 @@ surf_subsurface(PyObject *self, PyObject *args)
SDL_Rect *rect, temp;
SDL_Surface *sub;
PyObject *subobj;
- int pixeloffset;
- char *startpixel;
struct pgSubSurface_Data *data;
Uint8 alpha;
Uint32 colorkey;
@@ -2579,9 +2573,9 @@ surf_subsurface(PyObject *self, PyObject *args)
pgSurface_Lock((pgSurfaceObject *)self);
- pixeloffset =
- rect->x * PG_FORMAT_BytesPerPixel(format) + rect->y * surf->pitch;
- startpixel = ((char *)surf->pixels) + pixeloffset;
+ char *startpixel = ((char *)surf->pixels) +
+ rect->x * PG_FORMAT_BytesPerPixel(format) +
+ rect->y * surf->pitch;
sub = PG_CreateSurfaceFrom(startpixel, rect->w, rect->h, surf->pitch,
format->format);
@@ -2649,7 +2643,6 @@ surf_subsurface(PyObject *self, PyObject *args)
}
Py_INCREF(self);
data->owner = self;
- data->pixeloffset = pixeloffset;
data->offsetx = rect->x;
data->offsety = rect->y;
((pgSurfaceObject *)subobj)->subsurface = data;
@@ -3141,6 +3134,31 @@ surf_premul_alpha(pgSurfaceObject *self, PyObject *_null)
return final;
}
+static PyObject *
+surf_premul_alpha_ip(pgSurfaceObject *self, PyObject *_null)
+{
+ SDL_Surface *surf = pgSurface_AsSurface(self);
+ SURF_INIT_CHECK(surf)
+
+ if (!surf->w || !surf->h) {
+ Py_INCREF(self);
+ return (PyObject *)self;
+ }
+
+ pgSurface_Prep(self);
+
+ if (premul_surf_color_by_alpha(surf, surf) != 0) {
+ return RAISE(PyExc_ValueError,
+ "source surface to be alpha pre-multiplied must have "
+ "alpha channel");
+ }
+
+ pgSurface_Unprep(self);
+
+ Py_INCREF(self);
+ return (PyObject *)self;
+}
+
static int
_get_buffer_0D(PyObject *obj, Py_buffer *view_p, int flags)
{
@@ -3559,18 +3577,22 @@ _release_buffer(Py_buffer *view_p)
{
pg_bufferinternal *internal;
PyObject *consumer_ref;
- PyObject *consumer;
+ PyObject *consumer = NULL;
assert(view_p && view_p->obj && view_p->internal);
internal = (pg_bufferinternal *)view_p->internal;
consumer_ref = internal->consumer_ref;
assert(consumer_ref && PyWeakref_CheckRef(consumer_ref));
- consumer = PyWeakref_GetObject(consumer_ref);
- if (consumer) {
- if (!pgSurface_UnlockBy((pgSurfaceObject *)view_p->obj, consumer)) {
- PyErr_Clear();
- }
+
+ if (PyWeakref_GetRef(consumer_ref, &consumer) != 1) {
+ PyErr_Clear(); // ignore any errors here
}
+
+ if (!pgSurface_UnlockBy((pgSurfaceObject *)view_p->obj, consumer)) {
+ PyErr_Clear();
+ }
+ Py_XDECREF(consumer);
+
Py_DECREF(consumer_ref);
PyMem_Free(internal);
Py_DECREF(view_p->obj);
diff --git a/src_c/surflock.c b/src_c/surflock.c
index ad7919ae0c..62cd3acdbd 100644
--- a/src_c/surflock.c
+++ b/src_c/surflock.c
@@ -39,18 +39,12 @@ pgSurface_LockBy(pgSurfaceObject *, PyObject *);
static int
pgSurface_UnlockBy(pgSurfaceObject *, PyObject *);
-static void
-_lifelock_dealloc(PyObject *);
-
static void
pgSurface_Prep(pgSurfaceObject *surfobj)
{
struct pgSubSurface_Data *data = ((pgSurfaceObject *)surfobj)->subsurface;
if (data != NULL) {
- SDL_Surface *surf = pgSurface_AsSurface(surfobj);
- SDL_Surface *owner = pgSurface_AsSurface(data->owner);
pgSurface_LockBy((pgSurfaceObject *)data->owner, (PyObject *)surfobj);
- surf->pixels = ((char *)owner->pixels) + data->pixeloffset;
}
}
@@ -118,20 +112,29 @@ pgSurface_UnlockBy(pgSurfaceObject *surfobj, PyObject *lockobj)
pgSurfaceObject *surf = (pgSurfaceObject *)surfobj;
int found = 0;
int noerror = 1;
+ int weakref_getref_result;
if (surf->locklist != NULL) {
PyObject *item, *ref;
Py_ssize_t len = PyList_Size(surf->locklist);
while (--len >= 0 && !found) {
item = PyList_GetItem(surf->locklist, len);
- ref = PyWeakref_GetObject(item);
- if (ref == lockobj) {
- if (PySequence_DelItem(surf->locklist, len) == -1) {
- return 0;
- }
- else {
- found = 1;
+
+ weakref_getref_result = PyWeakref_GetRef(item, &ref);
+ if (weakref_getref_result == -1) {
+ noerror = 0;
+ }
+ if (weakref_getref_result == 1) {
+ if (ref == lockobj) {
+ if (PySequence_DelItem(surf->locklist, len) == -1) {
+ Py_DECREF(ref);
+ return 0;
+ }
+ else {
+ found = 1;
+ }
}
+ Py_DECREF(ref);
}
}
@@ -139,8 +142,12 @@ pgSurface_UnlockBy(pgSurfaceObject *surfobj, PyObject *lockobj)
len = PyList_Size(surf->locklist);
while (--len >= 0) {
item = PyList_GetItem(surf->locklist, len);
- ref = PyWeakref_GetObject(item);
- if (ref == Py_None) {
+
+ weakref_getref_result = PyWeakref_GetRef(item, &ref);
+ if (weakref_getref_result == -1) {
+ noerror = 0;
+ }
+ else if (weakref_getref_result == 0) {
if (PySequence_DelItem(surf->locklist, len) == -1) {
noerror = 0;
}
@@ -148,6 +155,9 @@ pgSurface_UnlockBy(pgSurfaceObject *surfobj, PyObject *lockobj)
found++;
}
}
+ else if (weakref_getref_result == 1) {
+ Py_DECREF(ref);
+ }
}
}
@@ -169,51 +179,6 @@ pgSurface_UnlockBy(pgSurfaceObject *surfobj, PyObject *lockobj)
return noerror;
}
-static PyTypeObject pgLifetimeLock_Type = {
- PyVarObject_HEAD_INIT(NULL, 0).tp_name = "pygame.surflock.SurfLifeLock",
- .tp_basicsize = sizeof(pgLifetimeLockObject),
- .tp_dealloc = _lifelock_dealloc,
- .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
- .tp_weaklistoffset = offsetof(pgLifetimeLockObject, weakrefs),
-};
-
-/* lifetimelock object internals */
-static void
-_lifelock_dealloc(PyObject *self)
-{
- pgLifetimeLockObject *lifelock = (pgLifetimeLockObject *)self;
-
- if (lifelock->weakrefs != NULL) {
- PyObject_ClearWeakRefs(self);
- }
-
- pgSurface_UnlockBy((pgSurfaceObject *)lifelock->surface,
- lifelock->lockobj);
- Py_DECREF(lifelock->surface);
- Py_TYPE(self)->tp_free(self);
-}
-
-static PyObject *
-pgSurface_LockLifetime(PyObject *surfobj, PyObject *lockobj)
-{
- pgLifetimeLockObject *life;
- if (surfobj == NULL) {
- return RAISE(pgExc_SDLError, SDL_GetError());
- }
-
- life = PyObject_New(pgLifetimeLockObject, &pgLifetimeLock_Type);
- if (life != NULL) {
- life->surface = surfobj;
- life->lockobj = lockobj;
- life->weakrefs = NULL;
- Py_INCREF(surfobj);
- if (!pgSurface_LockBy((pgSurfaceObject *)surfobj, lockobj)) {
- return NULL;
- }
- }
- return (PyObject *)life;
-}
-
static PyMethodDef _surflock_methods[] = {{NULL, NULL, 0, NULL}};
/*DOC*/ static char _surflock_doc[] =
@@ -234,10 +199,6 @@ MODINIT_DEFINE(surflock)
NULL,
NULL};
- if (PyType_Ready(&pgLifetimeLock_Type) < 0) {
- return NULL;
- }
-
/* Create the module and add the functions */
module = PyModule_Create(&_module);
if (module == NULL) {
@@ -245,14 +206,12 @@ MODINIT_DEFINE(surflock)
}
/* export the c api */
- c_api[0] = &pgLifetimeLock_Type;
- c_api[1] = pgSurface_Prep;
- c_api[2] = pgSurface_Unprep;
- c_api[3] = pgSurface_Lock;
- c_api[4] = pgSurface_Unlock;
- c_api[5] = pgSurface_LockBy;
- c_api[6] = pgSurface_UnlockBy;
- c_api[7] = pgSurface_LockLifetime;
+ c_api[0] = pgSurface_Prep;
+ c_api[1] = pgSurface_Unprep;
+ c_api[2] = pgSurface_Lock;
+ c_api[3] = pgSurface_Unlock;
+ c_api[4] = pgSurface_LockBy;
+ c_api[5] = pgSurface_UnlockBy;
apiobj = encapsulate_api(c_api, "surflock");
if (PyModule_AddObject(module, PYGAMEAPI_LOCAL_ENTRY, apiobj)) {
Py_XDECREF(apiobj);
diff --git a/src_c/transform.c b/src_c/transform.c
index 6d579f941d..94ef27453c 100644
--- a/src_c/transform.c
+++ b/src_c/transform.c
@@ -2289,13 +2289,6 @@ HSL_to_RGB(float h, float s, float l, Uint8 *r, Uint8 *g, Uint8 *b)
static void
modify_hsl(SDL_Surface *surf, SDL_Surface *dst, float h, float s, float l)
{
- int x, y;
- Uint8 r, g, b, a;
- float s_h = 0, s_s = 0, s_l = 0;
- SDL_PixelFormat *fmt = surf->format;
- Uint8 *srcp8 = (Uint8 *)surf->pixels;
- Uint8 *dstp8 = (Uint8 *)dst->pixels;
-
int surf_locked = 0;
if (SDL_MUSTLOCK(surf)) {
if (SDL_LockSurface(surf) == 0) {
@@ -2309,6 +2302,13 @@ modify_hsl(SDL_Surface *surf, SDL_Surface *dst, float h, float s, float l)
}
}
+ int x, y;
+ Uint8 r, g, b, a;
+ float s_h = 0, s_s = 0, s_l = 0;
+ SDL_PixelFormat *fmt = surf->format;
+ Uint8 *srcp8 = (Uint8 *)surf->pixels;
+ Uint8 *dstp8 = (Uint8 *)dst->pixels;
+
if (PG_FORMAT_BytesPerPixel(fmt) == 4 ||
PG_FORMAT_BytesPerPixel(fmt) == 3) {
const int src_skip =
@@ -2320,10 +2320,12 @@ modify_hsl(SDL_Surface *surf, SDL_Surface *dst, float h, float s, float l)
const int Ridx = fmt->Rshift >> 3;
const int Gidx = fmt->Gshift >> 3;
const int Bidx = fmt->Bshift >> 3;
+ const int Aidx = fmt->Ashift >> 3;
#else
const int Ridx = 3 - (fmt->Rshift >> 3);
const int Gidx = 3 - (fmt->Gshift >> 3);
const int Bidx = 3 - (fmt->Bshift >> 3);
+ const int Aidx = 3 - (fmt->Ashift >> 3);
#endif
int height = surf->h;
@@ -2353,6 +2355,8 @@ modify_hsl(SDL_Surface *surf, SDL_Surface *dst, float h, float s, float l)
dstp8[Ridx] = r;
dstp8[Gidx] = g;
dstp8[Bidx] = b;
+ if (fmt->Amask)
+ dstp8[Aidx] = srcp8[Aidx];
srcp8 += PG_FORMAT_BytesPerPixel(fmt);
dstp8 += PG_FORMAT_BytesPerPixel(fmt);
@@ -2462,6 +2466,7 @@ surf_hsl(PyObject *self, PyObject *args, PyObject *kwargs)
if (src->format->Rmask != dst->format->Rmask ||
src->format->Gmask != dst->format->Gmask ||
src->format->Bmask != dst->format->Bmask ||
+ src->format->Amask != dst->format->Amask ||
PG_SURF_BytesPerPixel(src) != PG_SURF_BytesPerPixel(dst)) {
return RAISE(PyExc_ValueError,
"Source and destination surfaces need the same format.");
diff --git a/src_c/window.c b/src_c/window.c
index c3f3718386..5849b92048 100644
--- a/src_c/window.c
+++ b/src_c/window.c
@@ -3,6 +3,7 @@
#include "pygame.h"
#include "pgcompat.h"
+#include "pgopengl.h"
#include "doc/sdl2_video_doc.h"
#include "doc/window_doc.h"
@@ -40,9 +41,7 @@ static PyObject *
pg_display_resource(char *filename)
{
PyObject *imagemodule = NULL;
- PyObject *load_basicfunc = NULL;
PyObject *pkgdatamodule = NULL;
- PyObject *resourcefunc = NULL;
PyObject *fresult = NULL;
PyObject *result = NULL;
PyObject *name = NULL;
@@ -51,19 +50,12 @@ pg_display_resource(char *filename)
if (!pkgdatamodule)
goto display_resource_end;
- resourcefunc = PyObject_GetAttrString(pkgdatamodule, resourcefunc_name);
- if (!resourcefunc)
- goto display_resource_end;
-
imagemodule = PyImport_ImportModule(imagemodule_name);
if (!imagemodule)
goto display_resource_end;
- load_basicfunc = PyObject_GetAttrString(imagemodule, load_basicfunc_name);
- if (!load_basicfunc)
- goto display_resource_end;
-
- fresult = PyObject_CallFunction(resourcefunc, "s", filename);
+ fresult =
+ PyObject_CallMethod(pkgdatamodule, resourcefunc_name, "s", filename);
if (!fresult)
goto display_resource_end;
@@ -80,21 +72,21 @@ pg_display_resource(char *filename)
PyErr_Clear();
}
- result = PyObject_CallFunction(load_basicfunc, "O", fresult);
+ result =
+ PyObject_CallMethod(imagemodule, load_basicfunc_name, "O", fresult);
if (!result)
goto display_resource_end;
display_resource_end:
Py_XDECREF(pkgdatamodule);
- Py_XDECREF(resourcefunc);
Py_XDECREF(imagemodule);
- Py_XDECREF(load_basicfunc);
Py_XDECREF(fresult);
Py_XDECREF(name);
return result;
}
static PyTypeObject pgWindow_Type;
+static GL_glViewport_Func p_glViewport = NULL;
#define pgWindow_Check(x) \
(PyObject_IsInstance((x), (PyObject *)&pgWindow_Type))
@@ -211,7 +203,24 @@ window_flip(pgWindowObject *self, PyObject *_null)
Py_RETURN_NONE;
}
-// Callback function for surface auto resize
+/* Exception already set */
+static int
+_window_opengl_set_viewport(SDL_Window *window, SDL_GLContext context,
+ int wnew, int hnew)
+{
+ if (SDL_GL_MakeCurrent(window, context) < 0) {
+ PyErr_SetString(pgExc_SDLError, SDL_GetError());
+ return -1;
+ }
+ if (p_glViewport == NULL) {
+ PyErr_SetString(pgExc_SDLError, "glViewport function is unavailable");
+ return -1;
+ }
+ p_glViewport(0, 0, wnew, hnew);
+ return 0;
+}
+
+// Callback function for surface auto resize or OpenGL viewport update
static int SDLCALL
_resize_event_watch(void *userdata, SDL_Event *event)
{
@@ -232,6 +241,15 @@ _resize_event_watch(void *userdata, SDL_Event *event)
return 0;
}
+ if (event_window_pg->context != NULL) {
+ if (_window_opengl_set_viewport(event_window, event_window_pg->context,
+ event->window.data1,
+ event->window.data2) < 0) {
+ return PyErr_WarnEx(PyExc_RuntimeWarning,
+ "Failed to set OpenGL viewport", 0);
+ }
+ }
+
if (!event_window_pg->surf)
return 0;
@@ -588,6 +606,13 @@ window_set_size(pgWindowObject *self, PyObject *arg, void *v)
* relying on the event callback */
self->surf->surf = SDL_GetWindowSurface(self->_win);
}
+ if (self->context != NULL) {
+ /* Update the OpenGL viewport immediately instead of relying on the
+ * event callback */
+ if (_window_opengl_set_viewport(self->_win, self->context, w, h) < 0) {
+ return -1;
+ }
+ }
return 0;
}
@@ -970,12 +995,17 @@ window_init(pgWindowObject *self, PyObject *args, PyObject *kwargs)
self->_is_borrowed = SDL_FALSE;
self->surf = NULL;
- if (SDL_GetWindowFlags(self->_win) & SDL_WINDOW_OPENGL) {
+ if (flags & SDL_WINDOW_OPENGL) {
SDL_GLContext context = SDL_GL_CreateContext(self->_win);
if (context == NULL) {
PyErr_SetString(pgExc_SDLError, SDL_GetError());
return -1;
}
+ /* As stated in the 'Remarks' of the docs
+ * (https://wiki.libsdl.org/SDL2/SDL_GL_GetProcAddress) on Windows
+ * SDL_GL_GetProcAddress is only valid after an OpenGL context has been
+ * created */
+ p_glViewport = (GL_glViewport_Func)SDL_GL_GetProcAddress("glViewport");
self->context = context;
}
else {
@@ -1036,6 +1066,32 @@ window_from_display_module(PyTypeObject *cls, PyObject *_null)
return (PyObject *)self;
}
+static PyObject *
+window_flash(pgWindowObject *self, PyObject *arg)
+{
+#if SDL_VERSION_ATLEAST(2, 0, 16)
+ long operation = PyLong_AsLong(arg);
+ if (operation == -1 && PyErr_Occurred()) {
+ return RAISE(PyExc_TypeError,
+ "'operation' must be an integer. "
+ "Must correspond with FLASH_CANCEL, FLASH_BRIEFLY, or "
+ "FLASH_UNTIL_FOCUSED.");
+ }
+
+ if (operation != SDL_FLASH_CANCEL && operation != SDL_FLASH_BRIEFLY &&
+ operation != SDL_FLASH_UNTIL_FOCUSED) {
+ return RAISE(PyExc_ValueError, "Unsupported window flash operation.");
+ }
+
+ if (SDL_FlashWindow(self->_win, operation) < 0) {
+ return RAISE(pgExc_SDLError, SDL_GetError());
+ }
+ Py_RETURN_NONE;
+#else
+ return RAISE(pgExc_SDLError, "'Window.flash' requires SDL 2.0.16+");
+#endif /* SDL_VERSION_ATLEAST(2, 0, 16) */
+}
+
PyObject *
window_repr(pgWindowObject *self)
{
@@ -1101,6 +1157,7 @@ static PyMethodDef window_methods[] = {
DOC_WINDOW_GETSURFACE},
{"from_display_module", (PyCFunction)window_from_display_module,
METH_CLASS | METH_NOARGS, DOC_WINDOW_FROMDISPLAYMODULE},
+ {"flash", (PyCFunction)window_flash, METH_O, DOC_WINDOW_FLASH},
{NULL, NULL, 0, NULL}};
static PyGetSetDef _window_getset[] = {
@@ -1141,6 +1198,7 @@ static PyTypeObject pgWindow_Type = {
PyVarObject_HEAD_INIT(NULL, 0).tp_name = "pygame.window.Window",
.tp_basicsize = sizeof(pgWindowObject),
.tp_dealloc = (destructor)window_dealloc,
+ .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
.tp_doc = DOC_WINDOW,
.tp_methods = window_methods,
.tp_init = (initproc)window_init,
diff --git a/src_py/__init__.py b/src_py/__init__.py
index 9cc924420c..ac29abd3c8 100644
--- a/src_py/__init__.py
+++ b/src_py/__init__.py
@@ -315,6 +315,12 @@ def Window(title="pygame window", size=(640, 480), position=None, **kwargs): #
_attribute_undefined("pygame.Window")
+try:
+ import pygame.typing
+except (ImportError, OSError):
+ typing = MissingModule("typing", urgent=0)
+
+
# there's also a couple "internal" modules not needed
# by users, but putting them here helps "dependency finder"
# programs get everything they need (like py2exe)
diff --git a/src_py/meson.build b/src_py/meson.build
index 561aebaadb..541c54cd69 100644
--- a/src_py/meson.build
+++ b/src_py/meson.build
@@ -18,6 +18,7 @@ python_sources = files(
'sprite.py',
'surfarray.py',
'sysfont.py',
+ 'typing.py',
'version.py',
)
py.install_sources(python_sources, subdir: pg)
diff --git a/src_py/typing.py b/src_py/typing.py
new file mode 100644
index 0000000000..883fe2def1
--- /dev/null
+++ b/src_py/typing.py
@@ -0,0 +1,59 @@
+"""Set of common pygame type aliases for proper typehint annotations"""
+
+# NOTE: `src_py/typing.py` and `buildconfig/stubs/pygame/typing.pyi` must be duplicates.
+# Use the command `python buildconfig/stubs/gen_stubs.py` to copy typing.py to typing.pyi
+
+import sys
+from typing import IO, Callable, Tuple, Union, TypeVar, Protocol, SupportsIndex
+
+if sys.version_info >= (3, 9):
+ from os import PathLike as _PathProtocol
+else:
+ _T = TypeVar("_T", bound=Union[str, bytes])
+
+ class _PathProtocol(Protocol[_T]):
+ def __fspath__(self) -> _T: ...
+
+
+# For functions that take a file name
+PathLike = Union[str, bytes, _PathProtocol[str], _PathProtocol[bytes]]
+# Most pygame functions that take a file argument should be able to handle a FileLike type
+FileLike = Union[PathLike, IO[bytes], IO[str]]
+
+_T_co = TypeVar("_T_co", covariant=True)
+
+
+class SequenceLike(Protocol[_T_co]):
+ """
+ Variant of the standard `Sequence` ABC that only requires `__getitem__` and `__len__`.
+ """
+
+ def __getitem__(self, __i: SupportsIndex) -> _T_co: ...
+ def __len__(self) -> int: ...
+
+
+# Modify typehints when it is possible to annotate sizes
+
+# Pygame handles float without errors in most cases where a coordinate is expected,
+# usually rounding to int. Also, 'Union[int, float] == float'
+Coordinate = SequenceLike[float]
+# This is used where ints are strictly required
+IntCoordinate = SequenceLike[int]
+
+# Used for functions that return an RGBA tuple
+RGBATuple = Tuple[int, int, int, int]
+ColorLike = Union[int, str, SequenceLike[int]]
+
+_CanBeRect = SequenceLike[Union[float, Coordinate]]
+
+
+class _HasRectAttribute(Protocol):
+ # An object that has a rect attribute that is either a rect, or a function
+ # that returns a rect conforms to the rect protocol
+ rect: Union["RectLike", Callable[[], "RectLike"]]
+
+
+RectLike = Union[_CanBeRect, _HasRectAttribute]
+
+# cleanup namespace
+del sys, IO, Callable, Tuple, Union, TypeVar, Protocol, SupportsIndex
diff --git a/test/display_test.py b/test/display_test.py
index 5325df817b..681d491d8b 100644
--- a/test/display_test.py
+++ b/test/display_test.py
@@ -714,6 +714,9 @@ def test_update_negative(self):
r3 = pygame.Rect(-10, 0, -100, -100)
pygame.display.update(r3)
+ # random point in rect
+ self.assertEqual(self.screen.get_at((50, 50)), (0, 255, 0))
+
self.question("Is the screen green in (0, 0, 100, 100)?")
def test_update_sequence(self):
@@ -728,6 +731,47 @@ def test_update_sequence(self):
pygame.display.update(rects)
pygame.event.pump() # so mac updates
+ # random points in rect
+ for random_point in ((50, 50), (150, 50), (250, 50), (350, 350)):
+ self.assertEqual(self.screen.get_at(random_point), (0, 255, 0))
+
+ self.question(f"Is the screen green in {rects}?")
+
+ def test_update_dict_values(self):
+ """only updates the part of the display given by the rects."""
+ self.screen.fill("green")
+ rects = {
+ "foo": pygame.Rect(0, 0, 100, 100),
+ "foobar": pygame.Rect(100, 0, 100, 100),
+ "hello": pygame.Rect(200, 0, 100, 100),
+ "hi": pygame.Rect(300, 300, 100, 100),
+ }
+ pygame.display.update(rects.values())
+ pygame.event.pump() # so mac updates
+
+ # random points in rect
+ for random_point in ((50, 50), (150, 50), (250, 50), (350, 350)):
+ self.assertEqual(self.screen.get_at(random_point), (0, 255, 0))
+
+ self.question(f"Is the screen green in {' '.join(map(str, rects.values()))}?")
+
+ def test_update_generator(self):
+ """only updates the part of the display given by the rects."""
+ self.screen.fill("green")
+ rects = [
+ pygame.Rect(0, 0, 100, 100),
+ pygame.Rect(100, 0, 100, 100),
+ pygame.Rect(200, 0, 100, 100),
+ pygame.Rect(300, 300, 100, 100),
+ ]
+ # make a generator from list, pass list with rect duplicates
+ pygame.display.update(i for i in (rects * 5))
+ pygame.event.pump() # so mac updates
+
+ # random points in rect
+ for random_point in ((50, 50), (150, 50), (250, 50), (350, 350)):
+ self.assertEqual(self.screen.get_at(random_point), (0, 255, 0))
+
self.question(f"Is the screen green in {rects}?")
def test_update_none_skipped(self):
@@ -743,6 +787,10 @@ def test_update_none_skipped(self):
pygame.display.update(rects)
pygame.event.pump() # so mac updates
+ # random points in rect
+ for random_point in ((50, 50), (150, 50), (250, 50), (350, 350)):
+ self.assertEqual(self.screen.get_at(random_point), (0, 255, 0))
+
self.question(f"Is the screen green in {rects}?")
def test_update_none(self):
@@ -757,6 +805,11 @@ def test_update_no_args(self):
self.screen.fill("green")
pygame.display.update()
pygame.event.pump() # so mac updates
+
+ # random points in rect
+ for random_point in ((50, 50), (150, 50), (250, 50), (350, 350)):
+ self.assertEqual(self.screen.get_at(random_point), (0, 255, 0))
+
self.question(f"Is the WHOLE screen green?")
def test_update_args(self):
@@ -766,6 +819,9 @@ def test_update_args(self):
pygame.event.pump() # so mac updates
self.question("Is the screen green in (100, 100, 100, 100)?")
+ # random points in rect
+ self.assertEqual(self.screen.get_at((150, 150)), (0, 255, 0))
+
def test_update_incorrect_args(self):
"""raises a ValueError when inputs are wrong."""
@@ -1022,6 +1078,11 @@ def test_message_box_buttons(self):
)
self.assertEqual(result, 0)
+ def test_message_box_parent_window_none(self):
+ pygame.display.message_box(
+ "Test", "Just close this messagebox", parent_window=None
+ )
+
if __name__ == "__main__":
unittest.main()
diff --git a/test/event_test.py b/test/event_test.py
index c3a3d5e1c3..43e4783eee 100644
--- a/test/event_test.py
+++ b/test/event_test.py
@@ -1,4 +1,5 @@
import collections
+import os
import time
import unittest
@@ -818,12 +819,10 @@ def test_pump(self):
"""Ensure pump() functions properly."""
pygame.event.pump()
- # @unittest.skipIf(
- # os.environ.get("SDL_VIDEODRIVER") == pygame.NULL_VIDEODRIVER,
- # 'requires the SDL_VIDEODRIVER to be a non-null value',
- # )
- # Fails on SDL 2.0.18
- @unittest.skip("flaky test, and broken on 2.0.18 windows")
+ @unittest.skipIf(
+ os.environ.get("SDL_VIDEODRIVER") == pygame.NULL_VIDEODRIVER,
+ 'requires the SDL_VIDEODRIVER to be a non-null value',
+ )
def test_set_grab__and_get_symmetric(self):
"""Ensure event grabbing can be enabled and disabled.
@@ -888,12 +887,10 @@ def test_get_blocked__event_sequence(self):
self.assertTrue(blocked)
- # @unittest.skipIf(
- # os.environ.get("SDL_VIDEODRIVER") == pygame.NULL_VIDEODRIVER,
- # 'requires the SDL_VIDEODRIVER to be a non-null value',
- # )
- # Fails on SDL 2.0.18
- @unittest.skip("flaky test, and broken on 2.0.18 windows")
+ @unittest.skipIf(
+ os.environ.get("SDL_VIDEODRIVER") == pygame.NULL_VIDEODRIVER,
+ 'requires the SDL_VIDEODRIVER to be a non-null value',
+ )
def test_get_grab(self):
"""Ensure get_grab() works as expected"""
surf = pygame.display.set_mode((10, 10))
diff --git a/test/font_test.py b/test/font_test.py
index 20f8aaf153..f008da2e69 100644
--- a/test/font_test.py
+++ b/test/font_test.py
@@ -1,5 +1,4 @@
# -*- coding: utf-8 -*-
-from re import T
import sys
import os
import io
diff --git a/test/freetype_test.py b/test/freetype_test.py
index 200a9933bd..dccd7ab780 100644
--- a/test/freetype_test.py
+++ b/test/freetype_test.py
@@ -1767,6 +1767,10 @@ def test_get_init(self):
# Test if get_init() gets the init state.
self.assertTrue(ft.get_init())
+ def test_was_init_deprecated(self):
+ with self.assertWarns(DeprecationWarning):
+ self.assertTrue(ft.was_init())
+
def test_cache_size(self):
DEFAULT_CACHE_SIZE = 64
self.assertEqual(ft.get_cache_size(), DEFAULT_CACHE_SIZE)
diff --git a/test/geometry_test.py b/test/geometry_test.py
index b69b316908..f819a7a5f2 100644
--- a/test/geometry_test.py
+++ b/test/geometry_test.py
@@ -95,6 +95,13 @@ def testConstructionTUP_XYR_int(self):
self.assertEqual(2.0, c.y)
self.assertEqual(3.0, c.r)
+ def testConstruction_zero_radius(self):
+ c = Circle(1, 2, 0)
+
+ self.assertEqual(1.0, c.x)
+ self.assertEqual(2.0, c.y)
+ self.assertEqual(0, c.r)
+
def test_x(self):
"""Ensures changing the x attribute moves the circle and does not change
the circle's radius.
@@ -183,7 +190,7 @@ def test_r__invalid_value(self):
with self.assertRaises(TypeError):
c.radius = value
- for value in (-10.3234, -1, 0, 0.0):
+ for value in (-10.3234, -1):
with self.assertRaises(ValueError):
c.r = value
with self.assertRaises(ValueError):
@@ -317,7 +324,7 @@ def test_area_invalid_value(self):
with self.assertRaises(TypeError):
c.area = value
- for value in (-10.3234, -1, 0, 0.0):
+ for value in (-10.3234, -1):
with self.assertRaises(ValueError):
c.area = value
@@ -352,7 +359,7 @@ def test_circumference_invalid_value(self):
with self.assertRaises(TypeError):
c.circumference = value
- for value in (-10.3234, -1, 0, 0.0):
+ for value in (-10.3234, -1):
with self.assertRaises(ValueError):
c.circumference = value
@@ -390,7 +397,7 @@ def test_diameter_invalid_value(self):
with self.assertRaises(TypeError):
c.diameter = value
- for value in (-10.3234, -1, 0, 0.0):
+ for value in (-10.3234, -1):
with self.assertRaises(ValueError):
c.diameter = value
with self.assertRaises(ValueError):
diff --git a/test/image_test.py b/test/image_test.py
index e412c40508..8bff1dd037 100644
--- a/test/image_test.py
+++ b/test/image_test.py
@@ -506,9 +506,9 @@ def convertRGBAtoPremultiplied(surface_to_modify):
for y in range(surface_to_modify.get_height()):
color = surface_to_modify.get_at((x, y))
premult_color = (
- color[0] * color[3] / 255,
- color[1] * color[3] / 255,
- color[2] * color[3] / 255,
+ ((color[0] + 1) * color[3]) >> 8,
+ ((color[1] + 1) * color[3]) >> 8,
+ ((color[2] + 1) * color[3]) >> 8,
color[3],
)
surface_to_modify.set_at((x, y), premult_color)
@@ -570,7 +570,7 @@ def test_frombytes__and_tobytes(self):
import itertools
- fmts = ("RGBA", "ARGB", "BGRA")
+ fmts = ("RGBA", "ARGB", "BGRA", "ABGR")
fmt_permutations = itertools.permutations(fmts, 2)
fmt_combinations = itertools.combinations(fmts, 2)
diff --git a/test/key_test.py b/test/key_test.py
index dc7a6276d8..758010b420 100644
--- a/test/key_test.py
+++ b/test/key_test.py
@@ -1,5 +1,6 @@
import os
import time
+import platform
import unittest
import pygame
@@ -186,9 +187,10 @@ def test_import(self):
"""does it import?"""
import pygame.key
- # fixme: test_get_focused failing systematically in some linux
- # fixme: test_get_focused failing on SDL 2.0.18 on Windows
- @unittest.skip("flaky test, and broken on 2.0.18 windows")
+ @unittest.skipIf(
+ not ("Windows" in platform.system() or "Darwin" in platform.system()),
+ "Not windows or macOS - we skip.",
+ )
def test_get_focused(self):
# This test fails in SDL2 in some linux
# This test was skipped in SDL1.
diff --git a/test/rect_test.py b/test/rect_test.py
index 307feb8b29..dc64cc9e11 100644
--- a/test/rect_test.py
+++ b/test/rect_test.py
@@ -2877,6 +2877,9 @@ def testRepr(self):
repr(rect), "FRect(12.345679, 34.000000, 56.000000, 78.000000)"
)
+ # test that a large rect repr doesn't error
+ self.assertIsInstance(repr(Rect(-2e38, -2e38, -2e38, -2e38)), str)
+
def test_clipline__equal_endpoints_no_overlap(self):
"""Ensures clipline handles lines with both endpoints the same.
diff --git a/test/surface_test.py b/test/surface_test.py
index d0c01c28a5..ca60ccb8e0 100644
--- a/test/surface_test.py
+++ b/test/surface_test.py
@@ -1203,6 +1203,15 @@ def test_blit__SCALPHA32_TO_OPAQUE32(self):
# +3 gets the "alpha channel" in this pixel format
assert surf1_bytes[i * 4 + 3] == 0
+ def test_blit_default_dest(self):
+ surf1 = pygame.Surface((20, 20))
+ surf1.fill("black")
+ surf2 = pygame.Surface((10, 10))
+ surf2.fill("red")
+ surf1.blit(surf2)
+ self.assertEqual(surf1.get_at((0, 0)), pygame.Color("red"))
+ self.assertEqual(surf1.get_at((10, 10)), pygame.Color("black"))
+
class GeneralSurfaceTests(unittest.TestCase):
@unittest.skipIf(
@@ -4029,6 +4038,81 @@ def test_surface_premul_alpha(self):
),
)
+ def test_surface_premul_alpha_ip(self):
+ """Ensure that .premul_alpha_ip() works correctly"""
+
+ # basic functionality at valid bit depths - 32, 16 & 8
+ s1 = pygame.Surface((100, 100), pygame.SRCALPHA, 32)
+ s1.fill(pygame.Color(255, 255, 255, 100))
+ s1.premul_alpha_ip()
+ s1_alpha = s1
+ self.assertEqual(s1_alpha.get_at((50, 50)), pygame.Color(100, 100, 100, 100))
+
+ # 16-bit color has less precision
+ s2 = pygame.Surface((100, 100), pygame.SRCALPHA, 16)
+ s2.fill(
+ pygame.Color(
+ int(15 / 15 * 255),
+ int(15 / 15 * 255),
+ int(15 / 15 * 255),
+ int(10 / 15 * 255),
+ )
+ )
+ s2.premul_alpha_ip()
+ s2_alpha = s2
+ self.assertEqual(
+ s2_alpha.get_at((50, 50)),
+ pygame.Color(
+ int(10 / 15 * 255),
+ int(10 / 15 * 255),
+ int(10 / 15 * 255),
+ int(10 / 15 * 255),
+ ),
+ )
+
+ # invalid surface - we need alpha to pre-multiply
+ invalid_surf = pygame.Surface((100, 100), 0, 32)
+ invalid_surf.fill(pygame.Color(255, 255, 255, 100))
+ with self.assertRaises(ValueError):
+ invalid_surf.premul_alpha_ip()
+
+ # churn a bunch of values
+ test_colors = [
+ (200, 30, 74),
+ (76, 83, 24),
+ (184, 21, 6),
+ (74, 4, 74),
+ (76, 83, 24),
+ (184, 21, 234),
+ (160, 30, 74),
+ (96, 147, 204),
+ (198, 201, 60),
+ (132, 89, 74),
+ (245, 9, 224),
+ (184, 112, 6),
+ ]
+
+ for r, g, b in test_colors:
+ for a in range(255):
+ with self.subTest(r=r, g=g, b=b, a=a):
+ surf = pygame.Surface((10, 10), pygame.SRCALPHA, 32)
+ surf.fill(pygame.Color(r, g, b, a))
+ surf.premul_alpha_ip()
+ self.assertEqual(
+ surf.get_at((5, 5)),
+ Color(
+ ((r + 1) * a) >> 8,
+ ((g + 1) * a) >> 8,
+ ((b + 1) * a) >> 8,
+ a,
+ ),
+ )
+
+ for size in [(0, 0), (1, 0), (0, 1), (10, 10)]:
+ surf = pygame.Surface(size, pygame.SRCALPHA, 32)
+ surf.fill((32, 44, 4, 123))
+ self.assertIs(surf, surf.premul_alpha_ip())
+
class SurfaceSelfBlitTest(unittest.TestCase):
"""Blit to self tests.
@@ -4268,6 +4352,38 @@ def test_fill(self):
for y in range(5, 480, 10):
self.assertEqual(screen.get_at((10, y)), screen.get_at((330, 480 - y)))
+ def test_fill_negative_rectpos(self):
+ """Regression test for https://github.com/pygame-community/pygame-ce/issues/2938"""
+ screen = pygame.display.set_mode((640, 480))
+ other_surface = screen.copy()
+
+ negative_x_rect = pygame.Rect(-10, 10, 20, 20)
+ negative_x_rect_drawn = pygame.Rect(0, 10, 10, 20)
+ negative_y_rect = pygame.Rect(100, -10, 20, 20)
+ negative_y_rect_drawn = pygame.Rect(100, 0, 20, 10)
+ negative_both = pygame.Rect(-10, -10, 20, 20)
+ negative_both_drawn = pygame.Rect(0, 0, 10, 10)
+
+ red_rect_1 = screen.fill("red", negative_x_rect)
+ blue_rect_1 = screen.fill("blue", negative_y_rect)
+ green_rect_1 = screen.fill("green", negative_both)
+
+ red_rect_2 = other_surface.fill("red", negative_x_rect_drawn)
+ blue_rect_2 = other_surface.fill("blue", negative_y_rect_drawn)
+ green_rect_2 = other_surface.fill("green", negative_both_drawn)
+
+ self.assertEqual(red_rect_1, red_rect_2)
+ self.assertEqual(blue_rect_1, blue_rect_2)
+ self.assertEqual(green_rect_1, green_rect_2)
+
+ # verify both have the same pixels
+ width, height = screen.get_size()
+ for row in range(height):
+ for col in range(width):
+ self.assertEqual(
+ screen.get_at((col, row)), other_surface.get_at((col, row))
+ )
+
if __name__ == "__main__":
unittest.main()
diff --git a/test/test_utils/__init__.py b/test/test_utils/__init__.py
index 16ffb105b8..040d7b8876 100644
--- a/test/test_utils/__init__.py
+++ b/test/test_utils/__init__.py
@@ -3,8 +3,6 @@
import sys
import tempfile
-is_pygame_pkg = __name__.startswith("pygame.tests.")
-
###############################################################################
@@ -22,11 +20,8 @@ def geterror():
###############################################################################
this_dir = os.path.dirname(os.path.abspath(__file__))
-trunk_dir = os.path.split(os.path.split(this_dir)[0])[0]
-if is_pygame_pkg:
- test_module = "tests"
-else:
- test_module = "test"
+this_dir_parent = os.path.split(this_dir)[0]
+trunk_dir = os.path.split(this_dir_parent)[0]
def trunk_relative_path(relative):
@@ -34,7 +29,7 @@ def trunk_relative_path(relative):
def fixture_path(path):
- return trunk_relative_path(os.path.join(test_module, "fixtures", path))
+ return os.path.join(this_dir_parent, "fixtures", path)
def example_path(path):
diff --git a/test/transform_test.py b/test/transform_test.py
index 2756487288..5c96ca29fb 100644
--- a/test/transform_test.py
+++ b/test/transform_test.py
@@ -450,6 +450,50 @@ def test_hsl(self):
for v1, v2 in zip(expected_rgb, actual_rgb):
self.assertAlmostEqual(v1, v2, delta=1)
+ surf = pygame.Surface((1, 1), flags=pygame.SRCALPHA)
+ dest = pygame.Surface((1, 1), flags=pygame.SRCALPHA)
+
+ alpha_colors = [
+ (0, 0, 0, 0),
+ (255, 255, 255, 255),
+ (255, 0, 0, 255),
+ (0, 255, 0, 255),
+ (0, 0, 255, 255),
+ (127, 127, 127, 127),
+ (11, 23, 34, 255),
+ (1, 0, 1, 255),
+ ]
+
+ for color in alpha_colors:
+ c_a = color[3]
+ for h in range(0, 360, 10):
+ for s in range(0, 100, 10):
+ for l in range(0, 100, 10):
+ surf.fill(color)
+ ns = s / 100.0
+ nl = l / 100.0
+
+ hsl_surf = pygame.transform.hsl(surf, h, ns, nl)
+ pygame.transform.hsl(surf, h, ns, nl, dest_surface=dest)
+
+ nh = h / 360.0
+ modified_hsl = modify_hsl(*rgb_to_hsl(color[:3]), nh, ns, nl)
+ expected_rgb = hsl_to_rgb(modified_hsl)
+
+ # without destination
+ actual_color = hsl_surf.get_at((0, 0))
+ actual_rgb = actual_color.rgb
+ for v1, v2 in zip(expected_rgb, actual_rgb):
+ self.assertAlmostEqual(v1, v2, delta=1)
+ self.assertEqual(c_a, actual_color.a)
+
+ # with destination
+ actual_color = dest.get_at((0, 0))
+ actual_rgb = actual_color.rgb
+ for v1, v2 in zip(expected_rgb, actual_rgb):
+ self.assertAlmostEqual(v1, v2, delta=1)
+ self.assertEqual(c_a, actual_color.a)
+
def test_grayscale_simd_assumptions(self):
# The grayscale SIMD algorithm relies on the destination surface pitch
# being exactly width * 4 (4 bytes per pixel), for maximum speed.
diff --git a/test/window_test.py b/test/window_test.py
index 5ac7f6aa5b..a06216714e 100644
--- a/test/window_test.py
+++ b/test/window_test.py
@@ -1,6 +1,7 @@
import unittest
import pygame
import os
+import platform
from pygame import Window
from pygame.version import SDL
@@ -8,6 +9,9 @@
pygame.init()
+IS_PYPY = "PyPy" == platform.python_implementation()
+
+
class WindowTypeTest(unittest.TestCase):
DEFAULT_TITLE = "pygame window"
@@ -325,6 +329,7 @@ def test_from_display_module(self):
pygame.display.quit()
pygame.init()
+ @unittest.skipIf(IS_PYPY, "for some reason this test is flaky on pypy")
def test_window_surface(self):
win = Window(size=(640, 480))
surf = win.get_surface()
@@ -342,6 +347,7 @@ def test_window_surface(self):
win.destroy()
self.assertRaises(pygame.error, lambda: surf.fill((0, 0, 0)))
+ @unittest.skipIf(IS_PYPY, "for some reason this test is flaky on pypy")
def test_window_surface_with_display_module(self):
# get_surface() should raise an error if the set_mode() is not called.
pygame.display.set_mode((640, 480))
@@ -397,6 +403,52 @@ def test_window_opengl(self):
pygame.display.quit()
pygame.init()
+ @unittest.skipIf(IS_PYPY, "for some reason this test is flaky on pypy")
+ def test_window_subclassable(self):
+ class WindowSubclass(Window):
+ def __init__(self, title="Different title", size=(640, 480), **flags):
+ super().__init__(title, size, pygame.WINDOWPOS_CENTERED, **flags)
+ self.attribute = 10
+
+ window = WindowSubclass()
+ self.assertTrue(issubclass(WindowSubclass, Window))
+ self.assertIsInstance(window, WindowSubclass)
+ self.assertEqual(window.title, "Different title")
+ self.assertEqual(window.attribute, 10)
+ window.destroy()
+
+ pygame.display.set_mode((200, 200))
+ window = WindowSubclass.from_display_module()
+ self.assertIsInstance(window, WindowSubclass)
+ self.assertEqual(window.size, (200, 200))
+
+ @unittest.skipIf(
+ SDL < (2, 0, 16),
+ "requires SDL 2.0.16+",
+ )
+ def test_window_flash(self):
+ window = pygame.Window()
+
+ with self.assertRaises(TypeError):
+ window.flash("string")
+ window.flash(2.2)
+ window.flash([0])
+
+ with self.assertRaises(ValueError):
+ window.flash(-1)
+ window.flash(3)
+
+ for operation in [
+ pygame.FLASH_CANCEL,
+ pygame.FLASH_BRIEFLY,
+ pygame.FLASH_UNTIL_FOCUSED,
+ ]:
+ try:
+ result = window.flash(operation)
+ self.assertIsNone(result)
+ except pygame.error:
+ pass
+
def tearDown(self):
self.win.destroy()