Color Blind Simulator

Origin

I was driving and as always when I tell someone that I am color blind they immediately test me on all the surroundings. I would have to explain different ways and use my surroundings to come up with the fastest way. Well, I was like, what happens if I could take an image and put a filter over it to show how it would look like to me. It would save time and I could use multiple pre-thought examples. It is a little tough to pick stuff on the spot that fully explains it.

Hindsight

I included two documents. They are what I found after I finished when researching a little bit more. I also found out they already existed such a thing to do what I was trying to accomplish. https://colororacle.org/links.html. Personally being able to showcase the software and verify it is accurate from a color-blind perspective was a key point that really made the software worth wild.

Writing the program

The first and most important decision, what programming language. I already built a few things messing with images so python was an obvious choice to me. I wrote a little bit of code to read in the file and it ran smoothly as expected.

photo = Image.open(imagePath)  # Your image
photo = photo.convert('RGB')
width = photo.size[0] # define W and H
height = photo.size[1]

I wrote in a little bit of extra code for a try-catch and expected errors for the file but that was the starting code. Now moving on to the project. How was I going to translate the image over?

Translation

First I thought, mask but creating a green mask made no sense, and how the heck would I even create a mask. I afterthought mapping but let's do the math.

255 ^ 3 = 16,581,375 which is a huge table to map. Also, how would I calculate that as the internet doesn't have that provide anything but theories? All of a sudden "I done did it". I got an algorithm from this website. Let me just say when I tried it a huge step was taken backward. The images came out all blue. Like all blue and that was no good.

I tweaked it and in the end, the algorithm just returning no blue was not accurate.

After a lot more research I found a table. I downloaded and cleaned it up, but a huge issue came up very quickly in this information.

If you look at the size it is easily noticeable that not all the colors are presented. I found a different table eventually and it was much better, but with more data comes another issue, time. Mapping all 1.6M colors to 3 different types would be a long load time and even if I got it into the table the amount of space it would take would be insane. The best way is to turn them to hex and go by that and a linked list from there. So now we have

9 1 digit + 90 2 digit + 155 3 digit => 654 digits * 3 color types => 1962 combinations for each of the times.. Yeah, let me just stop now. Too big is the point. So I ask what is the smallest amount to take with the least amount of result.

User testing

The first factor, has to be a factor of 255 which would be

1, 3, 5, 15, 17, 51, 85, 255

I can automatically knock out first and last because that would make no difference for 1 pixel in the table size and 255 would be a 100% loss. 51 is still a lot of pixels lost so I knock out that one and 85. 1, 3, 5, 15, 17, 51, 85, 255. This could be an issue. I tested 15 as 15 x 17 and if 15 looked good 17 would push it.

From that, I would say that wouldn't work

In order to test for 3 or 5, I had to go to people with actual vision and test how much of a difference between pixel counts mattered. I was decided that 3 was better but speed-wise, it was worth it as only 1 out of 10 could see a difference (I did a test of 6 girls and 4 guys for best results). I determined this by giving 10 examples of 3 photos. For 2 test, 2 photos were original, and 1 was 5 % For 2 test, 2 photos were original and 1 was 3 % For 2 test, 1 photo was original and 2 were 3 % For 2 test, 1 photo was original and 2 were 5 % For 2 test, 2 photos were 3% and 1 was 5% Math wise 255 / 3 = 85 ^ 3 => 614,125 255 / 5 = 51 ^ 3 => 132,651

So with all those numbers, I put smaller and BAM. My new table size

f = open("ColorBlind Table.txt")

def readline():
    s = f.readline().strip()
    while(len(s) <= 0):
        s = f.readline().strip()
    return s

def main():
    deut = [] # deuteranopia
    prot = [] # protanopia
    trit = [] # tritanopia
    s = f.readline() # Comments
    s = f.readline()
    while(not (s == "")):
        asp = s.strip().split(" ")
        types = asp[0] # What it is
        if(types == "deuteranopia"):
            asp.remove("deuteranopia")
            deut.append(asp)
        elif (types == "protanopia"):
            asp.remove("protanopia")
            prot.append(asp)
        elif (types == "tritanopia"):
            asp.remove("tritanopia")
            trit.append(asp)
        else:
            # That didn't work apparently
            print(types + " Doesn't exist yet")
        s = f.readline().strip()

At first, I was naming them types instead of a type so that caused a few issues. It was an easy mapping and writing to a file.

Issue

I ran into the issue that it was very slow. If I wanted to do it in real-time that was nowhere near possible as now it was 10 sec or so for 1 iPhone 2 MB photo. I tried to use NumPy... That failed so I just kept it at that speed and said FUCK IT

# Author MasterWard

from datetime import datetime
from os import listdir, path 
from PIL import Image, ImageDraw
# import numpy as np

f = open("Colorblind Table.txt") # Filling Database

def readline():
    s = f.readline().strip()
    while(len(s) <= 0):
        s = f.readline().strip()
    return s

# Note to use a switch statement
def match(a,b):
    R1 = a[0]
    G1 = a[1]
    B1 = a[2]
    R2 = R1 % 5
    G2 = G1 % 5
    B2 = B1 % 5
    R3 = 0
    G3 = 0
    B3 = 0
    if(R2 == 0):
        R3 = R1
    elif (R2 == 1):
        R3 = R1 - 1
    elif (R2 == 2):
        R3 = R1 - 2
    elif (R2 == 3):
        R3 = R1 + 2
    elif (R2 == 4):
        R3 = R1 + 1
    if(B2 == 0):
        B3 = B1
    elif (B2 == 1):
        B3 = B1 - 1
    elif (B2 == 2):
        B3 = B1 - 2
    elif (B2 == 3):
        B3 = B1 + 2
    elif (B2 == 4):
        B3 = B1 + 1
    if(G2 == 0):
        G3 = G1
    elif (G2 == 1):
        G3 = G1 - 1
    elif (G2 == 2):
        G3 = G1 - 2
    elif (G2 == 3):
        G3 = G1 + 2
    elif (G2 == 4):
        G3 = G1 + 1
    R3 /=5
    G3/=5
    B3/=5
    ip = ((R3) * 52 * 52)+ ((G3) * 52) + B3 
    ip = int(ip)
    return (int(b[ip][3]), int(b[ip][4]),int(b[ip][5]))


def main():
    stdirs = "C:\\Users\\School\\Desktop\\Testers" # Starting Directory
    ar = listdir(stdirs)
    for i in range(len(ar)):
        if(i >= len(ar)):
            break
        if("jpeg" in ar[i] or "png" in ar[i] or "jpg" in ar[i] or "JPG" in ar[i] or "PNG" in ar[i] or "JPEG" in ar[i]):
            print(ar[i] + " works")
        else:
            ar[i] = ""
            ar.remove("")
    print(ar)
    print(datetime.now().time())
    print(len(ar))
    phcount = 0 # Photo Count
    cons = 0;

    # Reading in the chart conversions
    types = 3 # Normal, Protanopia, Deutanopia, Tritanoptia
    deut = [] # deuteranopia
    prot = [] # protanopia
    trit = [] # tritanopia
    s = f.readline() # Comments
    s = f.readline()
    while(not (s == "")):
        asp = s.strip().split(" ")
        types = asp[0] # What it is
        if(types == "deuteranopia"):
            asp.remove("deuteranopia")
            deut.append(asp)
        elif (types == "protanopia"):
            asp.remove("protanopia")
            prot.append(asp)
        elif (types == "tritanopia"):
            asp.remove("tritanopia")
            trit.append(asp)
        else:
            # That didn't work apparently
            print(types + " Doesn't exist yet")
        s = f.readline().strip()
    # Reading the file names
    while (phcount < len(ar)):
    #    while(phcount < 4):
        print(ar[phcount])
        newname = stdirs + "\\thats\\" + ar[phcount][:(ar[phcount].index("."))]
        photo = Image.open(stdirs + "\\" + ar[phcount])  #your image
        photo = photo.convert('RGB')
        width = photo.size[0] # define W and H
        height = photo.size[1]
        img = Image.new('RGB', (width, height), "black") # Create black image
        pixels = img.load() # Creates pixel map
        #im = Image.open('hopper.jpg')
 #       a = np.asarray(photo)

        # deuteranopia
        for y in range(0, height): #each pixel has coordinates
            row = ""
            for x in range(0, width): # Width
                RGB = photo.getpixel((x,y))
                R,G,B = RGB
                RGB2 = match([R,G,B], deut)
                pixels[x,y] = RGB2

        phcount += 1
        img.save(newname + "d.jpg")
        pixels = img.load() 
        
        # protanopia
        for y in range(0, height): #each pixel has coordinates
            row = ""
            for x in range(0, width): # Width
                RGB = photo.getpixel((x,y))
                R,G,B = RGB
                RGB2 = match([R,G,B], deut)
                pixels[x,y] = RGB2

        phcount += 1
        img.save(newname + "p.jpg")
        pixels = img.load() 
        
        # tritanopia
        for y in range(0, height): #each pixel has coordinates
            row = ""
            for x in range(0, width): # Width
                RGB = photo.getpixel((x,y))
                R,G,B = RGB
                RGB2 = match([R,G,B], deut)
                pixels[x,y] = RGB2

        phcount += 1
        img.save(newname + "d.jpg")
        pixels = img.load()
     
main()

Other supporting alternatives

We have advanced so far in technology these days that they now have built-in things to help people like me. On video games, there is a color-blind mode which makes it easier to process the details... I know for iPhone under accessibility they have a feature to change all the colors to make it pop out. Everything was so different I was skeptical but then took a color blind test and it was so easy. So I know it works.

Last updated