Skip to content
HN On Hacker News ↗

What Colors Are We? Constructing A Color Space For Skin Tones

▲ 627 points 99 comments by automatoney 3w ago HN discussion ↗

Pangram verdict · v3.3

We believe that this entire text is human-written.

0 %

AI likelihood · overall

Human
100% human-written 0% AI-generated
SEGMENTS · HUMAN 1 of 1
SEGMENTS · AI 0 of 1
WORD COUNT 1,732
PEAK AI % 0% · §1
Analyzed
Aug 4
backend: pangram/v3.3
Segments scanned
1 windows
avg 1732 words each
Distribution
100 / 0%
human / AI fraction
Verdict
Human
Pangram v3.3

Article text · 1,732 words · 1 segments analyzed

Human AI-generated
§1 Human · 0%

If you're just looking for the results, below is a custom color picker based on the color space written in Javascript and a sample procedural generation algorithm in Python (Javascript equivalents are in the page source) - feel free to take this math and go have fun depicting our diverse world! The goal of this project was to define a color space that makes it easier to build inclusive color tools for a variety of contexts - such as character creators or digital art. If you see something here that sparks your curiosity, I would love for you to stick around and read below this section to learn more! R² = (What's R²?) │ Show Sphere What is each direction on the picker adjusting? Jump to that explanation here. # Plug the output of one of the select_point implementations into to_rgb(t, u, v) def select_point(r_square: float = 2.) -> tuple[float, float, float]: """Uniformly sample from the sphere deterministically""" radius = r_square ** (1. / 2) phi = uniform(0, 2 * math.pi) costheta = uniform(-1, 1) n = uniform(0, 1) theta = math.acos(costheta) r = radius * (n ** (1.0 / 3)) t = r * math.sin(theta) * math.cos(phi) u = r * math.sin(theta) * math.sin(phi) v = r * math.cos(theta) return (t, u, v) def select_point(r_square: float = 2.) -> tuple[float, float, float]: """Uniformly sample from the sphere using rejection sampling""" radius = r_square ** (1. / 2) R = radius + 1 while R > radius: t = uniform(-radius, radius) u = uniform(-radius, radius) v = uniform(-radius, radius) R = (t**2 + u**2 + v**2) ** (1.0 / 2) return (t, u, v) def to_rgb(t, u, v) -> tuple[int, int, int]: x = (t - 0.15) / 0.45 y = (v - 1.2 * t ** 2 + 0.2 * t + 0.655) / 1.84 z = u / 3.6 r = 28.77438370854 * x + 36.78307445559 * y - 19.69766918644 * z + 187.1436241611 g = 35.38327306318 * x - 2.009931981182 * y + 47.93462563172 * z + 137.1073825503 b = 36.14733717939 * x - 43.54346996173 * y - 28.50821294135 * z + 108.2241610738 return int(r), int(g), int(b) # Overview What colors are we? The short answer is maybe something like “brown” and the long answer is very, very long. Representing the broad range of human skin tones digitally is a hard problem. Often, a limited set of colors is presented as being good enough to cover the full spectrum of diversity. However, in using a specific set of colors, large groups of people are unable to accurately be represented, or might be unintentionally excluded. The goal of this work is to identify the broadest inclusive range of colors in the RGB color space that correspond to plausible, but simplified skin tones. In particular, the aim was to identify simple, “good enough” equations which define that area, allowing the range to be used in a variety of contexts. Calling the equations "good enough" is intended to keep the limitations of this work at the forefront - the results are a useful starting point, but should not be taken to be authoritative. # Introduction Although there have been improvements in the set of colors that are presented as representative of us, there's still a gap that needs to be closed. Emojis say we're 5 shades (or cartoon-yellow); a makeup brand might say 50; and a character creator might shrug and tell you to pick from all 16,777,216 options. If you look outside - or just at yourself - you'll quickly notice that none of those can compare to the variety of reality; one person is not just one color. Despite that, it can be useful to try to boil things down to fewer values. The Unicode Consortium and makeup companies can figure out their own ranges, but I believe we can find a better solution that's somewhere between "several" and "several million." Taking the digital art world as an example, images such as the one below are often circulated in an attempt to assist other artists in identifying plausible colors. "Flesh Cloud" by Tumblr user shiroxix In the video game world, nowadays the preset colors are often wide ranging and supplemented with a general color picker, but it would be even better if the initial experience presented better options. Screenshot from character creator for the early access game Paralives # Limitations Taking a step back to reality, this work has a number of inherent limitations. As mentioned before, skin tones are much more complicated than a single color. They vary widely between different areas of the body and are subject to complex biological processes. The perceived color of the skin is affected by blood flow, concentrations of melanin, complex scattering of light through the layers of the skin, as well as things like vitiligo, freckles, hyperpigmentation, scarring, and other common variations. Secondly, a variety of health conditions can cause people to have skin tones that are well outside what might be perceived as plausible. Argyria can often lead to skin that is blue-gray in color; high bilirubin can cause skin to be yellowish or greenish. Additionally, it's important to note that I am one person, not a researcher, and subject to my own general biases on top of my own perception of color. As far as I am aware I don't have color vision deficiency, but a lot of the choices I made are completely subjective. Finally, colors are not perceived consistently across display types and viewing environments. RGB values look different between different screens, and people look completely different under different lighting conditions. Broadening this work to address some of these limitations would be an interesting area of further investigation - a goal of this project was to be good enough for simple use cases, but these limitations might be more problematic in other contexts. The results here will mainly be applicable in contexts that relate to generating simplified representations of people. # Methodology What are all of those numbers, and how did you get them? Content warning - unscientific methodology below. In other words: good enough is fine if you're an engineer. Summary: Manually label colors in RGB in order to get a rough approximation of the dataset shape Perform a principal component analysis (PCA) with N=3 on the dataset to change the shape into something easier to work with Use your preferred graphing software to manually create equations that map a sphere in the target space onto the transformed data in the XYZ, or PCA space # Manually Labeling Colors Data labeling UI Sometimes the best place to start is by doing a ton of tedious work. I didn't label every color in RGB, but there certainly were a lot. This is the UI I built for labeling - just a simple webpage where you click to move a face between the yes and no side. Initially they were just squares, but I ended up drawing a face and slapping the colors onto it. Gazing into my lumpy research assistant's eyes made it much easier to quickly go “yeah I can imagine that person walking around”. Graphing that data, you get the following shape - for visualization it's kind of like a banana shape that swoops between 0, 0, 0 and 255, 255, 255. The curve is more towards red and away from blue. Matplotlib graph of the labeled data This is where I got stuck for the longest. For your sake, I'm going to skip over all of my dead ends and struggles along the way. There were many. Far too many. Things involving convex hulls, regression misadventures, 4th degree polynomials, and the worst looking code I've ever written. The result of all of that was that I decided I needed some way to transform the shape into something easier to work with: that's when I learned about principal component analysis and was immediately inspired. # Humanities Intermission Although all of the math and technology here is fun, it's important to address the fact that technology exists in a social context. Stating things plainly: lighter skin colors have both presently and historically been celebrated and prioritized while darker skin colors have been marginalized and maligned. Racism and colorism are systemically present in many cultures, in both overt and subtle ways. Below are some great videos, essays and projects that address these issues through a variety of lenses. I highly recommend taking a look, especially if you want a break before we get into the math. In her video series The Darkest Shade, Nyma Tang reviews the darkest shades from a variety of makeup brands. In her own words, "It's important for makeup brands to make products for all shades and as someone with a darker skin tone, I want to be able to help others who struggle to find the same!" In a similar vein, Kat Blaque's video (for introspective hot people) Youthforia's Blackface Foundation is about a makeup brand that released a dark shade that was literally black - far darker than what would be plausibly useful. Finally in the makeup space, the Vox video How beauty brands failed women of color talks about the history of discrimination in the beauty industry and the limited availability of deeper shades. There's a lot of great expert interviews as well as a discussion of the intersection with Black history. Taking a look at video games, Me, On The Screen: Race in Animal Crossing: New Leaf by Austin Walker is an excellent essay about the author's struggle to see himself represented in video games. In the years since, Animal Crossing has gotten much better about inclusivity but the essay also goes into his history of not seeing himself - or seeing characters that look like him be stuck in racist tropes. The Humanae photography project by Angélica Dass is "an unusually direct reflection on the color of the skin, attempting to document humanity's true colors rather than the untrue labels “white”, “red”, “black” and “yellow” associated with race." In a way it's really similar to the work here - except with a focus on documenting and conversing instead of describing, and using Pantone shades over hex codes. For more direct resources, the video It's not a Coincidence. It's Colorism. by Tee Noir gives a description of what colorism is, and a few ways it manifests in modern pop culture.