Skip to content

Restructure and port surface init and convert to SDL3 #3371

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
345 changes: 334 additions & 11 deletions src_c/surface.c
Original file line number Diff line number Diff line change
Expand Up @@ -483,7 +483,13 @@ surface_init(pgSurfaceObject *self, PyObject *args, PyObject *kwds)
int bpp;
Uint32 Rmask, Gmask, Bmask, Amask;
SDL_Surface *surface;

#if SDL_VERSION_ATLEAST(3, 0, 0)
PG_PixelFormatEnum format = SDL_PIXELFORMAT_UNKNOWN;
#else
SDL_PixelFormat default_format;
default_format.palette = NULL;
#endif

char *kwids[] = {"size", "flags", "depth", "masks", NULL};
if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|iOO", kwids, &size, &flags,
Expand All @@ -510,10 +516,172 @@ surface_init(pgSurfaceObject *self, PyObject *args, PyObject *kwds)
return -1;
}

default_format.palette = NULL;

surface_cleanup(self);

#if SDL_VERSION_ATLEAST(3, 0, 0)
if (depth && masks) { /* all info supplied, most errorchecking
* needed */
if (pgSurface_Check(depth)) {
PyErr_SetString(PyExc_ValueError,
"cannot pass surface for depth and color masks");
return -1;
}
if (!pg_IntFromObj(depth, &bpp)) {
PyErr_SetString(PyExc_ValueError,
"invalid bits per pixel depth argument");
return -1;
}
if (!PySequence_Check(masks) || PySequence_Length(masks) != 4) {
PyErr_SetString(PyExc_ValueError,
"masks argument must be sequence of four numbers");
return -1;
}
if (!pg_UintFromObjIndex(masks, 0, &Rmask) ||
!pg_UintFromObjIndex(masks, 1, &Gmask) ||
!pg_UintFromObjIndex(masks, 2, &Bmask) ||
!pg_UintFromObjIndex(masks, 3, &Amask)) {
PyErr_SetString(PyExc_ValueError,
"invalid mask values in masks sequence");
return -1;
}

format = SDL_MasksToPixelFormatEnum(bpp, Rmask, Gmask, Bmask, Amask);
}
else if (depth && PyNumber_Check(depth)) { /* use default masks */
if (!pg_IntFromObj(depth, &bpp)) {
PyErr_SetString(PyExc_ValueError,
"invalid bits per pixel depth argument");
return -1;
}
if (flags & PGS_SRCALPHA) {
switch (bpp) {
case 16:
format = SDL_PIXELFORMAT_ARGB4444;
break;
case 32:
format = SDL_PIXELFORMAT_ARGB8888;
break;
default:
PyErr_SetString(
PyExc_ValueError,
"no standard masks exist for given bitdepth with "
"alpha");
return -1;
}
}
else {
switch (bpp) {
case 8:
format = SDL_PIXELFORMAT_INDEX8;
break;
case 12:
format = SDL_PIXELFORMAT_XRGB4444;
break;
case 15:
format = SDL_PIXELFORMAT_XRGB1555;
break;
case 16:
format = SDL_PIXELFORMAT_RGB565;
break;
case 24:
#if SDL_BYTEORDER == SDL_BIG_ENDIAN
format = SDL_PIXELFORMAT_RGB24;
#else
format = SDL_PIXELFORMAT_BGR24;
#endif
break;
case 32:
format = SDL_PIXELFORMAT_XRGB8888;
break;
default:
PyErr_SetString(PyExc_ValueError,
"nonstandard bit depth given");
return -1;
}
}
}
else { /* no depth or surface */
if (depth && pgSurface_Check(depth)) {
format = PG_SURF_FORMATENUM(((pgSurfaceObject *)depth)->surf);
}
else if (pg_GetDefaultWindowSurface()) {
format = PG_SURF_FORMATENUM(
pgSurface_AsSurface(pg_GetDefaultWindowSurface()));
}
else {
format = SDL_PIXELFORMAT_XRGB8888;
}

if (flags & PGS_SRCALPHA) {
switch (SDL_BITSPERPIXEL(format)) {
case 16:
format = SDL_PIXELFORMAT_ARGB4444;
break;
case 24:
// we automatically step up to 32 if video is 24, fall
// through to case below
case 32:
format = SDL_PIXELFORMAT_ARGB8888;
break;
default:
PyErr_SetString(
PyExc_ValueError,
"no standard masks exist for given bitdepth with "
"alpha");
return -1;
}
}
}

if (format == SDL_PIXELFORMAT_UNKNOWN) {
PyErr_SetString(PyExc_ValueError, "Invalid mask values");
return -1;
}

surface = PG_CreateSurface(width, height, format);
if (!surface) {
PyErr_SetString(pgExc_SDLError, SDL_GetError());
return -1;
}

if (!(flags & PGS_SRCALPHA)) {
/* We ignore the error if any. */
SDL_SetSurfaceBlendMode(surface, SDL_BLENDMODE_NONE);

/* When the display format has a full alpha channel (macOS right now),
* Surfaces may be created with an unreqested alpha channel, which
* could cause issues.
* pygame Surfaces are supposed to be (0, 0, 0, 255) by default.
* This is a simple fix to fill it with (0, 0, 0, 255) if necessary.
* See Github issue:
* https://github.com/pygame-community/pygame-ce/issues/796
*/
PG_PixelFormat *surf_format;
SDL_Palette *surf_palette;
if (!PG_GetSurfaceDetails(surface, &surf_format, &surf_palette)) {
PyErr_SetString(pgExc_SDLError, SDL_GetError());
SDL_FreeSurface(surface);
return -1;
}

if (surf_format->Amask != 0) {
SDL_FillRect(surface, NULL,
PG_MapRGBA(surf_format, surf_palette, 0, 0, 0, 255));
}
}

if (SDL_ISPIXELFORMAT_INDEXED(PG_SURF_FORMATENUM(surface))) {
/* Give the surface something other than an all white palette.
* */
SDL_Palette *surf_palette = SDL_CreateSurfacePalette(surface);
if (SDL_SetPaletteColors(surf_palette, default_palette_colors, 0,
default_palette_size - 1) != 0) {
PyErr_SetString(pgExc_SDLError, SDL_GetError());
SDL_FreeSurface(surface);
return -1;
}
}
#else
if (depth && masks) { /* all info supplied, most errorchecking
* needed */
if (pgSurface_Check(depth)) {
Expand Down Expand Up @@ -614,11 +782,7 @@ surface_init(pgSurfaceObject *self, PyObject *args, PyObject *kwds)
}
else {
pix = &default_format;
#if SDL_VERSION_ATLEAST(3, 0, 0)
pix->bits_per_pixel = 32;
#else
pix->BitsPerPixel = 32;
#endif
pix->Amask = 0;
pix->Rmask = 0xFF0000;
pix->Gmask = 0xFF00;
Expand Down Expand Up @@ -702,6 +866,7 @@ surface_init(pgSurfaceObject *self, PyObject *args, PyObject *kwds)
return -1;
}
}
#endif

if (surface) {
self->surf = surface;
Expand Down Expand Up @@ -1495,6 +1660,168 @@ surf_convert(pgSurfaceObject *self, PyObject *args)

pgSurface_Prep(self);

#if SDL_VERSION_ATLEAST(3, 0, 0)
if ((has_colorkey = SDL_HasColorKey(surf))) {
PG_PixelFormat *surf_format;
SDL_Palette *surf_palette;
if (!PG_GetSurfaceDetails(surf, &surf_format, &surf_palette)) {
return RAISE(pgExc_SDLError, SDL_GetError());
}

SDL_GetColorKey(surf, &colorkey);
if (SDL_ISPIXELFORMAT_ALPHA(PG_SURF_FORMATENUM(surf))) {
PG_GetRGBA(colorkey, surf_format, surf_palette, &key_r, &key_g,
&key_b, &key_a);
}
else {
PG_GetRGB(colorkey, surf_format, surf_palette, &key_r, &key_g,
&key_b);
}
}

if (argobject) {
if (pgSurface_Check(argobject)) {
src = pgSurface_AsSurface(argobject);
newsurf = PG_ConvertSurface(surf, src->format);
}
else {
/* will be updated later, initialize to make static analyzer happy
*/
int bpp = 0;
SDL_Palette *palette = NULL;
PG_PixelFormatEnum format_enum = SDL_PIXELFORMAT_UNKNOWN;

// PATH 1 = from bpp
if (pg_IntFromObj(argobject, &bpp)) {
if (flags != UINT32_MAX && flags & PGS_SRCALPHA) {
switch (bpp) {
case 16:
format_enum = SDL_PIXELFORMAT_ARGB4444;
break;
case 32:
format_enum = SDL_PIXELFORMAT_ARGB8888;
break;
default:
return RAISE(PyExc_ValueError,
"no standard masks exist for given "
"bitdepth with alpha");
}
}
else {
switch (bpp) {
case 8:
format_enum = SDL_PIXELFORMAT_INDEX8;
break;
case 12:
format_enum = SDL_PIXELFORMAT_XRGB4444;
break;
case 15:
format_enum = SDL_PIXELFORMAT_XRGB1555;
break;
case 16:
format_enum = SDL_PIXELFORMAT_RGB565;
break;
case 24:
#if SDL_BYTEORDER == SDL_BIG_ENDIAN
format_enum = SDL_PIXELFORMAT_RGB24;
#else
format_enum = SDL_PIXELFORMAT_BGR24;
#endif
break;
case 32:
format_enum = SDL_PIXELFORMAT_XRGB8888;
break;
default:
return RAISE(PyExc_ValueError,
"nonstandard bit depth given");
}
}
}
// PATH 2 = from masks only
else if (PySequence_Check(argobject) &&
PySequence_Size(argobject) == 4) {
Uint32 Rmask, Gmask, Bmask, Amask;

if (!pg_UintFromObjIndex(argobject, 0, &Rmask) ||
!pg_UintFromObjIndex(argobject, 1, &Gmask) ||
!pg_UintFromObjIndex(argobject, 2, &Bmask) ||
!pg_UintFromObjIndex(argobject, 3, &Amask)) {
pgSurface_Unprep(self);
return RAISE(PyExc_ValueError,
"invalid color masks given");
}
Uint32 mask = Rmask | Gmask | Bmask | Amask;

// This code shocked me. -Starbuck, Mar. 2025
// Like what if you have a hole in the mask?
// Like a blank alpha mask first-- it would just terminate
// the whole loop right?
// I think this whole code path should be deprecated.
for (bpp = 0; bpp < 32; ++bpp) {
if (!(mask >> bpp)) {
break;
}
}

format_enum = SDL_MasksToPixelFormatEnum(bpp, Rmask, Gmask,
Bmask, Amask);
}
else {
pgSurface_Unprep(self);
return RAISE(
PyExc_ValueError,
"invalid argument specifying new format to convert to");
}

// If the destination format is indexed, provide a new palette or
// copy over existing palette.
if (SDL_ISPIXELFORMAT_INDEXED(format_enum)) {
if (SDL_ISPIXELFORMAT_INDEXED(PG_SURF_FORMATENUM(surf))) {
palette = PG_GetSurfacePalette(surf);
}
else {
/* Give the surface something other than an all white
* palette.
*/
palette = SDL_AllocPalette(default_palette_size);
SDL_SetPaletteColors(palette, default_palette_colors, 0,
default_palette_size);
}
}

newsurf = SDL_ConvertSurfaceAndColorspace(
surf, format_enum, palette, SDL_GetSurfaceColorspace(surf), 0);

// In this scenario, we allocated the palette, so we also need
// to remove our reference to it.
if (SDL_ISPIXELFORMAT_INDEXED(format_enum) &&
!SDL_ISPIXELFORMAT_INDEXED(PG_SURF_FORMATENUM(surf))) {
SDL_FreePalette(palette);
}

SDL_SetSurfaceBlendMode(newsurf, SDL_BLENDMODE_NONE);
}
}
else {
newsurf = pg_DisplayFormat(surf);
if (newsurf) {
SDL_SetSurfaceBlendMode(newsurf, SDL_BLENDMODE_NONE);
}
}

if (newsurf == NULL) {
return RAISE(pgExc_SDLError, SDL_GetError());
}

if (has_colorkey) {
colorkey = SDL_MapSurfaceRGBA(newsurf, key_r, key_g, key_b, key_a);
if (SDL_SetColorKey(newsurf, SDL_TRUE, colorkey) != 0) {
PyErr_SetString(pgExc_SDLError, SDL_GetError());
SDL_FreeSurface(newsurf);
return NULL;
}
}
#else
if ((has_colorkey = SDL_HasColorKey(surf))) {
SDL_GetColorKey(surf, &colorkey);
if (SDL_ISPIXELFORMAT_ALPHA(PG_SURF_FORMATENUM(surf))) {
Expand Down Expand Up @@ -1607,13 +1934,8 @@ surf_convert(pgSurfaceObject *self, PyObject *args)
PyExc_ValueError,
"invalid argument specifying new format to convert to");
}
#if SDL_VERSION_ATLEAST(3, 0, 0)
format.bits_per_pixel = (Uint8)bpp;
format.bytes_per_pixel = (bpp + 7) / 8;
#else
format.BitsPerPixel = (Uint8)bpp;
format.BytesPerPixel = (bpp + 7) / 8;
#endif
if (PG_FORMAT_BitsPerPixel((&format)) > 8) {
/* Allow a 8 bit source surface with an empty palette to be
* converted to a format without a palette (pygame-ce issue
Expand Down Expand Up @@ -1662,6 +1984,7 @@ surf_convert(pgSurfaceObject *self, PyObject *args)
return NULL;
}
}
#endif

pgSurface_Unprep(self);

Expand Down
Loading