Personally, I wouldn't approach this using for
loops as they are slow, inefficient and error-prone in Python. However, I have implemented it with for
loops so it matches your way of thinking and is easier to understand. If you look through some of my other answers you will find better ways of doing it.
I note a couple of things:
you don't need to take the square root of the distances, you can simply compare the squared distances. If a**2 > b**2
, it follows a>b
, as long as there are no negatives involved which will always be the case here because both a
and b
are themselves the sums of squares
as your output image will consist of just 3 colours, it can best be stored as a palette image - see here. So I did that.
#!/usr/bin/env python3
from PIL import Image
def closestColour(px):
"""Returns palette index for whichever colour (out of R, G and B) is nearest"""
# Unpack the tuple into its constituent parts
R, G, B = thispx
# Calculate distance to solid Red, solid Green and solid Blue corners of colour cube
# No need for sqrt(), if a**2 < b**2 we know a>b as long as no negative numbers are involved and there aren't because a amd b are sums of squares
RdistSquared = (255-R)**2 + G**2 + B**2
GdistSquared = R**2 + (255-G)**2 + B**2
BdistSquared = R**2 + G**2 + (255-B)**2
# Find index of minimum value in list
values = [RdistSquared, GdistSquared, BdistSquared]
mi = values.index(min(values))
# Map red to blue, blue to green and green to red
lkup = [2, 0, 1]
return lkup[mi]
# Open input image and load pixels
im = Image.open('billiards.jpg')
px = im.load()
# List of output pixels
outpx = []
for y in range(im.height):
for x in range(im.width):
thispx = px[x,y]
outpx.append(closestColour(thispx))
# Create palettised output image, same size as original
out = Image.new('P', im.size)
# Stuff in the list of pixels
out.putdata(outpx)
# Stuff in the palette, index 0 = Red, index 1 = Green, index 2 = Blue
out.putpalette([255,0,0, 0,255,0, 0,0,255])
out.save('result.png')
That transforms this:

Photo by Sandesh Sharma on Unsplash
into this:

By the way, there is no real need to write any Python to do this, you can just do it in the Terminal with ImageMagick, like this:
# Make a small palette of the colours we want to remap to
magick xc:red xc:lime xc:blue +append PNG8:rgb-palette.png

# Remap to the new palette, without dithering and then re-order colour channels
magick billiards.jpg +dither -remap rgb-palette.png -channel-fx ',1,2,0' result.png