Skip to content

Commit f46a4b6

Browse files
authored
Merge pull request #2354 from oddbookworm/transform.pixelate
pygame.transform.pixelate
2 parents d269627 + 9ee2c8c commit f46a4b6

File tree

4 files changed

+136
-0
lines changed

4 files changed

+136
-0
lines changed

buildconfig/stubs/pygame/transform.pyi

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -459,3 +459,28 @@ def hsl(
459459
460460
.. versionadded:: 2.5.0
461461
"""
462+
463+
def pixelate(
464+
surface: Surface,
465+
pixel_size: int,
466+
dest_surface: Optional[Surface] = None,
467+
) -> Surface:
468+
"""Returns a pixelated version of the original surface.
469+
470+
``pixel_size`` is an integer describing how large you want the pixels in the final pixelated image to be.
471+
An optional destination surface can be passed which is faster than creating a new Surface. This destination
472+
surface must have the same dimensions (width, height) and same depth and format as the source Surface.
473+
474+
:param pygame.Surface surface: the surface to pixelate.
475+
476+
:param int pixel_size: how large the pixels in the pixelated image should be.
477+
478+
:param pygame.Surface dest_surface: An optional destination surface to store the pixelated image.
479+
If provided, it should have the same dimensions and depth as the source surface.
480+
481+
:returns: A new surface that's been pixelated.
482+
483+
.. versionadded:: 2.5.6
484+
.. note::
485+
``pixel_size`` must be >= 1. A ``ValueError`` is raised otherwise.
486+
"""

src_c/doc/transform_doc.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,4 @@
2121
#define DOC_TRANSFORM_SOLIDOVERLAY "solid_overlay(surface, color, dest_surface=None, keep_alpha=False) -> Surface\nReplaces non transparent pixels with the provided color."
2222
#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) -> int\nFinds which, and how many pixels in a surface are within a threshold of a 'search_color' or a 'search_surf'."
2323
#define DOC_TRANSFORM_HSL "hsl(surface, hue=0, saturation=0, lightness=0, dest_surface=None) -> Surface\nChange the hue, saturation, and lightness of a surface."
24+
#define DOC_TRANSFORM_PIXELATE "pixelate(surface, pixel_size, dest_surface=None) -> Surface\nReturns a pixelated version of the original surface."

src_c/transform.c

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232

3333
#include <math.h>
3434
#include <string.h>
35+
#include <limits.h>
3536

3637
#include "simd_shared.h"
3738
#include "simd_transform.h"
@@ -4224,6 +4225,65 @@ surf_invert(PyObject *self, PyObject *args, PyObject *kwargs)
42244225
return (PyObject *)pgSurface_New(newsurf);
42254226
}
42264227

4228+
static PyObject *
4229+
surf_pixelate(PyObject *self, PyObject *args, PyObject *kwargs)
4230+
{
4231+
pgSurfaceObject *src;
4232+
pgSurfaceObject *dst = NULL;
4233+
int pixel_size;
4234+
SDL_Surface *new_surf;
4235+
pgSurfaceObject *intermediate;
4236+
4237+
static char *kwds[] = {"surface", "pixel_size", "dest_surface", NULL};
4238+
4239+
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O!i|O!", kwds,
4240+
&pgSurface_Type, &src, &pixel_size,
4241+
&pgSurface_Type, &dst)) {
4242+
return NULL;
4243+
}
4244+
4245+
if (pixel_size < 1) {
4246+
PyErr_SetString(PyExc_ValueError, "pixel_size must be greater than 0");
4247+
return NULL;
4248+
}
4249+
4250+
SDL_Surface *src_surf = pgSurface_AsSurface(src);
4251+
SURF_INIT_CHECK(src_surf);
4252+
4253+
int width = (int)round((double)src_surf->w / pixel_size);
4254+
int height = (int)round((double)src_surf->h / pixel_size);
4255+
if (width < 1) {
4256+
width = 1;
4257+
}
4258+
if (height < 1) {
4259+
height = 1;
4260+
}
4261+
4262+
SDL_Surface *temp = scale_to(src, NULL, width, height);
4263+
if (!temp) {
4264+
return NULL; /* Exception already set in scale_to */
4265+
}
4266+
intermediate = pgSurface_New(temp);
4267+
if (intermediate == NULL) {
4268+
SDL_FreeSurface(temp);
4269+
return NULL; /* Exception already set in scale_to */
4270+
}
4271+
4272+
new_surf = scale_to(intermediate, dst, src_surf->w, src_surf->h);
4273+
Py_DECREF(intermediate);
4274+
4275+
if (new_surf == NULL) {
4276+
return NULL; /* Exception already set in scale_to */
4277+
}
4278+
4279+
if (dst) {
4280+
Py_INCREF(dst);
4281+
return (PyObject *)dst;
4282+
}
4283+
4284+
return (PyObject *)pgSurface_New(new_surf);
4285+
}
4286+
42274287
static PyMethodDef _transform_methods[] = {
42284288
{"scale", (PyCFunction)surf_scale, METH_VARARGS | METH_KEYWORDS,
42294289
DOC_TRANSFORM_SCALE},
@@ -4267,6 +4327,8 @@ static PyMethodDef _transform_methods[] = {
42674327
METH_VARARGS | METH_KEYWORDS, DOC_TRANSFORM_SOLIDOVERLAY},
42684328
{"hsl", (PyCFunction)surf_hsl, METH_VARARGS | METH_KEYWORDS,
42694329
DOC_TRANSFORM_HSL},
4330+
{"pixelate", (PyCFunction)surf_pixelate, METH_VARARGS | METH_KEYWORDS,
4331+
DOC_TRANSFORM_PIXELATE},
42704332
{NULL, NULL, 0, NULL}};
42714333

42724334
MODINIT_DEFINE(transform)

test/transform_test.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,18 @@
99
from pygame.tests.test_utils import example_path
1010

1111

12+
def surfaces_have_same_pixels(surf1, surf2):
13+
if surf1.get_size() != surf2.get_size():
14+
return False
15+
16+
for row in range(surf1.get_height()):
17+
for col in range(surf1.get_width()):
18+
if surf1.get_at((col, row)) != surf2.get_at((col, row)):
19+
return False
20+
21+
return True
22+
23+
1224
def show_image(s, images=[]):
1325
# pygame.display.init()
1426
size = s.get_rect()[2:]
@@ -1859,6 +1871,42 @@ def smoothscale_invalid_scale():
18591871
)
18601872
self.assertEqual(smaller_surface.get_size(), (k, 1))
18611873

1874+
def test_pixelate(self):
1875+
"""Test pygame.transform.pixelate"""
1876+
# test that pixelating the original with a pixel_size of 1 yields the original back
1877+
data_fname = example_path("data")
1878+
path = os.path.join(data_fname, "alien3.png")
1879+
image = pygame.image.load(path) # Get an indexed surface.
1880+
1881+
no_change = pygame.transform.pixelate(image, 1)
1882+
1883+
self.assertTrue(surfaces_have_same_pixels(image, no_change))
1884+
1885+
# test that pixelating a square image with a pixel_size equal to the side length
1886+
# yields a surface of a single color, which is the average of the surf
1887+
square = pygame.transform.scale(image, (50, 50))
1888+
square_pixelated = pygame.transform.pixelate(square, 50)
1889+
square_resized = pygame.transform.scale(
1890+
pygame.transform.scale(square, (1, 1)), square.get_size()
1891+
)
1892+
1893+
self.assertTrue(surfaces_have_same_pixels(square_pixelated, square_resized))
1894+
1895+
# test a variety of arguments raise an exception
1896+
for arg in (0, -1):
1897+
with self.assertRaises(ValueError, msg=f"Running with pixel_size = {arg}"):
1898+
pygame.transform.pixelate(image, arg)
1899+
1900+
for arg in (-1_000_000_000_000_000_000, 1_000_000_000_000_000_000):
1901+
with self.assertRaises(
1902+
OverflowError, msg=f"Running with pixel_size = {arg}"
1903+
):
1904+
pygame.transform.pixelate(image, arg)
1905+
1906+
for arg in ("one", 1.0, None):
1907+
with self.assertRaises(TypeError, msg=f"Running with pixel_size = {arg}"):
1908+
pygame.transform.pixelate(image, arg)
1909+
18621910

18631911
class TransformDisplayModuleTest(unittest.TestCase):
18641912
def setUp(self):

0 commit comments

Comments
 (0)