Show HN: Simple algorithm and color space to generate diverse skin tones
Overview and Takeaway
The project defines a skin‑tone color space using a sphere in TUV space to generate a wide range of plausible skin tones for inclusive tools such as character creators or digital art.
Methodology: Data Labeling, PCA, Manual Function Fitting
The author labeled skin‑tone colors in RGB, applied PCA to align variation with axes, then manually fitted a spherical function in Desmos to map TUV to XYZ and back to RGB.
First, a simple webpage UI let the author click faces into a "yes" (skin‑tone) or "no" (non‑skin) bucket, producing a labeled RGB dataset that resembled a banana shape curving from dark to light.
Second, principal component analysis (PCA) rotated and stretched this dataset so its main directions of variation lay along the XYZ axes, providing a matrix to convert between RGB and XYZ space.
Third, in Desmos the author started with a sphere equation R² = t² + u² + v² and adjusted the definitions of t, u, v in terms of x, y, z until the sphere enveloped the point cloud; inverting those relations gave the final conversion formulas used in the code.
Results: Color Picker UI, R^2 Parameter, and Generation Code
The picker UI exposes three orthogonal controls (T for deep/fair, U for flushed/ochre, V for cool/warm) and a single R² parameter governs sphere radius, with sample Python/Javascript code showing uniform sampling and RGB conversion.
The UI labels the axes as:
- Up/Down on the square: Deep ↔ Fair (variable T)
- Left/Right on the square: Flushed ↔ Ochre (variable U)
- Left/Right on the slider: Cool ↔ Warm (variable V)
Changing R² scales the sphere uniformly; for example R² = 2.0 was used in the sample implementation to produce a broad range of tones, while lower values reduce variation without clipping extremes.
The core conversion functions are:
def select_point(r_square: float = 2.) -> tuple[float, float, float]:
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 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)
Limitations and Subjectivity
The work acknowledges simplifications, biases, display variability, and health‑condition variations that limit applicability.
As stated in the Limitations section, skin tone is influenced by blood flow, melanin concentration, light scattering, vitiligo, freckles, hyperpigmentation, scarring, and conditions such as argyria or high bilirubin that produce colors outside the modeled range.
The author notes personal bias, the lack of rigorous scientific validation, and that RGB values appear differently across screens and lighting, so the space is intended as a "good enough" starting point for simplified representations.
Humanities Context and Related Resources
The author provides links to videos, essays, and projects discussing colorism and representation, situating the technical work in a social context.
The Humanities Intermission references Nyma Tang’s "The Darkest Shade" makeup series, Kat Blaque’s video on a black foundation, Vox’s "How beauty brands failed women of color", essays on race in Animal Crossing, the Humanae photography project, Tee Noir’s explanation of colorism, and Writing With Color’s guide to describing skin tone.
Community Feedback from Hacker News
Commenters praised the approach, noted potential underrepresentation of pale tones, and raised concerns about bias toward fairer skin.
- @383toast observed: "Seems to underrepresent pale skin tone variety"
- @moralestapia remarked: "It might be a bit biased though, as there's much more variation on fairer skin tones than on dark ones (there's only one that looks like the colloquial 'black')." This contradicts research and social media from the past decade.
- Positive remarks included @vermarish calling the work "beautiful" and appreciating the manual function fitting and sampling from different R² values, and @stephantul praising the introspective aside on related resources.
Future Work and Technical Improvements
The author outlines refinements: expert labeling, modeling skin conditions, formal equation generation via symbolic regression, optimizing equations, exploring other color spaces before PCA.
Future work sections suggest refining the space with multiple expert labelers, simulating conditions like jaundice or vitiligo by modifying base tones, using symbolic regression on better training data to derive cleaner equations, optimizing the current formulas (noting a matrix‑multiplication‑like step), and testing whether preprocessing RGB data in another color space yields simpler or more representative results.