[Rod Stephens Books]
Index Books Python Examples About Rod Contact
[Mastodon] [Bluesky] [Facebook]
[Build Your Own Python Action Arcade!]

[Build Your Own Ray Tracer With Python]

[Beginning Database Design Solutions, Second Edition]

[Beginning Software Engineering, Second Edition]

[Essential Algorithms, Second Edition]

[The Modern C# Challenge]

[WPF 3d, Three-Dimensional Graphics with WPF and C#]

[The C# Helper Top 100]

[Interview Puzzles Dissected]

Title: Pixelate an area on an image in Python

[A pixelated area in Python]

Sometimes you may want to pixelate parts of an image, perhaps to protect someone's identity. In many countries, for example, you need someone's consent to use their image in public forums like Facebook, Bluesky, or Mastodon. This program lets you click and drag to select an area and then pixelates that area.

The code that builds the menus (particularly the radio buttons in the Block Size menu), lets you select an area, loads and saves images, and builds a scrolled frame is interesting but I've covered it before so I'm not going to say much about those parts of the program. For information about building a scrolled frame and selecting areas on a picture, see these posts:

This post describes the most interesting part of the program: the method that pixelates an area of the picture.

Pixelation

[The program pixelates the image in blocks] Before we get to the code, let's talk about the basic approach.

When the program pixelates an area, it basically divides the image into blocks as shown in the picture on the right. It then pixelates the blocks that overlap with the target area. To pixelate a block, the code averages the colors of the pixels in that block.

Working with fixed blocks has a couple of useful consequences over working with the exact target area.

First, because the blocks begin at the image's upper left corner rather than the edge of the selected area, all pixelations use the same block boundaries. That means if you use the program multiple times to pixelate several areas, the pixelated blocks line up properly in the different areas. You won't have the blocks in one area misaligned with the blocks in another.

Second, if you pixelate an area multiple times, the result doesn't change. For example, suppose a block initially is colored light brown. If you pixelate that block again, all of its pixels have the same light brown color so their average color is light brown and they will use that color every subsequent time you pixelate the block. If the block bounds changed depending on the target area, then the average color of the pixels in each block might change in subsequent pixelations so the block's color could change.

Finally, coloring entire blocks means each provides the same amount of obscurity. For example, suppose we divided the whole picture into blocks but then only colored the part of the blocks that were inside the target area. Then partial blocks would not average their pixel values across as many pixels so the result wouldn't be a well obscured. In the worst case, if a block only overlapped the target area by a single pixel, that pixel's value would be unchanged so it would not be obscured at all. (This isn't the most obvious or important of the advantages of using whole blocks, but it's something.)

That's the general approach. Divide the image into blocks. For any block that overlaps the target area, average the colors of the pixels in that block. The next section describes the code that does this.

Pixelation Code

The following pixelate_area function pixelates an area on an image.

def pixelate_area(image, block_size, xmin, ymin, xmax, ymax): '''Pixelate the indicated area.''' # Restrict the rectangle to fit on the image. img_wid = image.width img_hgt = image.height if xmin < 0: xmin = 0 if xmax >= img_wid: xmax = img_wid - 1 if ymin < 0: ymin = 0 if ymax >= img_hgt: ymax = img_hgt - 1 if xmin >= xmax: return if ymin >= ymax: return # Get a Draw object to fill the boxes. dr = ImageDraw.Draw(image) # Pixelate the area. pixels = image.load() start_x = block_size * (xmin // block_size) start_y = block_size * (ymin // block_size) for y in range(start_y, ymax, block_size): for x in range(start_x, xmax, block_size): # Pixellate the box with upper left corner (x, y). # Get the average of the pixels' color values. total_r = total_g = total_b = 0 num_pixels = 0 for dy in range(block_size): if y + dy >= img_hgt: break for dx in range(block_size): if x + dx >= img_wid: break (r, g, b) = pixels[x + dx, y + dy] total_r += r total_g += g total_b += b num_pixels += 1 if num_pixels == 0: continue r = total_r // num_pixels g = total_g // num_pixels b = total_b // num_pixels # Give all pixels in the box this color. box = (x, y, x + block_size - 1, y + block_size - 1) fill = (r,g,b) outline = None # 'pink' # Outline for debugging. dr.rectangle(box, fill=fill, outline=outline) # Outline the target area for debugging. # dr.rectangle((xmin, ymin, xmax, ymax), outline='yellow', width=1)

The code first gets the image's size and ensures that the bounds of the area to pixelate don't exceed the image's bounds. It then gets an ImageDraw.Draw object to use when filling the pixelated blocks.

Next, the function calls the image's load method to loads its data into a PixelAccess object named pixels for quick and easy access.

It then calculates the X and Y coordinates if the first block that overlaps the image. The code then enters two loops that range over X and Y coordinates. The loops use a step size of block_size to generate the coordinates of the pixels in the upper left corners of the overlapping blocks.

Inside this double loop, the code makes variables dy and dx loop from 0 to block_size - 1. That makes the values y + dy and x + dx loop over the coordinates of the pixels inside the block.

The code checks the values of the coordinates to ensure that they lie on the image and uses break statements to avoid trying to look at pixels that lie off of the image.

For each pixel in the block, the code uses pixels[x + dx, y + dy] to get the pixel's red, green, and blue color components and adds them to the color totals. After it has finished examining every pixel in the block, the function averages the color components. It then uses the ImageDraw.Draw object's rectangle method to fill the block with the average color.

Note that the function draws directly on the image so the original image is modified and the function doesn't need to return anything to pass the result back to the calling code. If you want to preserve the original image, make a copy and pass that into the function.

Conclusion

This program lets you obscure parts of an image. Be sure to use a large enough block size to hide the image adequately. For example, the left image in the following picture is still pretty recognizable. The image on the right obscures the face more than necessary so you can't recognize the face but you also can't really recognize that it is a face. The middle image is in the Goldilocks zone—you can't identify the person but you can still tell it's a face. (I got the original picture from Wikimedia Commons.)
[The right block size obscures a face but you can tell that it *is* a face]

Download the example to experiment with it and to see additional details.

© 2025 Rocky Mountain Computer Consulting, Inc. All rights reserved.