Saltar al contenido

pattern y las texturas

Los dos sistemas de coordenadas de un patrón (que son distintos entre sí por omisión), el viewBox del patrón, patternTransform, y las costuras que aparecen al escalar.

Los dos sistemas de un patrón

Un pattern define una celda que se repite en mosaico. Hay dos decisiones independientes: dónde está y cuánto mide la celda, y en qué coordenadas está dibujado su contenido.

patternUnits controla lo primero, es decir x, y, width y height del pattern. Su valor por omisión es objectBoundingBox, así que un width="0.2" significa «una quinta parte del ancho de la forma que use el patrón».

patternContentUnits controla lo segundo. Su valor por omisión es userSpaceOnUse, así que un circle cx="5" dentro del patrón está a 5 unidades del lienzo, no a 5 veces el ancho de la forma.

La mezcla por omisión es la peor combinación posible: la celda se dimensiona en relativo y el contenido se dibuja en absoluto, así que al cambiar el tamaño de la forma la celda crece y el contenido no. El patrón se ve distinto en cada forma que lo use, y no de una manera controlable.

La configuración que funciona es poner los dos en userSpaceOnUse, o dicho de otra forma, poner patternUnits="userSpaceOnUse" y dejar el otro en su valor por omisión:

HTML
<svg viewBox="0 0 240 100" width="480">  <defs>    <pattern id="puntos" patternUnits="userSpaceOnUse"             x="0" y="0" width="12" height="12">      <circle cx="6" cy="6" r="2" fill="#89b4fa" />    </pattern>  </defs>  <rect x="10" y="10" width="100" height="80" fill="url(#puntos)" />  <rect x="130" y="10" width="40"  height="80" fill="url(#puntos)" /></svg>

Ahora los dos rectángulos tienen puntos del mismo tamaño y con el mismo paso, que es lo que cualquiera esperaría. La celda mide 12 por 12 unidades del lienzo, el círculo está en el centro de esa celda, y todo es predecible.

Con la configuración por omisión, el rectángulo estrecho tendría una celda de otro tamaño y los círculos aparecerían recortados o dispersos según el caso.

El viewBox del patrón

Hay una tercera vía que evita pensar en los dos sistemas: dar al pattern su propio viewBox. Cuando lo tiene, el contenido se dibuja en las coordenadas de ese viewBox y se escala para encajar en la celda, exactamente igual que un symbol. patternContentUnits se ignora por completo.

HTML
<pattern id="cuadros" patternUnits="userSpaceOnUse"         width="20" height="20" viewBox="0 0 10 10">  <rect width="5" height="5" fill="#313244" />  <rect x="5" y="5" width="5" height="5" fill="#313244" /></pattern>

El contenido se dibuja en una rejilla de 10 y se escala a una celda de 20. Cambiar el tamaño de la celda escala el dibujo entero sin tocar el contenido, que es justo lo que quieres cuando el patrón viene de un diseñador que lo dibujó en su propia rejilla.

Esta es la forma que recomiendo por defecto: patternUnits="userSpaceOnUse" más viewBox. Dos atributos y el patrón deja de sorprender.

patternTransform

patternTransform aplica una transformación al espacio del patrón entero: rotación, escala, sesgado. Es lo que permite rayas diagonales sin dibujarlas diagonales:

HTML
<pattern id="rayas" patternUnits="userSpaceOnUse"         width="10" height="10" patternTransform="rotate(45)">  <line x1="0" y1="0" x2="0" y2="10" stroke="#45475a" stroke-width="4" /></pattern>

Una línea vertical, un patrón rotado 45 grados, y salen rayas diagonales con el paso correcto. Dibujarlas diagonales dentro de la celda exigiría calcular las intersecciones con los bordes para que casen entre celdas, que es exactamente el problema que patternTransform evita.

Con una escala en patternTransform puedes también hacer que el patrón se adapte a un nivel de zoom sin regenerarlo.

Las costuras

Un patrón produce costuras cuando el contenido de la celda toca sus bordes. La causa es el antialiasing: el borde de la celda cae entre dos píxeles, cada celda pinta su mitad con cobertura parcial, y la suma de las dos mitades no llega a la cobertura completa. Aparece una retícula de líneas más claras.

Tres soluciones, en orden de preferencia.

Uno: que el contenido no toque los bordes. Es la solución de verdad. Un patrón de puntos con el círculo centrado y de radio menor que la mitad de la celda no tiene costuras porque no hay nada que cortar.

Dos: dibujar el contenido cortado por los cuatro bordes explícitamente. Si el motivo tiene que atravesar el borde, dibújalo cuatro veces desplazado (arriba, abajo, izquierda, derecha) para que cada celda pinte también los trozos que le entran de las vecinas. Es lo que hace un editor de texturas sin costuras.

Tres: alinear la celda con la rejilla de píxeles. Si el width de la celda en píxeles finales es un entero, los bordes caen entre píxeles y no hay reparto. Funciona, es frágil, y se rompe en cuanto el usuario hace zoom o el elemento cambia de tamaño.

Patrones útiles de verdad

Tres que se usan más de lo que parece.

Rayado para la accesibilidad de un gráfico. Cuando dos series se distinguen solo por color, un patrón de rayas en una de ellas las distingue también para quien no percibe ese contraste de color. Es una recomendación concreta, y se retoma en el nivel 29.

Rejilla de fondo. Dos líneas por celda, en userSpaceOnUse, con la celda alineada con las marcas del eje. Un solo elemento en lugar de cien líneas.

Imagen como textura. Un image dentro del patrón permite rellenar una forma con una fotografía. Es la manera de hacer un mapa coropleto con textura, o una máscara de recorte con contenido. Ojo: si el SVG se va a servir como imagen, la referencia externa no se carga, y hay que incrustar la imagen como data URI.