Complementary masking

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
6 messages Options
Reply | Threaded
Open this post in threaded view
|

Complementary masking

J. R. Haigh
Dear SVG standards editors,
        Something which I frequently want to do is to clip 2 objects such that one fits tightly with the other. I create a clipping path for object A and a complementary clipping path for object B. This is cumbersome because updating the original clipping path does not update its complement.
        It also suffers from a common misrendering of antialiased edges which leaves a ‘hairline’ of translucency, 1 pixel thick regardless of the zoom. This is a misrendering because supersampling diminishes the hairline – surely a correct rendering of /scalable/ vector graphics should not be affected by the zoom level or resolution, i.e. be ‘resolution-agnostic’.
        Furthermore, when trying to generalise this to masking, it gets even more cumbersome. Creating a complement mask object may be a challenge in itself, but to have to update it manually each time the original is altered is even more cumbersome.
        However, I've found a way of using compositing filters to demonstrate the concept of complement masking, and this can be used for complementary clipping without hairlines even without resolution-agnostic rendering:

    <filter
       style="color-interpolation-filters:sRGB"
       id="filter-complementary-masking"
       inkscape:label="Complementary Masking">
      <feImage
         id="feImage-objectA"
         xlink:href="#objectA"
         result="result1" />
      <feComposite
         id="feComposite-mask"
         in2="SourceAlpha"
         operator="in"
         result="result2" />
      <feImage
         id="feImage-objectB"
         xlink:href="#objectB"
         result="result3" />
      <feComposite
         id="feComposite-complementMask"
         in2="SourceAlpha"
         operator="out"
         result="result4" />
      <feComposite
         id="feComposite-add"
         in2="result2"
         operator="arithmetic"
         k2="1"
         k3="1" />
    </filter>

It's not very practical, though, because it comes with a few problems which make it cumbersome:
• behaves very differently from the SVG mask element (https://www.w3.org/TR/SVG/masking#Masking ) for which this is conceptually a generalisation of;
        - the XML looks very different and is applied using the Filter Editor in Inkscape rather than the ‘Object’ → ‘Mask’ → ‘Set’ menu item;
        - the original objects remain;
        - the masked objects do not appear in their original positions;
        - the masked objects move when the path is moved or when it is altered such that the left or top edges of the bounding box move (the objects should stay still regardless of how the path is manipulated);
• in Inkscape 0.91 (at least), the feImage filter renders clipped paths at fixed resolution (anything closer than 1:1 zoom appears pixelated; even when the clipped path is part of a group, only the clipped path has fixed resolution, other objects within the group are scalable).

        Ideally, the masking and clipping of the SVG standard should be updated to provide a native means to do complementary masking. The resolution-dependent rendering remains a problem, though, particularly for complementary clipping because I also often want object A and object B to be clones of the same object, but for there to be 1 or more other objects between the normal clip and the complement clip in the Z-order.
        I suggest that the amendment to the standard wrt. complementary clipping be:
• introduce 2 new insideness rules, ‘zero’ and ‘even’[1], used for the fill-rule and clip-rule properties – an svg:clipPath element can be used for a complement clipping by setting clip-rule to ‘zero’ in the default case where the ‘original clip’ uses the ‘nonzero’ insideness rule, or ‘even’ where the original is set to ‘odd’;
• require resolution-agnostic rendering such that hairlines are fixed on the complementary clipping and other situations in which they appear.
(Note 1: Also please make ‘odd’ a synonym of ‘evenodd’, as the insideness is just when it's odd, not even.)
        For the complementary masking, the svg:mask element can be used as it is in the defs, staying consistent with svg:clipPath which is also used as-is, but in the drawing, something needs to take exactly 2 input objects, so maybe there should be a new container element which takes exactly 2 children and has a mask property specifying an svg:mask element (from the defs) and applies it as a mask to the first element and as a complement mask to the second element, much in the manner of the above filter. The specifics of how best to specify the 2 input objects needs some more thought.
        The complementary masking would perhaps be most commonly useful for merging 2 different objects, but the usecase of weaving objects together still applies:
• an object is cloned (this will be the main one which others are weaved into);
• 1 or more other objects are grouped with either of the clones with the clone at the back of the group;
• the group and the other clone (or the 2 groups if both clones have had objects grouped to them) are then complementary masked together again.
        More than 2 objects can be complementary masked by nesting such maskings and using n - 1 svg:mask references where n is the number of objects that are desired to be complementary masked. Overall it's a pretty comprehensive generalisation I think, at least as far as I can see.
        I hope that these suggestions are useful for progressing the standard.

Yours faithfully,
J. R. Haigh.
--
Sent from Claws Mail on NixOS.


Reply | Threaded
Open this post in threaded view
|

Re: Complementary masking

Paul LeBeau
Hi J.R.

It is possible to create an SVG that has uses two masks that are "complments" of one another and share a single path definition.

Here's a simple example:

<svg width="500" height="300">
  <defs>
    <path id="maskshape" d="M0 0 L200 0 C250 100 250 200 200 300 L0 300 Z"/>
    
    <mask id="mask1">
      <rect width="500" height="300" fill="black"/>
      <use xlink:href="#maskshape" fill="white"/>
    </mask>
    <mask id="mask1-complement">
      <rect width="500" height="300" fill="white"/>
      <use xlink:href="#maskshape" fill="black"/>
    </mask>
  </defs>

  <rect width="500" height="300" fill="darkorange" mask="url(#mask1)" />
  <rect width="500" height="300" fill="darkslateblue" mask="url(#mask1-complement)" />
</svg>



Regards

Paul


On 2 June 2016 at 20:52, J. R. Haigh <[hidden email]> wrote:
Dear SVG standards editors,
        Something which I frequently want to do is to clip 2 objects such that one fits tightly with the other. I create a clipping path for object A and a complementary clipping path for object B. This is cumbersome because updating the original clipping path does not update its complement.
        It also suffers from a common misrendering of antialiased edges which leaves a ‘hairline’ of translucency, 1 pixel thick regardless of the zoom. This is a misrendering because supersampling diminishes the hairline – surely a correct rendering of /scalable/ vector graphics should not be affected by the zoom level or resolution, i.e. be ‘resolution-agnostic’.
        Furthermore, when trying to generalise this to masking, it gets even more cumbersome. Creating a complement mask object may be a challenge in itself, but to have to update it manually each time the original is altered is even more cumbersome.
        However, I've found a way of using compositing filters to demonstrate the concept of complement masking, and this can be used for complementary clipping without hairlines even without resolution-agnostic rendering:

    <filter
       style="color-interpolation-filters:sRGB"
       id="filter-complementary-masking"
       inkscape:label="Complementary Masking">
      <feImage
         id="feImage-objectA"
         xlink:href="#objectA"
         result="result1" />
      <feComposite
         id="feComposite-mask"
         in2="SourceAlpha"
         operator="in"
         result="result2" />
      <feImage
         id="feImage-objectB"
         xlink:href="#objectB"
         result="result3" />
      <feComposite
         id="feComposite-complementMask"
         in2="SourceAlpha"
         operator="out"
         result="result4" />
      <feComposite
         id="feComposite-add"
         in2="result2"
         operator="arithmetic"
         k2="1"
         k3="1" />
    </filter>

It's not very practical, though, because it comes with a few problems which make it cumbersome:
• behaves very differently from the SVG mask element (https://www.w3.org/TR/SVG/masking#Masking ) for which this is conceptually a generalisation of;
        - the XML looks very different and is applied using the Filter Editor in Inkscape rather than the ‘Object’ → ‘Mask’ → ‘Set’ menu item;
        - the original objects remain;
        - the masked objects do not appear in their original positions;
        - the masked objects move when the path is moved or when it is altered such that the left or top edges of the bounding box move (the objects should stay still regardless of how the path is manipulated);
• in Inkscape 0.91 (at least), the feImage filter renders clipped paths at fixed resolution (anything closer than 1:1 zoom appears pixelated; even when the clipped path is part of a group, only the clipped path has fixed resolution, other objects within the group are scalable).

        Ideally, the masking and clipping of the SVG standard should be updated to provide a native means to do complementary masking. The resolution-dependent rendering remains a problem, though, particularly for complementary clipping because I also often want object A and object B to be clones of the same object, but for there to be 1 or more other objects between the normal clip and the complement clip in the Z-order.
        I suggest that the amendment to the standard wrt. complementary clipping be:
• introduce 2 new insideness rules, ‘zero’ and ‘even’[1], used for the fill-rule and clip-rule properties – an svg:clipPath element can be used for a complement clipping by setting clip-rule to ‘zero’ in the default case where the ‘original clip’ uses the ‘nonzero’ insideness rule, or ‘even’ where the original is set to ‘odd’;
• require resolution-agnostic rendering such that hairlines are fixed on the complementary clipping and other situations in which they appear.
(Note 1: Also please make ‘odd’ a synonym of ‘evenodd’, as the insideness is just when it's odd, not even.)
        For the complementary masking, the svg:mask element can be used as it is in the defs, staying consistent with svg:clipPath which is also used as-is, but in the drawing, something needs to take exactly 2 input objects, so maybe there should be a new container element which takes exactly 2 children and has a mask property specifying an svg:mask element (from the defs) and applies it as a mask to the first element and as a complement mask to the second element, much in the manner of the above filter. The specifics of how best to specify the 2 input objects needs some more thought.
        The complementary masking would perhaps be most commonly useful for merging 2 different objects, but the usecase of weaving objects together still applies:
• an object is cloned (this will be the main one which others are weaved into);
• 1 or more other objects are grouped with either of the clones with the clone at the back of the group;
• the group and the other clone (or the 2 groups if both clones have had objects grouped to them) are then complementary masked together again.
        More than 2 objects can be complementary masked by nesting such maskings and using n - 1 svg:mask references where n is the number of objects that are desired to be complementary masked. Overall it's a pretty comprehensive generalisation I think, at least as far as I can see.
        I hope that these suggestions are useful for progressing the standard.

Yours faithfully,
J. R. Haigh.
--
Sent from Claws Mail on NixOS.



Reply | Threaded
Open this post in threaded view
|

Re: Complementary masking

J. R. Haigh
Hi Paul,

At 2016-06-03Fri02:18:58+1200, Paul LeBeau sent:
> Hi J.R.
> It is possible to create an SVG that has uses two masks that are "complments" of one another and share a single path definition.

        That is very interesting, and useful for complementary clipping if either rendering is hairline-free or one is not concerned about hairlines. It achieves the same as the first of the 2 bullet points of my complementary clipping suggestion, but in a more elaborate (and less obvious) manner. (The other bullet point being to require resolution-agnostic rendering, i.e. hairline-free rendering.)

> Here's a simple example:
> <svg width="500" height="300">
>   <defs>
>     <path id="maskshape" d="M0 0 L200 0 C250 100 250 200 200 300 L0 300 Z"/>
>     <mask id="mask1">
>       <rect width="500" height="300" fill="black"/>
>       <use xlink:href="#maskshape" fill="white"/>
>     </mask>
>     <mask id="mask1-complement">
>       <rect width="500" height="300" fill="white"/>
>       <use xlink:href="#maskshape" fill="black"/>
>     </mask>
>   </defs>
>   <rect width="500" height="300" fill="darkorange" mask="url(#mask1)" />
>   <rect width="500" height="300" fill="darkslateblue" mask="url(#mask1-complement)" />
> </svg>
> https://jsfiddle.net/tukk4so2/

        Note that the first rectangle is unnecessary (https://jsfiddle.net/tukk4so2/4/ ) because the undrawn element colour is transparent black. In fact, the first mask is just a clip and can be done as normal with a clipPath element (https://jsfiddle.net/tukk4so2/5/ ):

<svg width="500" height="300">
  <defs>
    <clipPath id="clip1">
      <path id="clipshape" d="M0 0 L200 0 C250 100 250 200 200 300 L0 300 Z"/>
    </clipPath>
    <mask id="mask-clip1-complement">
      <rect width="500" height="300" fill="white"/>
      <use xlink:href="#clipshape" fill="black"/>
    </mask>
  </defs>
  <rect width="500" height="300" fill="darkorange" clip-path="url(#clip1)"/>
  <rect width="500" height="300" fill="darkslateblue" mask="url(#mask-clip1-complement)"/>
</svg>

With my proposed addition of ‘zero’ and ‘even’ insideness rules, the second rectangle would also be unnecessary and thus your example could be simplified to:

<svg width="500" height="300">
  <defs>
    <path id="clipshape" d="M0 0 L200 0 C250 100 250 200 200 300 L0 300 Z"/>
    <mask id="mask-clip1">
      <use xlink:href="#clipshape" fill="white"/>
    </mask>
    <mask id="mask-clip1-complement">
      <use xlink:href="#clipshape" fill="white" fill-rule="zero"/>
    </mask>
  </defs>
  <rect width="500" height="300" fill="darkorange" mask="url(#mask-clip1)"/>
  <rect width="500" height="300" fill="darkslateblue" mask="url(#mask-clip1-complement)"/>
</svg>

Combining these 2 simplifications… Ah, sorry, I realise that there's a mistake in my suggestion – I didn't realise that ‘clip-rule’ applied only to “graphics elements within a ‘clipPath’ element” and not to elements for which ‘clip-path’ applies to (https://www.w3.org/TR/SVG/masking#ClipRuleProperty ). In that case, 2 clipPath elements are still needed, not 1 as in my suggestion. Nonetheless, even with 2 clipPath elements, it is still a slight improvement:

<svg width="500" height="300">
  <defs>
    <clipPath id="clip1">
      <path id="clipshape" d="M0 0 L200 0 C250 100 250 200 200 300 L0 300 Z"/>
    </clipPath>
    <clipPath id="clip1-complement">
      <use xlink:href="#clipshape" clip-rule="zero"/>
    </clipPath>
  </defs>
  <rect width="500" height="300" fill="darkorange" clip-path="url(#clip1)"/>
  <rect width="500" height="300" fill="darkslateblue" clip-path="url(#clip1-complement)"/>
</svg>

        The reason that I renamed ‘maskshape’ to ‘clipshape’ is because all these variants only deal with the special-case of complementary clipping, even where masks are used to achieve this. They also are all subject to hairline artifacts due to the way that antialiasing being rendered is actually not exactly the clip special-case of masks. Thanks for demonstrating that complementary clipping is a special case of masking. I didn't notice that; I only noticed 2 of 3 of the following paths of specialisation (forming a sort of ‘parallelogram’ network, now with a crosslink revealed):
• complementary masking → complementary clipping → clipping;
• complementary masking → masking → clipping;
• complementary masking → masking → complementary clipping → clipping.

  clipping    <---    masking
      ^           ,'     ^
      |         ,'       |
      |       ,'         |
      |     L'           |
complementary <--- complementary
  clipping            masking

So with only clipping and masking available, complementary masking is still out-of-reach (ignoring filters, which are more general than all of this).
        For complementary masking (where both the original and the mask are added), there needs to be some additional container drawing element or attribute on an existing container element such as svg:g that makes its internal Z-order irrelevant and blends its children as addition before writing the result of the container to the canvas, rather than each being written over the last as defined at https://www.w3.org/TR/SVG/masking#SimpleAlphaBlending .
        Complementary clipping being a special-case of complementary masking, such a container element or property would also allow a workaround to the hairline artifacts of common rendering.

---- Hairlines ----
        The hairline is clearly visible in the example when the colours are the same or similar:

<svg width="500" height="300">
  <defs>
    <clipPath id="clip1">
      <path id="clipshape" d="M0 0 L200 0 C250 100 250 200 200 300 L0 300 Z"/>
    </clipPath>
    <mask id="mask-clip1-complement">
      <rect width="500" height="300" fill="white"/>
      <use xlink:href="#clipshape" fill="black"/>
    </mask>
  </defs>
  <rect width="500" height="300" fill="darkslateblue" clip-path="url(#clip1)"/>
  <rect width="500" height="300" fill="darkslateblue" mask="url(#mask-clip1-complement)"/>
</svg>
https://jsfiddle.net/tukk4so2/6/

And here's a demonstration of the translucency, with a third object behind of a contrasting colour:

<svg width="500" height="300">
  <defs>
    <clipPath id="clip1">
      <path id="clipshape" d="M0 0 L200 0 C250 100 250 200 200 300 L0 300 Z"/>
    </clipPath>
    <mask id="mask-clip1-complement">
      <rect width="500" height="300" fill="white"/>
      <use xlink:href="#clipshape" fill="black"/>
    </mask>
  </defs>
  <rect width="500" height="300" fill="red"/>
  <rect width="500" height="300" fill="black" clip-path="url(#clip1)"/>
  <rect width="500" height="300" fill="darkslateblue" mask="url(#mask-clip1-complement)"/>
</svg>
https://jsfiddle.net/tukk4so2/7/

        Seeing as the hairline issue is not specific to clipping, I've separated it into its own thread: ‘Hairline-free rendering – accurate antialiasing of a composition’ http://lists.w3.org/Archives/Public/www-svg/2016Jun/0004.html

Regards,
J. R. Haigh.

--
Sent from Claws Mail on NixOS.

Reply | Threaded
Open this post in threaded view
|

Re: Complementary masking

J. R. Haigh
In reply to this post by Paul LeBeau
Hi Paul,

At 2016-06-03Fri02:18:58+1200, Paul LeBeau sent:
> Hi J.R.
> It is possible to create an SVG that has uses two masks that are "complments" of one another and share a single path definition.

        That is very interesting, and useful for complementary clipping if either rendering is hairline-free or one is not concerned about hairlines. It achieves the same as the first of the 2 bullet points of my complementary clipping suggestion, but in a more elaborate (and less obvious) manner. (The other bullet point being to require resolution-agnostic rendering, i.e. hairline-free rendering.)

> Here's a simple example:
> <svg width="500" height="300">
>   <defs>
>     <path id="maskshape" d="M0 0 L200 0 C250 100 250 200 200 300 L0 300 Z"/>
>     <mask id="mask1">
>       <rect width="500" height="300" fill="black"/>
>       <use xlink:href="#maskshape" fill="white"/>
>     </mask>
>     <mask id="mask1-complement">
>       <rect width="500" height="300" fill="white"/>
>       <use xlink:href="#maskshape" fill="black"/>
>     </mask>
>   </defs>
>   <rect width="500" height="300" fill="darkorange" mask="url(#mask1)" />
>   <rect width="500" height="300" fill="darkslateblue" mask="url(#mask1-complement)" />
> </svg>
> https://jsfiddle.net/tukk4so2/

        Note that the first rectangle is unnecessary (https://jsfiddle.net/tukk4so2/4/ ) because the undrawn element colour is transparent black. In fact, the first mask is just a clip and can be done as normal with a clipPath element (https://jsfiddle.net/tukk4so2/5/ ):

<svg width="500" height="300">
  <defs>
    <clipPath id="clip1">
      <path id="clipshape" d="M0 0 L200 0 C250 100 250 200 200 300 L0 300 Z"/>
    </clipPath>
    <mask id="mask-clip1-complement">
      <rect width="500" height="300" fill="white"/>
      <use xlink:href="#clipshape" fill="black"/>
    </mask>
  </defs>
  <rect width="500" height="300" fill="darkorange" clip-path="url(#clip1)"/>
  <rect width="500" height="300" fill="darkslateblue" mask="url(#mask-clip1-complement)"/>
</svg>

With my proposed addition of ‘zero’ and ‘even’ insideness rules, the second rectangle would also be unnecessary and thus your example could be simplified to:

<svg width="500" height="300">
  <defs>
    <path id="clipshape" d="M0 0 L200 0 C250 100 250 200 200 300 L0 300 Z"/>
    <mask id="mask-clip1">
      <use xlink:href="#clipshape" fill="white"/>
    </mask>
    <mask id="mask-clip1-complement">
      <use xlink:href="#clipshape" fill="white" fill-rule="zero"/>
    </mask>
  </defs>
  <rect width="500" height="300" fill="darkorange" mask="url(#mask-clip1)"/>
  <rect width="500" height="300" fill="darkslateblue" mask="url(#mask-clip1-complement)"/>
</svg>

Combining these 2 simplifications… Ah, sorry, I realise that there's a mistake in my suggestion – I didn't realise that ‘clip-rule’ applied only to “graphics elements within a ‘clipPath’ element” and not to elements for which ‘clip-path’ applies to (https://www.w3.org/TR/SVG/masking#ClipRuleProperty ). In that case, 2 clipPath elements are still needed, not 1 as in my suggestion. Nonetheless, even with 2 clipPath elements, it is still a slight improvement:

<svg width="500" height="300">
  <defs>
    <clipPath id="clip1">
      <path id="clipshape" d="M0 0 L200 0 C250 100 250 200 200 300 L0 300 Z"/>
    </clipPath>
    <clipPath id="clip1-complement">
      <use xlink:href="#clipshape" clip-rule="zero"/>
    </clipPath>
  </defs>
  <rect width="500" height="300" fill="darkorange" clip-path="url(#clip1)"/>
  <rect width="500" height="300" fill="darkslateblue" clip-path="url(#clip1-complement)"/>
</svg>

        The reason that I renamed ‘maskshape’ to ‘clipshape’ is because all these variants only deal with the special-case of complementary clipping, even where masks are used to achieve this. They also are all subject to hairline artifacts due to the way that antialiasing being rendered is actually not exactly the clip special-case of masks. Thanks for demonstrating that complementary clipping is a special case of masking. I didn't notice that; I only noticed 2 of 3 of the following paths of specialisation (forming a sort of ‘parallelogram’ network, now with a crosslink revealed):
• complementary masking → complementary clipping → clipping;
• complementary masking → masking → clipping;
• complementary masking → masking → complementary clipping → clipping.

  clipping    <---    masking
      ^           ,'     ^
      |         ,'       |
      |       ,'         |
      |     L'           |
complementary <--- complementary
  clipping            masking

So with only clipping and masking available, complementary masking is still out-of-reach (ignoring filters, which are more general than all of this).
        For complementary masking (where both the original and the mask are added), there needs to be some additional container drawing element or attribute on an existing container element such as svg:g that makes its internal Z-order irrelevant and blends its children as addition before writing the result of the container to the canvas, rather than each being written over the last as defined at https://www.w3.org/TR/SVG/masking#SimpleAlphaBlending .
        Complementary clipping being a special-case of complementary masking, such a container element or property would also allow a workaround to the hairline artifacts of common rendering.

---- Hairlines ----
        The hairline is clearly visible in the example when the colours are the same or similar:

<svg width="500" height="300">
  <defs>
    <clipPath id="clip1">
      <path id="clipshape" d="M0 0 L200 0 C250 100 250 200 200 300 L0 300 Z"/>
    </clipPath>
    <mask id="mask-clip1-complement">
      <rect width="500" height="300" fill="white"/>
      <use xlink:href="#clipshape" fill="black"/>
    </mask>
  </defs>
  <rect width="500" height="300" fill="darkslateblue" clip-path="url(#clip1)"/>
  <rect width="500" height="300" fill="darkslateblue" mask="url(#mask-clip1-complement)"/>
</svg>
https://jsfiddle.net/tukk4so2/6/

And here's a demonstration of the translucency, with a third object behind of a contrasting colour:

<svg width="500" height="300">
  <defs>
    <clipPath id="clip1">
      <path id="clipshape" d="M0 0 L200 0 C250 100 250 200 200 300 L0 300 Z"/>
    </clipPath>
    <mask id="mask-clip1-complement">
      <rect width="500" height="300" fill="white"/>
      <use xlink:href="#clipshape" fill="black"/>
    </mask>
  </defs>
  <rect width="500" height="300" fill="red"/>
  <rect width="500" height="300" fill="black" clip-path="url(#clip1)"/>
  <rect width="500" height="300" fill="darkslateblue" mask="url(#mask-clip1-complement)"/>
</svg>
https://jsfiddle.net/tukk4so2/7/

        Seeing as the hairline issue is not specific to clipping, I've separated it into its own thread: ‘Hairline-free rendering – accurate antialiasing of a composition’ http://lists.w3.org/Archives/Public/www-svg/2016Jun/0004.html

Regards,
J. R. Haigh.

--
Sent from Claws Mail on NixOS.

Reply | Threaded
Open this post in threaded view
|

Re: Complementary masking

J. R. Haigh
In reply to this post by Paul LeBeau
Hi Paul,

At 2016-06-03Fri02:18:58+1200, Paul LeBeau sent:
> Hi J.R.
> It is possible to create an SVG that has uses two masks that are "complments" of one another and share a single path definition.

        That is very interesting, and useful for complementary clipping if either rendering is hairline-free or one is not concerned about hairlines. It achieves the same as the first of the 2 bullet points of my complementary clipping suggestion, but in a more elaborate (and less obvious) manner. (The other bullet point being to require resolution-agnostic rendering, i.e. hairline-free rendering.)

> Here's a simple example:
> <svg width="500" height="300">
>   <defs>
>     <path id="maskshape" d="M0 0 L200 0 C250 100 250 200 200 300 L0 300 Z"/>
>     <mask id="mask1">
>       <rect width="500" height="300" fill="black"/>
>       <use xlink:href="#maskshape" fill="white"/>
>     </mask>
>     <mask id="mask1-complement">
>       <rect width="500" height="300" fill="white"/>
>       <use xlink:href="#maskshape" fill="black"/>
>     </mask>
>   </defs>
>   <rect width="500" height="300" fill="darkorange" mask="url(#mask1)" />
>   <rect width="500" height="300" fill="darkslateblue" mask="url(#mask1-complement)" />
> </svg>
> https://jsfiddle.net/tukk4so2/

        Note that the first rectangle is unnecessary (https://jsfiddle.net/tukk4so2/4/ ) because the undrawn element colour is transparent black. In fact, the first mask is just a clip and can be done as normal with a clipPath element (https://jsfiddle.net/tukk4so2/5/ ):

<svg width="500" height="300">
  <defs>
    <clipPath id="clip1">
      <path id="clipshape" d="M0 0 L200 0 C250 100 250 200 200 300 L0 300 Z"/>
    </clipPath>
    <mask id="mask-clip1-complement">
      <rect width="500" height="300" fill="white"/>
      <use xlink:href="#clipshape" fill="black"/>
    </mask>
  </defs>
  <rect width="500" height="300" fill="darkorange" clip-path="url(#clip1)"/>
  <rect width="500" height="300" fill="darkslateblue" mask="url(#mask-clip1-complement)"/>
</svg>

With my proposed addition of ‘zero’ and ‘even’ insideness rules, the second rectangle would also be unnecessary and thus your example could be simplified to:

<svg width="500" height="300">
  <defs>
    <path id="clipshape" d="M0 0 L200 0 C250 100 250 200 200 300 L0 300 Z"/>
    <mask id="mask-clip1">
      <use xlink:href="#clipshape" fill="white"/>
    </mask>
    <mask id="mask-clip1-complement">
      <use xlink:href="#clipshape" fill="white" fill-rule="zero"/>
    </mask>
  </defs>
  <rect width="500" height="300" fill="darkorange" mask="url(#mask-clip1)"/>
  <rect width="500" height="300" fill="darkslateblue" mask="url(#mask-clip1-complement)"/>
</svg>

Combining these 2 simplifications… Ah, sorry, I realise that there's a mistake in my suggestion – I didn't realise that ‘clip-rule’ applied only to “graphics elements within a ‘clipPath’ element” and not to elements for which ‘clip-path’ applies to (https://www.w3.org/TR/SVG/masking#ClipRuleProperty ). In that case, 2 clipPath elements are still needed, not 1 as in my suggestion. Nonetheless, even with 2 clipPath elements, it is still a slight improvement:

<svg width="500" height="300">
  <defs>
    <clipPath id="clip1">
      <path id="clipshape" d="M0 0 L200 0 C250 100 250 200 200 300 L0 300 Z"/>
    </clipPath>
    <clipPath id="clip1-complement">
      <use xlink:href="#clipshape" clip-rule="zero"/>
    </clipPath>
  </defs>
  <rect width="500" height="300" fill="darkorange" clip-path="url(#clip1)"/>
  <rect width="500" height="300" fill="darkslateblue" clip-path="url(#clip1-complement)"/>
</svg>

        The reason that I renamed ‘maskshape’ to ‘clipshape’ is because all these variants only deal with the special-case of complementary clipping, even where masks are used to achieve this. They also are all subject to hairline artifacts due to the way that antialiasing being rendered is actually not exactly the clip special-case of masks. Thanks for demonstrating that complementary clipping is a special case of masking. I didn't notice that; I only noticed 2 of 3 of the following paths of specialisation (forming a sort of ‘parallelogram’ network, now with a crosslink revealed):
• complementary masking → complementary clipping → clipping;
• complementary masking → masking → clipping;
• complementary masking → masking → complementary clipping → clipping.

  clipping    <---    masking
      ^           ,'     ^
      |         ,'       |
      |       ,'         |
      |     L'           |
complementary <--- complementary
  clipping            masking

So with only clipping and masking available, complementary masking is still out-of-reach (ignoring filters, which are more general than all of this).
        For complementary masking (where both the original and the mask are added), there needs to be some additional container drawing element or attribute on an existing container element such as svg:g that makes its internal Z-order irrelevant and blends its children as addition before writing the result of the container to the canvas, rather than each being written over the last as defined at https://www.w3.org/TR/SVG/masking#SimpleAlphaBlending .
        Complementary clipping being a special-case of complementary masking, such a container element or property would also allow a workaround to the hairline artifacts of common rendering.

---- Hairlines ----
        The hairline is clearly visible in the example when the colours are the same or similar:

<svg width="500" height="300">
  <defs>
    <clipPath id="clip1">
      <path id="clipshape" d="M0 0 L200 0 C250 100 250 200 200 300 L0 300 Z"/>
    </clipPath>
    <mask id="mask-clip1-complement">
      <rect width="500" height="300" fill="white"/>
      <use xlink:href="#clipshape" fill="black"/>
    </mask>
  </defs>
  <rect width="500" height="300" fill="darkslateblue" clip-path="url(#clip1)"/>
  <rect width="500" height="300" fill="darkslateblue" mask="url(#mask-clip1-complement)"/>
</svg>
https://jsfiddle.net/tukk4so2/6/

And here's a demonstration of the translucency, with a third object behind of a contrasting colour:

<svg width="500" height="300">
  <defs>
    <clipPath id="clip1">
      <path id="clipshape" d="M0 0 L200 0 C250 100 250 200 200 300 L0 300 Z"/>
    </clipPath>
    <mask id="mask-clip1-complement">
      <rect width="500" height="300" fill="white"/>
      <use xlink:href="#clipshape" fill="black"/>
    </mask>
  </defs>
  <rect width="500" height="300" fill="red"/>
  <rect width="500" height="300" fill="black" clip-path="url(#clip1)"/>
  <rect width="500" height="300" fill="darkslateblue" mask="url(#mask-clip1-complement)"/>
</svg>
https://jsfiddle.net/tukk4so2/7/

        Seeing as the hairline issue is not specific to clipping, I've separated it into its own thread: ‘Hairline-free rendering – accurate antialiasing of a composition’ http://lists.w3.org/Archives/Public/www-svg/2016Jun/0004.html

Regards,
J. R. Haigh.

--
Sent from Claws Mail on NixOS.

Reply | Threaded
Open this post in threaded view
|

Re: Complementary masking

J. R. Haigh
In reply to this post by J. R. Haigh
P.s.: I see 2 duplicates of my last email in the archive – sorry for these. I had connection trouble while sending it and my client reported only 1 success and that the other tries failed.
        Also I notice a mistake: “where both the original and the mask are added” should have been “where both the original mask and the complement mask are added”. (Corrected inline.)

Date: Sat, 4 Jun 2016 22:55:39 +0000
From: "J. R. Haigh" <[hidden email]>
To: Paul LeBeau <[hidden email]>
Cc: www-svg <[hidden email]>
Subject: Re: Complementary masking


Hi Paul,

At 2016-06-03Fri02:18:58+1200, Paul LeBeau sent:
> Hi J.R.
> It is possible to create an SVG that has uses two masks that are "complments" of one another and share a single path definition.  

        That is very interesting, and useful for complementary clipping if either rendering is hairline-free or one is not concerned about hairlines. It achieves the same as the first of the 2 bullet points of my complementary clipping suggestion, but in a more elaborate (and less obvious) manner. (The other bullet point being to require resolution-agnostic rendering, i.e. hairline-free rendering.)

> Here's a simple example:
> <svg width="500" height="300">
>   <defs>
>     <path id="maskshape" d="M0 0 L200 0 C250 100 250 200 200 300 L0 300 Z"/>
>     <mask id="mask1">
>       <rect width="500" height="300" fill="black"/>
>       <use xlink:href="#maskshape" fill="white"/>
>     </mask>
>     <mask id="mask1-complement">
>       <rect width="500" height="300" fill="white"/>
>       <use xlink:href="#maskshape" fill="black"/>
>     </mask>
>   </defs>
>   <rect width="500" height="300" fill="darkorange" mask="url(#mask1)" />
>   <rect width="500" height="300" fill="darkslateblue" mask="url(#mask1-complement)" />
> </svg>
> https://jsfiddle.net/tukk4so2/ 

        Note that the first rectangle is unnecessary (https://jsfiddle.net/tukk4so2/4/ ) because the undrawn element colour is transparent black. In fact, the first mask is just a clip and can be done as normal with a clipPath element (https://jsfiddle.net/tukk4so2/5/ ):

<svg width="500" height="300">
  <defs>
    <clipPath id="clip1">
      <path id="clipshape" d="M0 0 L200 0 C250 100 250 200 200 300 L0 300 Z"/>
    </clipPath>
    <mask id="mask-clip1-complement">
      <rect width="500" height="300" fill="white"/>
      <use xlink:href="#clipshape" fill="black"/>
    </mask>
  </defs>
  <rect width="500" height="300" fill="darkorange" clip-path="url(#clip1)"/>
  <rect width="500" height="300" fill="darkslateblue" mask="url(#mask-clip1-complement)"/>
</svg>

With my proposed addition of ‘zero’ and ‘even’ insideness rules, the second rectangle would also be unnecessary and thus your example could be simplified to:

<svg width="500" height="300">
  <defs>
    <path id="clipshape" d="M0 0 L200 0 C250 100 250 200 200 300 L0 300 Z"/>
    <mask id="mask-clip1">
      <use xlink:href="#clipshape" fill="white"/>
    </mask>
    <mask id="mask-clip1-complement">
      <use xlink:href="#clipshape" fill="white" fill-rule="zero"/>
    </mask>
  </defs>
  <rect width="500" height="300" fill="darkorange" mask="url(#mask-clip1)"/>
  <rect width="500" height="300" fill="darkslateblue" mask="url(#mask-clip1-complement)"/>
</svg>

Combining these 2 simplifications… Ah, sorry, I realise that there's a mistake in my suggestion – I didn't realise that ‘clip-rule’ applied only to “graphics elements within a ‘clipPath’ element” and not to elements for which ‘clip-path’ applies to (https://www.w3.org/TR/SVG/masking#ClipRuleProperty ). In that case, 2 clipPath elements are still needed, not 1 as in my suggestion. Nonetheless, even with 2 clipPath elements, it is still a slight improvement:

<svg width="500" height="300">
  <defs>
    <clipPath id="clip1">
      <path id="clipshape" d="M0 0 L200 0 C250 100 250 200 200 300 L0 300 Z"/>
    </clipPath>
    <clipPath id="clip1-complement">
      <use xlink:href="#clipshape" clip-rule="zero"/>
    </clipPath>
  </defs>
  <rect width="500" height="300" fill="darkorange" clip-path="url(#clip1)"/>
  <rect width="500" height="300" fill="darkslateblue" clip-path="url(#clip1-complement)"/>
</svg>

        The reason that I renamed ‘maskshape’ to ‘clipshape’ is because all these variants only deal with the special-case of complementary clipping, even where masks are used to achieve this. They also are all subject to hairline artifacts due to the way that antialiasing being rendered is actually not exactly the clip special-case of masks. Thanks for demonstrating that complementary clipping is a special case of masking. I didn't notice that; I only noticed 2 of 3 of the following paths of specialisation (forming a sort of ‘parallelogram’ network, now with a crosslink revealed):
• complementary masking → complementary clipping → clipping;
• complementary masking → masking → clipping;
• complementary masking → masking → complementary clipping → clipping.

  clipping    <---    masking
      ^           ,'     ^
      |         ,'       |
      |       ,'         |
      |     L'           |
complementary <--- complementary
  clipping            masking

So with only clipping and masking available, complementary masking is still out-of-reach (ignoring filters, which are more general than all of this).
        For complementary masking (where both the original mask and the complement mask are added), there needs to be some additional container drawing element or attribute on an existing container element such as svg:g that makes its internal Z-order irrelevant and blends its children as addition before writing the result of the container to the canvas, rather than each being written over the last as defined at https://www.w3.org/TR/SVG/masking#SimpleAlphaBlending .
        Complementary clipping being a special-case of complementary masking, such a container element or property would also allow a workaround to the hairline artifacts of common rendering.

---- Hairlines ----
        The hairline is clearly visible in the example when the colours are the same or similar:

<svg width="500" height="300">
  <defs>
    <clipPath id="clip1">
      <path id="clipshape" d="M0 0 L200 0 C250 100 250 200 200 300 L0 300 Z"/>
    </clipPath>
    <mask id="mask-clip1-complement">
      <rect width="500" height="300" fill="white"/>
      <use xlink:href="#clipshape" fill="black"/>
    </mask>
  </defs>
  <rect width="500" height="300" fill="darkslateblue" clip-path="url(#clip1)"/>
  <rect width="500" height="300" fill="darkslateblue" mask="url(#mask-clip1-complement)"/>
</svg>
https://jsfiddle.net/tukk4so2/6/

And here's a demonstration of the translucency, with a third object behind of a contrasting colour:

<svg width="500" height="300">
  <defs>
    <clipPath id="clip1">
      <path id="clipshape" d="M0 0 L200 0 C250 100 250 200 200 300 L0 300 Z"/>
    </clipPath>
    <mask id="mask-clip1-complement">
      <rect width="500" height="300" fill="white"/>
      <use xlink:href="#clipshape" fill="black"/>
    </mask>
  </defs>
  <rect width="500" height="300" fill="red"/>
  <rect width="500" height="300" fill="black" clip-path="url(#clip1)"/>
  <rect width="500" height="300" fill="darkslateblue" mask="url(#mask-clip1-complement)"/>
</svg>
https://jsfiddle.net/tukk4so2/7/

        Seeing as the hairline issue is not specific to clipping, I've separated it into its own thread: ‘Hairline-free rendering – accurate antialiasing of a composition’ http://lists.w3.org/Archives/Public/www-svg/2016Jun/0004.html

Regards,
J. R. Haigh.
--
Sent from Claws Mail on NixOS.