[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: Resize an image more quickly (and learn a software engineering lesson)

[This Mandelbrot image was resized so its margins are a specified width]

This example shows a way to make setting image borders a bit faster. More importantly, it demonstrates an important software engineering principle that I should have remembered going in.

My post Resize an image so it has desired margins in Python and PIL, redux resizes an image so it has desired margins. To do that, it examines all of the pixels in the image to see which have the border color. It then resizes the image so its border has a desired thickness. The process can take a few seconds for a large image, so I write this example to speed things up a bit.

Finding Margins Faster

The previous versions of this program examine every pixel in the image to see which ones are part of the image's border. It should be much faster to examine the pixels along the picture's edges and until finding a pixel that doesn't have the border color.

For example, to find the topmost edge of the current border, you examine the first row of pixels. If any of those is not the border color, that's where the border ends. If all of those pixels have the border's color, you examine the second row of pixels. You continue moving down the rows until you find a pixel that doesn't have the border color.

Suppose an image has 1,000×1,000 pixels. Examining them all requires looking at all 1,000,000 pixels. If the border is 10 pixels wide, then checking the edges makes you look at only 4×10×1,000 = 40,000 pixels, only 4% or the total number of pixels.

Searching for Borders

Here's the code the original post used to find the image's non-border area.

xmax = 0 xmin = image.width - 1 ymax = 0 ymin = image.height - 1 for y in range(image.height): for x in range(image.width): r, g, b = pixels[x, y] dr = abs(r - margin_r) dg = abs(g - margin_r) db = abs(b - margin_r) if dr + dg + db > color_distance: # Manhattan distance. if x < xmin: xmin = x if x > xmax: xmax = x if y < ymin: ymin = y if y > ymax: ymax = y

This code examines every pixel in the image.

Here's the new code.

# Find the leftmost non-margin pixel. xmax = image.width - 1 for x in range(image.width): for y in range(image.height): r, g, b = pixels[x, y] dr = abs(r - margin_r) dg = abs(g - margin_r) db = abs(b - margin_r) if dr + dg + db > color_distance: # Manhattan distance. xmax = x break # Find the rightmost non-margin pixel. xmin = 0 for x in range(image.width - 1, -1, -1): for y in range(image.height): r, g, b = pixels[x, y] dr = abs(r - margin_r) dg = abs(g - margin_r) db = abs(b - margin_r) if dr + dg + db > color_distance: # Manhattan distance. xmin = x break # Find the topmost non-margin pixel. ymax = image.height - 1 for y in range(image.height): for x in range(image.width): r, g, b = pixels[x, y] dr = abs(r - margin_r) dg = abs(g - margin_r) db = abs(b - margin_r) if dr + dg + db > color_distance: # Manhattan distance. ymax = y break # Find the bottommost non-margin pixel. ymin = 0 for y in range(image.height - 1, -1, -1): for x in range(image.width): r, g, b = pixels[x, y] dr = abs(r - margin_r) dg = abs(g - margin_r) db = abs(b - margin_r) if dr + dg + db > color_distance: # Manhattan distance. ymin = y break

This code considers the image's four sides separately. First it checks the left side, then the right, top, and bottom. The code is longer but still straightforward. [Book: Beginning Software Engineering]

Timing the Results

The example program wraps the two versions of the border-finding code in timing statements and here are the results for a fairly large 900×750 pixel image.

Old Method: (20, 979, 20, 739), 0.2527 seconds New Method: (20, 979, 20, 739), 0.0408 seconds

The new method is much faster than the old one, taking only about 16% as long, however, that's not the big takeaway. The real find here is that the old method only took about a quarter of a second on a fairly large image. The whole process takes around 4 or 5 seconds on my computer. That means the new code saves time but the overall process is dominated by something else and the speedup only saves less than a quarter of a second.

As near as I can tell after some profiling, the program spends most of its time in tkinter's askopenfilename function and there's nothing I can do about that.

Conclusion

This whole example epitomizes a key software engineering principle: don't waste time optimizing code that doesn't need it. Modifying the code does make it faster, but not significantly. Most of the program's time is spent in code that I can't do anything about.

Optimizing the border-finding code makes it slightly faster but also more complicated so it's really not worth the effort.

© 2024 - 2026 Rocky Mountain Computer Consulting, Inc. All rights reserved.