Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

feImage with internal source

Goal

I'm trying to write a SVG filter to render a wave lighting atop a given shape. So inside that shape I want the waves to apply, but I want no visible effect outside that shape. As far as I understand, I cannot use the same image source both for the hight map used for the waves and as the target domain for an atop composition. So I thought a <feImage> element might be used to obtain the height map from a separate gradient-filled object. But so far I haven't succeeded in getting that object visible inside the filter effect area.

First attempt

Code I have so far:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg version="1.1" width="512" height="512"
     xmlns="http://www.w3.org/2000/svg"
     xmlns:xlink="http://www.w3.org/1999/xlink">
  <defs>
    <radialGradient id="waveFill" r="5%" spreadMethod="reflect">
      <stop offset="0%" stop-opacity="1.00"/>
      <stop offset="20%" stop-opacity="0.90"/>
      <stop offset="40%" stop-opacity="0.65"/>
      <stop offset="60%" stop-opacity="0.35"/>
      <stop offset="80%" stop-opacity="0.10"/>
      <stop offset="100%" stop-opacity="0.00"/>
    </radialGradient>
    <rect id="waveImg" x="-210" y="-105" width="420" height="210"
          stroke="none" fill="url(#waveFill)"/>
    <filter id="waveFilter">
      <feImage xlink:href="#waveImg" result="waves"/>
      <!-- feSpecularLighting and so on will be added later on -->
      <feComposite operator="atop" in="waves" in2="SourceImage"/>
    </filter>
  </defs>
  <g transform="translate(256 256)">
    <!-- use xlink:href="#waveImg"/ works -->
    <ellipse rx="200" ry="100" fill="#0000ff" stroke="none"
             style="filter:url(#waveFilter)"/>
  </g>
</svg>

Relevant documentation

The documentation for <feImage> states:

If the ‘xlink:href’ references a stand-alone image resource such as a JPEG, PNG or SVG file, then the image resource is rendered according to the behavior of the ‘image’ element; otherwise, the referenced resource is rendered according to the behavior of the ‘use’ element. In either case, the current user coordinate system depends on the value of attribute ‘primitiveUnits’ on the ‘filter’ element.

Second attempt

use instead of image looks all right to me, but I'm unsure how the rectangular region is defined in this case. The documentation for use seems to indicate that in such a case (the referenced element is neither a symbol nor a svg element), the width and height properties are irrelevant, but how does that describe a rectangular region? I also thought that perhaps a change of primitive units would help. Or boxing the image inside a symbol. So my second attempt was the following one:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg version="1.1" width="512" height="512"
     xmlns="http://www.w3.org/2000/svg"
     xmlns:xlink="http://www.w3.org/1999/xlink">
  <defs>
    <radialGradient id="waveFill" r="5%" spreadMethod="reflect">
      <stop offset="0%" stop-opacity="1.00"/>
      <stop offset="20%" stop-opacity="0.90"/>
      <stop offset="40%" stop-opacity="0.65"/>
      <stop offset="60%" stop-opacity="0.35"/>
      <stop offset="80%" stop-opacity="0.10"/>
      <stop offset="100%" stop-opacity="0.00"/>
    </radialGradient>
    <symbol viewBox="0 0 100 100" id="waveImg" preserveAspectRatio="none">
      <rect width="100" height="100" stroke="none" fill="url(#waveFill)"/>
    </symbol>
    <filter id="waveFilter" primitiveUnits="objectBoundingBox">
      <feImage xlink:href="#waveImg" x="0" y="0" width="100%" height="100%"
               preserveAspectRatio="none" result="waves"/>
      <!-- feSpecularLighting and so on will be added later on -->
      <feComposite operator="atop" in="waves" in2="SourceImage"/>
    </filter>
  </defs>
  <g transform="translate(256 256)">
    <!-- use xlink:href="#waveImg"/ works -->
    <ellipse rx="200" ry="100" fill="#0000ff" stroke="none"
             style="filter:url(#waveFilter)"/>
    <ellipse rx="200" ry="100" fill="none" stroke="#ff0000"/>
  </g>
</svg>

Still no image visible. What am I doing wrong?

Core question

How can I use a fragment of my SVG file as a bump map for lighting, without also using it as the SourceGraphic of my filter?

Rasterizer

This image isn't intended for web deployment, but will be rasterized locally. So browser compatibility isn't much of an issue; if the thing renders correctly under either Inkscape or rsvg-convert, that's enough for me.

like image 343
MvG Avatar asked Nov 06 '13 21:11

MvG


1 Answers

There are a lot of reasons why this might not work.

  1. You may need to specify units for your svg width and height (px, pt, em, % whatever). IE doesn't like unspecified SVG element dimensions.
  2. You're using the wrong term for your feComposite input: "SourceImage" should be "SourceGraphic".
  3. Filters are better applied directly via the filter property not via a style property.
  4. I do not think you can reference a symbol directly in an feImage, you have to <use it first, and then reference the id on the use element. (In any case, why not just use the rect directly?)

Here is a version that works in Chrome (and probably Safari) using your methodology.

<svg version="1.1" width="512px" height="512px" viewbox="0 0 512 512"
     xmlns="http://www.w3.org/2000/svg"
     xmlns:xlink="http://www.w3.org/1999/xlink">   <defs>
    <radialGradient id="waveFill" r="5%" spreadMethod="reflect">
      <stop offset="0%" stop-opacity="1.00"/>
      <stop offset="20%" stop-opacity="0.90"/>
      <stop offset="40%" stop-opacity="0.65"/>
      <stop offset="60%" stop-opacity="0.35"/>
      <stop offset="80%" stop-opacity="0.10"/>
      <stop offset="100%" stop-opacity="0.00"/>
    </radialGradient>

      <rect id="waveImg" width="100%" height="100%" stroke="none" fill="url(#waveFill)"/>

    <filter id="waveFilter" primitiveUnits="objectBoundingBox">
      <feImage xlink:href="#waveImg" x="0%" y="0%" width="100%" height="100%" result="waves"/>
      <!-- feSpecularLighting and so on will be added later on -->
      <feComposite operator="atop" in="waves" in2="SourceGraphic"/>
    </filter>   
     </defs>  
     <g transform="translate(256 256)">


    <ellipse rx="200" ry="100" fill="#0000ff" stroke="none"
             filter="url(#waveFilter)"/>
    <ellipse rx="200" ry="100" fill="none" stroke="#ff0000"/>   </g> </svg>

However, this won't work in Firefox because object inputs to feImage are not supported, and from my testing, feImage units are pretty buggy in IE.

There is no reason why you can't re-use your Source Graphic for lighting effects - here is a shorter version of what you're trying to do that works cross browser and avoids feImage.

<svg version="1.1" x="0px" y="0px" width="512px" height="512px" viewbox="0 0 512 512"
     xmlns="http://www.w3.org/2000/svg"
     xmlns:xlink="http://www.w3.org/1999/xlink">   <defs>
    <radialGradient id="waveFill" r="5%" spreadMethod="reflect">
      <stop offset="0%" stop-opacity="1.00"/>
      <stop offset="20%" stop-opacity="0.90"/>
      <stop offset="40%" stop-opacity="0.65"/>
      <stop offset="60%" stop-opacity="0.35"/>
      <stop offset="80%" stop-opacity="0.10"/>
      <stop offset="100%" stop-opacity="0.00"/>
    </radialGradient>

    <filter id="waveFilter" x="0%" y="0%" width="100%" height="100%">
      <feFlood flood-color="#0000ff" result="blue"/>
      <feComposite operator="in" in2="SourceGraphic" in="blue"/>
    </filter>  
    </defs> 
    <g transform="translate(256 256)">
    <!-- use xlink:href="#waveImg"/ works -->
    <ellipse rx="200" ry="100" fill="url(#waveFill)" filter="url(#waveFilter)"/>
    <ellipse rx="200" ry="100" fill="none" stroke="#ff0000"/>   </g> </svg>

And (because why not) here's an example with specular lighting added in:

<svg version="1.1" x="0px" y="0px" width="512px" height="512px" viewbox="0 0 512 512"
     xmlns="http://www.w3.org/2000/svg"
     xmlns:xlink="http://www.w3.org/1999/xlink">
  <defs>
    <radialGradient id="waveFill" r="5%" spreadMethod="reflect">
      <stop offset="0%" stop-opacity="1.00"/>
      <stop offset="20%" stop-opacity="0.90"/>
      <stop offset="40%" stop-opacity="0.65"/>
      <stop offset="60%" stop-opacity="0.35"/>
      <stop offset="80%" stop-opacity="0.10"/>
      <stop offset="100%" stop-opacity="0.00"/>
    </radialGradient>

    <filter id="waveFilter" x="0%" y="0%" width="100%" height="100%">
      <feFlood flood-color="#0000ff" result="blue"/>
      <feComposite operator="in" in2="SourceGraphic" in="blue"                 
          result="sourcewithblue"/>

      <feSpecularLighting in="SourceAlpha" result="specOut" 
         specularConstant="2" specularExponent="20" lighting-color="white" 
         surfaceScale="2">
        <fePointLight  x="-200" y="-200" z="200"/> 
      </feSpecularLighting>

      <feComposite operator="arithmetic" in="specOut" in2="sourcewithblue" k1="0" k2="1" k3="1" result="unclippedoutput"/>
      <feComposite operator="in" in="unclippedoutput" in2="SourceGraphic"/>

    </filter>
  </defs>
 <g transform="translate(256 256)">
<ellipse rx="200" ry="100" fill="url(#waveFill)" filter="url(#waveFilter)"/>
<ellipse rx="200" ry="100" fill="none" stroke="#ff0000"/>
</g>
</svg>
like image 54
Michael Mullany Avatar answered Sep 22 '22 15:09

Michael Mullany