Skip to content
Tech News
← Back to articles

Show HN: Simple algorithm and color space to generate diverse skin tones

read original more articles
Why This Matters

This project introduces a straightforward algorithm and color space designed to generate diverse and inclusive skin tones, which is crucial for creating more realistic and representative digital characters and art. By simplifying the process of depicting a wide range of skin colors, it enhances inclusivity and accuracy in digital tools used across the tech industry. This advancement supports more authentic visual representation in gaming, animation, and virtual environments.

Key Takeaways

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

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)

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.

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.

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.

... continue reading