Skip to content

Commit 1565a39

Browse files
committed
Added area kwarg to mask.to_surface
1 parent 0b542bc commit 1565a39

File tree

5 files changed

+107
-13
lines changed

5 files changed

+107
-13
lines changed

buildconfig/stubs/pygame/mask.pyi

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ class Mask:
5252
setcolor: Optional[ColorValue] = (255, 255, 255, 255),
5353
unsetcolor: Optional[ColorValue] = (0, 0, 0, 255),
5454
dest: Union[RectValue, Coordinate] = (0, 0),
55+
area: Optional[RectValue] = None,
5556
) -> Surface: ...
5657

5758
MaskType = Mask

docs/reST/ref/mask.rst

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -570,7 +570,7 @@ to store which parts collide.
570570

571571
| :sl:`Returns a surface with the mask drawn on it`
572572
| :sg:`to_surface() -> Surface`
573-
| :sg:`to_surface(surface=None, setsurface=None, unsetsurface=None, setcolor=(255, 255, 255, 255), unsetcolor=(0, 0, 0, 255), dest=(0, 0)) -> Surface`
573+
| :sg:`to_surface(surface=None, setsurface=None, unsetsurface=None, setcolor=(255, 255, 255, 255), unsetcolor=(0, 0, 0, 255), dest=(0, 0), area=None) -> Surface`
574574
575575
Draws this mask on the given surface. Set bits (bits set to 1) and unset
576576
bits (bits set to 0) can be drawn onto a surface.
@@ -612,6 +612,12 @@ to store which parts collide.
612612
mask (i.e. position ``(0, 0)`` on the mask always corresponds to
613613
position ``(0, 0)`` on the ``setsurface`` and ``unsetsurface``)
614614
:type dest: Rect or tuple(int, int) or list(int, int) or Vector2(int, int)
615+
:param area: (optional) rectangular portion of the mask to draw. It can be a
616+
rect-like object (a Rect, a tuple or a list with 4 numbers or an object with a
617+
rect attribute, etc) or it can be None (the default) in which case it will use the
618+
entire mask. Just like with Surface.blit, if the rect's topleft is negative
619+
the final destination will be ``dest - rect.topleft``.
620+
:type area: Rect or rect-like object
615621

616622
:returns: the ``surface`` parameter (or a newly created surface if no
617623
``surface`` parameter was provided) with this mask drawn on it

src_c/doc/mask_doc.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,4 @@
2525
#define DOC_MASK_MASK_CONNECTEDCOMPONENT "connected_component() -> Mask\nconnected_component(pos) -> Mask\nReturns a mask containing a connected component"
2626
#define DOC_MASK_MASK_CONNECTEDCOMPONENTS "connected_components() -> [Mask, ...]\nconnected_components(minimum=0) -> [Mask, ...]\nReturns a list of masks of connected components"
2727
#define DOC_MASK_MASK_GETBOUNDINGRECTS "get_bounding_rects() -> [Rect, ...]\nReturns a list of bounding rects of connected components"
28-
#define DOC_MASK_MASK_TOSURFACE "to_surface() -> Surface\nto_surface(surface=None, setsurface=None, unsetsurface=None, setcolor=(255, 255, 255, 255), unsetcolor=(0, 0, 0, 255), dest=(0, 0)) -> Surface\nReturns a surface with the mask drawn on it"
28+
#define DOC_MASK_MASK_TOSURFACE "to_surface() -> Surface\nto_surface(surface=None, setsurface=None, unsetsurface=None, setcolor=(255, 255, 255, 255), unsetcolor=(0, 0, 0, 255), dest=(0, 0), area=None) -> Surface\nReturns a surface with the mask drawn on it"

src_c/mask.c

Lines changed: 44 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1895,8 +1895,9 @@ extract_color(SDL_Surface *surf, PyObject *color_obj, Uint8 rgba_color[],
18951895
*/
18961896
static void
18971897
draw_to_surface(SDL_Surface *surf, bitmask_t *bitmask, int x_dest, int y_dest,
1898-
int draw_setbits, int draw_unsetbits, SDL_Surface *setsurf,
1899-
SDL_Surface *unsetsurf, Uint32 *setcolor, Uint32 *unsetcolor)
1898+
SDL_Rect *area_rect, int draw_setbits, int draw_unsetbits,
1899+
SDL_Surface *setsurf, SDL_Surface *unsetsurf, Uint32 *setcolor,
1900+
Uint32 *unsetcolor)
19001901
{
19011902
Uint8 *pixel = NULL;
19021903
Uint8 bpp;
@@ -1912,6 +1913,15 @@ draw_to_surface(SDL_Surface *surf, bitmask_t *bitmask, int x_dest, int y_dest,
19121913
return;
19131914
}
19141915

1916+
if (area_rect->x < 0) {
1917+
x_dest -= area_rect->x;
1918+
area_rect->w += area_rect->x;
1919+
}
1920+
if (area_rect->y < 0) {
1921+
y_dest -= area_rect->y;
1922+
area_rect->h += area_rect->y;
1923+
}
1924+
19151925
/* There is also nothing to do when the destination position is such that
19161926
* nothing will be drawn on the surface. */
19171927
if ((x_dest >= surf->w) || (y_dest >= surf->h) || (-x_dest > bitmask->w) ||
@@ -1921,13 +1931,23 @@ draw_to_surface(SDL_Surface *surf, bitmask_t *bitmask, int x_dest, int y_dest,
19211931

19221932
bpp = surf->format->BytesPerPixel;
19231933

1934+
// clamp rect width and height to not stick out of the mask
1935+
area_rect->w = MIN(area_rect->w, bitmask->w - area_rect->x);
1936+
area_rect->h = MIN(area_rect->h, bitmask->h - area_rect->y);
1937+
19241938
xm_start = (x_dest < 0) ? -x_dest : 0;
1939+
if (area_rect->x > 0) {
1940+
xm_start += area_rect->x;
1941+
}
19251942
x_start = (x_dest > 0) ? x_dest : 0;
1926-
x_end = MIN(surf->w, bitmask->w + x_dest);
1943+
x_end = MIN(MIN(surf->w, bitmask->w + x_dest), x_dest + area_rect->w);
19271944

19281945
ym_start = (y_dest < 0) ? -y_dest : 0;
1946+
if (area_rect->y > 0) {
1947+
ym_start += area_rect->y;
1948+
}
19291949
y_start = (y_dest > 0) ? y_dest : 0;
1930-
y_end = MIN(surf->h, bitmask->h + y_dest);
1950+
y_end = MIN(MIN(surf->h, bitmask->h + y_dest), y_dest + area_rect->h);
19311951

19321952
if (NULL == setsurf && NULL == unsetsurf) {
19331953
/* Draw just using color values. No surfaces. */
@@ -2074,7 +2094,8 @@ mask_to_surface(PyObject *self, PyObject *args, PyObject *kwargs)
20742094
{
20752095
PyObject *surfobj = Py_None, *setcolorobj = NULL, *unsetcolorobj = NULL;
20762096
PyObject *setsurfobj = Py_None, *unsetsurfobj = Py_None;
2077-
PyObject *destobj = NULL;
2097+
PyObject *destobj = NULL, *areaobj = NULL;
2098+
SDL_Rect *area_rect, temp_rect;
20782099
SDL_Surface *surf = NULL, *setsurf = NULL, *unsetsurf = NULL;
20792100
bitmask_t *bitmask = pgMask_AsBitmap(self);
20802101
Uint32 *setcolor_ptr = NULL, *unsetcolor_ptr = NULL;
@@ -2087,11 +2108,11 @@ mask_to_surface(PyObject *self, PyObject *args, PyObject *kwargs)
20872108

20882109
static char *keywords[] = {"surface", "setsurface", "unsetsurface",
20892110
"setcolor", "unsetcolor", "dest",
2090-
NULL};
2111+
"area", NULL};
20912112

2092-
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|OOOOOO", keywords,
2093-
&surfobj, &setsurfobj, &unsetsurfobj,
2094-
&setcolorobj, &unsetcolorobj, &destobj)) {
2113+
if (!PyArg_ParseTupleAndKeywords(
2114+
args, kwargs, "|OOOOOOO", keywords, &surfobj, &setsurfobj,
2115+
&unsetsurfobj, &setcolorobj, &unsetcolorobj, &destobj, &areaobj)) {
20952116
return NULL; /* Exception already set. */
20962117
}
20972118

@@ -2207,6 +2228,19 @@ mask_to_surface(PyObject *self, PyObject *args, PyObject *kwargs)
22072228
}
22082229
}
22092230

2231+
if (areaobj && areaobj != Py_None) {
2232+
if (!(area_rect = pgRect_FromObject(areaobj, &temp_rect))) {
2233+
PyErr_SetString(PyExc_TypeError, "invalid rectstyle argument");
2234+
goto to_surface_error;
2235+
}
2236+
}
2237+
else {
2238+
temp_rect.x = temp_rect.y = 0;
2239+
temp_rect.w = bitmask->w;
2240+
temp_rect.h = bitmask->h;
2241+
area_rect = &temp_rect;
2242+
}
2243+
22102244
if (!pgSurface_Lock((pgSurfaceObject *)surfobj)) {
22112245
PyErr_SetString(PyExc_RuntimeError, "cannot lock surface");
22122246
goto to_surface_error;
@@ -2229,7 +2263,7 @@ mask_to_surface(PyObject *self, PyObject *args, PyObject *kwargs)
22292263

22302264
Py_BEGIN_ALLOW_THREADS; /* Release the GIL. */
22312265

2232-
draw_to_surface(surf, bitmask, x_dest, y_dest, draw_setbits,
2266+
draw_to_surface(surf, bitmask, x_dest, y_dest, area_rect, draw_setbits,
22332267
draw_unsetbits, setsurf, unsetsurf, setcolor_ptr,
22342268
unsetcolor_ptr);
22352269

test/mask_test.py

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2822,7 +2822,6 @@ def test_to_surface__dest_default(self):
28222822
assertSurfaceFilled(self, to_surface, expected_color, mask_rect)
28232823
assertSurfaceFilledIgnoreArea(self, to_surface, surface_color, mask_rect)
28242824

2825-
@unittest.expectedFailure
28262825
def test_to_surface__area_param(self):
28272826
"""Ensures to_surface accepts an area arg/kwarg."""
28282827
expected_ref_count = 2
@@ -2854,6 +2853,60 @@ def test_to_surface__area_param(self):
28542853
self.assertEqual(to_surface.get_size(), size)
28552854
assertSurfaceFilled(self, to_surface, expected_color)
28562855

2856+
def test_to_surface__area_output(self):
2857+
dst = pygame.Surface((2, 2))
2858+
mask = pygame.mask.Mask((2, 2), fill=True)
2859+
2860+
white = (255, 255, 255, 255)
2861+
black = (0, 0, 0, 255)
2862+
2863+
# make sure the surface is black, this is to make sure the test surface and mask are different colors
2864+
self.assertEqual(dst.get_at((0, 0)), black)
2865+
# make sure the mask is set and the default color after conversion is white
2866+
self.assertEqual(mask.to_surface().get_at((0, 0)), white)
2867+
2868+
test_areas_plus_destinations_and_expected_pattern = [
2869+
[{"dest": (0, 0), "area": None}, [[white, white], [white, white]]],
2870+
[{"dest": (0, 0), "area": (0, 0, 1, 1)}, [[white, black], [black, black]]],
2871+
[{"dest": (0, 0), "area": (0, 0, 2, 2)}, [[white, white], [white, white]]],
2872+
[{"dest": (1, 0), "area": (0, 0, 1, 2)}, [[black, white], [black, white]]],
2873+
[{"dest": (0, 0), "area": (0, 0, 0, 0)}, [[black, black], [black, black]]],
2874+
[{"dest": (1, 0), "area": (-1, 0, 1, 1)}, [[black, black], [black, black]]],
2875+
[{"dest": (0, 0), "area": (-1, 0, 2, 1)}, [[black, white], [black, black]]],
2876+
[
2877+
{"dest": (1, 1), "area": (-2, -2, 1, 1)},
2878+
[[black, black], [black, black]],
2879+
],
2880+
[
2881+
{"dest": (0, 1), "area": (0, 0, 50, 50)},
2882+
[[black, black], [white, white]],
2883+
],
2884+
[
2885+
{"dest": (-1, 0), "area": (-1, 0, 2, 2)},
2886+
[[white, black], [white, black]],
2887+
],
2888+
[
2889+
{"dest": (0, -1), "area": (-1, 0, 3, 2)},
2890+
[[black, white], [black, black]],
2891+
],
2892+
[{"dest": (0, 0), "area": (2, 2, 2, 2)}, [[black, black], [black, black]]],
2893+
[
2894+
{"dest": (-5, -5), "area": (-5, -5, 6, 6)},
2895+
[[white, black], [black, black]],
2896+
],
2897+
[
2898+
{"dest": (-5, -5), "area": (-5, -5, 1, 1)},
2899+
[[black, black], [black, black]],
2900+
],
2901+
]
2902+
2903+
for kwargs, pattern in test_areas_plus_destinations_and_expected_pattern:
2904+
surface = dst.copy()
2905+
mask.to_surface(surface=surface, **kwargs)
2906+
for y, row in enumerate(pattern):
2907+
for x, value in enumerate(row):
2908+
self.assertEqual(surface.get_at((x, y)), value)
2909+
28572910
def test_to_surface__area_default(self):
28582911
"""Ensures the default area is correct."""
28592912
expected_color = pygame.Color("white")

0 commit comments

Comments
 (0)