diff --git a/buildconfig/stubs/pygame/surface.pyi b/buildconfig/stubs/pygame/surface.pyi index 7c551af38d..85f24f0194 100644 --- a/buildconfig/stubs/pygame/surface.pyi +++ b/buildconfig/stubs/pygame/surface.pyi @@ -80,8 +80,14 @@ class Surface: ) -> Union[List[Rect], None]: ... def fblits( self, - blit_sequence: Iterable[Tuple[Surface, Union[Coordinate, RectValue]]], - special_flags: int = 0, / + blit_sequence: Iterable[ + Union[ + Tuple[Surface, Union[Coordinate, RectValue]], + Tuple[Surface, Sequence[Union[Coordinate, RectValue]]], + ] + ], + special_flags: int = 0, + /, ) -> None: ... @overload def convert(self, surface: Surface, /) -> Surface: ... diff --git a/docs/reST/ref/surface.rst b/docs/reST/ref/surface.rst index 1862ae1395..08d4c357a3 100644 --- a/docs/reST/ref/surface.rst +++ b/docs/reST/ref/surface.rst @@ -197,7 +197,8 @@ .. method:: fblits | :sl:`draw many surfaces onto the calling surface at their corresponding location and the same special_flags` - | :sg:`fblits(blit_sequence=((source, dest), ...), special_flags=0, /) -> None` + | :sg:`fblits(blit_sequence=((source, dest), ...), special_flags=0/) -> None` + | :sg:`fblits(blit_sequence=((source, [dest1, dest2, ...]), ...), special_flags=0/) -> None` This method takes a sequence of tuples (source, dest) as input, where source is a Surface object and dest is its destination position on this Surface. It draws each source Surface diff --git a/src_c/alphablit.c b/src_c/alphablit.c index e975218fda..e0b75da455 100644 --- a/src_c/alphablit.c +++ b/src_c/alphablit.c @@ -61,6 +61,11 @@ blit_blend_premultiplied(SDL_BlitInfo *info); static int SoftBlitPyGame(SDL_Surface *src, SDL_Rect *srcrect, SDL_Surface *dst, SDL_Rect *dstrect, int blend_flags); + +int +SoftMultiBlitPyGame(SDL_Surface *src, SDL_Surface *dst, int blend_flags, + BlitSequence *destinations); + extern int SDL_RLESurface(SDL_Surface *surface); extern void @@ -580,6 +585,80 @@ SoftBlitPyGame(SDL_Surface *src, SDL_Rect *srcrect, SDL_Surface *dst, return (okay ? 0 : -1); } +void +pg_multi_blitcopy(SDL_Surface *restrict src, SDL_Surface *restrict dst, + BlitSequence *restrict destinations) +{ + Py_ssize_t i; + const int src_skip = src->pitch / 4; + const int dst_skip = dst->pitch / 4; + + Uint32 *const src_start = (Uint32 *)src->pixels; + + for (i = 0; i < destinations->size; i++) { + BlitDestination *item = &destinations->sequence[i]; + Uint32 *dstp32 = item->pixels; + int h = item->rows; + const int copy_w = item->width * 4; + Uint32 *srcp32 = src_start + item->src_offset; + + while (h--) { + memcpy(dstp32, srcp32, copy_w); + srcp32 += src_skip; + dstp32 += dst_skip; + } + } +} + +int +SoftMultiBlitPyGame(SDL_Surface *src, SDL_Surface *dst, int blend_flags, + BlitSequence *destinations) +{ + int okay = 1; + int src_locked = 0, dst_locked = 0; + + if (SDL_MUSTLOCK(dst)) { + if (SDL_LockSurface(dst) < 0) + okay = 0; + else + dst_locked = 1; + } + if (SDL_MUSTLOCK(src)) { + if (SDL_LockSurface(src) < 0) + okay = 0; + else + src_locked = 1; + } + + if (okay) { + SDL_BlendMode src_blend; + switch (blend_flags) { + case 0: + /* unhandled cases */ + if (SDL_GetSurfaceBlendMode(src, &src_blend) != 0 || + (src_blend == SDL_BLENDMODE_NONE && src->format->Amask) || + SDL_HasColorKey(src)) { + okay = 0; + break; + } + + /* blitcopy */ + pg_multi_blitcopy(src, dst, destinations); + break; + default: + okay = 0; + break; + } + } + + if (dst_locked) + SDL_UnlockSurface(dst); + if (src_locked) + SDL_UnlockSurface(src); + + return okay ? 0 : -1; +} + /* --------------------------------------------------------- */ static void diff --git a/src_c/doc/surface_doc.h b/src_c/doc/surface_doc.h index 358629573f..60e7fdd0b7 100644 --- a/src_c/doc/surface_doc.h +++ b/src_c/doc/surface_doc.h @@ -2,7 +2,7 @@ #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_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 images onto another" -#define DOC_SURFACE_FBLITS "fblits(blit_sequence=((source, dest), ...), special_flags=0, /) -> None\ndraw many surfaces onto the calling surface at their corresponding location and the same special_flags" +#define DOC_SURFACE_FBLITS "fblits(blit_sequence=((source, dest), ...), special_flags=0/) -> None\nfblits(blit_sequence=((source, [dest1, dest2, ...]), ...), special_flags=0/) -> None\ndraw many surfaces onto the calling surface at their corresponding location and 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 an image" #define DOC_SURFACE_CONVERTALPHA "convert_alpha() -> Surface\nchange the pixel format of an image including per pixel alphas" #define DOC_SURFACE_COPY "copy() -> Surface\ncreate a new copy of a Surface" diff --git a/src_c/surface.c b/src_c/surface.c index 3f90293434..e2e58d8306 100644 --- a/src_c/surface.c +++ b/src_c/surface.c @@ -262,6 +262,8 @@ static SDL_Surface * pg_DisplayFormat(SDL_Surface *surface); static int _PgSurface_SrcAlpha(SDL_Surface *surf); +int +pg_HasSurfaceRLE(SDL_Surface *surface); static PyGetSetDef surface_getsets[] = { {"_pixels_address", (getter)surf_get_pixels_address, NULL, @@ -2150,54 +2152,222 @@ surf_blits(pgSurfaceObject *self, PyObject *args, PyObject *keywds) #define FBLITS_ERR_TUPLE_REQUIRED 11 #define FBLITS_ERR_INCORRECT_ARGS_NUM 12 #define FBLITS_ERR_FLAG_NOT_NUMERIC 13 +#define FBLITS_ERR_CACHE_NOT_NUMERIC 14 +#define FBLITS_ERR_CACHE_NOT_SAMEFMT 15 +#define FBLITS_ERR_CACHE_RLE_NOT_SUPPORTED 16 +#define FBLITS_ERR_FLAG_NOT_SUPPORTED 17 +#define FBLITS_ERR_NO_MEMORY 18 +#define FBLITS_ERR_INVALID_SEQUENCE_LENGTH 19 +#define FBLITS_ERR_INVALID_DESTINATION 20 +#define FBLITS_ERR_INVALID_SEQUENCE 21 int -_surf_fblits_item_check_and_blit(pgSurfaceObject *self, PyObject *item, +_surf_fblits_item_check_and_blit(pgSurfaceObject *src_surf, SDL_Surface *src, + pgSurfaceObject *dest, int x, int y, int blend_flags) { - PyObject *src_surf, *blit_pos; - SDL_Surface *src; - SDL_Rect *src_rect, temp, dest_rect; + SDL_Rect dest_rect = {x, y, src->w, src->h}; + + if (pgSurface_Blit(dest, src_surf, &dest_rect, NULL, blend_flags)) + return BLITS_ERR_BLIT_FAIL; + + return 0; +} - /* Check that the item is a tuple of length 2 */ - if (!PyTuple_Check(item) || PyTuple_GET_SIZE(item) != 2) { - return FBLITS_ERR_TUPLE_REQUIRED; +int +_surf_fblits_multiblit_item_check_and_blit(PyObject *src_surf, + pgSurfaceObject *self, + SDL_Surface *src, SDL_Surface *dst, + PyObject *pos_sequence, + int blend_flags, + BlitSequence *destinations) +{ + SDL_Surface *subsurface; + int suboffsetx = 0, suboffsety = 0; + SDL_Rect orig_clip, sub_clip; + int error = 0; + Py_ssize_t i; + + /* Check that the source and destination surfaces have the same format */ + if (src->format->format != dst->format->format || + src->format->BytesPerPixel != dst->format->BytesPerPixel || + src->format->BytesPerPixel != 4) { + return FBLITS_ERR_CACHE_NOT_SAMEFMT; } - /* Extract the Surface and destination objects from the - * (Surface, dest) tuple */ - src_surf = PyTuple_GET_ITEM(item, 0); - blit_pos = PyTuple_GET_ITEM(item, 1); + /* rule out RLE */ + if (pg_HasSurfaceRLE(src) || pg_HasSurfaceRLE(dst) || + (src->flags & SDL_RLEACCEL) || (dst->flags & SDL_RLEACCEL)) { + return FBLITS_ERR_CACHE_RLE_NOT_SUPPORTED; + } - /* Check that the source is a Surface */ - if (!pgSurface_Check(src_surf)) { - return BLITS_ERR_SOURCE_NOT_SURFACE; + /* manage destinations memory */ + Py_ssize_t new_size = PySequence_Fast_GET_SIZE(pos_sequence); + if (new_size > destinations->alloc_size) { + destinations->sequence = (BlitDestination *)realloc( + destinations->sequence, new_size * sizeof(BlitDestination)); + + if (!destinations->sequence) + return FBLITS_ERR_NO_MEMORY; + + destinations->size = destinations->alloc_size = new_size; } - if (!(src = pgSurface_AsSurface(src_surf))) { - return BLITS_ERR_SEQUENCE_SURF; + else if (new_size > 0 && new_size <= destinations->alloc_size) { + destinations->size = new_size; + } + else { + if (new_size == 0) + return 0; + return FBLITS_ERR_INVALID_SEQUENCE_LENGTH; + } + + if (self->subsurface) { + PyObject *owner; + struct pgSubSurface_Data *subdata; + + subdata = self->subsurface; + owner = subdata->owner; + subsurface = pgSurface_AsSurface(owner); + suboffsetx = subdata->offsetx; + suboffsety = subdata->offsety; + + while (((pgSurfaceObject *)owner)->subsurface) { + subdata = ((pgSurfaceObject *)owner)->subsurface; + owner = subdata->owner; + subsurface = pgSurface_AsSurface(owner); + suboffsetx += subdata->offsetx; + suboffsety += subdata->offsety; + } + + SDL_GetClipRect(subsurface, &orig_clip); + SDL_GetClipRect(dst, &sub_clip); + sub_clip.x += suboffsetx; + sub_clip.y += suboffsety; + SDL_SetClipRect(subsurface, &sub_clip); + dst = subsurface; + } + else { + pgSurface_Prep(self); + subsurface = NULL; + } + + /* load destinations */ + PyObject **seq_items = PySequence_Fast_ITEMS(pos_sequence); + Py_ssize_t current_size = 0; + SDL_Rect src_dest = {0, 0, src->w, src->h}; + SDL_Rect temp, *argrect; + const SDL_Rect *clip_rect = &dst->clip_rect; + for (i = 0; i < destinations->size; i++) { + PyObject *item = seq_items[i]; + + if (pg_TwoIntsFromObj(item, &src_dest.x, &src_dest.y)) { + } + else if ((argrect = pgRect_FromObject(item, &temp))) { + src_dest.x = argrect->x; + src_dest.y = argrect->y; + } + else { + return FBLITS_ERR_INVALID_DESTINATION; + } + + src_dest.x += suboffsetx; + src_dest.y += suboffsety; + + SDL_Rect clipped; + if (!SDL_IntersectRect(clip_rect, &src_dest, &clipped)) + continue; /* Skip out of bounds destinations */ + + BlitDestination *d_item = &destinations->sequence[current_size++]; + + d_item->pixels = + (Uint32 *)dst->pixels + clipped.y * dst->pitch / 4 + clipped.x; + d_item->width = clipped.w; + d_item->rows = clipped.h; + d_item->src_offset = + (src_dest.x < clip_rect->x ? clip_rect->x - src_dest.x : 0) + + (src_dest.y < clip_rect->y ? clip_rect->y - src_dest.y : 0) * + src->pitch / 4; } - /* Try to extract a valid blit position */ - if (pg_TwoIntsFromObj(blit_pos, &dest_rect.x, &dest_rect.y)) { + if (!(destinations->size = current_size)) { + if (subsurface) + SDL_SetClipRect(subsurface, &orig_clip); + else + pgSurface_Unprep(self); + return 0; + } + + pgSurface_Prep((pgSurfaceObject *)src_surf); + + error = SoftMultiBlitPyGame(src, dst, blend_flags, destinations); + + if (subsurface) + SDL_SetClipRect(subsurface, &orig_clip); + else + pgSurface_Unprep(self); + pgSurface_Unprep((pgSurfaceObject *)src_surf); + + if (error == -1) + error = BLITS_ERR_BLIT_FAIL; + + return error; +} + +static void +_surf_fblits_blit(pgSurfaceObject *self, PyObject *item, int blend_flags, + BlitSequence *destinations, int *error) +{ + PyObject *src_surf, *pos_or_seq; + SDL_Surface *src, *dst = pgSurface_AsSurface(self); + SDL_Rect temp, *argrect = NULL; + if (!dst) { + *error = BLITS_ERR_DISPLAY_SURF_QUIT; + return; } - else if ((src_rect = pgRect_FromObject(blit_pos, &temp))) { - dest_rect.x = src_rect->x; - dest_rect.y = src_rect->y; + + int x, y; + + if (PyTuple_Check(item) && PyTuple_GET_SIZE(item) == 2) { + /* (Surface, dest) */ + src_surf = PyTuple_GET_ITEM(item, 0); + pos_or_seq = PyTuple_GET_ITEM(item, 1); } else { - return BLITS_ERR_INVALID_DESTINATION; + *error = FBLITS_ERR_TUPLE_REQUIRED; + return; } - dest_rect.w = src->w; - dest_rect.h = src->h; + /* Check that the source is a Surface */ + if (!pgSurface_Check(src_surf)) { + *error = BLITS_ERR_SOURCE_NOT_SURFACE; + return; + } + if (!(src = pgSurface_AsSurface(src_surf))) { + *error = BLITS_ERR_SEQUENCE_SURF; + return; + } - /* Perform the blit */ - if (pgSurface_Blit(self, (pgSurfaceObject *)src_surf, &dest_rect, NULL, - blend_flags)) { - return BLITS_ERR_BLIT_FAIL; + if (!src->w || !src->h) + return; + + if (pg_TwoIntsFromObj(pos_or_seq, &x, &y) || + (argrect = pgRect_FromObject(pos_or_seq, &temp))) { + if (argrect) { + x = argrect->x; + y = argrect->y; + } + *error = _surf_fblits_item_check_and_blit( + (pgSurfaceObject *)src_surf, src, self, x, y, blend_flags); + return; } - return 0; + if (!pgSequenceFast_Check(pos_or_seq)) { + *error = FBLITS_ERR_INVALID_SEQUENCE; + return; + } + + *error = _surf_fblits_multiblit_item_check_and_blit( + src_surf, self, src, dst, pos_or_seq, blend_flags, destinations); } static PyObject * @@ -2210,6 +2380,7 @@ surf_fblits(pgSurfaceObject *self, PyObject *const *args, Py_ssize_t nargs) int blend_flags = 0; /* Default flag is 0, opaque */ int error = 0; int is_generator = 0; + BlitSequence destinations = {NULL, 0, 0}; if (nargs == 0 || nargs > 2) { error = FBLITS_ERR_INCORRECT_ARGS_NUM; @@ -2234,21 +2405,21 @@ surf_fblits(pgSurfaceObject *self, PyObject *const *args, Py_ssize_t nargs) Py_ssize_t i; PyObject **sequence_items = PySequence_Fast_ITEMS(blit_sequence); for (i = 0; i < PySequence_Fast_GET_SIZE(blit_sequence); i++) { - item = sequence_items[i]; - error = _surf_fblits_item_check_and_blit(self, item, blend_flags); - if (error) { + _surf_fblits_blit(self, sequence_items[i], blend_flags, + &destinations, &error); + + if (error) goto on_error; - } } } - /* Generator path */ else if (PyIter_Check(blit_sequence)) { is_generator = 1; while ((item = PyIter_Next(blit_sequence))) { - error = _surf_fblits_item_check_and_blit(self, item, blend_flags); - if (error) { + _surf_fblits_blit(self, item, blend_flags, &destinations, &error); + + if (error) goto on_error; - } + Py_DECREF(item); } @@ -2262,12 +2433,15 @@ surf_fblits(pgSurfaceObject *self, PyObject *const *args, Py_ssize_t nargs) goto on_error; } + free(destinations.sequence); + Py_RETURN_NONE; on_error: if (is_generator) { Py_XDECREF(item); } + free(destinations.sequence); switch (error) { case BLITS_ERR_SEQUENCE_REQUIRED: return RAISE( @@ -2300,10 +2474,32 @@ surf_fblits(pgSurfaceObject *self, PyObject *const *args, Py_ssize_t nargs) case FBLITS_ERR_INCORRECT_ARGS_NUM: return RAISE(PyExc_ValueError, "Incorrect number of parameters passed: need at " - "least one, 2 at max"); + "least one, 3 at max"); case FBLITS_ERR_FLAG_NOT_NUMERIC: return RAISE(PyExc_TypeError, "The special_flags parameter must be an int"); + case FBLITS_ERR_CACHE_NOT_NUMERIC: + return RAISE(PyExc_TypeError, + "The cache parameter must be a bool"); + case FBLITS_ERR_CACHE_NOT_SAMEFMT: + return RAISE(PyExc_TypeError, + "The source surface has wrong format"); + case FBLITS_ERR_CACHE_RLE_NOT_SUPPORTED: + return RAISE(PyExc_TypeError, + "RLE acceleration while caching is not supported"); + case FBLITS_ERR_FLAG_NOT_SUPPORTED: + return RAISE(PyExc_NotImplementedError, + "The flag used or blit mode selected is not " + "supported for this operation"); + case FBLITS_ERR_NO_MEMORY: + return RAISE(PyExc_MemoryError, "No memory available"); + case FBLITS_ERR_INVALID_SEQUENCE_LENGTH: + return RAISE(PyExc_ValueError, "Invalid sequence length for blit"); + case FBLITS_ERR_INVALID_DESTINATION: + return RAISE(PyExc_TypeError, + "Invalid destination position for blit"); + case FBLITS_ERR_INVALID_SEQUENCE: + return RAISE(PyExc_TypeError, "Invalid sequence for multi-blit"); } return RAISE(PyExc_TypeError, "Unknown error"); } diff --git a/src_c/surface.h b/src_c/surface.h index 3ae05eb408..c02b60c31f 100644 --- a/src_c/surface.h +++ b/src_c/surface.h @@ -337,6 +337,17 @@ } while (0) #endif +typedef struct { + Uint32 *pixels; + int width, rows, src_offset; +} BlitDestination; + +typedef struct { + BlitDestination *sequence; + Py_ssize_t alloc_size; + Py_ssize_t size; +} BlitSequence; + int surface_fill_blend(SDL_Surface *surface, SDL_Rect *rect, Uint32 color, int blendargs); @@ -352,6 +363,14 @@ int pygame_Blit(SDL_Surface *src, SDL_Rect *srcrect, SDL_Surface *dst, SDL_Rect *dstrect, int blend_flags); +int +SoftMultiBlitPyGame(SDL_Surface *src, SDL_Surface *dst, int blend_flags, + BlitSequence *destinations); + +void +pg_multi_blitcopy(SDL_Surface *restrict src, SDL_Surface *restrict dst, + BlitSequence *restrict destinations); + int premul_surf_color_by_alpha(SDL_Surface *src, SDL_Surface *dst);