Precisión decimal y el peso real
Cuántos decimales necesita de verdad una coordenada, cómo se calcula el error visible en píxeles, por qué esta optimización sí sobrevive a la compresión, y dónde no se puede recortar.
Los decimales de un atributo d son la única parte de un SVG donde recortar elimina información en lugar de redundancia, y por eso son la única optimización cuya ganancia llega intacta al usuario. También es la única que puede estropear el dibujo de forma sutil e irreversible. Hay un cálculo sencillo que dice cuántos decimales necesitas, y depende del tamaño al que se va a ver el gráfico.
- Calcular la precisión necesaria a partir del
viewBoxy el tamaño de presentación. - Cuantificar el ahorro de bytes al bajar de seis decimales a dos.
- Explicar por qué esta optimización no la deshace la compresión.
- Identificar los tres sitios donde recortar precisión rompe cosas.
Cuántos decimales hacen falta
La cuenta es directa. Si el viewBox tiene V unidades de ancho y el gráfico se presenta con P píxeles de ancho, cada unidad vale P / V píxeles. Un error de una unidad en la última cifra decimal se convierte en:
error en pixeles = (P / V) * 10^(-decimales)
Con un icono de viewBox="0 0 24 24" presentado a 24 píxeles, cada unidad vale 1 píxel. Un decimal da un error de 0,1 píxeles; dos, de 0,01. Un decimal es suficiente para ese icono, y el segundo ya está por debajo de lo que un antialiasing de 8 bits distingue.
Con el mismo icono presentado a 96 píxeles (un icono de aplicación), cada unidad vale 4 píxeles y un decimal da 0,4 píxeles de error: visible en un borde. Ahí hacen falta dos.
Con un mapa de viewBox="0 0 1000 800" presentado a 800 píxeles, cada unidad vale 0,8 píxeles. Cero decimales dan un error de 0,8 píxeles, que se ve; un decimal da 0,08, que no. Un mapa con seis decimales por coordenada está desperdiciando cinco.
La regla práctica, expresada al revés:
decimales necesarios ≈ ceil( log10( P / V ) ) + 1
Y con un margen para el zoom del navegador: suma uno más si el gráfico se puede ampliar.
En la mayoría de los casos reales la respuesta es dos decimales, y tres si hay arcos o el gráfico es ampliable. Seis, que es lo que producen muchas herramientas, no lo justifica nada.
El ahorro
Una coordenada con seis decimales ocupa unos 10 caracteres (412.483921). Con dos, unos 6 (412.48). Con la parte entera pequeña y el punto, la diferencia media ronda el 35 o el 40 por ciento de la longitud de la cadena d.
En un icono de línea, la d es la mayor parte del fichero. En un mapa o una ilustración compleja, es prácticamente todo el fichero. Ahí una reducción de precisión del 40 por ciento en la d es una reducción del 35 por ciento del fichero.
Y a diferencia del resto de optimizaciones, esa reducción llega al usuario. Los dígitos de un decimal son, desde el punto de vista de un compresor, información de alta entropía: no se repiten, no siguen un patrón, no se predicen. Brotli no puede hacer nada con ellos. Eliminarlos elimina bytes reales de la transferencia.
Es la razón de que en un fichero de mapa, la precisión sea la única palanca que importa. Todo lo demás (indentación, atributos, nombres) comprime a nada.
Cómo se recorta
En SVGO, con el parámetro de precisión de los plugins numéricos:
export default {
plugins: [
{
name: 'preset-default',
params: {
overrides: {
removeViewBox: false,
cleanupNumericValues: { floatPrecision: 2 },
convertPathData: {
floatPrecision: 2,
transformPrecision: 4,
},
},
},
},
],
};
Dos parámetros distintos y conviene entender la diferencia.
floatPrecision es la precisión de las coordenadas finales. Es el que decide el error visible.
transformPrecision es la precisión de los coeficientes de las matrices de transformación cuando SVGO las aplica a la geometría. Ese número tiene que ser mayor, porque un error en un coeficiente de matriz se multiplica por la coordenada: un error de 0,01 en un factor de escala aplicado a una coordenada de 800 produce un error de 8 unidades. Bajar transformPrecision es la forma más rápida de destrozar un dibujo grande.
Regla: transformPrecision al menos dos unidades por encima de floatPrecision.
Si generas la d desde tu propio código, el redondeo es tuyo:
const r = (n, d = 2) => Number(n.toFixed(d));
function ruta(puntos) {
return puntos
.map((p, i) => `${i ? 'L' : 'M'}${r(p.x)} ${r(p.y)}`)
.join('');
}
Nótese el Number(): toFixed devuelve una cadena con ceros de relleno (12.50), y convertir a número los elimina. Ese cero final es un byte por coordenada que se suma.
Los tres sitios donde no se puede recortar
Uno: los coeficientes de una matriz de transformación. Ya explicado. El error se multiplica.
Dos: los radios de un arco elíptico. Como se vio en el nivel 17, cuando los extremos de un arco están casi a la distancia máxima que permiten los radios, un redondeo puede empujarlos por encima y disparar la corrección automática de la especificación, que escala los radios. El resultado es un arco ligeramente distinto de sus vecinos, visible como una irregularidad en un gráfico circular. Tres decimales como mínimo en geometría con arcos.
Tres: los valores de stdDeviation, baseFrequency y scale en los filtros. Un baseFrequency="0.008" redondeado a dos decimales se convierte en 0.01, un cambio del 25 por ciento que altera visiblemente el ruido. Los valores pequeños son especialmente sensibles al redondeo absoluto, porque el error relativo se dispara. SVGO no toca los atributos de filtro por defecto; si configuras un recorte global, exclúyelos.
El error visible que calcula la fórmula es el desplazamiento de un punto. Hay un fallo peor y no lineal: dos puntos cercanos que se redondean al mismo valor.
Cuando dos vértices consecutivos de una ruta distan menos que la resolución del redondeo, el redondeo los colapsa en uno. Si el segmento entre ellos era el que impedía que la ruta se cruzara consigo misma, la ruta redondeada se autointerseca. Y con fill-rule="nonzero", una autointersección cambia qué región se considera interior: aparece un agujero donde no había, o desaparece uno que sí había.
El síntoma es característico: el icono se ve bien en la vista previa a tamaño grande, y a 16 píxeles aparece un punto blanco o una muesca en una zona concreta. La gente lo atribuye al antialiasing y no lo es: es un cambio topológico de la geometría.
Dónde ocurre con más frecuencia: en las esquinas apretadas de un icono, en las uniones de un trazo expandido (donde los dos lados del trazo se juntan mucho en un ángulo agudo), y en las letras convertidas a contorno, donde las serifas y las uniones de trazos tienen detalle a escala de décimas de unidad.
Cómo detectarlo antes de que llegue a producción: la comparación visual del artículo anterior, pero rasterizada al tamaño real de uso, no a un tamaño grande. Un icono comparado a 256 píxeles pasa el test; el mismo a 16, no.
Y cómo evitarlo sin renunciar al ahorro: no aplicar una precisión global. Un decimal más para los ficheros que vienen de contornos de texto o de trazos expandidos, y el recorte agresivo para geometría limpia. Distinguirlos es fácil: un fichero con más de trescientos comandos en un viewBox de 24 unidades viene de un contorno, casi seguro.
Coge un icono complejo y genera cinco versiones con precisión de 0 a 4 decimales. Rasteriza las cinco a 16, 24 y 64 píxeles y compáralas con la original píxel a píxel. Anota, para cada tamaño, cuál es la precisión mínima que pasa con una tolerancia razonable. La tabla resultante es la configuración de tu pipeline, medida en lugar de adivinada.