feImage y feTile

facebook-svg gplus-svg twitter-svg

El filtro feImage es un filtro primitivo que se utiliza para cargar una imagen externa ( jpg, png, gif, svg . . .  ).
Para indicar la fuente de la imagen utilizamos el atributo xlink:href.

<feImage xlink:href=". . . beagle400.jpg" width="1" height="1" result="beagle" />

Los atributos width y height indican la anchura y la altura de la imagen. En el siguiente ejemplo width="1" height="1" indican que la imagen cubre todo el lienzo SVG.

<feImage xlink:href=". . . beagle400.jpg" width="1" height="1" result=" beagle" />

El elemento  <feImage> también tiene asignado un atributo result para poder referenciarlo más tarde.

<feImage xlink:href=". . . beagle400.jpg" width="1" height="1" result="beagle" />

En el siguiente ejemplo queremos transformar la imagen del perro ( beagle400.jpg ) en una imagen en blanco y negro. Para esto utilizamos feColorMatrix, otro filtro primitivo cuyo atributo in identifica el objeto al cual se le aplica el filtro. En este caso in="beagle" porque aplicamos el filtro a la imagen cargada con feImage ( cuyo resultado result="beagle").

<svg viewBox="0 0 300 300" width="300" height="300">
      <defs>
        <filter id="imagen" primitiveUnits="objectBoundingBox" x="0"  y="0" >
          <feImage xlink:href="https://s3-us-west-2.amazonaws.com/s.cdpn.io/222579/beagle400.jpg" width="1" height="1" result="beagle" />
          <feColorMatrix in='beagle'  type='matrix' values='0.3333 0.3333 0.3333 0 0 0.3333 0.3333 0.3333 0 0 0.3333 0.3333 0.3333 0 0 0 0 0 1 0'/>
        </filter>
      </defs>
      <rect width="100%" height="100%" style="filter:url(#imagen);"/>
  </svg>

See the Pen SVG feImage #1* by Gabi (@enxaneta) on CodePen.

feImage y feTile

El elemento feImage puede tener dos atributos más: x e y. El valor por defecto de estos dos atributos es 0, lo que quiere decir que el punto de inserción de la imagen ( la esquina izquierda arriba de esta ) se encuentra en el punto 0,0, o sea la esquina izquierda arriba del lienzo SVG.

En el siguiente ejemplo x=".5"  y=".5", lo que indica que el punto de inserción de la imagen se encuentra justo en el centro del lienzo SVG.

<feImage xlink:href=". . . beagle400.jpg" x=".5"  y=".5" width=".4" height=".4" />

Otro filtro primitivo feTile hace que la imagen se repita tanto en x como en y ( "Tile" en inglés quiere decir losa ). El efecto es similar a un patrón (pattern) SVG. En el siguiente ejemplo voy a utilizar feImage para cargar la imagen, y después voy a utilizar feTile para hacer que la imagen se repita hasta cubrir completamente el lienzo SVG.

<svg viewBox="0 0 300 300" width="300" height="300" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
      <defs>
        <filter id="image" primitiveUnits="objectBoundingBox" >
          <feImage xlink:href=". . . beagle400.jpg" x=".5"  y=".5" width=".4" height=".4" />
          <feTile/>
        </filter>
      </defs>
      <rect width="100%" height="100%" style="filter:url(#image);"/>
  </svg>

See the Pen SVG feImage #2 + feTile* by Gabi (@enxaneta) on CodePen.

Utilizar fragmentos

En el siguiente ejemplo queremos utilizar un filtro para combinar dos imágenes: la imagen del perro ( beagle400.jpg ) y una imagen generada utilizando un patrón. El problema es que en este momento esto no funciona en Firefox y el culpable es el bug 455986 ( los filtros feImage con xlink:href no funcionan con fragmentos ).

El fragmento en cuestión es el rectángulo #feImagePatron, generado dentro del mismo SVG.

<svg viewBox="0 0 300 300" width="300" height="300">
          <defs>
1. genera el patrón a utilizar como relleno"         
             <pattern id="_trama" patternUnits="userSpaceOnUse" width="5" height="5" patternTransform="rotate(25)">
                <line id="linea" y2="5" stroke="#f00" />
             </pattern> 
2. genera el rectángulo rect id="feImagePatron"
         <rect id="feImagePatron" width="100%" height="100%" fill="url(#_trama)" />    
         </defs>
3. Crea el filtro
  <filter id="imagen" primitiveUnits="objectBoundingBox" >
4. Importa la primera imagen (el perro) con feImage
         <feImage result="a" xlink:href=". . . beagle400.jpg" x='0' y='0' width="100%"  height="100%"  />
5. Importa la segunda imagen (el rectángulo) con feImage
         <feImage result="b" xlink:href="#feImagePatron" x='0' y='0' />
6. combina las dos imagenes ( a y b ) con feMerge
         <feMerge>
             <feMergeNode in="a" />
             <feMergeNode in="b" />
         </feMerge>
  </filter>
<svg>

See the Pen SVG feImage + fragmentos* by Gabi (@enxaneta) on CodePen.

Solucionar el problema

Podemos solucionar este problema utilizando data URI. Pero primero veamos la imagen SVG que queremos utilizar:

<svg viewBox="0 0 300 300" width="300" height="300" xmlns="http://www.w3.org/2000/svg">
  <defs>
        <pattern id="_trama" patternUnits="userSpaceOnUse" width="5" height="5" patternTransform="rotate(25)">
           <line y2="5" stroke="#f00" />
        </pattern> 
  </defs>
  <rect  width="100%" height="100%" fill="url(#_trama)" />
  </svg>

Dentro de un elemento <defs> creamos el patrón id="_trama" y después lo utilizamos como relleno de un rectángulo.

Lea más acerca de patrones SVG, y en particular acerca de este patrón.

A continuación tenemos que transformarlo a data URI.

Una propuesta que se puede ver por ahí es utilizar el código SVG no cifrado ( unencoded ) así:

<feImage result="d" xlink:href="data:image/svg+xml;utf8,<svg viewBox='0 0 300 300' width='300' height='300' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink'><defs><pattern id='_trama' patternUnits='userSpaceOnUse' width='5' height='5' patternTransform='rotate(25)'><line y2='5' stroke='#f00' /></pattern></defs><rect  width='100%' height='100%' fill='url(#_trama)' /></svg>" x='0' y='0' />

La verdad es que parece críptico, pero no lo es. Mirémos detenidamente el código. Lo destacado en rojo indica al navegador que lo que viene a continuación es un data:uri, ( data: ) y que se trata de una imagen SVG ( image/svg+xml ).

Normalmente cuando utilizamos SVG en HTML5 no utilizamos espacios de nombres ( namespace ), pero en este caso es importante utilizarlos. No olvidemos que, al fin y al cabo, los data:uri son el equivalente a una referencia hacia una fuente externa.

<svg . . . xmlns='http://www.w3.org/2000/svg' . . .

Lo que viene a continuación es fácil de entender porque ya lo hemos visto. Es el mismo código de arriba, pero sin espacios de línea.

See the Pen SVG feImage unencoded data URI by Gabi (@enxaneta) on CodePen.

Y esto funciona pero -de nuevo- solo en Google Chrome. Para que funcione en Safari y en Firefox tenemos que dar un paso más.

Optimizar el código

Para optimizar el código vamos a seguir estas reglas:

a. Lo ponemos todo entre comillas dobles ( " ) y ponemos los atributos entre comillas sencillas ( por ejemplo height = '122px' ). De esta manera no tendremos que escaparlas.

b. Ciframos los caracteres no-alfanuméricos, los así llamados caracteres reservados o caracteres inseguros ( unsafe in URLs characters ) pero no los espacios en blanco:

< se transforma en %3C
> se transforma en %3E

Por ejemplo <svg> vuelve a ser %3Csvg%3E

# se transforma en %23

Por ejemplo fill = "#abcdef" vuelve a ser fill = "%23abcdef"

el guión - se transforma en %2D

Por ejemplo stroke-width="2" vuelve a ser stroke%2Dwidth="2"

<feImage result="d" xlink:href="data:image/svg+xml;utf8,%3Csvg viewBox='0 0 300 300' width='300' height='300' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink'%3E%3Cdefs%3E%3Cpattern id='trama' patternUnits='userSpaceOnUse' width='5' height='5' patternTransform='rotate(25)'%3E%3Cline y2='5' stroke='%23f00' /%3E%3C/pattern%3E%3C/defs%3E%3Crect width='100%' height='100%' fill='url(%23trama)' /%3E%3C/svg%3E" x='0' y='0' />

Y ahora sí que funciona:

See the Pen SVG feImage data URI by Gabi (@enxaneta) on CodePen.