Skip to content

Commit d628234

Browse files
authored
Merge pull request #2304 from ScriptLineStudios/transform.silhouette
transform.solid_overlay
2 parents 70f6bde + 74b7a8f commit d628234

File tree

5 files changed

+287
-0
lines changed

5 files changed

+287
-0
lines changed

buildconfig/stubs/pygame/transform.pyi

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,12 @@ def rotate(surface: Surface, angle: float) -> Surface: ...
1919
def rotozoom(surface: Surface, angle: float, scale: float) -> Surface: ...
2020
def scale2x(surface: Surface, dest_surface: Optional[Surface] = None) -> Surface: ...
2121
def grayscale(surface: Surface, dest_surface: Optional[Surface] = None) -> Surface: ...
22+
def solid_overlay(
23+
surface: Surface,
24+
color: ColorLike,
25+
dest_surface: Optional[Surface] = None,
26+
keep_alpha: bool = False,
27+
) -> Surface: ...
2228
def smoothscale(
2329
surface: Surface,
2430
size: Point,

docs/reST/ref/transform.rst

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,31 @@ Instead, always begin with the original image and scale to the desired size.)
350350

351351
.. ## pygame.transform.grayscale ##
352352
353+
.. function:: solid_overlay
354+
355+
| :sl:`replaces non transparent pixels with the provided color`
356+
| :sg:`solid_overlay(surface, color, dest_surface=None, keep_alpha=False) -> Surface`
357+
358+
Returns a new version of the original surface with all non transparent pixels set to the color provided.
359+
360+
An optional destination surface can be passed which is faster than creating a new
361+
Surface.
362+
This destination surface must have the same dimensions (width, height) and
363+
depth as the source Surface.
364+
365+
:param pygame.Surface surface: The target surface.
366+
367+
:param pygame.Color color: Color which all non transparent within the target surface must be set to.
368+
369+
:param dest_surface: Optional destination surface to which the changes will be applied.
370+
:type dest_surface: pygame.Surface or None
371+
372+
:param bool keep_alpha: Optional parameter that controls whether to keep the surface alpha when replacing with the color.
373+
374+
.. versionadded:: 2.5.2
375+
376+
.. ## pygame.transform.solid_overlay ##
377+
353378
.. function:: threshold
354379

355380
| :sl:`finds which, and how many pixels in a surface are within a threshold of a 'search_color' or a 'search_surf'.`

src_c/doc/transform_doc.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,5 +18,6 @@
1818
#define DOC_TRANSFORM_AVERAGECOLOR "average_color(surface, rect=None, consider_alpha=False) -> tuple\nfinds the average color of a surface"
1919
#define DOC_TRANSFORM_INVERT "invert(surface, dest_surface=None) -> Surface\ninverts the RGB elements of a surface"
2020
#define DOC_TRANSFORM_GRAYSCALE "grayscale(surface, dest_surface=None) -> Surface\ngrayscale a surface"
21+
#define DOC_TRANSFORM_SOLIDOVERLAY "solid_overlay(surface, color, dest_surface=None, keep_alpha=False) -> Surface\nreplaces non transparent pixels with the provided color"
2122
#define DOC_TRANSFORM_THRESHOLD "threshold(dest_surface, surface, search_color, threshold=(0,0,0,0), set_color=(0,0,0,0), set_behavior=1, search_surf=None, inverse_set=False) -> num_threshold_pixels\nfinds which, and how many pixels in a surface are within a threshold of a 'search_color' or a 'search_surf'."
2223
#define DOC_TRANSFORM_HSL "hsl(surface, hue, saturation, lightness, dest_surface=None) -> Surface\nChange the hue, saturation, and lightness of a surface."

src_c/transform.c

Lines changed: 223 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2215,6 +2215,227 @@ surf_grayscale(PyObject *self, PyObject *args, PyObject *kwargs)
22152215
}
22162216
}
22172217

2218+
SDL_Surface *
2219+
solid_overlay(pgSurfaceObject *srcobj, Uint32 color, pgSurfaceObject *dstobj,
2220+
const int keep_alpha)
2221+
{
2222+
SDL_Surface *src = pgSurface_AsSurface(srcobj);
2223+
SDL_Surface *newsurf;
2224+
SDL_PixelFormat *fmt = src->format;
2225+
Uint8 a;
2226+
2227+
if (!dstobj) {
2228+
newsurf = newsurf_fromsurf(src, srcobj->surf->w, srcobj->surf->h);
2229+
if (!newsurf)
2230+
return NULL;
2231+
}
2232+
else {
2233+
newsurf = pgSurface_AsSurface(dstobj);
2234+
}
2235+
2236+
if (newsurf->w != src->w || newsurf->h != src->h) {
2237+
return (SDL_Surface *)(RAISE(
2238+
PyExc_ValueError,
2239+
"Destination surface must be the same size as source surface."));
2240+
}
2241+
2242+
if (fmt->BytesPerPixel != newsurf->format->BytesPerPixel ||
2243+
fmt->format != newsurf->format->format) {
2244+
return (SDL_Surface *)(RAISE(
2245+
PyExc_ValueError,
2246+
"Source and destination surfaces need the same format."));
2247+
}
2248+
2249+
/* If the source surface has no alpha channel, we can't overlay with alpha
2250+
* blending. */
2251+
if (!SDL_ISPIXELFORMAT_ALPHA(fmt->format))
2252+
return newsurf;
2253+
2254+
/* If we are keeping the src alpha, then we need to remove the alpha from
2255+
* the color so it's easier to add the base pixel alpha back in */
2256+
if (keep_alpha) {
2257+
color &= ~fmt->Amask;
2258+
}
2259+
2260+
int src_lock = SDL_MUSTLOCK(src);
2261+
int dst_lock = src != newsurf && SDL_MUSTLOCK(newsurf);
2262+
2263+
if (src_lock && SDL_LockSurface(src) < 0) {
2264+
return NULL;
2265+
}
2266+
if (dst_lock && SDL_LockSurface(newsurf) < 0) {
2267+
if (src_lock) {
2268+
SDL_UnlockSurface(src);
2269+
}
2270+
return NULL;
2271+
}
2272+
2273+
/* optimized path for 32 bit surfaces */
2274+
if (fmt->BytesPerPixel == 4) {
2275+
/* This algorithm iterates over each pixel's alpha channel. If it's not
2276+
* zero, the pixel is set to the desired color. If the keep_alpha flag
2277+
* is set, the original alpha value is retained, allowing the overlay
2278+
* color to inherit the surface pixel's alpha value. */
2279+
#if SDL_BYTEORDER == SDL_LIL_ENDIAN
2280+
const int dst_ashift = fmt->Ashift;
2281+
const char _a_off = fmt->Ashift >> 3;
2282+
#else
2283+
const int dst_ashift = 24 - newsurf->format->Ashift;
2284+
const char _a_off = 3 - (newsurf->format->Ashift >> 3);
2285+
#endif
2286+
2287+
Uint8 *srcp = (Uint8 *)src->pixels + _a_off;
2288+
Uint32 *dstp = (Uint32 *)newsurf->pixels;
2289+
2290+
const int src_skip = src->pitch - src->w * 4;
2291+
const int dst_skip = newsurf->pitch / 4 - newsurf->w;
2292+
int n, height = src->h;
2293+
2294+
if (srcobj == dstobj) {
2295+
if (!keep_alpha) {
2296+
while (height--) {
2297+
LOOP_UNROLLED4(
2298+
{
2299+
if (*srcp)
2300+
*dstp = color;
2301+
srcp += 4;
2302+
dstp++;
2303+
},
2304+
n, src->w);
2305+
srcp += src_skip;
2306+
dstp += dst_skip;
2307+
}
2308+
}
2309+
else {
2310+
while (height--) {
2311+
LOOP_UNROLLED4(
2312+
{
2313+
if ((a = *srcp)) {
2314+
*dstp = color | (a << dst_ashift);
2315+
}
2316+
srcp += 4;
2317+
dstp++;
2318+
},
2319+
n, src->w);
2320+
srcp += src_skip;
2321+
dstp += dst_skip;
2322+
}
2323+
}
2324+
}
2325+
else {
2326+
if (!keep_alpha) {
2327+
while (height--) {
2328+
LOOP_UNROLLED4(
2329+
{
2330+
if (*srcp)
2331+
*dstp = color;
2332+
srcp += 4;
2333+
dstp++;
2334+
},
2335+
n, src->w);
2336+
srcp += src_skip;
2337+
dstp += dst_skip;
2338+
}
2339+
}
2340+
else {
2341+
while (height--) {
2342+
LOOP_UNROLLED4(
2343+
{
2344+
if ((a = *srcp)) {
2345+
*dstp = color | (a << dst_ashift);
2346+
}
2347+
srcp += 4;
2348+
dstp++;
2349+
},
2350+
n, src->w);
2351+
srcp += src_skip;
2352+
dstp += dst_skip;
2353+
}
2354+
}
2355+
}
2356+
}
2357+
else /* path for 16 bit surfaces */
2358+
{
2359+
int x, y;
2360+
Uint8 r, g, b;
2361+
Uint16 *src_row = (Uint16 *)src->pixels;
2362+
Uint16 *dst_row = (Uint16 *)newsurf->pixels;
2363+
const int src_skip = src->pitch / 2 - src->w;
2364+
const int dst_skip = newsurf->pitch / 2 - newsurf->w;
2365+
2366+
Uint8 Cr, Cg, Cb, Ca;
2367+
SDL_GetRGBA(color, fmt, &Cr, &Cg, &Cb, &Ca);
2368+
const Uint16 color16 = (Uint16)SDL_MapRGBA(fmt, Cr, Cg, Cb, Ca);
2369+
2370+
for (y = 0; y < src->h; y++) {
2371+
for (x = 0; x < src->w; x++) {
2372+
SDL_GetRGBA((Uint32)*src_row, fmt, &r, &g, &b, &a);
2373+
2374+
if (a) {
2375+
if (keep_alpha)
2376+
*dst_row = (Uint16)SDL_MapRGBA(fmt, Cr, Cg, Cb, a);
2377+
else
2378+
*dst_row = color16;
2379+
}
2380+
2381+
src_row++;
2382+
dst_row++;
2383+
}
2384+
src_row += src_skip;
2385+
dst_row += dst_skip;
2386+
}
2387+
}
2388+
2389+
if (src_lock)
2390+
SDL_UnlockSurface(src);
2391+
if (dst_lock)
2392+
SDL_UnlockSurface(newsurf);
2393+
2394+
return newsurf;
2395+
}
2396+
2397+
static PyObject *
2398+
surf_solid_overlay(PyObject *self, PyObject *args, PyObject *kwargs)
2399+
{
2400+
pgSurfaceObject *surfobj;
2401+
PyObject *colorobj;
2402+
Uint32 color;
2403+
2404+
pgSurfaceObject *surfobj2 = NULL;
2405+
SDL_Surface *newsurf;
2406+
SDL_Surface *surf;
2407+
int keep_alpha = 0;
2408+
2409+
static char *keywords[] = {"surface", "color", "dest_surface",
2410+
"keep_alpha", NULL};
2411+
2412+
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O!O|O!i", keywords,
2413+
&pgSurface_Type, &surfobj, &colorobj,
2414+
&pgSurface_Type, &surfobj2, &keep_alpha))
2415+
return NULL;
2416+
2417+
surf = pgSurface_AsSurface(surfobj);
2418+
2419+
if (!pg_MappedColorFromObj(colorobj, surf->format, &color,
2420+
PG_COLOR_HANDLE_ALL)) {
2421+
return RAISE(PyExc_TypeError, "invalid color argument");
2422+
}
2423+
2424+
newsurf = solid_overlay(surfobj, color, surfobj2, keep_alpha);
2425+
2426+
if (!newsurf) {
2427+
return NULL;
2428+
}
2429+
2430+
if (surfobj2) {
2431+
Py_INCREF(surfobj2);
2432+
return (PyObject *)surfobj2;
2433+
}
2434+
else {
2435+
return (PyObject *)pgSurface_New(newsurf);
2436+
}
2437+
}
2438+
22182439
#define MIN3(a, b, c) MIN(MIN(a, b), c)
22192440
#define MAX3(a, b, c) MAX(MAX(a, b), c)
22202441

@@ -3793,6 +4014,8 @@ static PyMethodDef _transform_methods[] = {
37934014
DOC_TRANSFORM_INVERT},
37944015
{"grayscale", (PyCFunction)surf_grayscale, METH_VARARGS | METH_KEYWORDS,
37954016
DOC_TRANSFORM_GRAYSCALE},
4017+
{"solid_overlay", (PyCFunction)surf_solid_overlay,
4018+
METH_VARARGS | METH_KEYWORDS, DOC_TRANSFORM_SOLIDOVERLAY},
37964019
{"hsl", (PyCFunction)surf_hsl, METH_VARARGS | METH_KEYWORDS,
37974020
DOC_TRANSFORM_HSL},
37984021
{NULL, NULL, 0, NULL}};

test/transform_test.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -494,6 +494,38 @@ def test_hsl(self):
494494
self.assertAlmostEqual(v1, v2, delta=1)
495495
self.assertEqual(c_a, actual_color.a)
496496

497+
def test_solid_overlay(self):
498+
test_surface = pygame.Surface((20, 20), pygame.SRCALPHA)
499+
test_surface.fill((0, 0, 0, 0))
500+
pygame.draw.rect(test_surface, (0, 0, 0), (10, 10, 10, 10))
501+
502+
surface = pygame.Surface((20, 20), pygame.SRCALPHA)
503+
surface.fill((0, 0, 0, 0))
504+
pygame.draw.rect(surface, (255, 0, 0), (10, 10, 10, 10))
505+
506+
surface = pygame.transform.solid_overlay(surface, (0, 0, 0))
507+
508+
for x in range(20):
509+
for y in range(20):
510+
self.assertEqual(surface.get_at((x, y)), test_surface.get_at((x, y)))
511+
512+
pygame.transform.solid_overlay(surface, (0, 0, 0), surface)
513+
514+
for x in range(20):
515+
for y in range(20):
516+
self.assertEqual(surface.get_at((x, y)), test_surface.get_at((x, y)))
517+
518+
test_surface.fill((0, 0, 0, 0))
519+
pygame.draw.polygon(test_surface, (200, 100, 50), [(0, 0), (4, 4), (4, 2)])
520+
521+
surface.fill((0, 0, 0, 0))
522+
pygame.draw.polygon(surface, (255, 0, 0), [(0, 0), (4, 4), (4, 2)])
523+
surface = pygame.transform.solid_overlay(surface, (200, 100, 50))
524+
525+
for x in range(20):
526+
for y in range(20):
527+
self.assertEqual(surface.get_at((x, y)), test_surface.get_at((x, y)))
528+
497529
def test_grayscale_simd_assumptions(self):
498530
# The grayscale SIMD algorithm relies on the destination surface pitch
499531
# being exactly width * 4 (4 bytes per pixel), for maximum speed.

0 commit comments

Comments
 (0)