Skip to content

Commit 5ff6bbd

Browse files
authored
Merge pull request #2670 from Matiiss/matiiss-add-mask-to_surface-area-kwarg
Added `area` kwarg to `mask.to_surface`
2 parents 06a0c18 + e563e94 commit 5ff6bbd

File tree

5 files changed

+419
-84
lines changed

5 files changed

+419
-84
lines changed

buildconfig/stubs/pygame/mask.pyi

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ class Mask:
5353
setcolor: Optional[ColorLike] = (255, 255, 255, 255),
5454
unsetcolor: Optional[ColorLike] = (0, 0, 0, 255),
5555
dest: Union[RectLike, Point] = (0, 0),
56+
area: Optional[RectLike] = None,
5657
) -> Surface: ...
5758
if sys.version_info >= (3, 12):
5859
def __buffer__(self, flags: int, /) -> memoryview[int]: ...

docs/reST/ref/mask.rst

Lines changed: 9 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.
@@ -610,6 +610,14 @@ to store which parts collide.
610610
mask (i.e. position ``(0, 0)`` on the mask always corresponds to
611611
position ``(0, 0)`` on the ``setsurface`` and ``unsetsurface``)
612612
:type dest: Rect or tuple(int, int) or list(int, int) or Vector2(int, int)
613+
:param area: (optional) rectangular portion of the mask to draw. It can be a
614+
rect-like object (a Rect, a tuple or a list with 4 numbers or an object with a
615+
rect attribute, etc) or it can be None (the default) in which case it will use the
616+
entire mask. If the given rect pertrudes outside the bounds of the mask (as returned
617+
by :meth:`get_rect`), it will get clipped to fit those boundaries. If the final rect
618+
is smaller than the mask and no destination ``surface`` is given, the returned surface
619+
will have the same size as this final rect.
620+
:type area: Rect or rect-like object
613621

614622
:returns: the ``surface`` parameter (or a newly created surface if no
615623
``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: 105 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1985,7 +1985,8 @@ extract_color(SDL_Surface *surf, PyObject *color_obj, Uint8 rgba_color[],
19851985
*/
19861986
static void
19871987
draw_to_surface(SDL_Surface *surf, bitmask_t *bitmask, int x_dest, int y_dest,
1988-
int draw_setbits, int draw_unsetbits, SDL_Surface *setsurf,
1988+
int x_area_offset, int y_area_offset, int draw_setbits,
1989+
int draw_unsetbits, SDL_Surface *setsurf,
19891990
SDL_Surface *unsetsurf, Uint32 *setcolor, Uint32 *unsetcolor)
19901991
{
19911992
Uint8 *pixel = NULL;
@@ -2050,10 +2051,12 @@ draw_to_surface(SDL_Surface *surf, bitmask_t *bitmask, int x_dest, int y_dest,
20502051

20512052
for (y = y_start, ym = ym_start; y < y_end; ++y, ++ym) {
20522053
pixel = (Uint8 *)surf->pixels + y * surf->pitch + x_start * bpp;
2053-
setpixel = (Uint8 *)setsurf->pixels + ym * setsurf->pitch +
2054-
xm_start * bpp;
2055-
unsetpixel = (Uint8 *)unsetsurf->pixels + ym * unsetsurf->pitch +
2056-
xm_start * bpp;
2054+
setpixel = (Uint8 *)setsurf->pixels +
2055+
(y_area_offset + ym) * setsurf->pitch +
2056+
(x_area_offset + xm_start) * bpp;
2057+
unsetpixel = (Uint8 *)unsetsurf->pixels +
2058+
(y_area_offset + ym) * unsetsurf->pitch +
2059+
(x_area_offset + xm_start) * bpp;
20572060

20582061
for (x = x_start, xm = xm_start; x < x_end;
20592062
++x, ++xm, pixel += bpp, setpixel += bpp, unsetpixel += bpp) {
@@ -2084,13 +2087,15 @@ draw_to_surface(SDL_Surface *surf, bitmask_t *bitmask, int x_dest, int y_dest,
20842087
draw_unsetbits && NULL != unsetsurf && unsetsurf->h > ym;
20852088

20862089
if (use_setsurf) {
2087-
setpixel = (Uint8 *)setsurf->pixels + ym * setsurf->pitch +
2088-
xm_start * bpp;
2090+
setpixel = (Uint8 *)setsurf->pixels +
2091+
(y_area_offset + ym) * setsurf->pitch +
2092+
(x_area_offset + xm_start) * bpp;
20892093
}
20902094

20912095
if (use_unsetsurf) {
20922096
unsetpixel = (Uint8 *)unsetsurf->pixels +
2093-
ym * unsetsurf->pitch + xm_start * bpp;
2097+
(y_area_offset + ym) * unsetsurf->pitch +
2098+
(x_area_offset + xm_start) * bpp;
20942099
}
20952100

20962101
for (x = x_start, xm = xm_start; x < x_end;
@@ -2158,31 +2163,93 @@ mask_to_surface(PyObject *self, PyObject *args, PyObject *kwargs)
21582163
{
21592164
PyObject *surfobj = Py_None, *setcolorobj = NULL, *unsetcolorobj = NULL;
21602165
PyObject *setsurfobj = Py_None, *unsetsurfobj = Py_None;
2161-
PyObject *destobj = NULL;
2166+
PyObject *destobj = NULL, *areaobj = NULL;
2167+
SDL_Rect *area_rect, temp_rect;
21622168
SDL_Surface *surf = NULL, *setsurf = NULL, *unsetsurf = NULL;
2163-
bitmask_t *bitmask = pgMask_AsBitmap(self);
2169+
bitmask_t *bitmask = pgMask_AsBitmap(self), *area_bitmask;
21642170
Uint32 *setcolor_ptr = NULL, *unsetcolor_ptr = NULL;
21652171
Uint32 setcolor, unsetcolor;
21662172
int draw_setbits = 0, draw_unsetbits = 0;
21672173
int created_surfobj = 0; /* Set to 1 if this func creates the surfobj. */
21682174
int x_dest = 0, y_dest = 0; /* Default destination coordinates. */
21692175
Uint8 dflt_setcolor[] = {255, 255, 255, 255}; /* Default set color. */
21702176
Uint8 dflt_unsetcolor[] = {0, 0, 0, 255}; /* Default unset color. */
2177+
int create_area_bitmask = 0;
21712178

21722179
static char *keywords[] = {"surface", "setsurface", "unsetsurface",
21732180
"setcolor", "unsetcolor", "dest",
2174-
NULL};
2181+
"area", NULL};
21752182

2176-
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|OOOOOO", keywords,
2177-
&surfobj, &setsurfobj, &unsetsurfobj,
2178-
&setcolorobj, &unsetcolorobj, &destobj)) {
2183+
if (!PyArg_ParseTupleAndKeywords(
2184+
args, kwargs, "|OOOOOOO", keywords, &surfobj, &setsurfobj,
2185+
&unsetsurfobj, &setcolorobj, &unsetcolorobj, &destobj, &areaobj)) {
21792186
return NULL; /* Exception already set. */
21802187
}
21812188

2189+
if (NULL != destobj) {
2190+
int tempx = 0, tempy = 0;
2191+
2192+
/* Destination coordinates can be extracted from:
2193+
* - lists/tuples with 2 items
2194+
* - Rect (or Rect like) objects (uses x, y values) */
2195+
if (pg_TwoIntsFromObj(destobj, &tempx, &tempy)) {
2196+
x_dest = tempx;
2197+
y_dest = tempy;
2198+
}
2199+
else {
2200+
SDL_Rect temp_rect;
2201+
SDL_Rect *dest_rect = pgRect_FromObject(destobj, &temp_rect);
2202+
2203+
if (NULL != dest_rect) {
2204+
x_dest = dest_rect->x;
2205+
y_dest = dest_rect->y;
2206+
}
2207+
else {
2208+
PyErr_SetString(PyExc_TypeError, "invalid dest argument");
2209+
goto to_surface_error;
2210+
}
2211+
}
2212+
}
2213+
2214+
if (areaobj && areaobj != Py_None) {
2215+
if (!(area_rect = pgRect_FromObject(areaobj, &temp_rect))) {
2216+
PyErr_SetString(PyExc_TypeError, "invalid rectstyle argument");
2217+
goto to_surface_error;
2218+
}
2219+
2220+
memcpy(&temp_rect, area_rect, sizeof(temp_rect));
2221+
area_rect = &temp_rect;
2222+
2223+
pgRect_Normalize(area_rect);
2224+
2225+
if (area_rect->x < 0) {
2226+
// x_dest -= area_rect->x;
2227+
area_rect->w += area_rect->x;
2228+
area_rect->x = 0;
2229+
}
2230+
if (area_rect->y < 0) {
2231+
// y_dest -= area_rect->y;
2232+
area_rect->h += area_rect->y;
2233+
area_rect->y = 0;
2234+
}
2235+
2236+
// clamp rect width and height to not stick out of the mask
2237+
area_rect->w = MAX(MIN(area_rect->w, bitmask->w - area_rect->x), 0);
2238+
area_rect->h = MAX(MIN(area_rect->h, bitmask->h - area_rect->y), 0);
2239+
2240+
create_area_bitmask = 1;
2241+
}
2242+
else {
2243+
temp_rect.x = temp_rect.y = 0;
2244+
temp_rect.w = bitmask->w;
2245+
temp_rect.h = bitmask->h;
2246+
area_rect = &temp_rect;
2247+
}
2248+
21822249
if (Py_None == surfobj) {
2183-
surfobj =
2184-
PyObject_CallFunction((PyObject *)&pgSurface_Type, "(ii)ii",
2185-
bitmask->w, bitmask->h, PGS_SRCALPHA, 32);
2250+
surfobj = PyObject_CallFunction((PyObject *)&pgSurface_Type, "(ii)ii",
2251+
area_rect->w, area_rect->h,
2252+
PGS_SRCALPHA, 32);
21862253

21872254
if (NULL == surfobj) {
21882255
if (!PyErr_Occurred()) {
@@ -2266,31 +2333,6 @@ mask_to_surface(PyObject *self, PyObject *args, PyObject *kwargs)
22662333
draw_unsetbits = 1;
22672334
}
22682335

2269-
if (NULL != destobj) {
2270-
int tempx = 0, tempy = 0;
2271-
2272-
/* Destination coordinates can be extracted from:
2273-
* - lists/tuples with 2 items
2274-
* - Rect (or Rect like) objects (uses x, y values) */
2275-
if (pg_TwoIntsFromObj(destobj, &tempx, &tempy)) {
2276-
x_dest = tempx;
2277-
y_dest = tempy;
2278-
}
2279-
else {
2280-
SDL_Rect temp_rect;
2281-
SDL_Rect *dest_rect = pgRect_FromObject(destobj, &temp_rect);
2282-
2283-
if (NULL != dest_rect) {
2284-
x_dest = dest_rect->x;
2285-
y_dest = dest_rect->y;
2286-
}
2287-
else {
2288-
PyErr_SetString(PyExc_TypeError, "invalid dest argument");
2289-
goto to_surface_error;
2290-
}
2291-
}
2292-
}
2293-
22942336
if (!pgSurface_Lock((pgSurfaceObject *)surfobj)) {
22952337
PyErr_SetString(PyExc_RuntimeError, "cannot lock surface");
22962338
goto to_surface_error;
@@ -2311,14 +2353,32 @@ mask_to_surface(PyObject *self, PyObject *args, PyObject *kwargs)
23112353
goto to_surface_error;
23122354
}
23132355

2356+
if (create_area_bitmask) {
2357+
area_bitmask = bitmask_create(area_rect->w, area_rect->h);
2358+
if (NULL == area_bitmask) {
2359+
PyErr_Format(PyExc_MemoryError,
2360+
"failed to allocate memory for a mask");
2361+
return NULL;
2362+
}
2363+
2364+
bitmask_draw(area_bitmask, bitmask, -area_rect->x, -area_rect->y);
2365+
}
2366+
else {
2367+
area_bitmask = bitmask;
2368+
}
2369+
23142370
Py_BEGIN_ALLOW_THREADS; /* Release the GIL. */
23152371

2316-
draw_to_surface(surf, bitmask, x_dest, y_dest, draw_setbits,
2317-
draw_unsetbits, setsurf, unsetsurf, setcolor_ptr,
2318-
unsetcolor_ptr);
2372+
draw_to_surface(surf, area_bitmask, x_dest, y_dest, area_rect->x,
2373+
area_rect->y, draw_setbits, draw_unsetbits, setsurf,
2374+
unsetsurf, setcolor_ptr, unsetcolor_ptr);
23192375

23202376
Py_END_ALLOW_THREADS; /* Obtain the GIL. */
23212377

2378+
if (create_area_bitmask) {
2379+
bitmask_free(area_bitmask);
2380+
}
2381+
23222382
if (NULL != unsetsurf &&
23232383
!pgSurface_Unlock((pgSurfaceObject *)unsetsurfobj)) {
23242384
PyErr_SetString(PyExc_RuntimeError, "cannot unlock unsetsurface");

0 commit comments

Comments
 (0)