Open RazrFalcon opened 5 years ago
Another example:
<svg viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg">
<filter id="filter1">
<feFlood flood-color="green" x="0" y="0" width="10" height="10"/>
<feOffset dx="5" dy="5"/>
<feTile/>
</filter>
<rect x="40" y="40" width="120" height="120" filter="url(#filter1)"/>
<rect x="28" y="28" width="144" height="144" fill="none" stroke="black"/>
<rect x="28" y="28" width="10" height="10" fill="none" stroke="black"/>
<rect id="frame" x="1" y="1" width="198" height="198" fill="none" stroke="black"/>
</svg>
I assume that resvg
is the correct one, mainly because this is what I've expected to get. But the results a very different.
Looks like Chrome uses object's (x, y) position and not the filter area (x, y) position as the tile start position. Which one should be used?
It does look like anyone is correct in the first case - well at least not the way I interpret the spec... In the second case I think the four ones on the right are correct. This could certainly use more tests. (I made https://jsfiddle.net/oryusfvk/1/ to get some kind of feel for the first case.)
I don't think feTile
is the problem here though - it seems this is more about filter primitive subregions and/or clipping of outputs.
I'll try to get a motivation down...
First case: feFlood
generates a 10x10 green square. When used as input to the feOffset
, this square is clipped against the filter region (per [1]), making the input a 6x6 green square that is shifted down and right by 5 units, and there clipped by its filter primitive subregion (0,0 10x10; inherited from it input) - the result being a 1x1 square (at the "bottom right"). feTile
then tiles this using (x + i * width, y + j * height)
(see spec for feTile
; in short the x,y,width,height reference the input subregion). So we get tile positions (considering the "diagonal" for brevity) as (0, 0), (10, 10), (20, 20), (30, 30)... the contents of each tile being the 1x1 square. Only the tiles that "fall within" the filter primitive subregion of the feTile
would be rendered though (i and j in [5, 5+10-1] or so). An observation here based on the results presented is that Firefox (and librsvg) seems to tile using the clipped width/height (6) and not the specified (10).
Second case: Here the input to the feTile
(and feOffset
) is clipped out by the filter region (being 28,28 144x144) and thus the input tile is fully transparent.
As for "tile start positions" (it has already been covered above), this is determined by the filter primitive subregion of the feTile
input. I.e the x
and y
of the input determine it, making it 0,0 in this case. (It looks like Chrome may have some "drift" here based on the additional test I linked above.)
So I'd say that feTile
is reasonably clearly defined (perhaps with an exception for the case where clipping happens, but it seems to me that is reasonaly well-defined too?), but maybe not sufficiently tested. Definitely no interop though...
[1] https://drafts.fxtf.org/filter-effects/#FilterPrimitiveSubRegion
The filter region acts as a hard clip clipping rectangle on the filter primitive’s input image(s).
making the input a 6x6 green square
After reading this 10 times it strikes me that 0x0 is in the global coordinates, not the filter ones... That's explain everything. So yes, it's not a feTile
problem.
So I'd say that feTile is reasonably clearly defined
Well, if it was then all apps would render it exactly the same. The current filter
support situation is basically tragic. And since we don't have a reference implementation than it's the spec to blame.
0,0 10x10; inherited from it input
But if I have something like this:
<svg id="svg1" viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg">
<filter id="filter1">
<feFlood flood-color="red" x="50" y="50" width="100" height="100"/>
<feFlood flood-color="green"/>
</filter>
<rect id="rect1" x="20" y="20" width="160" height="160" filter="url(#filter1)"/>
<rect id="frame" x="1" y="1" width="198" height="198" fill="none" stroke="black"/>
</svg>
I will get a green rect with an area of the filter region and not with an area of the previous filter primitive. In all applications. Is this because feFlood
ignores the in
attribute?
So what is exactly the default filter primitive region? I can't understand it from the spec.
or feTile (which is special because its principal function is to replicate the referenced node in X and Y and thereby produce a usually larger result
But in my example, the referenced node is feOffset
and not feFlood
.
So what is exactly the default filter primitive region? I can't understand it from the spec.
feFlood
has no inputs. So the second feFlood
does not inherit its filter subregion from the first one. From the spec:
If there are no referenced nodes (e.g., for feImage or feTurbulence), ... the default subregion is 0%, 0%, 100%, 100%,
the default subregion is 0%, 0%, 100%, 100%, where as a special-case the percentages are relative to the dimensions of the filter region
So in my original example, feOffset
will be equal to the filter region and therefore I'm losing the 10x10 feFlood
region. Then feTile
input is equal to the filter region and I have no idea how should I tile it now.
So filter primitive region is inherited from the previous one, but only in some cases? Then in which one? Because the spec lists only feImage
and feTurbulence
.
So filter primitive region is inherited from the previous one, but only in some cases? Then in which one? Because the spec lists only feImage and feTurbulence.
It lists those two as examples, but they are not the only ones. In the spec it says:
x, y, width and height default to the union (i.e., tightest fitting bounding box) of the subregions defined for all referenced nodes.
By "referenced nodes" here, it means input nodes. I.e. any primitive that takes an in
or in2
.
I think all of the results for the first example are incorrect. I've created a fiddle showing what I think the spec says for each step.
Expected output should be as follows: http://jsfiddle.net/Lc6v1uyp/1/
Chrome is wrong because the spec says that primitive inputs are clipped by the filter region. However it appears to be scrolling in parts of the feFlood that are outside the filter region.
Firefox is wrong (I think) because it appears to be using a tiling width and height that is too small. The spec says that the tile spacing is set by the input subregion. Firefox appears to be using the clipped 6x6 subregion as the tile size instead of the defined 10x10 subregion.
The spec says that the input image is hard-clipped, but it isn't explicit about whether that clip affects the primitive subregion bounds. Chrome obviously doesn't think it does, because it is using the correct (IMO) tiling offsets.
If there is any takeaway from this, it is that the primitive subregion section should be clear about whether or not the subregion bounds are affected by the clip.
As for the second example, I agree with @fsoder. The last four are correct. The output of the feFlood gets completely clipped away. So the subsequent primitives have a blank input.
Thanks. This clarifies the things a bit.
Looks like it's really a filter primitive region issue.
I've made 3 tests which in my opinion should all produce the exact results, but they are not:
<svg id="svg1" viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg">
<filter id="filter1">
<feFlood flood-color="green" x="4" y="4" width="10" height="10"/>
<feOffset dx="5" dy="5"/>
<feTile x="50" y="50" width="100" height="100"/>
</filter>
<filter id="filter2">
<feFlood flood-color="gray" x="4" y="4" width="10" height="10"/>
<feOffset dx="5" dy="5"/>
<feTile/>
</filter>
<rect id="rect2" x="20" y="20" width="160" height="160" filter="url(#filter2)"/>
<rect id="rect1" x="20" y="20" width="160" height="160" filter="url(#filter1)"/>
<rect id="rect3" x="50" y="50" width="100" height="100" fill="none" stroke="black"/>
<rect id="frame" x="1" y="1" width="198" height="198" fill="none" stroke="black"/>
</svg>
<svg id="svg1" viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg">
<filter id="filter1">
<feFlood flood-color="green" x="20" y="20" width="10" height="10"/>
<feOffset dx="5" dy="5"/>
<feTile x="50" y="50" width="100" height="100"/>
</filter>
<filter id="filter2">
<feFlood flood-color="gray" x="20" y="20" width="10" height="10"/>
<feOffset dx="5" dy="5"/>
<feTile/>
</filter>
<rect id="rect2" x="20" y="20" width="160" height="160" filter="url(#filter2)"/>
<rect id="rect1" x="20" y="20" width="160" height="160" filter="url(#filter1)"/>
<rect id="rect3" x="50" y="50" width="100" height="100" fill="none" stroke="black"/>
<rect id="frame" x="1" y="1" width="198" height="198" fill="none" stroke="black"/>
</svg>
<svg id="svg1" viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg">
<filter id="filter1">
<feFlood flood-color="green" x="2%" y="2%" width="10" height="10"/>
<feOffset dx="5" dy="5"/>
<feTile x="50" y="50" width="100" height="100"/>
</filter>
<filter id="filter2">
<feFlood flood-color="gray" x="4" y="4" width="10" height="10"/>
<feOffset dx="5" dy="5"/>
<feTile/>
</filter>
<rect id="rect2" x="20" y="20" width="160" height="160" filter="url(#filter2)"/>
<rect id="rect1" x="20" y="20" width="160" height="160" filter="url(#filter1)"/>
<rect id="rect3" x="50" y="50" width="100" height="100" fill="none" stroke="black"/>
<rect id="frame" x="1" y="1" width="198" height="198" fill="none" stroke="black"/>
</svg>
And in short:
<feFlood flood-color="green" x="4" y="4" width="10" height="10"/>
<!-- it shouldn't matter where I've started, as long as the feTile input is 10x10px -->
<feFlood flood-color="green" x="20" y="20" width="10" height="10"/>
<!-- 2% == 4px -->
<feFlood flood-color="green" x="2%" y="2%" width="10" height="10"/>
Should they all be equal?
The first and third will be equal, but the second one would not. The second one would have a different phase (4, 14, 24, 34, ... for first and third while 20, 30, 40, 50, ... for the second).
https://drafts.fxtf.org/filter-effects/#feTileElement
Example:
As you can see, everyone understand it differently (Batik crashed).
I think that in this test the correct result is in Chrome. At least this is what I've expected. But I'm not sure that this is actually what should be rendered.