Curvas: C, S, Q y T
Bézier cúbica y cuadrática, qué hacen exactamente los puntos de control, la regla de reflexión de los comandos abreviados, y por qué la continuidad de una curva depende de un alineamiento que nadie comprueba.
Las curvas de Bézier son la única manera que tiene SVG de describir una forma que no sea recta ni un arco de elipse, y por tanto son el noventa por ciento de cualquier icono, letra o ilustración. Son cuatro comandos que se reducen a dos ideas: una curva definida por puntos de control, y una abreviatura que espeja el punto de control anterior. Esa abreviatura es lo que hace que una curva sea suave, y también lo que hace que se rompa cuando alguien edita mal la cadena.
- Escribir una cúbica con
Cy una cuadrática conQ, y situar cada punto de control en el dibujo. - Aplicar la regla de reflexión de
SyTy saber qué ocurre cuando el comando anterior no es compatible. - Distinguir continuidad geométrica de continuidad de tangente y comprobar si una unión es suave.
- Convertir una cuadrática a cúbica exacta y saber por qué la conversión inversa no existe.
La cúbica: C
C x1 y1 x2 y2 x y traza una Bézier cúbica desde el punto actual hasta (x, y), usando (x1, y1) como punto de control del extremo de salida y (x2, y2) como punto de control del extremo de llegada.
<svg viewBox="0 0 200 120" width="400">
<path d="M 20 100 C 20 20 180 20 180 100"
fill="none" stroke="#89b4fa" stroke-width="3" />
<!-- Las varillas de control, para ver la geometria -->
<path d="M 20 100 L 20 20 M 180 100 L 180 20"
fill="none" stroke="#f9e2af" stroke-width="1" stroke-dasharray="3 3" />
<circle cx="20" cy="20" r="3" fill="#f9e2af" />
<circle cx="180" cy="20" r="3" fill="#f9e2af" />
</svg>
La curva no pasa por los puntos de control. Lo único que garantizan es la dirección de salida y de llegada: la curva sale del punto inicial en dirección al primer control, y llega al final desde la dirección del segundo. La distancia del control al extremo controla cuánto «tira» la curva en esa dirección.
La formulación es un polinomio de grado tres en un parámetro t de 0 a 1:
// Punto de una cubica en el parametro t
function cubica(p0, p1, p2, p3, t) {
const u = 1 - t;
const a = u * u * u, b = 3 * u * u * t, c = 3 * u * t * t, d = t * t * t;
return {
x: a * p0.x + b * p1.x + c * p2.x + d * p3.x,
y: a * p0.y + b * p1.y + c * p2.y + d * p3.y,
};
}
Dos propiedades que salen de esa fórmula y se usan a diario. La primera: la curva está contenida en la envolvente convexa de los cuatro puntos. Eso significa que puedes acotar una curva sin evaluarla. La segunda: el parámetro t no es proporcional a la longitud de arco. Avanzar t de 0,5 a 0,6 no recorre el 10 por ciento de la curva. Por eso animar un objeto con t uniforme produce un movimiento que acelera y frena solo, y por eso getPointAtLength() existe: parametriza por longitud real, que es lo que quieres para mover algo a velocidad constante.
La cuadrática: Q
Q x1 y1 x y usa un único punto de control compartido por los dos extremos. Es un polinomio de grado dos.
<path d="M 20 100 Q 100 10 180 100" fill="none" stroke="#a6e3a1" stroke-width="3" />
Una cuadrática es menos expresiva: no puede tener un punto de inflexión, así que no puede hacer una ese. Con una cúbica sí, porque tiene dos controles independientes. A cambio, la cuadrática pesa menos (cuatro números frente a seis) y se evalúa más rápido.
Su presencia en la web no es histórica: es el tipo de curva de las fuentes TrueType. Un icono convertido desde una tipografía llega lleno de Q. Las fuentes PostScript y OpenType con contornos CFF usan cúbicas, y de ahí que un mismo glifo pueda llegarte con C o con Q según de dónde venga.
Toda cuadrática se puede expresar como una cúbica exacta, elevando el grado:
// Q(p0, c, p1) equivale a C(c1, c2, p1) con estos controles
function quadACubic(p0, c, p1) {
return {
c1: { x: p0.x + (2 / 3) * (c.x - p0.x), y: p0.y + (2 / 3) * (c.y - p0.y) },
c2: { x: p1.x + (2 / 3) * (c.x - p1.x), y: p1.y + (2 / 3) * (c.y - p1.y) },
};
}
La conversión inversa no existe: una cúbica genérica no se puede representar con una cuadrática, solo aproximar troceándola. Esta asimetría es la razón de que las bibliotecas de morphing normalicen todo a cúbicas antes de interpolar.
S y T: la reflexión
S x2 y2 x y es una cúbica en la que el primer punto de control no se escribe: se calcula como la reflexión del segundo punto de control del comando anterior respecto al punto actual. T x y hace lo mismo con la cuadrática, y como la cuadrática solo tiene un control, T no lleva ningún punto de control escrito.
<!-- Ese en dos tramos, con reflexion -->
<path d="M 20 60 C 50 10 80 10 110 60 S 170 110 190 60"
fill="none" stroke="#cba6f7" stroke-width="3" />
La reflexión es lo que garantiza que la unión sea suave: si el control de entrada y el de salida están alineados con el punto de unión y a la misma distancia, la tangente es continua y no se ve el codo.
La regla tiene un caso límite que hay que saber: si el comando anterior no es del tipo compatible, el punto de control reflejado coincide con el punto actual. Para S, compatible significa C o S. Para T, significa Q o T. De modo que un S que sigue a un L se comporta como una cúbica cuyo primer control está pegado al punto de partida: la curva sale recta y luego gira. No es un error, es la regla; pero es el origen del clásico «he movido un comando y la curva se ha vuelto rara».
Ojo con la variante mixta: un S después de un Q no refleja nada, porque Q no es compatible con S. Es el tipo de detalle que rompe una d cuando alguien la edita a mano cambiando un Q por un C sin mirar lo que viene detrás.
Hay tres grados de continuidad en una unión de curvas, y solo el tercero es el que percibe el ojo en una superficie brillante. G0 es que los extremos coinciden: la forma no tiene huecos. G1 es que las tangentes están alineadas: no hay codo. G2 es que además coincide la curvatura: la transición es imperceptible incluso bajo un reflejo especular.
S te da G1 automáticamente, y para un icono a 24 píxeles eso basta y sobra. Pero G1 no es G2: si las distancias de los controles al punto de unión son muy distintas, la curvatura da un salto y en una forma grande y brillante se ve una línea de tensión. Los programas de CAD tienen herramientas dedicadas a comprobar G2 precisamente porque no se detecta a ojo en el trazado, solo en el sombreado. En la web esto importa en un sitio concreto: los logotipos grandes con degradado, donde un salto de curvatura produce una banda visible en el degradado que nadie sabe de dónde sale. Si te ocurre, la causa está en dos curvas unidas con G1 y controles asimétricos, no en el degradado.
Uso práctico: curvas desde datos
En un gráfico de líneas, la interpolación suave es exactamente este problema. Dados n puntos, hay que colocar 2(n-1) puntos de control. La solución que más se usa es la de Catmull-Rom convertida a Bézier, porque pasa por todos los puntos (es interpolante, no aproximante) y calcula la tangente en cada uno a partir de sus vecinos:
// Catmull-Rom a cubicas de Bezier. tension 0 = curva estandar
function suavizar(puntos, tension = 0) {
if (puntos.length < 2) return '';
const k = (1 - tension) / 6;
let d = `M ${puntos[0][0]} ${puntos[0][1]}`;
for (let i = 0; i < puntos.length - 1; i++) {
const p0 = puntos[i - 1] ?? puntos[i];
const p1 = puntos[i];
const p2 = puntos[i + 1];
const p3 = puntos[i + 2] ?? p2;
const c1x = p1[0] + (p2[0] - p0[0]) * k;
const c1y = p1[1] + (p2[1] - p0[1]) * k;
const c2x = p2[0] - (p3[0] - p1[0]) * k;
const c2y = p2[1] - (p3[1] - p1[1]) * k;
d += ` C ${c1x} ${c1y} ${c2x} ${c2y} ${p2[0]} ${p2[1]}`;
}
return d;
}
Funciona, y tiene un defecto conocido que hay que anticipar: sobrepasa. Si tres puntos consecutivos suben mucho y luego bajan, la curva se sale por encima del máximo real, y en un gráfico de datos eso es una mentira visual. Un valor máximo de 100 que se dibuja llegando a 107 es un error de representación, no un detalle estético. La alternativa correcta para series de datos es una interpolación monótona, que ajusta las tangentes para que la curva nunca supere los valores de los puntos. Es lo que hace curveMonotoneX en la biblioteca que se estudia en el nivel 28.
Toma una serie de ocho valores donde uno sea un pico aislado y dibújala tres veces: con L, con la función suavizar de arriba, y con la misma función pero con tension a 0,5. Marca con una línea horizontal el valor máximo real y comprueba en cuál de las tres la curva lo sobrepasa. Después decide qué versión pondrías en un informe que alguien va a usar para tomar una decisión.