Various stackoverflow answers and comments show examples that use the following construct inside an svg filter:
<feImage href="#id_of_some_element_in_current_svg">
(or legacy syntax xlink:href=
instead of href=
),
There is always the caveat "this won't work in Firefox because this; if you want something that works in Firefox, use an inline data URI as input for feImage rather than an object reference".
For instance:
Ok, so I'm trying that. And it seems to work, as long as I don't try to geometrically transform the result.
In my application, I also need to apply arbitrary transform matrices (often with nonuniform scaling and shearing) to the result of the svg filter. But I haven't been able to get this to work in Firefox at all-- in Firefox, it always seems to apply some uniform scaling that I didn't ask for, instead.
Example
Here's a simple example showing the problem.
The feImage (successfully made from an inline data URI) is a 100x100 gray square, with little blue,red,green squares inside:
and I'm attempting to draw 9 copies of it with various uniform and nonuniform scales.
I'm expressing each transform using scale(...)
here, but I've also tried expressing the same thing using matrix(...)
and that doesn't work either.
<!DOCTYPE html><html><head><meta charset="utf-8" ></head><body>
<svg width="480" height="480">
<defs>
<filter id="the_filter">
<feImage href='data:image/svg+xml,
<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100">
<!-- a gray square with little blue,red,green squares inside -->
<rect x="0" y="0" width="100" height="100" fill="gray" />
<rect x="10" y="10" width="10" height="10" fill="blue" />
<rect x="80" y="10" width="10" height="10" fill="red" />
<rect x="10" y="80" width="10" height="10" fill="green" />
</svg>'
/>
</filter>
</defs>
<!-- a blue frame around the entire svg -->
<rect x="0" y="0" width="100%" height="100%" stroke-width="10" stroke="blue" fill="none" />
<!-- 9 rects with the filter applied, with various uniform and nonuniform scales -->
<rect x="0" y="0" width="100" height="100" transform="translate(20 20) scale(.5 .5)" filter="url(#the_filter)" />
<rect x="0" y="0" width="100" height="100" transform="translate(100 20) scale(1 .5)" filter="url(#the_filter)" />
<rect x="0" y="0" width="100" height="100" transform="translate(245 20) scale(2 .5)" filter="url(#the_filter)" />
<rect x="0" y="0" width="100" height="100" transform="translate(20 100) scale(.5 1)" filter="url(#the_filter)" />
<rect x="0" y="0" width="100" height="100" transform="translate(100 100) scale(1 1)" filter="url(#the_filter)" />
<rect x="0" y="0" width="100" height="100" transform="translate(245 100) scale(2 1)" filter="url(#the_filter)" />
<rect x="0" y="0" width="100" height="100" transform="translate(20 245) scale(.5 2)" filter="url(#the_filter)" />
<rect x="0" y="0" width="100" height="100" transform="translate(100 245) scale(1 2)" filter="url(#the_filter)" />
<rect x="0" y="0" width="100" height="100" transform="translate(245 245) scale(2 2)" filter="url(#the_filter)" />
</svg>
</body></html>
Result in chrome 131.0.6778.139, chromium 131.0.6778.85, opera 115.0.5322.109 (correct):
Result in firefox 133.0.3 (wrong):
How do I nonuniformly scale those feImages in a way that works in firefox as well as the other browsers?
The answer you seek is preserveAspectRatio
. It's the only change I made to the code in your snippet. The snippet below assigns the value none
. The default is supposed to be xMidYMid meet
, which preserves the aspect ratio and the mid-point. That is the default that Firefox applies. Apparently Chrome does not.
<!DOCTYPE html><html><head><meta charset="utf-8" ></head><body>
<svg width="480" height="480">
<defs>
<filter id="the_filter">
<feImage preserveAspectRatio="none" href='data:image/svg+xml,
<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100">
<!-- a gray square with little blue,red,green squares inside -->
<rect x="0" y="0" width="100" height="100" fill="gray" />
<rect x="10" y="10" width="10" height="10" fill="blue" />
<rect x="80" y="10" width="10" height="10" fill="red" />
<rect x="10" y="80" width="10" height="10" fill="green" />
</svg>'
/>
</filter>
</defs>
<!-- a blue frame around the entire svg -->
<rect x="0" y="0" width="100%" height="100%" stroke-width="10" stroke="blue" fill="none" />
<!-- 9 rects with the filter applied, with various uniform and nonuniform scales -->
<rect x="0" y="0" width="100" height="100" transform="translate(20 20) scale(.5 .5)" filter="url(#the_filter)" />
<rect x="0" y="0" width="100" height="100" transform="translate(100 20) scale(1 .5)" filter="url(#the_filter)" />
<rect x="0" y="0" width="100" height="100" transform="translate(245 20) scale(2 .5)" filter="url(#the_filter)" />
<rect x="0" y="0" width="100" height="100" transform="translate(20 100) scale(.5 1)" filter="url(#the_filter)" />
<rect x="0" y="0" width="100" height="100" transform="translate(100 100) scale(1 1)" filter="url(#the_filter)" />
<rect x="0" y="0" width="100" height="100" transform="translate(245 100) scale(2 1)" filter="url(#the_filter)" />
<rect x="0" y="0" width="100" height="100" transform="translate(20 245) scale(.5 2)" filter="url(#the_filter)" />
<rect x="0" y="0" width="100" height="100" transform="translate(100 245) scale(1 2)" filter="url(#the_filter)" />
<rect x="0" y="0" width="100" height="100" transform="translate(245 245) scale(2 2)" filter="url(#the_filter)" />
</svg>
</body></html>