Recorte con trazados y regionesClipping with Paths and Regions

Descargar ejemplo descargar el ejemploDownload Sample Download the sample

Usar rutas de acceso a los gráficos de clip a áreas específicas y para crear regionesUse paths to clip graphics to specific areas, and to create regions

A veces es necesario restringir la representación de gráficos a un área determinada.It's sometimes necessary to restrict the rendering of graphics to a particular area. Esto se conoce como recorte.This is known as clipping. Puede utilizar el recorte de efectos especiales, como esta imagen de un objeto monkey ven a través de un principal:You can use clipping for special effects, such as this image of a monkey seen through a keyhole:

El área recorte es el área de la pantalla en la que se representan los gráficos.The clipping area is the area of the screen in which graphics are rendered. No se representa todo lo que se muestra fuera del área de recorte.Anything that is displayed outside of the clipping area is not rendered. El área de recorte normalmente se define mediante un rectángulo o un SKPath objeto, pero también puede definir un área de recorte mediante un SKRegion objeto.The clipping area is usually defined by a rectangle or an SKPath object, but you can alternatively define a clipping area using an SKRegion object. Estos dos tipos de objetos en primer lugar parecen estar relacionadas con porque se puede crear una región de una ruta de acceso.These two types of objects at first seem related because you can create a region from a path. Sin embargo, no se puede crear una ruta de acceso de una región e internamente son muy diferentes: una ruta de acceso consta de una serie de líneas y curvas, mientras que una región se define mediante una serie de líneas de exploración horizontal.However, you cannot create a path from a region, and they are very different internally: A path comprises a series of lines and curves, while a region is defined by a series of horizontal scan lines.

La imagen anterior se creó mediante la Monkey a través de ojo de cerradura página.The image above was created by the Monkey through Keyhole page. El MonkeyThroughKeyholePage clase define una ruta de acceso utilizando los datos SVG y utiliza el constructor para cargar un mapa de bits de recursos del programa:The MonkeyThroughKeyholePage class defines a path using SVG data and uses the constructor to load a bitmap from program resources:

public class MonkeyThroughKeyholePage : ContentPage
{
    SKBitmap bitmap;
    SKPath keyholePath = SKPath.ParseSvgPathData(
        "M 300 130 L 250 350 L 450 350 L 400 130 A 70 70 0 1 0 300 130 Z");

    public MonkeyThroughKeyholePage()
    {
        Title = "Monkey through Keyhole";

        SKCanvasView canvasView = new SKCanvasView();
        canvasView.PaintSurface += OnCanvasViewPaintSurface;
        Content = canvasView;

        string resourceID = "SkiaSharpFormsDemos.Media.SeatedMonkey.jpg";
        Assembly assembly = GetType().GetTypeInfo().Assembly;

        using (Stream stream = assembly.GetManifestResourceStream(resourceID))
        {
            bitmap = SKBitmap.Decode(stream);
        }
    }
    ...
}

Aunque la keyholePath objeto describe el contorno de un principal, las coordenadas son completamente arbitrarias y reflejan lo que era útil cuando los datos de ruta de acceso fue creados.Although the keyholePath object describes the outline of a keyhole, the coordinates are completely arbitrary and reflect what was convenient when the path data was devised. Por este motivo, el PaintSurface controlador obtiene los límites de esta ruta de acceso y las llamadas Translate y Scale para mover la ruta de acceso al centro de la pantalla y convertirlo en casi tan altos como la pantalla:For this reason, the PaintSurface handler obtains the bounds of this path and calls Translate and Scale to move the path to the center of the screen and to make it nearly as tall as the screen:

public class MonkeyThroughKeyholePage : ContentPage
{
    ...
    void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
    {
        SKImageInfo info = args.Info;
        SKSurface surface = args.Surface;
        SKCanvas canvas = surface.Canvas;

        canvas.Clear();

        // Set transform to center and enlarge clip path to window height
        SKRect bounds;
        keyholePath.GetTightBounds(out bounds);

        canvas.Translate(info.Width / 2, info.Height / 2);
        canvas.Scale(0.98f * info.Height / bounds.Height);
        canvas.Translate(-bounds.MidX, -bounds.MidY);

        // Set the clip path
        canvas.ClipPath(keyholePath);

        // Reset transforms
        canvas.ResetMatrix();

        // Display monkey to fill height of window but maintain aspect ratio
        canvas.DrawBitmap(bitmap,
            new SKRect((info.Width - info.Height) / 2, 0,
                       (info.Width + info.Height) / 2, info.Height));
    }
}

Pero no se representa la ruta de acceso.But the path is not rendered. En su lugar, las siguientes las transformaciones, la ruta de acceso se usa para establecer un área de recorte con esta instrucción:Instead, following the transforms, the path is used to set a clipping area with this statement:

canvas.ClipPath(keyholePath);

El PaintSurface controlador, a continuación, restablece las transformaciones con una llamada a ResetMatrix y dibuja el mapa de bits para ampliar el alto de la pantalla completa.The PaintSurface handler then resets the transforms with a call to ResetMatrix and draws the bitmap to extend to the full height of the screen. Este código supone que el mapa de bits es cuadrada, que es este mapa de bits determinado.This code assumes that the bitmap is square, which this particular bitmap is. El mapa de bits se representa solo dentro del área definido por el trazado de recorte:The bitmap is rendered only within the area defined by the clipping path:

El trazado de recorte cuando está sujeto a las transformaciones en vigor el ClipPath se llama al método, y no a las transformaciones en vigor cuando un objeto gráfico (por ejemplo, un mapa de bits) se muestra.The clipping path is subject to the transforms in effect when the ClipPath method is called, and not to the transforms in effect when a graphical object (such as a bitmap) is displayed. El trazado de recorte es parte del estado del lienzo que se guarda con el Save método y restaurada con el Restore método.The clipping path is part of the canvas state that is saved with the Save method and restored with the Restore method.

Combinar trazados de recorteCombining Clipping Paths

En realidad, el área de recorte no es "set" el ClipPath método.Strictly speaking, the clipping area is not "set" by the ClipPath method. En su lugar, se combina con el trazado de recorte existente, que comienza como un tamaño igual al lienzo del rectángulo.Instead, it is combined with the existing clipping path, which begins as a rectangle equal in size to the canvas. Puede obtener los límites rectangulares del área recorte mediante la ClipBounds propiedad o el ClipDeviceBounds propiedad.You can obtain the rectangular bounds of the clipping area using the ClipBounds property or the ClipDeviceBounds property. El ClipBounds propiedad devuelve un SKRect valor que refleja cualquier transforma que pueda estar en vigor.The ClipBounds property returns an SKRect value that reflects any transforms that might be in effect. El ClipDeviceBounds propiedad devuelve un RectI valor.The ClipDeviceBounds property returns a RectI value. Esto es un rectángulo con las dimensiones de entero y describe el área de recorte en las dimensiones de píxeles reales.This is a rectangle with integer dimensions, and describes the clipping area in actual pixel dimensions.

Todas las llamadas a ClipPath reduce el área de recorte combinando el área de recorte con una nueva área.Any call to ClipPath reduces the clipping area by combining the clipping area with a new area. La sintaxis completa de la ClipPath método es:The full syntax of the ClipPath method is:

public void ClipPath(SKPath path, SKClipOperation operation = SKClipOperation.Intersect, Boolean antialias = false);

También hay un ClipRect método que combina el área de recorte con un rectángulo:There is also a ClipRect method that combines the clipping area with a rectangle:

public Void ClipRect(SKRect rect, SKClipOperation operation = SKClipOperation.Intersect, Boolean antialias = false);

De forma predeterminada, el área de recorte resultante es una intersección del área recorte existente y la SKPath o SKRect que se especifica en el ClipPath o ClipRect método.By default, the resultant clipping area is an intersection of the existing clipping area and the SKPath or SKRect that is specified in the ClipPath or ClipRect method. Esto se muestra en el Clip forman una intersección de cuatro círculos página.This is demonstrated in the Four Circles Intersect Clip page. El PaintSurface controlador en el FourCircleInteresectClipPage clase reutiliza el mismo SKPath objeto para crear cuatro círculos superpuestos, cada uno de los cuales reduce el área de recorte mediante llamadas sucesivas a ClipPath:The PaintSurface handler in the FourCircleInteresectClipPage class reuses the same SKPath object to create four overlapping circles, each of which reduces the clipping area through successive calls to ClipPath:

void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
    SKImageInfo info = args.Info;
    SKSurface surface = args.Surface;
    SKCanvas canvas = surface.Canvas;

    canvas.Clear();

    float size = Math.Min(info.Width, info.Height);
    float radius = 0.4f * size;
    float offset = size / 2 - radius;

    // Translate to center
    canvas.Translate(info.Width / 2, info.Height / 2);

    using (SKPath path = new SKPath())
    {
        path.AddCircle(-offset, -offset, radius);
        canvas.ClipPath(path, SKClipOperation.Intersect);

        path.Reset();
        path.AddCircle(-offset, offset, radius);
        canvas.ClipPath(path, SKClipOperation.Intersect);

        path.Reset();
        path.AddCircle(offset, -offset, radius);
        canvas.ClipPath(path, SKClipOperation.Intersect);

        path.Reset();
        path.AddCircle(offset, offset, radius);
        canvas.ClipPath(path, SKClipOperation.Intersect);

        using (SKPaint paint = new SKPaint())
        {
            paint.Style = SKPaintStyle.Fill;
            paint.Color = SKColors.Blue;
            canvas.DrawPaint(paint);
        }
    }
}

Lo que queda es la intersección de estos cuatro círculos:What's left is the intersection of these four circles:

El SKClipOperation enumeración tiene solo dos miembros:The SKClipOperation enumeration has only two members:

  • Difference Quita la ruta de acceso especificada o un rectángulo de área de recorte existenteDifference removes the specified path or rectangle from the existing clipping area

  • Intersect forma una intersección con la ruta de acceso especificada o un rectángulo con área de recorte existenteIntersect intersects the specified path or rectangle with the existing clipping area

Si reemplaza los cuatro SKClipOperation.Intersect argumentos en el FourCircleIntersectClipPage clase con SKClipOperation.Difference, verá lo siguiente:If you replace the four SKClipOperation.Intersect arguments in the FourCircleIntersectClipPage class with SKClipOperation.Difference, you'll see the following:

Se quitaron cuatro círculos superpuestos en el área de recorte.Four overlapping circles have been removed from the clipping area.

El Clip operaciones página muestra la diferencia entre estas dos operaciones con sólo un par de círculos.The Clip Operations page illustrates the difference between these two operations with just a pair of circles. El primer círculo de la izquierda se agrega al área de recorte con la operación de ajuste predeterminado de Intersect, mientras que el segundo círculo de la derecha se agrega al área de recorte con la operación de recorte indicada por la etiqueta de texto:The first circle on the left is added to the clipping area with the default clip operation of Intersect, while the second circle on the right is added to the clipping area with the clip operation indicated by the text label:

El ClipOperationsPage clase define dos SKPaint objetos como campos y, a continuación, divide la pantalla en dos áreas rectangulares.The ClipOperationsPage class defines two SKPaint objects as fields, and then divides the screen up into two rectangular areas. Estas áreas son diferentes dependiendo de si el teléfono está en modo vertical u horizontal.These areas are different depending on whether the phone is in portrait or landscape mode. El DisplayClipOp clase, a continuación, muestra el texto y llamadas ClipPath con las rutas de acceso de dos círculo para ilustrar cada operación de recorte:The DisplayClipOp class then displays the text and calls ClipPath with the two circle paths to illustrate each clip operation:

void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
    SKImageInfo info = args.Info;
    SKSurface surface = args.Surface;
    SKCanvas canvas = surface.Canvas;

    canvas.Clear();

    float x = 0;
    float y = 0;

    foreach (SKClipOperation clipOp in Enum.GetValues(typeof(SKClipOperation)))
    {
        // Portrait mode
        if (info.Height > info.Width)
        {
            DisplayClipOp(canvas, new SKRect(x, y, x + info.Width, y + info.Height / 2), clipOp);
            y += info.Height / 2;
        }
        // Landscape mode
        else
        {
            DisplayClipOp(canvas, new SKRect(x, y, x + info.Width / 2, y + info.Height), clipOp);
            x += info.Width / 2;
        }
    }
}

void DisplayClipOp(SKCanvas canvas, SKRect rect, SKClipOperation clipOp)
{
    float textSize = textPaint.TextSize;
    canvas.DrawText(clipOp.ToString(), rect.MidX, rect.Top + textSize, textPaint);
    rect.Top += textSize;

    float radius = 0.9f * Math.Min(rect.Width / 3, rect.Height / 2);
    float xCenter = rect.MidX;
    float yCenter = rect.MidY;

    canvas.Save();

    using (SKPath path1 = new SKPath())
    {
        path1.AddCircle(xCenter - radius / 2, yCenter, radius);
        canvas.ClipPath(path1);

        using (SKPath path2 = new SKPath())
        {
            path2.AddCircle(xCenter + radius / 2, yCenter, radius);
            canvas.ClipPath(path2, clipOp);

            canvas.DrawPaint(fillPaint);
        }
    }

    canvas.Restore();
}

Una llamada a DrawPaint normalmente hace que todo el lienzo para rellenarse con los que SKPaint objeto, pero en este caso, el método simplemente pinta dentro del área de recorte.Calling DrawPaint normally causes the entire canvas to be filled with that SKPaint object, but in this case, the method just paints within the clipping area.

Exploración de las regionesExploring Regions

También puede definir un área de recorte en términos de un SKRegion objeto.You can also define a clipping area in terms of an SKRegion object.

Una recién creada SKRegion objeto que describe un área vacía.A newly created SKRegion object describes an empty area. Normalmente es la primera llamada en el objeto SetRect para que la región describe un área rectangular.Usually the first call on the object is SetRect so that the region describes a rectangular area. El parámetro SetRect es un SKRectI valor — un rectángulo entero coordina porque especifica el rectángulo de píxeles.The parameter to SetRect is an SKRectI value — a rectangle with integer coordinates because it specifies the rectangle in terms of pixels. A continuación, puede llamar a SetPath con un SKPath objeto.You can then call SetPath with an SKPath object. Esto crea una región que es el mismo que el interior de la ruta de acceso, pero se recorta según la región rectangular inicial.This creates a region that is the same as the interior of the path, but clipped to the initial rectangular region.

La región también se puede modificar mediante una llamada a uno de los Op sobrecargas del método, como esta:The region can also be modified by calling one of the Op method overloads, such as this one:

public Boolean Op(SKRegion region, SKRegionOperation op)

El SKRegionOperation es similar a la enumeración SKClipOperation pero tiene más miembros:The SKRegionOperation enumeration is similar to SKClipOperation but it has more members:

  • Difference

  • Intersect

  • Union

  • XOR

  • ReverseDifference

  • Replace

La región que se va a realizar la Op llamada en se combina con la región especificada como un parámetro en función de la SKRegionOperation miembro.The region that you're making the Op call on is combined with the region specified as a parameter based on the SKRegionOperation member. Cuando finalmente se adecuado para el recorte de una región, que puede establecer como el área de recorte del lienzo mediante la ClipRegion método SKCanvas:When you finally get a region suitable for clipping, you can set that as the clipping area of the canvas using the ClipRegion method of SKCanvas:

public void ClipRegion(SKRegion region, SKClipOperation operation = SKClipOperation.Intersect)

Captura de pantalla siguiente muestra las áreas de recorte basadas en las operaciones de seis región.The following screenshot shows clipping areas based on the six region operations. El círculo de la izquierda es la región que el Op se llama al método en, y el círculo de la derecha es la región que se pasa a la Op método:The left circle is the region that the Op method is called on, and the right circle is the region passed to the Op method:

¿Son estos todas las posibilidades de combinar estas dos círculos?Are these all the possibilities of combining these two circles? Observe la imagen resultante como una combinación de tres componentes, que por sí solos se ve en el Difference, Intersect, y ReverseDifference operaciones.Consider the resultant image as a combination of three components, which by themselves are seen in the Difference, Intersect, and ReverseDifference operations. El número total de combinaciones es dos a la tercera potencia, o también ocho.The total number of combinations is two to the third power, or eight. Los dos que faltan son la región original (que resulta de llamar no Op en absoluto) y una región completamente vacía.The two that are missing are the original region (which results from not calling Op at all) and an entirely empty region.

Es más difícil para usar regiones de recorte, debe crear primero una ruta de acceso y, a continuación, en una región de dicha ruta de acceso y, a continuación, combinar varias regiones.It's harder to use regions for clipping because you need to first create a path, and then a region from that path, and then combine multiple regions. La estructura general de la operaciones de región es muy similar a la página Clip operaciones pero la RegionOperationsPage clase divide la pantalla de en seis áreas y Muestra el trabajo adicional necesario para usar las regiones para este trabajo:The overall structure of the Region Operations page is very similar to Clip Operations but the RegionOperationsPage class divides the screen up into six areas and shows the extra work required to use regions for this job:

void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
    SKImageInfo info = args.Info;
    SKSurface surface = args.Surface;
    SKCanvas canvas = surface.Canvas;

    canvas.Clear();

    float x = 0;
    float y = 0;
    float width = info.Height > info.Width ? info.Width / 2 : info.Width / 3;
    float height = info.Height > info.Width ? info.Height / 3 : info.Height / 2;

    foreach (SKRegionOperation regionOp in Enum.GetValues(typeof(SKRegionOperation)))
    {
        DisplayClipOp(canvas, new SKRect(x, y, x + width, y + height), regionOp);

        if ((x += width) >= info.Width)
        {
            x = 0;
            y += height;
        }
    }
}

void DisplayClipOp(SKCanvas canvas, SKRect rect, SKRegionOperation regionOp)
{
    float textSize = textPaint.TextSize;
    canvas.DrawText(regionOp.ToString(), rect.MidX, rect.Top + textSize, textPaint);
    rect.Top += textSize;

    float radius = 0.9f * Math.Min(rect.Width / 3, rect.Height / 2);
    float xCenter = rect.MidX;
    float yCenter = rect.MidY;

    SKRectI recti = new SKRectI((int)rect.Left, (int)rect.Top,
                                (int)rect.Right, (int)rect.Bottom);

    using (SKRegion wholeRectRegion = new SKRegion())
    {
        wholeRectRegion.SetRect(recti);

        using (SKRegion region1 = new SKRegion(wholeRectRegion))
        using (SKRegion region2 = new SKRegion(wholeRectRegion))
        {
            using (SKPath path1 = new SKPath())
            {
                path1.AddCircle(xCenter - radius / 2, yCenter, radius);
                region1.SetPath(path1);
            }

            using (SKPath path2 = new SKPath())
            {
                path2.AddCircle(xCenter + radius / 2, yCenter, radius);
                region2.SetPath(path2);
            }

            region1.Op(region2, regionOp);

            canvas.Save();
            canvas.ClipRegion(region1);
            canvas.DrawPaint(fillPaint);
            canvas.Restore();
        }
    }
}

Esta es una gran diferencia entre el ClipPath método y el ClipRegion método:Here's a big difference between the ClipPath method and the ClipRegion method:

Importante

A diferencia de la ClipPath método, el ClipRegion método no se ve afectado por las transformaciones.Unlike the ClipPath method, the ClipRegion method is not affected by transforms.

Para comprender el motivo de esta diferencia, es útil comprender qué una región está.To understand the rationale for this difference, it's helpful to understand what a region is. Si ha pensado en cómo las operaciones de clip o las operaciones de región podrían implementarse internamente, probablemente parece muy complicado.If you've thought about how the clip operations or region operations might be implemented internally, it probably seems very complicated. Se combinan varias rutas de acceso potencialmente muy complejos y el contorno de la ruta de acceso resultante es probablemente una pesadilla algorítmica.Several potentially very complex paths are being combined, and the outline of the resultant path is likely an algorithmic nightmare.

Este trabajo se ha simplificado considerablemente si cada ruta de acceso se reduce a una serie de líneas de exploración horizontal, como las de tubos de vacío anticuado televisores.This job is simplified considerably if each path is reduced to a series of horizontal scan lines, such as those in old-fashioned vacuum tube TVs. Cada línea de barrido es simplemente una línea horizontal con un punto inicial y un punto de conexión.Each scan line is simply a horizontal line with a start point and an end point. Por ejemplo, un círculo con un radio de 10 píxeles se puede descomponer en 20 líneas de exploración horizontal, cada uno de los cuales comienza en la parte izquierda del círculo y termina en la parte derecha.For example, a circle with a radius of 10 pixels can be decomposed into 20 horizontal scan lines, each of which starts at the left part of the circle and ends at the right part. Combinar dos círculos con cualquier operación de región pasa a ser muy sencillo porque es simplemente una cuestión de examen de las coordenadas de inicio y finalización de cada par de líneas de exploración correspondiente.Combining two circles with any region operation becomes very simple because it's simply a matter of examining the start and end coordinates of each pair of corresponding scan lines.

Esto es lo que es una región: una serie de líneas horizontales que definen un área.This is what a region is: A series of horizontal scan lines that define an area.

Sin embargo, cuando un área se reduce a una serie de análisis de las líneas, estas líneas se basan en una dimensión de píxel concreto de examen.However, when an area is reduced to a series of scan lines, these scan lines are based on a particular pixel dimension. En realidad, la región no es un objeto de gráficos vectoriales.Strictly speaking, the region is not a vector graphics object. Está más cerca de naturaleza un mapa de bits monocromático comprimido a una ruta de acceso.It is closer in nature to a compressed monochrome bitmap than to a path. Por lo tanto, las regiones no se pueden escalar o girar sin perder fidelidad y por ese motivo no se transforman cuando se usa para las áreas de recorte.Consequently, regions cannot be scaled or rotated without losing fidelity, and for this reason they are not transformed when used for clipping areas.

Sin embargo, puede aplicar transformaciones a regiones para fines de dibujo.However, you can apply transforms to regions for painting purposes. El región Paint programa demuestra claramente la naturaleza interna de regiones.The Region Paint program vividly demonstrates the inner nature of regions. El RegionPaintPage clase crea un SKRegion objeto según un SKPath de un círculo de radio de 10 unidades.The RegionPaintPage class creates an SKRegion object based on an SKPath of a 10-unit radius circle. Una transformación, a continuación, expande ese círculo para rellenar la página:A transform then expands that circle to fill the page:

void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
    SKImageInfo info = args.Info;
    SKSurface surface = args.Surface;
    SKCanvas canvas = surface.Canvas;

    canvas.Clear();

    int radius = 10;

    // Create circular path
    using (SKPath circlePath = new SKPath())
    {
        circlePath.AddCircle(0, 0, radius);

        // Create circular region
        using (SKRegion circleRegion = new SKRegion())
        {
            circleRegion.SetRect(new SKRectI(-radius, -radius, radius, radius));
            circleRegion.SetPath(circlePath);

            // Set transform to move it to center and scale up
            canvas.Translate(info.Width / 2, info.Height / 2);
            canvas.Scale(Math.Min(info.Width / 2, info.Height / 2) / radius);

            // Fill region
            using (SKPaint fillPaint = new SKPaint())
            {
                fillPaint.Style = SKPaintStyle.Fill;
                fillPaint.Color = SKColors.Orange;

                canvas.DrawRegion(circleRegion, fillPaint);
            }

            // Stroke path for comparison
            using (SKPaint strokePaint = new SKPaint())
            {
                strokePaint.Style = SKPaintStyle.Stroke;
                strokePaint.Color = SKColors.Blue;
                strokePaint.StrokeWidth = 0.1f;

                canvas.DrawPath(circlePath, strokePaint);
            }
        }
    }
}

El DrawRegion llamada rellena la región en color naranja, mientras que el DrawPath llamada trazos de la ruta de acceso original en azul para la comparación:The DrawRegion call fills the region in orange, while the DrawPath call strokes the original path in blue for comparison:

La región es claramente una serie de coordenadas discretas.The region is clearly a series of discrete coordinates.

Si no tiene que usar transformaciones en relación con las áreas de recorte, puede usar las regiones de recorte, como el trébol de cuatro – muestra la página.If you don't need to use transforms in connection with your clipping areas, you can use regions for clipping, as the Four-Leaf Clover page demonstrates. El FourLeafCloverPage clase construye una región compuesta de cuatro regiones circulares, establece esa región compuesta como el área de recorte y, a continuación, dibuja una serie de líneas rectas 360 procedentes del centro de la página:The FourLeafCloverPage class constructs a composite region from four circular regions, sets that composite region as the clipping area, and then draws a series of 360 straight lines emanating from the center of the page:

void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
    SKImageInfo info = args.Info;
    SKSurface surface = args.Surface;
    SKCanvas canvas = surface.Canvas;

    canvas.Clear();

    float xCenter = info.Width / 2;
    float yCenter = info.Height / 2;
    float radius = 0.24f * Math.Min(info.Width, info.Height);

    using (SKRegion wholeScreenRegion = new SKRegion())
    {
        wholeScreenRegion.SetRect(new SKRectI(0, 0, info.Width, info.Height));

        using (SKRegion leftRegion = new SKRegion(wholeScreenRegion))
        using (SKRegion rightRegion = new SKRegion(wholeScreenRegion))
        using (SKRegion topRegion = new SKRegion(wholeScreenRegion))
        using (SKRegion bottomRegion = new SKRegion(wholeScreenRegion))
        {
            using (SKPath circlePath = new SKPath())
            {
                // Make basic circle path
                circlePath.AddCircle(xCenter, yCenter, radius);

                // Left leaf
                circlePath.Transform(SKMatrix.MakeTranslation(-radius, 0));
                leftRegion.SetPath(circlePath);

                // Right leaf
                circlePath.Transform(SKMatrix.MakeTranslation(2 * radius, 0));
                rightRegion.SetPath(circlePath);

                // Make union of right with left
                leftRegion.Op(rightRegion, SKRegionOperation.Union);

                // Top leaf
                circlePath.Transform(SKMatrix.MakeTranslation(-radius, -radius));
                topRegion.SetPath(circlePath);

                // Combine with bottom leaf
                circlePath.Transform(SKMatrix.MakeTranslation(0, 2 * radius));
                bottomRegion.SetPath(circlePath);

                // Make union of top with bottom
                bottomRegion.Op(topRegion, SKRegionOperation.Union);

                // Exclusive-OR left and right with top and bottom
                leftRegion.Op(bottomRegion, SKRegionOperation.XOR);

                // Set that as clip region
                canvas.ClipRegion(leftRegion);

                // Set transform for drawing lines from center
                canvas.Translate(xCenter, yCenter);

                // Draw 360 lines
                for (double angle = 0; angle < 360; angle++)
                {
                    float x = 2 * radius * (float)Math.Cos(Math.PI * angle / 180);
                    float y = 2 * radius * (float)Math.Sin(Math.PI * angle / 180);

                    using (SKPaint strokePaint = new SKPaint())
                    {
                        strokePaint.Color = SKColors.Green;
                        strokePaint.StrokeWidth = 2;

                        canvas.DrawLine(0, 0, x, y, strokePaint);
                    }
                }
            }
        }
    }
}

Realmente no parece un trébol de cuatro – hoja, pero es una imagen que serían difícil de representación sin recortes:It doesn't really look like a four-leaf clover, but it's an image that might otherwise be hard to render without clipping: