Search code examples
xamlsvgxamarin.formsmaui.net-8.0

Heart shaped image clip - rotate/scale a Geometry object


Final goal

Clip an image with a shape of heart.

Attempt

I have converted the svg path data of this source into it's geometry equivalent. Except that I have to rotate the final Geometry object, unlike in wpf there is no such Rotation property or RotateTo() ... Transform() method defined for types PathGeometry and PathFigure since both inherit from BindableObject and not VisualElement.

Question

  • I am stuck, how can I overcome this issue?
  • Is there a way to make the image clipping object scalable/resizable?
<DataTemplate>
...
 <Image Aspect="AspectFill"
        Source="{Binding .}"
        VerticalOptions="Center"
        WidthRequest="300">
     <Image.Clip>
         <PathGeometry>
             <PathGeometry.Figures>
                 <PathFigureCollection>
                     <PathFigure IsClosed="False" StartPoint="0,200">
                         <PathFigure.Segments>
                             <PathSegmentCollection>
                                 <LineSegment Point="0,0" />
                                 <LineSegment Point="200,0" />
                                 <ArcSegment IsLargeArc="True"
                                             Point="200,200"
                                             RotationAngle="90"
                                             Size="100,100"
                                             SweepDirection="Clockwise" />

                                 <ArcSegment IsLargeArc="True"
                                             Point="0,200"
                                             RotationAngle="90"
                                             Size="100,100"
                                             SweepDirection="Clockwise" />
                             </PathSegmentCollection>
                         </PathFigure.Segments>
                     </PathFigure>
                 </PathFigureCollection>
             </PathGeometry.Figures>
         </PathGeometry>
     </Image.Clip>
 </Image>
...
<DataTemplate>

Screenshot

enter image description here

Red: is the desired shape.

Blue: is the actual clip that needs to be rotated to obtain the red one.

Note that the blue and red shapes are exactly the same shape (data path), with only difference the rotation.

<svg
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
version="1.1" height="315" width="342" viewBox="20 -10 315 342">
 <defs>
  <style type="text/css"><![CDATA[
    .outline { stroke:none; stroke-width:0 }
  ]]></style>
   <g id="heart">
   <path
    stroke="red"
    pathLength="1"
    stroke-width="5"
    d="M0 200 v-200 h200 
    a100,100 90 0,1 0,200
    a100,100 90 0,1 -200,0
    z" />
  </g>
 </defs>
 <desc>
   a nearly perfect heart
     made of two arcs and a right angle
 </desc>
  <use xlink:href="#heart" class="outline " fill="none" transform="rotate(225,150,121)" />
</svg>

Basically this is what you get without the rotation:

<svg
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
version="1.1" height="315" width="342" viewBox="20 -10 315 342">
 <defs>
  <style type="text/css"><![CDATA[
    .outline { stroke:none; stroke-width:0 }
  ]]></style>
   <g id="heart">
   <path
    stroke="red"
    pathLength="1"
    stroke-width="5"
    d="M0 200 v-200 h200 
    a100,100 90 0,1 0,200
    a100,100 90 0,1 -200,0
    z" />
  </g>
 </defs>
 <desc>
   a nearly perfect heart
     made of two arcs and a right angle
 </desc>
  <use xlink:href="#heart" class="outline " fill="none"" />
</svg>

ps: I am open to other approaches.

Wpf: How to flip PathGeometry vertically?


Solution

  • With regards to the svg geometry you can take advantage of many javaScript based tools/libraries to transform paths to "hardcoded" path data commands.

    You can use the svg-path-editor to conveniently recalculate all path commands according to rotation.

    1. apply a transformation/rotation
    2. apply x/y offsets via translate to realign the path data based on it's bounding box – use browser supported JS getBBox() method to get the appropriate offsets.
    3. convert commands to all absolute

    The final path data should look something like this

    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 342 312" style="border:1px solid #ccc; height:90vmin">
      <path 
    d="
       M 312.42 170.58 
       L 171 312 
       L 29.58 170.58 
       A 100 100 0 0 1 171 29.16 
       A 100 100 0 0 1 312.42 170.58 
    "/></svg>

    BTW: if arc rx and ry radii parameters (1. and 2. A command parameter) are equal – the arc is perfectly circular (or non-elliptical) so a specified rotation angle doesn't have any rendering effect – so it can be set to 0.

    According to MS docs: "How to: Create an Elliptical Arc" you should be able to translate these SVG values like so

    wpf/xaml svg
    StartPoint="312.42, 170.58" M 312.42 170.58
    LineSegment Point="171,312" L 171 312
    LineSegment Point="29.58,170.58" L 29.58 170.58
    ArcSegment Size="100,100" RotationAngle="0" IsLargeArc="True" SweepDirection="Clockwise" Point="171,29.16" A 100 100 0 1 1 171 29.16
    wpf/xaml arc to SVG terminology:
    Size=[rx,rx] (x and y radius ... calling it "size" ... no comment),
    RotationAngle=x-axis-rotation,
    IsLargeArc=largeArc flag,
    SweepDirection=sweep flag(1 =clockwise; 0=counterclockwise)
    Point=[x,y] (final on-path point)
    See also "W3C spec: §9.3.8. The elliptical arc curve commands"
    ArcSegment Size="100,100" RotationAngle="0" IsLargeArc="True" SweepDirection="Clockwise" Point="312.42,170.58" A 100 100 0 1 1 312.42 170.58