w3c / fxtf-drafts

Mirror of https://hg.fxtf.org/drafts
https://drafts.fxtf.org/
Other
69 stars 49 forks source link

[filter-effects-1] Clarify the feTile area #327

Open RazrFalcon opened 5 years ago

RazrFalcon commented 5 years ago

https://drafts.fxtf.org/filter-effects/#feTileElement

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 x="50" y="50" width="100" height="100"/>
    </filter>
    <rect x="20" y="20" width="160" height="160" filter="url(#filter1)"/>
    <rect x="50" y="50" width="100" height="100" fill="none" stroke="black"/>

    <rect x="1" y="1" width="198" height="198" fill="none" stroke="black"/>
</svg>

1543747653

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.

RazrFalcon commented 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>

1543750136

I assume that resvg is the correct one, mainly because this is what I've expected to get. But the results a very different.

RazrFalcon commented 5 years ago

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?

fsoder commented 5 years ago

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).

RazrFalcon commented 5 years ago

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.

RazrFalcon commented 5 years ago

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.

BigBadaboom commented 5 years ago

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%,

RazrFalcon commented 5 years ago

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.

BigBadaboom commented 5 years ago

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.

BigBadaboom commented 5 years ago

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.

http://jsfiddle.net/Lc6v1uyp/

Expected output should be as follows: http://jsfiddle.net/Lc6v1uyp/1/

capture

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.

http://jsfiddle.net/Lc6v1uyp/2/

RazrFalcon commented 5 years ago

Thanks. This clarifies the things a bit.

RazrFalcon commented 5 years ago

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>

002

<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>

005

<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>

006

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?

fsoder commented 5 years ago

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).