⏱️ Lectura: 13 min

Un emoji solo te da cinco tonos de piel para elegir; una marca de maquillaje ofrece hasta 50; y un editor de personajes te suelta las 16.777.216 combinaciones del espacio RGB completo y te deja solo con el selector. Ninguna de esas tres opciones se parece a la variedad real de tonos de piel humanos, y esa brecha es la que intenta cerrar un proyecto open source publicado por el desarrollador Toney Alexander.

📑 En este artículo
  1. TL;DR
  2. Qué pasó: un espacio de color pensado para los tonos de piel reales
  3. Contexto e historia: entre 5 emojis y 16,7 millones de colores
  4. Detalles técnicos: cómo se construye el espacio de color
  5. Cómo empezar a probarlo
  6. Impacto y análisis: qué tan lejos llega el modelo
  7. Qué sigue
  8. Preguntas frecuentes
    1. ¿Este espacio de color reemplaza a la escala Fitzpatrick?
    2. ¿Necesito instalar alguna librería para usar el código?
    3. ¿Sirve para representación médica o forense?
    4. ¿Qué controla exactamente el parámetro R²?
    5. ¿El espacio de color es perceptualmente uniforme?
    6. ¿Puedo portar esto a un motor de videojuegos como Unity o Godot?
  9. Referencias

El resultado es un espacio de color matemático, no una paleta fija: define una región con forma de elipsoide dentro del cubo RGB donde caen los tonos de piel plausibles. Con un puñado de ecuaciones y un solo parámetro ajustable, cualquier desarrollador puede generar colores de piel creíbles para creadores de personajes, arte digital o herramientas de accesibilidad.

TL;DR

  • El proyecto define un espacio de color elipsoidal dentro del cubo RGB para representar tonos de piel humanos de forma más inclusiva que una paleta fija.
  • Usa un vector 3D (t, u, v) muestreado dentro de una esfera de radio ajustable (parámetro R²) y lo transforma a RGB con una matriz lineal fija.
  • El código de referencia está publicado en Python y JavaScript junto a un selector de color interactivo en la página del proyecto.
  • Compara la propuesta contra los extremos actuales: emojis con 5 tonos (basados en la escala Fitzpatrick), marcas de maquillaje con hasta 50 tonos, y el cubo RGB completo con 16.777.216 combinaciones.
  • El autor advierte que el modelo es “good enough” (suficientemente bueno), no clínicamente preciso: no captura vitíligo, hiperpigmentación, cicatrices ni condiciones como argiria o bilirrubina alta.
  • El caso de uso principal es diseño de personajes en videojuegos y arte digital, donde hoy se usan referencias informales como la imagen “Flesh Cloud” o paletas preestablecidas muy limitadas.

Qué pasó: un espacio de color pensado para los tonos de piel reales

Toney Alexander publicó una página interactiva que presenta un espacio de color propio para tonos de piel, acompañada del código fuente completo y un selector visual donde se puede mover un punto dentro de una esfera y ver el color resultante en tiempo real. El objetivo declarado es identificar, dentro del espacio RGB, la región más amplia posible de colores que correspondan a tonos de piel plausibles, aunque simplificados.

La palabra clave es “good enough”: el propio autor evita llamarlo un modelo autoritativo. Es una fórmula simple pensada para resolver un problema práctico y recurrente en el desarrollo de software: qué colores mostrar por defecto a alguien que está personalizando un avatar, un personaje de videojuego o una ilustración, sin curar a mano cientos de swatches.

Contexto e historia: entre 5 emojis y 16,7 millones de colores

El problema no es nuevo. Los emojis de personas usan modificadores de tono de piel basados en la escala Fitzpatrick, publicados como parte del Unicode Technical Report #51: apenas cinco tonos para representar a toda la humanidad. Una marca de maquillaje, en el extremo opuesto, puede ofrecer hasta 50 tonos distintos, mucho más granular pero todavía una lista cerrada.

En el medio queda el color picker genérico: 16.777.216 combinaciones posibles en RGB de 24 bits, técnicamente suficiente pero prácticamente inútil como punto de partida, porque no le dice nada al usuario sobre qué región de ese cubo corresponde a un tono de piel real.

La comunidad de arte digital resuelve esto hace años de forma informal, con imágenes de referencia como la llamada “Flesh Cloud”, una nube de puntos de color compartida por la usuaria de Tumblr shiroxix para ayudar a otros artistas a ubicar tonos de piel plausibles dentro del espacio de color. En videojuegos pasa algo similar: el creador de personajes del juego en early access Paralives ofrece muchos presets, pero termina apoyándose en un selector de color general cuando esos presets no alcanzan.

paleta de tonos de piel generada proceduralmente
Los editores de personajes suelen limitarse a paletas fijas de pocos tonos. Foto de National Institute of Allergy and Infectious Diseases en Unsplash

Detalles técnicos: cómo se construye el espacio de color

La construcción tiene dos pasos. Primero, se elige un punto al azar dentro de una esfera en un espacio auxiliar de tres coordenadas (t, u, v), con un radio controlado por el parámetro R² (el radio al cuadrado). Segundo, ese punto se transforma con una fórmula lineal fija que lo convierte en valores R, G y B.

El código fuente ofrece dos formas de resolver el primer paso. La analítica usa coordenadas esféricas y corrige el radio con una raíz cúbica para que la densidad de puntos sea uniforme en todo el volumen de la esfera, no solo cerca del centro:

def select_point(r_square=2.0):
    """Muestreo analitico uniforme dentro de la esfera"""
    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)

La alternativa por rechazo es más fácil de leer: genera puntos dentro de un cubo y descarta los que caen fuera del radio de la esfera, repitiendo hasta acertar. Es menos eficiente en el peor caso, pero más simple de portar a otro lenguaje sin arrastrar errores de signos en trigonometría.

El segundo paso, la transformación a RGB, es una combinación lineal fija de tres coeficientes por canal, calculados por el autor para que el resultado caiga dentro del rango de tonos de piel plausibles:

def to_rgb(t, u, v):
    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)

El parámetro R² es, en la práctica, el control de inclusividad del sistema: valores más grandes amplían la región de tonos de piel que el generador puede producir, mientras que valores chicos concentran los resultados cerca del centro del espacio.

💡 Tip: si integrás esto en un editor de personajes, exponé R² como un slider al usuario en vez de fijarlo en el código. Es la diferencia entre una paleta rígida y un rango ajustable de tonos de piel.

Método de muestreoCuándo usarloVentajaLimitación
Analítico (coordenadas esféricas)Cuando necesitás generar muchos tonos por segundo, por ejemplo en un editor con vista previa en vivoTiempo constante: siempre calcula el punto en un solo pasoLa fórmula con raíz cúbica y trigonometría es menos legible a simple vista
Por rechazoPrototipos rápidos o generación puntual de pocos tonosCódigo trivial de leer, depurar y portar a otro lenguajePuede iterar varias veces antes de encontrar un punto válido dentro de la esfera

El proyecto no publica benchmarks de rendimiento propios, así que no hay una cifra de referencia para citar acá. Si te importa medirlo en tu propio entorno, la forma más directa es envolver la llamada a select_point() con time.perf_counter() en Python o console.time() en JavaScript y comparar ambos métodos con la misma cantidad de muestras.

flowchart TD
    A["Muestreo (t, u, v) dentro de la esfera R2"] --> B["Proyeccion a x, y, z"]
    B --> C["Transformacion lineal to_rgb()"]
    C --> D[("Tono de piel final en RGB")]

Cómo empezar a probarlo

No hace falta instalar ningún paquete: el script de referencia solo usa la librería estándar de Python (math y random). Guardalo como color_space.py y ejecutalo con el intérprete que ya tenés instalado:

  • Windows: python color_space.py
  • macOS: python3 color_space.py
  • Linux: python3 color_space.py

Un primer script mínimo, para confirmar que la transformación funciona, convierte un solo punto cercano al centro del espacio:

import math
from random import uniform

def to_rgb(t, u, v):
    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)

print(to_rgb(0.0, 0.0, 0.0))

Ese comando imprime una tupla (r, g, b) que corresponde a un tono cercano al centro del espacio de color. Con eso confirmado, el siguiente paso es generar una paleta completa usando el muestreo por rechazo, útil como set de swatches por defecto en un creador de personajes:

def muestrear_punto(r_cuadrado=2.0):
    radio = r_cuadrado ** 0.5
    R = radio + 1

    while R > radio:
        t = uniform(-radio, radio)
        u = uniform(-radio, radio)
        v = uniform(-radio, radio)
        R = (t**2 + u**2 + v**2) ** 0.5

    return t, u, v

def generar_paleta(cantidad=8, r_cuadrado=2.0):
    paleta = []
    for _ in range(cantidad):
        t, u, v = muestrear_punto(r_cuadrado)
        r, g, b = to_rgb(t, u, v)
        r, g, b = max(0, min(255, r)), max(0, min(255, g)), max(0, min(255, b))
        paleta.append('#{:02x}{:02x}{:02x}'.format(r, g, b))
    return paleta

print(generar_paleta(cantidad=8))

El resultado es una lista de 8 códigos hexadecimales listos para usarse directamente como swatches en cualquier color picker web o motor de videojuegos: solo hay que portar las mismas cuatro operaciones aritméticas (suma, resta, multiplicación, división) al lenguaje que uses, sin dependencias externas.

visualizacion de una esfera en el espacio de color RGB
El parámetro R² define el radio de la esfera de muestreo. Foto de National Institute of Allergy and Infectious Diseases en Unsplash

Impacto y análisis: qué tan lejos llega el modelo

El propio autor dedica una sección entera del proyecto a explicar por qué este espacio de color no debería tratarse como definitivo. La piel real varía muchísimo entre distintas zonas del cuerpo y depende de procesos biológicos complejos: flujo sanguíneo, concentración de melanina y la dispersión de la luz a través de varias capas de tejido, además de vitíligo, pecas, hiperpigmentación y cicatrices.

Hay además condiciones médicas que empujan el tono de piel fuera de cualquier rango “plausible” definido a priori. La argiria puede dejar la piel de color azul grisáceo; una bilirrubina alta puede darle un tono amarillento o verdoso. Ningún espacio de color pensado para casos de uso generales, como este, intenta cubrir esos extremos.

El autor también es honesto sobre su propio sesgo: aclara que no es investigador, que no tiene una condición de daltonismo conocida, pero que muchas de las decisiones de diseño del espacio de color son subjetivas. Y hay un problema adicional que ningún código puede resolver: el mismo valor RGB se ve distinto según el panel, el brillo y la luz ambiente de quien lo mira.

⚠️ Ojo: el modelo no reemplaza clasificaciones dermatológicas ni sirve para contextos médicos o forenses. Es una herramienta de diseño, pensada para “good enough”, no para precisión clínica.

Dicho eso, el enfoque tiene un mérito práctico concreto: en vez de exigirle a cada equipo de desarrollo que arme su propia paleta a mano o que entrene un modelo de machine learning para algo tan acotado, ofrece una fórmula corta, determinística y fácil de auditar. Eso la hace apta tanto para un editor de personajes indie como para una herramienta de accesibilidad que necesite generar variantes de tono de piel bajo demanda.

💭 Clave: la diferencia frente a un dataset de miles de fotos etiquetadas es que acá la inclusividad está codificada en cuatro coeficientes lineales, no en gigabytes de datos de entrenamiento.

Qué sigue

El propio autor deja la puerta abierta a extender el trabajo hacia direcciones más rigurosas, como modelos que incorporen determinantes genéticos del tono de piel, un área que describe como interesante pero fuera del alcance de este proyecto. La motivación original fue resolver casos de uso simples, no construir un modelo biológicamente completo.

Para un equipo de desarrollo que quiera ir más allá del código publicado, una extensión razonable es convertir la salida a un espacio perceptualmente uniforme como OKLab o CIELAB antes de mostrarla al usuario, de forma que los saltos de tono en el selector se sientan parejos al ojo humano. El código original trabaja directo en RGB, que no tiene esa propiedad.

Probalo vos: copiá el bloque to_rgb() de este artículo, guardalo como color_space.py y corré python3 color_space.py (o python color_space.py en Windows) para generar tu primera paleta de tonos de piel en segundos.

📖 Resumen en Telegram: Ver resumen

Preguntas frecuentes

¿Este espacio de color reemplaza a la escala Fitzpatrick?

No. La escala Fitzpatrick clasifica la piel en seis categorías según su reacción a la radiación ultravioleta, con fines dermatológicos. El proyecto de Toney Alexander busca algo distinto: generar tonos de piel plausibles para arte digital y videojuegos, no clasificar tipos de piel médicamente.

¿Necesito instalar alguna librería para usar el código?

No. El script de referencia en Python usa solo math y random, ambas parte de la librería estándar. El equivalente en JavaScript tampoco depende de librerías externas y corre directo en el navegador.

¿Sirve para representación médica o forense?

No. El propio autor aclara que el modelo es “good enough” para casos de uso simples como personalización visual, no para contextos donde la precisión del tono de piel importa clínicamente.

¿Qué controla exactamente el parámetro R²?

Es el radio al cuadrado de la esfera de muestreo en el espacio auxiliar (t, u, v). Un R² más grande amplía el rango de tonos de piel que puede generar el algoritmo; uno más chico concentra los resultados cerca del centro.

¿El espacio de color es perceptualmente uniforme?

No. Trabaja directamente en RGB, que no es un espacio perceptualmente uniforme. Convertir la salida a OKLab o CIELAB antes de mostrarla es una extensión posible, pero no está incluida en el código original.

¿Puedo portar esto a un motor de videojuegos como Unity o Godot?

Sí. La lógica es independiente del lenguaje: solo hace falta portar las mismas operaciones aritméticas a C# o GDScript, algo directo si usás la variante por rechazo, que no depende de funciones trigonométricas.

Referencias

📱 ¿Te gusta este contenido? Únete a nuestro canal de Telegram @programacion donde publicamos a diario lo más relevante de tecnología, IA y desarrollo. Resúmenes rápidos, contenido fresco todos los días.

Imagen destacada: Foto de Susan Wilkinson en Unsplash


Andrés Morales

Desarrollador e investigador en inteligencia artificial. Escribe sobre modelos de lenguaje, frameworks, herramientas para devs y lanzamientos open source. Cubre papers de ML, ecosistema de startups tech y tendencias de programación.

0 Comentarios

Deja un comentario

Marcador de posición del avatar

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.