From cde0a36b1f37e568824bcc754aa448c18c66a5ba Mon Sep 17 00:00:00 2001 From: Michael Lamertz Date: Mon, 30 Jun 2025 19:18:12 +0200 Subject: [PATCH 01/13] Add logical_to_window and window_to_logical from SDL2 See https://wiki.libsdl.org/SDL2/SDL_RenderLogicalToWindow and https://wiki.libsdl.org/SDL2/SDL_RenderWindowToLogical When setting renderer.logical_size, these two functions offer translation between window space and logical space. --- docs/reST/ref/sdl2_video.rst | 16 ++++++++ src_c/doc/sdl2_video_doc.h | 2 + src_c/render.c | 74 ++++++++++++++++++++++++++++++++++++ test/render_test.py | 2 + 4 files changed, 94 insertions(+) diff --git a/docs/reST/ref/sdl2_video.rst b/docs/reST/ref/sdl2_video.rst index 99ce737c7f..5972585c29 100644 --- a/docs/reST/ref/sdl2_video.rst +++ b/docs/reST/ref/sdl2_video.rst @@ -424,6 +424,22 @@ drawing area on the target, or ``None`` to use the entire area of the current rendering target. + .. method:: logical_to_window + + | :sl:`Translates logical coordinates to window coordinates` + | :sg:`logical_to_window(logical_x, logical_y) -> (int, int)` + + :param logical_x: The x coordinate in logical space + :param logical_y: The y coordinate in logical space + + .. method:: window_to_logical + + | :sl:`Translates window coordinates to logical coordinates` + | :sg:`window_to_logical(window_x, window_y) -> (float, float)` + + :param window_x: The x coordinate in window space + :param window_y: The y coordinate in window space + .. method:: blit | :sl:`Draw textures using a Surface-like API` diff --git a/src_c/doc/sdl2_video_doc.h b/src_c/doc/sdl2_video_doc.h index 8ca553bdfc..f68b87eb84 100644 --- a/src_c/doc/sdl2_video_doc.h +++ b/src_c/doc/sdl2_video_doc.h @@ -41,6 +41,8 @@ #define DOC_SDL2_VIDEO_RENDERER_PRESENT "present() -> None\nUpdate the screen with any rendering performed since the previous call" #define DOC_SDL2_VIDEO_RENDERER_GETVIEWPORT "get_viewport() -> Rect\nGet the drawing area on the rendering target" #define DOC_SDL2_VIDEO_RENDERER_SETVIEWPORT "set_viewport(area) -> None\nSet the drawing area on the rendering target" +#define DOC_SDL2_VIDEO_RENDERER_LOGICALTOWINDOW "logical_to_window(logical_x, logical_y) -> (int, int)\nTranslates logical coordinates to window coordinates" +#define DOC_SDL2_VIDEO_RENDERER_WINDOWTOLOGICAL "window_to_logical(window_x, window_y) -> (float, float)\nTranslates window coordinates to logical coordinates" #define DOC_SDL2_VIDEO_RENDERER_BLIT "blit(source, dest, area=None, special_flags=0)-> Rect\nDraw textures using a Surface-like API" #define DOC_SDL2_VIDEO_RENDERER_DRAWLINE "draw_line(p1, p2) -> None\nDraw a line" #define DOC_SDL2_VIDEO_RENDERER_DRAWPOINT "draw_point(point) -> None\nDraw a point" diff --git a/src_c/render.c b/src_c/render.c index d237ced3e7..abbb455533 100644 --- a/src_c/render.c +++ b/src_c/render.c @@ -443,6 +443,76 @@ renderer_set_logical_size(pgRendererObject *self, PyObject *arg, void *closure) return 0; } +static PyObject * +renderer_logical_to_window(pgRendererObject *self, PyObject *const *args, + Py_ssize_t nargs) +{ + int x, y; + + if (nargs != 2) { + PyErr_SetString( + PyExc_TypeError, + "logical_to_window requires exactly 2 numeric arguments"); + return NULL; + } + + PyObject *logical_x = args[0]; + PyObject *logical_y = args[1]; + + double lx = PyFloat_AsDouble(logical_x); + if (PyErr_Occurred()) { + PyErr_SetString(PyExc_TypeError, + "The argument logical_x must be a number"); + return NULL; + } + + double ly = PyFloat_AsDouble(logical_y); + if (PyErr_Occurred()) { + PyErr_SetString(PyExc_TypeError, + "The argument logical_y must be a number"); + return NULL; + } + + SDL_RenderLogicalToWindow(self->renderer, lx, ly, &x, &y); + + return pg_tuple_couple_from_values_int(x, y); +} + +static PyObject * +renderer_window_to_logical(pgRendererObject *self, PyObject *const *args, + Py_ssize_t nargs) +{ + float x, y; + + if (nargs != 2) { + PyErr_SetString( + PyExc_TypeError, + "window_to_logical requires exactly 2 numeric arguments"); + return NULL; + } + + PyObject *window_x = args[0]; + PyObject *window_y = args[1]; + + int wx = (int)PyFloat_AsDouble(window_x); + if (PyErr_Occurred()) { + PyErr_SetString(PyExc_TypeError, + "The argument window_x must be a number"); + return NULL; + } + + int wy = (int)PyFloat_AsDouble(window_y); + if (PyErr_Occurred()) { + PyErr_SetString(PyExc_TypeError, + "The argument window_y must be a number"); + return NULL; + } + + SDL_RenderWindowToLogical(self->renderer, wx, wy, &x, &y); + + return pg_tuple_couple_from_values_int(x, y); +} + static PyObject * renderer_get_scale(pgRendererObject *self, void *closure) { @@ -582,6 +652,10 @@ static PyMethodDef renderer_methods[] = { METH_VARARGS | METH_KEYWORDS, DOC_SDL2_VIDEO_RENDERER_SETVIEWPORT}, {"get_viewport", (PyCFunction)renderer_get_viewport, METH_NOARGS, DOC_SDL2_VIDEO_RENDERER_GETVIEWPORT}, + {"logical_to_window", (PyCFunction)renderer_logical_to_window, + METH_FASTCALL, DOC_SDL2_VIDEO_RENDERER_LOGICALTOWINDOW}, + {"window_to_logical", (PyCFunction)renderer_window_to_logical, + METH_FASTCALL, DOC_SDL2_VIDEO_RENDERER_WINDOWTOLOGICAL}, {"compose_custom_blend_mode", (PyCFunction)renderer_compose_custom_blend_mode, METH_VARARGS | METH_KEYWORDS | METH_CLASS, diff --git a/test/render_test.py b/test/render_test.py index b04a635ddd..3674f7f450 100644 --- a/test/render_test.py +++ b/test/render_test.py @@ -165,6 +165,8 @@ def test_logical_size(self): self.assertEqual(self.renderer.logical_size, (0, 0)) self.renderer.logical_size = (10, 10) self.assertEqual(self.renderer.logical_size, (10, 10)) + self.assertEqual(self.renderer.logical_to_window(10, 10), (100, 100)) + self.assertEqual(self.renderer.window_to_logical(100, 100), (10, 10)) def test_scale(self): self.assertEqual(self.renderer.scale, (1.0, 1.0)) From a8a5b821f2a0c06efc823337d03afe79353bbd41 Mon Sep 17 00:00:00 2001 From: Michael Lamertz Date: Mon, 30 Jun 2025 20:10:38 +0200 Subject: [PATCH 02/13] Stubs added, return type of window_to_logical fixed --- buildconfig/stubs/pygame/_render.pyi | 2 ++ src_c/render.c | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/buildconfig/stubs/pygame/_render.pyi b/buildconfig/stubs/pygame/_render.pyi index 2a76653dec..8132e270af 100644 --- a/buildconfig/stubs/pygame/_render.pyi +++ b/buildconfig/stubs/pygame/_render.pyi @@ -40,6 +40,8 @@ class Renderer: def fill_rect(self, rect: RectLike) -> None: ... def fill_triangle(self, p1: Point, p2: Point, p3: Point) -> None: ... def get_viewport(self) -> Rect: ... + def logical_to_window(self, float: logical_x, float: logical_y) -> tuple[int, int]: ... + def window_to_logical(self, int: window_x, int: window_y) -> tuple[float, float]: ... def present(self) -> None: ... def set_viewport(self, area: Optional[RectLike]) -> None: ... def to_surface( diff --git a/src_c/render.c b/src_c/render.c index abbb455533..996eb7c369 100644 --- a/src_c/render.c +++ b/src_c/render.c @@ -510,7 +510,7 @@ renderer_window_to_logical(pgRendererObject *self, PyObject *const *args, SDL_RenderWindowToLogical(self->renderer, wx, wy, &x, &y); - return pg_tuple_couple_from_values_int(x, y); + return pg_tuple_couple_from_values_double(x, y); } static PyObject * From 1bc113b414d4448f0c365a0f2b2b655dc44ee376 Mon Sep 17 00:00:00 2001 From: Michael Lamertz Date: Mon, 30 Jun 2025 20:55:44 +0200 Subject: [PATCH 03/13] Fix devcheck by running `dev.py format` --- buildconfig/stubs/pygame/_render.pyi | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/buildconfig/stubs/pygame/_render.pyi b/buildconfig/stubs/pygame/_render.pyi index 8132e270af..6dbab4df67 100644 --- a/buildconfig/stubs/pygame/_render.pyi +++ b/buildconfig/stubs/pygame/_render.pyi @@ -40,8 +40,12 @@ class Renderer: def fill_rect(self, rect: RectLike) -> None: ... def fill_triangle(self, p1: Point, p2: Point, p3: Point) -> None: ... def get_viewport(self) -> Rect: ... - def logical_to_window(self, float: logical_x, float: logical_y) -> tuple[int, int]: ... - def window_to_logical(self, int: window_x, int: window_y) -> tuple[float, float]: ... + def logical_to_window( + self, float: logical_x, float: logical_y + ) -> tuple[int, int]: ... + def window_to_logical( + self, int: window_x, int: window_y + ) -> tuple[float, float]: ... def present(self) -> None: ... def set_viewport(self, area: Optional[RectLike]) -> None: ... def to_surface( From e8bc0951fcf9540f59da94497557f900c16c7b77 Mon Sep 17 00:00:00 2001 From: Michael Lamertz Date: Mon, 30 Jun 2025 21:32:59 +0200 Subject: [PATCH 04/13] Fix MS build that as warnings -> errors --- src_c/render.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src_c/render.c b/src_c/render.c index 996eb7c369..a67b6c6569 100644 --- a/src_c/render.c +++ b/src_c/render.c @@ -473,7 +473,7 @@ renderer_logical_to_window(pgRendererObject *self, PyObject *const *args, return NULL; } - SDL_RenderLogicalToWindow(self->renderer, lx, ly, &x, &y); + SDL_RenderLogicalToWindow(self->renderer, (float)lx, (float)ly, &x, &y); return pg_tuple_couple_from_values_int(x, y); } From c8c7aa8301aafdd77a08294569d9b533eb47d7b8 Mon Sep 17 00:00:00 2001 From: Michael Lamertz Date: Mon, 30 Jun 2025 21:34:18 +0200 Subject: [PATCH 05/13] Try to fix stubs... --- buildconfig/stubs/pygame/_render.pyi | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/buildconfig/stubs/pygame/_render.pyi b/buildconfig/stubs/pygame/_render.pyi index 6dbab4df67..e484eae4e4 100644 --- a/buildconfig/stubs/pygame/_render.pyi +++ b/buildconfig/stubs/pygame/_render.pyi @@ -41,10 +41,10 @@ class Renderer: def fill_triangle(self, p1: Point, p2: Point, p3: Point) -> None: ... def get_viewport(self) -> Rect: ... def logical_to_window( - self, float: logical_x, float: logical_y + self, logical_x: float, logical_y: float ) -> tuple[int, int]: ... def window_to_logical( - self, int: window_x, int: window_y + self, window_x: int, window_y: int ) -> tuple[float, float]: ... def present(self) -> None: ... def set_viewport(self, area: Optional[RectLike]) -> None: ... From 624bce8de9482cfdcfdf6007dc08e5d220fd993e Mon Sep 17 00:00:00 2001 From: Michael Lamertz Date: Wed, 2 Jul 2025 18:55:52 +0200 Subject: [PATCH 06/13] tuple instead of *args, SDL3 migration guide follow Both functions now expect a tuple, following the pygame conventions. For SDL3, the ported functions have been renamed and changed. > SDL_RenderWindowToLogical() and SDL_RenderLogicalToWindow() have been > renamed SDL_RenderCoordinatesFromWindow() and > SDL_RenderCoordinatesToWindow() and take floating point coordinates in > both directions. I settled on the following decisions * Regardless of the difference in the windows coordinates being ints in the old, and floats in the new version, I just aliased the new names to the old functions on the python side. * Even on SDL3, the window coordinates will be truncated to int to guarantee future compatibility with old code. --- buildconfig/stubs/pygame/_render.pyi | 10 ++- docs/reST/ref/sdl2_video.rst | 24 ++++++-- src_c/doc/sdl2_video_doc.h | 6 +- src_c/render.c | 92 +++++++++++++--------------- test/render_test.py | 10 ++- 5 files changed, 75 insertions(+), 67 deletions(-) diff --git a/buildconfig/stubs/pygame/_render.pyi b/buildconfig/stubs/pygame/_render.pyi index e484eae4e4..78b217d502 100644 --- a/buildconfig/stubs/pygame/_render.pyi +++ b/buildconfig/stubs/pygame/_render.pyi @@ -40,12 +40,10 @@ class Renderer: def fill_rect(self, rect: RectLike) -> None: ... def fill_triangle(self, p1: Point, p2: Point, p3: Point) -> None: ... def get_viewport(self) -> Rect: ... - def logical_to_window( - self, logical_x: float, logical_y: float - ) -> tuple[int, int]: ... - def window_to_logical( - self, window_x: int, window_y: int - ) -> tuple[float, float]: ... + def logical_to_window(self, point: Point) -> tuple[int, int]: ... + def window_to_logical(self, point: Point) -> tuple[float, float]: ... + def render_coordinates_to_window(self, point: Point) -> tuple[float, float]: ... + def render_coordinates_from_window(self, point: Point) -> tuple[float, float]: ... def present(self) -> None: ... def set_viewport(self, area: Optional[RectLike]) -> None: ... def to_surface( diff --git a/docs/reST/ref/sdl2_video.rst b/docs/reST/ref/sdl2_video.rst index 5972585c29..4310b3fe65 100644 --- a/docs/reST/ref/sdl2_video.rst +++ b/docs/reST/ref/sdl2_video.rst @@ -427,18 +427,30 @@ .. method:: logical_to_window | :sl:`Translates logical coordinates to window coordinates` - | :sg:`logical_to_window(logical_x, logical_y) -> (int, int)` + | :sg:`logical_to_window(point) -> (int, int)` - :param logical_x: The x coordinate in logical space - :param logical_y: The y coordinate in logical space + :param point: The coordinates in logical space. .. method:: window_to_logical | :sl:`Translates window coordinates to logical coordinates` - | :sg:`window_to_logical(window_x, window_y) -> (float, float)` + | :sg:`window_to_logical(point) -> (float, float)` - :param window_x: The x coordinate in window space - :param window_y: The y coordinate in window space + :param point: The coordinates in window space. + + .. method:: render_coordinates_to_window + + | :sl:`Alias for logical_to_window` + | :sg:`logical_to_window(point) -> (int, int)` + + :param point: The coordinates in logical space. + + .. method:: render_coordinates_from_window + + | :sl:`Alias for window_to_logical` + | :sg:`window_to_logical(point) -> (float, float)` + + :param point: The coordinates in window space. .. method:: blit diff --git a/src_c/doc/sdl2_video_doc.h b/src_c/doc/sdl2_video_doc.h index f68b87eb84..83e557132d 100644 --- a/src_c/doc/sdl2_video_doc.h +++ b/src_c/doc/sdl2_video_doc.h @@ -41,8 +41,10 @@ #define DOC_SDL2_VIDEO_RENDERER_PRESENT "present() -> None\nUpdate the screen with any rendering performed since the previous call" #define DOC_SDL2_VIDEO_RENDERER_GETVIEWPORT "get_viewport() -> Rect\nGet the drawing area on the rendering target" #define DOC_SDL2_VIDEO_RENDERER_SETVIEWPORT "set_viewport(area) -> None\nSet the drawing area on the rendering target" -#define DOC_SDL2_VIDEO_RENDERER_LOGICALTOWINDOW "logical_to_window(logical_x, logical_y) -> (int, int)\nTranslates logical coordinates to window coordinates" -#define DOC_SDL2_VIDEO_RENDERER_WINDOWTOLOGICAL "window_to_logical(window_x, window_y) -> (float, float)\nTranslates window coordinates to logical coordinates" +#define DOC_SDL2_VIDEO_RENDERER_LOGICALTOWINDOW "logical_to_window(point) -> (int, int)\nTranslates logical coordinates to window coordinates" +#define DOC_SDL2_VIDEO_RENDERER_WINDOWTOLOGICAL "window_to_logical(point) -> (float, float)\nTranslates window coordinates to logical coordinates" +#define DOC_SDL2_VIDEO_RENDERER_RENDERCOORDINATESTOWINDOW "logical_to_window(point) -> (int, int)\nAlias for logical_to_window" +#define DOC_SDL2_VIDEO_RENDERER_RENDERCOORDINATESFROMWINDOW "window_to_logical(point) -> (float, float)\nAlias for window_to_logical" #define DOC_SDL2_VIDEO_RENDERER_BLIT "blit(source, dest, area=None, special_flags=0)-> Rect\nDraw textures using a Surface-like API" #define DOC_SDL2_VIDEO_RENDERER_DRAWLINE "draw_line(p1, p2) -> None\nDraw a line" #define DOC_SDL2_VIDEO_RENDERER_DRAWPOINT "draw_point(point) -> None\nDraw a point" diff --git a/src_c/render.c b/src_c/render.c index a67b6c6569..7992171616 100644 --- a/src_c/render.c +++ b/src_c/render.c @@ -444,73 +444,56 @@ renderer_set_logical_size(pgRendererObject *self, PyObject *arg, void *closure) } static PyObject * -renderer_logical_to_window(pgRendererObject *self, PyObject *const *args, - Py_ssize_t nargs) +renderer_logical_to_window(pgRendererObject *self, PyObject *args, + PyObject *kwargs) { - int x, y; + float lx, ly; + PyObject *point; - if (nargs != 2) { - PyErr_SetString( - PyExc_TypeError, - "logical_to_window requires exactly 2 numeric arguments"); + static char *keywords[] = {"point", NULL}; + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O", keywords, &point)) { return NULL; } - - PyObject *logical_x = args[0]; - PyObject *logical_y = args[1]; - - double lx = PyFloat_AsDouble(logical_x); - if (PyErr_Occurred()) { - PyErr_SetString(PyExc_TypeError, - "The argument logical_x must be a number"); - return NULL; + if (!pg_TwoFloatsFromObj(point, &lx, &ly)) { + return RAISE(PyExc_TypeError, "invalid argument"); } - double ly = PyFloat_AsDouble(logical_y); - if (PyErr_Occurred()) { - PyErr_SetString(PyExc_TypeError, - "The argument logical_y must be a number"); - return NULL; - } +#if SDL_VERSION_ATLEAST(3, 0, 0) + float wx, wy; + SDL_RenderCoordinatesToWindow(self->renderer, lx, ly, &wx, &wy); - SDL_RenderLogicalToWindow(self->renderer, (float)lx, (float)ly, &x, &y); + return pg_tuple_couple_from_values_int((int)wx, (int)wy); +#else + int wx, wy; + SDL_RenderLogicalToWindow(self->renderer, lx, ly, &wx, &wy); - return pg_tuple_couple_from_values_int(x, y); + return pg_tuple_couple_from_values_int(wx, wy); +#endif } static PyObject * -renderer_window_to_logical(pgRendererObject *self, PyObject *const *args, - Py_ssize_t nargs) +renderer_window_to_logical(pgRendererObject *self, PyObject *args, + PyObject *kwargs) { - float x, y; - - if (nargs != 2) { - PyErr_SetString( - PyExc_TypeError, - "window_to_logical requires exactly 2 numeric arguments"); - return NULL; - } - - PyObject *window_x = args[0]; - PyObject *window_y = args[1]; + float lx, ly; + float wx, wy; + PyObject *point; - int wx = (int)PyFloat_AsDouble(window_x); - if (PyErr_Occurred()) { - PyErr_SetString(PyExc_TypeError, - "The argument window_x must be a number"); + static char *keywords[] = {"point", NULL}; + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O", keywords, &point)) { return NULL; } - - int wy = (int)PyFloat_AsDouble(window_y); - if (PyErr_Occurred()) { - PyErr_SetString(PyExc_TypeError, - "The argument window_y must be a number"); - return NULL; + if (!pg_TwoFloatsFromObj(point, &wx, &wy)) { + return RAISE(PyExc_TypeError, "invalid argument"); } - SDL_RenderWindowToLogical(self->renderer, wx, wy, &x, &y); +#if SDL_VERSION_ATLEAST(3, 0, 0) + SDL_RenderCoordinatesFromWindow(self->renderer, wx, wy, &lx, &ly); +#else + SDL_RenderWindowToLogical(self->renderer, (int)wx, (int)wy, &lx, &ly); +#endif - return pg_tuple_couple_from_values_double(x, y); + return pg_tuple_couple_from_values_double(lx, ly); } static PyObject * @@ -653,9 +636,16 @@ static PyMethodDef renderer_methods[] = { {"get_viewport", (PyCFunction)renderer_get_viewport, METH_NOARGS, DOC_SDL2_VIDEO_RENDERER_GETVIEWPORT}, {"logical_to_window", (PyCFunction)renderer_logical_to_window, - METH_FASTCALL, DOC_SDL2_VIDEO_RENDERER_LOGICALTOWINDOW}, + METH_VARARGS | METH_KEYWORDS, DOC_SDL2_VIDEO_RENDERER_LOGICALTOWINDOW}, {"window_to_logical", (PyCFunction)renderer_window_to_logical, - METH_FASTCALL, DOC_SDL2_VIDEO_RENDERER_WINDOWTOLOGICAL}, + METH_VARARGS | METH_KEYWORDS, DOC_SDL2_VIDEO_RENDERER_WINDOWTOLOGICAL}, + /* Next 2 are aliased from above for SDL2 -> SDL3 transition */ + {"render_coordinates_to_window", (PyCFunction)renderer_logical_to_window, + METH_VARARGS | METH_KEYWORDS, + DOC_SDL2_VIDEO_RENDERER_RENDERCOORDINATESTOWINDOW}, + {"render_coordinates_from_window", (PyCFunction)renderer_window_to_logical, + METH_VARARGS | METH_KEYWORDS, + DOC_SDL2_VIDEO_RENDERER_RENDERCOORDINATESFROMWINDOW}, {"compose_custom_blend_mode", (PyCFunction)renderer_compose_custom_blend_mode, METH_VARARGS | METH_KEYWORDS | METH_CLASS, diff --git a/test/render_test.py b/test/render_test.py index 3674f7f450..e5602f0b04 100644 --- a/test/render_test.py +++ b/test/render_test.py @@ -165,8 +165,14 @@ def test_logical_size(self): self.assertEqual(self.renderer.logical_size, (0, 0)) self.renderer.logical_size = (10, 10) self.assertEqual(self.renderer.logical_size, (10, 10)) - self.assertEqual(self.renderer.logical_to_window(10, 10), (100, 100)) - self.assertEqual(self.renderer.window_to_logical(100, 100), (10, 10)) + self.assertEqual(self.renderer.logical_to_window((10, 10)), (100, 100)) + self.assertEqual(self.renderer.window_to_logical((100, 100)), (10, 10)) + self.assertEqual( + self.renderer.render_coordinates_to_window((10, 10)), (100, 100) + ) + self.assertEqual( + self.renderer.render_coordinates_from_window((100, 100)), (10, 10) + ) def test_scale(self): self.assertEqual(self.renderer.scale, (1.0, 1.0)) From 78f305025aee3aca27f1590706d409585ae68cfe Mon Sep 17 00:00:00 2001 From: Michael Lamertz Date: Wed, 2 Jul 2025 19:38:29 +0200 Subject: [PATCH 07/13] Accept and return floats everywhere, sdl3 aliases removed --- buildconfig/stubs/pygame/_render.pyi | 4 +--- docs/reST/ref/sdl2_video.rst | 16 +--------------- src_c/doc/sdl2_video_doc.h | 4 +--- src_c/render.c | 11 ++--------- test/render_test.py | 6 ------ 5 files changed, 5 insertions(+), 36 deletions(-) diff --git a/buildconfig/stubs/pygame/_render.pyi b/buildconfig/stubs/pygame/_render.pyi index 78b217d502..bb4d87f645 100644 --- a/buildconfig/stubs/pygame/_render.pyi +++ b/buildconfig/stubs/pygame/_render.pyi @@ -40,10 +40,8 @@ class Renderer: def fill_rect(self, rect: RectLike) -> None: ... def fill_triangle(self, p1: Point, p2: Point, p3: Point) -> None: ... def get_viewport(self) -> Rect: ... - def logical_to_window(self, point: Point) -> tuple[int, int]: ... + def logical_to_window(self, point: Point) -> tuple[float, float]: ... def window_to_logical(self, point: Point) -> tuple[float, float]: ... - def render_coordinates_to_window(self, point: Point) -> tuple[float, float]: ... - def render_coordinates_from_window(self, point: Point) -> tuple[float, float]: ... def present(self) -> None: ... def set_viewport(self, area: Optional[RectLike]) -> None: ... def to_surface( diff --git a/docs/reST/ref/sdl2_video.rst b/docs/reST/ref/sdl2_video.rst index 4310b3fe65..e628b87ba8 100644 --- a/docs/reST/ref/sdl2_video.rst +++ b/docs/reST/ref/sdl2_video.rst @@ -427,7 +427,7 @@ .. method:: logical_to_window | :sl:`Translates logical coordinates to window coordinates` - | :sg:`logical_to_window(point) -> (int, int)` + | :sg:`logical_to_window(point) -> (float, float)` :param point: The coordinates in logical space. @@ -438,20 +438,6 @@ :param point: The coordinates in window space. - .. method:: render_coordinates_to_window - - | :sl:`Alias for logical_to_window` - | :sg:`logical_to_window(point) -> (int, int)` - - :param point: The coordinates in logical space. - - .. method:: render_coordinates_from_window - - | :sl:`Alias for window_to_logical` - | :sg:`window_to_logical(point) -> (float, float)` - - :param point: The coordinates in window space. - .. method:: blit | :sl:`Draw textures using a Surface-like API` diff --git a/src_c/doc/sdl2_video_doc.h b/src_c/doc/sdl2_video_doc.h index 83e557132d..3fd07689aa 100644 --- a/src_c/doc/sdl2_video_doc.h +++ b/src_c/doc/sdl2_video_doc.h @@ -41,10 +41,8 @@ #define DOC_SDL2_VIDEO_RENDERER_PRESENT "present() -> None\nUpdate the screen with any rendering performed since the previous call" #define DOC_SDL2_VIDEO_RENDERER_GETVIEWPORT "get_viewport() -> Rect\nGet the drawing area on the rendering target" #define DOC_SDL2_VIDEO_RENDERER_SETVIEWPORT "set_viewport(area) -> None\nSet the drawing area on the rendering target" -#define DOC_SDL2_VIDEO_RENDERER_LOGICALTOWINDOW "logical_to_window(point) -> (int, int)\nTranslates logical coordinates to window coordinates" +#define DOC_SDL2_VIDEO_RENDERER_LOGICALTOWINDOW "logical_to_window(point) -> (float, float)\nTranslates logical coordinates to window coordinates" #define DOC_SDL2_VIDEO_RENDERER_WINDOWTOLOGICAL "window_to_logical(point) -> (float, float)\nTranslates window coordinates to logical coordinates" -#define DOC_SDL2_VIDEO_RENDERER_RENDERCOORDINATESTOWINDOW "logical_to_window(point) -> (int, int)\nAlias for logical_to_window" -#define DOC_SDL2_VIDEO_RENDERER_RENDERCOORDINATESFROMWINDOW "window_to_logical(point) -> (float, float)\nAlias for window_to_logical" #define DOC_SDL2_VIDEO_RENDERER_BLIT "blit(source, dest, area=None, special_flags=0)-> Rect\nDraw textures using a Surface-like API" #define DOC_SDL2_VIDEO_RENDERER_DRAWLINE "draw_line(p1, p2) -> None\nDraw a line" #define DOC_SDL2_VIDEO_RENDERER_DRAWPOINT "draw_point(point) -> None\nDraw a point" diff --git a/src_c/render.c b/src_c/render.c index 7992171616..29ed0d542a 100644 --- a/src_c/render.c +++ b/src_c/render.c @@ -462,12 +462,12 @@ renderer_logical_to_window(pgRendererObject *self, PyObject *args, float wx, wy; SDL_RenderCoordinatesToWindow(self->renderer, lx, ly, &wx, &wy); - return pg_tuple_couple_from_values_int((int)wx, (int)wy); + return pg_tuple_couple_from_values_float(wx, wy); #else int wx, wy; SDL_RenderLogicalToWindow(self->renderer, lx, ly, &wx, &wy); - return pg_tuple_couple_from_values_int(wx, wy); + return pg_tuple_couple_from_values_float((float)wx, (float)wy); #endif } @@ -639,13 +639,6 @@ static PyMethodDef renderer_methods[] = { METH_VARARGS | METH_KEYWORDS, DOC_SDL2_VIDEO_RENDERER_LOGICALTOWINDOW}, {"window_to_logical", (PyCFunction)renderer_window_to_logical, METH_VARARGS | METH_KEYWORDS, DOC_SDL2_VIDEO_RENDERER_WINDOWTOLOGICAL}, - /* Next 2 are aliased from above for SDL2 -> SDL3 transition */ - {"render_coordinates_to_window", (PyCFunction)renderer_logical_to_window, - METH_VARARGS | METH_KEYWORDS, - DOC_SDL2_VIDEO_RENDERER_RENDERCOORDINATESTOWINDOW}, - {"render_coordinates_from_window", (PyCFunction)renderer_window_to_logical, - METH_VARARGS | METH_KEYWORDS, - DOC_SDL2_VIDEO_RENDERER_RENDERCOORDINATESFROMWINDOW}, {"compose_custom_blend_mode", (PyCFunction)renderer_compose_custom_blend_mode, METH_VARARGS | METH_KEYWORDS | METH_CLASS, diff --git a/test/render_test.py b/test/render_test.py index e5602f0b04..dbe02828e3 100644 --- a/test/render_test.py +++ b/test/render_test.py @@ -167,12 +167,6 @@ def test_logical_size(self): self.assertEqual(self.renderer.logical_size, (10, 10)) self.assertEqual(self.renderer.logical_to_window((10, 10)), (100, 100)) self.assertEqual(self.renderer.window_to_logical((100, 100)), (10, 10)) - self.assertEqual( - self.renderer.render_coordinates_to_window((10, 10)), (100, 100) - ) - self.assertEqual( - self.renderer.render_coordinates_from_window((100, 100)), (10, 10) - ) def test_scale(self): self.assertEqual(self.renderer.scale, (1.0, 1.0)) From d3599707f222e149963516969e1b764a1e4c2d0f Mon Sep 17 00:00:00 2001 From: Michael Lamertz Date: Wed, 2 Jul 2025 19:44:10 +0200 Subject: [PATCH 08/13] double, not float! --- src_c/render.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src_c/render.c b/src_c/render.c index 29ed0d542a..62acf87bce 100644 --- a/src_c/render.c +++ b/src_c/render.c @@ -462,12 +462,12 @@ renderer_logical_to_window(pgRendererObject *self, PyObject *args, float wx, wy; SDL_RenderCoordinatesToWindow(self->renderer, lx, ly, &wx, &wy); - return pg_tuple_couple_from_values_float(wx, wy); + return pg_tuple_couple_from_values_double(wx, wy); #else int wx, wy; SDL_RenderLogicalToWindow(self->renderer, lx, ly, &wx, &wy); - return pg_tuple_couple_from_values_float((float)wx, (float)wy); + return pg_tuple_couple_from_values_double((float)wx, (float)wy); #endif } From c80750e1bcfed3806cfdfe0929eabbd39cbb91e6 Mon Sep 17 00:00:00 2001 From: Michael Lamertz Date: Sat, 12 Jul 2025 18:34:46 +0200 Subject: [PATCH 09/13] Wording of error messages follows review suggestion --- src_c/render.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src_c/render.c b/src_c/render.c index 62acf87bce..bda1f81c25 100644 --- a/src_c/render.c +++ b/src_c/render.c @@ -455,7 +455,7 @@ renderer_logical_to_window(pgRendererObject *self, PyObject *args, return NULL; } if (!pg_TwoFloatsFromObj(point, &lx, &ly)) { - return RAISE(PyExc_TypeError, "invalid argument"); + return RAISE(PyExc_TypeError, "point must be a sequence of two numbers"); } #if SDL_VERSION_ATLEAST(3, 0, 0) @@ -484,7 +484,7 @@ renderer_window_to_logical(pgRendererObject *self, PyObject *args, return NULL; } if (!pg_TwoFloatsFromObj(point, &wx, &wy)) { - return RAISE(PyExc_TypeError, "invalid argument"); + return RAISE(PyExc_TypeError, "point must be a sequence of two numbers"); } #if SDL_VERSION_ATLEAST(3, 0, 0) From 048fde44e3c4e49174362d4dc042f38680e5e43f Mon Sep 17 00:00:00 2001 From: Michael Lamertz Date: Sat, 12 Jul 2025 19:14:09 +0200 Subject: [PATCH 10/13] modified test according to review --- test/render_test.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test/render_test.py b/test/render_test.py index dbe02828e3..1d5bbd9107 100644 --- a/test/render_test.py +++ b/test/render_test.py @@ -165,8 +165,15 @@ def test_logical_size(self): self.assertEqual(self.renderer.logical_size, (0, 0)) self.renderer.logical_size = (10, 10) self.assertEqual(self.renderer.logical_size, (10, 10)) + + def test_logical_window_mapping(self): + self.renderer.logical_size = (10,10) self.assertEqual(self.renderer.logical_to_window((10, 10)), (100, 100)) self.assertEqual(self.renderer.window_to_logical((100, 100)), (10, 10)) + with self.assertRaises(TypeError): + self.renderer.logical_to_window(42, 42) + with self.assertRaises(TypeError): + self.renderer.window_to_logical(42, 42) def test_scale(self): self.assertEqual(self.renderer.scale, (1.0, 1.0)) From 259f70041126509b24696b9e2076e1481b055bc1 Mon Sep 17 00:00:00 2001 From: Michael Lamertz Date: Sat, 12 Jul 2025 19:18:49 +0200 Subject: [PATCH 11/13] dev.py format --- src_c/render.c | 6 ++++-- test/render_test.py | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src_c/render.c b/src_c/render.c index bda1f81c25..15245eaa7c 100644 --- a/src_c/render.c +++ b/src_c/render.c @@ -455,7 +455,8 @@ renderer_logical_to_window(pgRendererObject *self, PyObject *args, return NULL; } if (!pg_TwoFloatsFromObj(point, &lx, &ly)) { - return RAISE(PyExc_TypeError, "point must be a sequence of two numbers"); + return RAISE(PyExc_TypeError, + "point must be a sequence of two numbers"); } #if SDL_VERSION_ATLEAST(3, 0, 0) @@ -484,7 +485,8 @@ renderer_window_to_logical(pgRendererObject *self, PyObject *args, return NULL; } if (!pg_TwoFloatsFromObj(point, &wx, &wy)) { - return RAISE(PyExc_TypeError, "point must be a sequence of two numbers"); + return RAISE(PyExc_TypeError, + "point must be a sequence of two numbers"); } #if SDL_VERSION_ATLEAST(3, 0, 0) diff --git a/test/render_test.py b/test/render_test.py index 1d5bbd9107..1e527d8b6b 100644 --- a/test/render_test.py +++ b/test/render_test.py @@ -167,7 +167,7 @@ def test_logical_size(self): self.assertEqual(self.renderer.logical_size, (10, 10)) def test_logical_window_mapping(self): - self.renderer.logical_size = (10,10) + self.renderer.logical_size = (10, 10) self.assertEqual(self.renderer.logical_to_window((10, 10)), (100, 100)) self.assertEqual(self.renderer.window_to_logical((100, 100)), (10, 10)) with self.assertRaises(TypeError): From b9c6e37d1d5f869cd602e9ab239e70332f644958 Mon Sep 17 00:00:00 2001 From: Michael Lamertz Date: Fri, 18 Jul 2025 14:09:54 +0200 Subject: [PATCH 12/13] logical_to_window and window_to_logical for _sdl2.video --- buildconfig/stubs/pygame/_sdl2/video.pyi | 2 ++ src_c/cython/pygame/_sdl2/video.pxd | 16 +++++++++++++ src_c/cython/pygame/_sdl2/video.pyx | 29 ++++++++++++++++++++++++ test/video_test.py | 12 ++++++++++ 4 files changed, 59 insertions(+) diff --git a/buildconfig/stubs/pygame/_sdl2/video.pyi b/buildconfig/stubs/pygame/_sdl2/video.pyi index e9ce224759..28c74caa18 100644 --- a/buildconfig/stubs/pygame/_sdl2/video.pyi +++ b/buildconfig/stubs/pygame/_sdl2/video.pyi @@ -146,6 +146,8 @@ class Renderer: def get_viewport(self) -> Rect: ... def set_viewport(self, area: Optional[RectLike]) -> None: ... logical_size: Iterable[int] + def logical_to_window(self, point: Point) -> tuple[float, float]: ... + def window_to_logical(self, point: Point) -> tuple[float, float]: ... scale: Iterable[float] target: Optional[Texture] def blit( diff --git a/src_c/cython/pygame/_sdl2/video.pxd b/src_c/cython/pygame/_sdl2/video.pxd index 1d87ba43b2..1fe2fc6354 100644 --- a/src_c/cython/pygame/_sdl2/video.pxd +++ b/src_c/cython/pygame/_sdl2/video.pxd @@ -369,6 +369,8 @@ cdef extern from "SDL.h" nogil: # https://wiki.libsdl.org/SDL_RenderSetLogicalSize # https://wiki.libsdl.org/SDL_RenderGetLogicalSize # https://wiki.libsdl.org/SDL_RenderGetIntegerScale + # https://wiki.libsdl.org/SDL2/SDL_RenderLogicalToWindow + # https://wiki.libsdl.org/SDL2/SDL_RenderWindowToLogical int SDL_RenderSetScale(SDL_Renderer* renderer, float scaleX, float scaleY) @@ -382,6 +384,20 @@ cdef extern from "SDL.h" nogil: int* w, int* h) int SDL_RenderGetIntegerScale(SDL_Renderer* renderer) + # Note: Must be changed to SDL_RenderCoordinatesToWindow for SDL3 + # https://wiki.libsdl.org/SDL3/SDL_RenderCoordinatesToWindow + void SDL_RenderLogicalToWindow(SDL_Renderer* renderer, + float lx, + float ly, + int *wx, + int *wy); + # Note: Must be changed to SDL_RenderCoordinatesFromWindow for SDL3 + # https://wiki.libsdl.org/SDL3/SDL_RenderCoordinatesFromWindow + void SDL_RenderWindowToLogical(SDL_Renderer* renderer, + int wx, + int wy, + float *lx, + float *ly); int SDL_VERSION_ATLEAST(int major, int minor, int patch) diff --git a/src_c/cython/pygame/_sdl2/video.pyx b/src_c/cython/pygame/_sdl2/video.pyx index af8ea28e1e..a0f9db9fd8 100644 --- a/src_c/cython/pygame/_sdl2/video.pyx +++ b/src_c/cython/pygame/_sdl2/video.pyx @@ -1037,6 +1037,35 @@ cdef class Renderer: if res < 0: raise error() + def logical_to_window(self, point): + """Translates logical coordinates to window coordinates + + :param point: The coordinates in logical space. + """ + cdef int wx + cdef int wy + + # Note: Must be changed to SDL_RenderCoordinatesToWindow for SDL3 + # https://wiki.libsdl.org/SDL3/SDL_RenderCoordinatesToWindow + SDL_RenderLogicalToWindow(self._renderer, point[0], point[1], &wx, &wy); + + # Return float for future compatibility with SDL3's RenderCoordinatesToWindow + return (float(wx), float(wy)) + + def window_to_logical(self, point): + """Translates window coordinates to logical coordinates + + :param point: The coordinates in window space. + """ + cdef float lx + cdef float ly + + # Note: Must be changed to SDL_RenderCoordinatesFromWindow for SDL3 + # https://wiki.libsdl.org/SDL3/SDL_RenderCoordinatesFromWindow + SDL_RenderWindowToLogical(self._renderer, point[0], point[1], &lx, &ly); + + return (lx, ly) + def draw_point(self, point): """Draw a point diff --git a/test/video_test.py b/test/video_test.py index f6876bc56c..9787b4e245 100644 --- a/test/video_test.py +++ b/test/video_test.py @@ -24,6 +24,18 @@ def test_renderer_set_viewport(self): renderer.set_viewport(rect) self.assertEqual(renderer.get_viewport(), (0, 0, 1920, 1080)) + def test_logical_window_mapping(self): + window = video.Window(title=self.default_caption, size=(100, 100)) + renderer = video.Renderer(window=window) + renderer.logical_size = (10, 10) + + self.assertEqual(renderer.logical_to_window((10, 10)), (100, 100)) + self.assertEqual(renderer.window_to_logical((100, 100)), (10, 10)) + with self.assertRaises(TypeError): + renderer.logical_to_window(42, 42) + with self.assertRaises(TypeError): + renderer.window_to_logical(42, 42) + @unittest.skipIf(IS_PYPY, "PyPy doesn't have sys.getrefcount") def test_renderer_to_surface_refcount(self): """Make sure to_surface doesn't leak memory due to reference counting.""" From d276fc5b3c0509c8031e193f7176d94ac60aa89d Mon Sep 17 00:00:00 2001 From: Michael Lamertz Date: Fri, 18 Jul 2025 15:30:56 +0200 Subject: [PATCH 13/13] versionadded added --- docs/reST/ref/sdl2_video.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/reST/ref/sdl2_video.rst b/docs/reST/ref/sdl2_video.rst index e628b87ba8..ea25eeb865 100644 --- a/docs/reST/ref/sdl2_video.rst +++ b/docs/reST/ref/sdl2_video.rst @@ -431,6 +431,9 @@ :param point: The coordinates in logical space. + .. versionadded:: 2.5.6 + + .. method:: window_to_logical | :sl:`Translates window coordinates to logical coordinates` @@ -438,6 +441,8 @@ :param point: The coordinates in window space. + .. versionadded:: 2.5.6 + .. method:: blit | :sl:`Draw textures using a Surface-like API`