Skip to content

pygame.transform.pixelate #2354

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 14 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
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
23 changes: 23 additions & 0 deletions buildconfig/stubs/pygame/transform.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -459,3 +459,26 @@ def hsl(

.. versionadded:: 2.5.0
"""

def pixelate(
surface: Surface,
pixel_size: int,
dest_surface: Optional[Surface] = None,
) -> Surface:
"""Returns a pixelated version of the original surface.

``pixel_size`` is an integer describing how large you want the pixels in the final pixelated image to be.
An optional destination surface can be passed which is faster than creating a new Surface. This destination
surface must have the same dimensions (width, height) and smae depth as the source surface.

:param pygame.Surface surface: the surface to pixelate.

:param int pixel_size: how large the pixels in the pixelated image should be.

:param pygame.Surface dest_surface: An optional destination surface to store the pixelated image.
If provided, it should have the same dimensions and depth as the source surface.

:returns: A new surface that's been pixelated.

..versionadded:: 2.5.6
"""
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ requires = [
"sphinx<=8.1.3",
"sphinx-autoapi<=3.3.2",
"pyproject-metadata!=0.9.1",
"astroid<=3.3.8",
]
build-backend = 'mesonpy'

Expand Down
1 change: 1 addition & 0 deletions src_c/doc/transform_doc.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,4 @@
#define DOC_TRANSFORM_SOLIDOVERLAY "solid_overlay(surface, color, dest_surface=None, keep_alpha=False) -> Surface\nReplaces non transparent pixels with the provided color."
#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'."
#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."
#define DOC_TRANSFORM_PIXELATE "pixelate(surface, pixel_size, dest_surface=None) -> Surface\nReturns a pixelated version of the original surface."
51 changes: 51 additions & 0 deletions src_c/transform.c
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@

#include <math.h>
#include <string.h>
#include <limits.h>

#include "simd_shared.h"
#include "simd_transform.h"
Expand Down Expand Up @@ -4225,6 +4226,54 @@ surf_invert(PyObject *self, PyObject *args, PyObject *kwargs)
return (PyObject *)pgSurface_New(newsurf);
}

static PyObject *
surf_pixelate(PyObject *self, PyObject *args, PyObject *kwargs)
{
pgSurfaceObject *src;
pgSurfaceObject *dst = NULL;
int pixel_size;
SDL_Surface *new_surf;
pgSurfaceObject *intermediate;

static char *kwds[] = {"surface", "pixel_size", "dest_surface", NULL};

if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O!i|O!", kwds,
&pgSurface_Type, &src, &pixel_size,
&pgSurface_Type, &dst)) {
return NULL;
}

if (pixel_size < 1) {
PyErr_SetString(PyExc_ValueError,
"Pixel size must be a nonnegative integer");
return NULL;
}

int width = (int)round((double)src->surf->w / pixel_size);
int height = (int)round((double)src->surf->h / pixel_size);

SDL_Surface *temp = scale_to(src, NULL, width, height);
intermediate = pgSurface_New(temp);
if (intermediate == NULL) {
SDL_FreeSurface(temp);
return NULL; /* Exception already set in scale_to */
}

new_surf = scale_to(intermediate, dst, src->surf->w, src->surf->h);
Py_DECREF(intermediate);

if (new_surf == NULL) {
return NULL; /* Exception already set in scale_to */
}

if (dst) {
Py_INCREF(dst);
return (PyObject *)dst;
}

return (PyObject *)pgSurface_New(new_surf);
}

static PyMethodDef _transform_methods[] = {
{"scale", (PyCFunction)surf_scale, METH_VARARGS | METH_KEYWORDS,
DOC_TRANSFORM_SCALE},
Expand Down Expand Up @@ -4268,6 +4317,8 @@ static PyMethodDef _transform_methods[] = {
METH_VARARGS | METH_KEYWORDS, DOC_TRANSFORM_SOLIDOVERLAY},
{"hsl", (PyCFunction)surf_hsl, METH_VARARGS | METH_KEYWORDS,
DOC_TRANSFORM_HSL},
{"pixelate", (PyCFunction)surf_pixelate, METH_VARARGS | METH_KEYWORDS,
DOC_TRANSFORM_PIXELATE},
{NULL, NULL, 0, NULL}};

MODINIT_DEFINE(transform)
Expand Down
48 changes: 48 additions & 0 deletions test/transform_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,18 @@
from pygame.tests.test_utils import example_path


def surfaces_have_same_pixels(surf1, surf2):
if surf1.get_size() != surf2.get_size():
return False

for row in range(surf1.get_height()):
for col in range(surf1.get_width()):
if surf1.get_at((col, row)) != surf2.get_at((col, row)):
return False

return True


def show_image(s, images=[]):
# pygame.display.init()
size = s.get_rect()[2:]
Expand Down Expand Up @@ -1859,6 +1871,42 @@ def smoothscale_invalid_scale():
)
self.assertEqual(smaller_surface.get_size(), (k, 1))

def test_pixelate(self):
"""Test pygame.transform.pixelate"""
# test that pixelating the original with a pixel_size of 1 yields the original back
data_fname = example_path("data")
path = os.path.join(data_fname, "alien3.png")
image = pygame.image.load(path) # Get an indexed surface.

no_change = pygame.transform.pixelate(image, 1)

self.assertTrue(surfaces_have_same_pixels(image, no_change))

# test that pixelating a square image with a pixel_size equal to the side length
# yields a surface of a single color, which is the average of the surf
square = pygame.transform.scale(image, (50, 50))
square_pixelated = pygame.transform.pixelate(square, 50)
square_resized = pygame.transform.scale(
pygame.transform.scale(square, (1, 1)), square.get_size()
)

self.assertTrue(surfaces_have_same_pixels(square_pixelated, square_resized))

# test a variety of arguments raise an exception
for arg in (0, -1):
with self.assertRaises(ValueError, msg=f"Running with pixel_size = {arg}"):
pygame.transform.pixelate(image, arg)

for arg in (-1_000_000_000_000_000_000, 1_000_000_000_000_000_000):
with self.assertRaises(
OverflowError, msg=f"Running with pixel_size = {arg}"
):
pygame.transform.pixelate(image, arg)

for arg in ("one", 1.0, None):
with self.assertRaises(TypeError, msg=f"Running with pixel_size = {arg}"):
pygame.transform.pixelate(image, arg)


class TransformDisplayModuleTest(unittest.TestCase):
def setUp(self):
Expand Down
Loading