Partager via


Dégradé linéaire SkiaSharp

La SKPaint classe définit une Color propriété utilisée pour traitr des lignes ou remplir des zones avec une couleur unie. Vous pouvez également traiter des lignes ou remplir des zones avec des dégradés, qui sont des mélanges progressifs de couleurs :

Exemple de dégradé linéaire

Le type de dégradé le plus simple est un dégradé linéaire . Le mélange de couleurs se produit sur une ligne (appelée ligne dégradée) d’un point à un autre. Les lignes perpendiculaires à la ligne de dégradé ont la même couleur. Vous créez un dégradé linéaire à l’aide de l’une des deux méthodes statiques SKShader.CreateLinearGradient . La différence entre les deux surcharges est que l’une comprend une transformation de matrice et l’autre ne le fait pas.

Ces méthodes retournent un objet de type SKShader que vous définissez sur la Shader propriété de SKPaint. Si la Shader propriété n’est pas null, elle remplace la Color propriété. Toute ligne qui est tracée ou toute zone remplie à l’aide de cet SKPaint objet est basée sur le dégradé plutôt que sur la couleur unie.

Remarque

La Shader propriété est ignorée lorsque vous incluez un SKPaint objet dans un DrawBitmap appel. Vous pouvez utiliser la Color propriété de définir un niveau de SKPaint transparence pour afficher une bitmap (comme décrit dans l’article Affichage des bitmaps SkiaSharp), mais vous ne pouvez pas utiliser la Shader propriété pour afficher une bitmap avec une transparence de dégradé. D’autres techniques sont disponibles pour afficher des bitmaps avec des transparencies dégradées : elles sont décrites dans les articles Des dégradés circulaires SkiaSharp et des modes de composition et de fusion SkiaSharp.

Dégradés d’angle à coin

Souvent, un dégradé linéaire s’étend d’un coin d’un rectangle à un autre. Si le point de départ est le coin supérieur gauche du rectangle, le dégradé peut s’étendre :

  • verticalement dans le coin inférieur gauche
  • horizontalement dans le coin supérieur droit
  • en diagonale vers le coin inférieur droit

Le dégradé linéaire diagonal est illustré dans la première page de la section SkiaSharp Shaders and Other Effects de l’exemple. La page Dégradé d’angle à coin crée un SKCanvasView élément dans son constructeur. Le PaintSurface gestionnaire crée un SKPaint objet dans une using instruction, puis définit un rectangle carré de 300 pixels centré dans le canevas :

public class CornerToCornerGradientPage : ContentPage
{
    ···
    public CornerToCornerGradientPage ()
    {
        Title = "Corner-to-Corner Gradient";

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

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

        canvas.Clear();

        using (SKPaint paint = new SKPaint())
        {
            // Create 300-pixel square centered rectangle
            float x = (info.Width - 300) / 2;
            float y = (info.Height - 300) / 2;
            SKRect rect = new SKRect(x, y, x + 300, y + 300);

            // Create linear gradient from upper-left to lower-right
            paint.Shader = SKShader.CreateLinearGradient(
                                new SKPoint(rect.Left, rect.Top),
                                new SKPoint(rect.Right, rect.Bottom),
                                new SKColor[] { SKColors.Red, SKColors.Blue },
                                new float[] { 0, 1 },
                                SKShaderTileMode.Repeat);

            // Draw the gradient on the rectangle
            canvas.DrawRect(rect, paint);
            ···
        }
    }
}

La Shader propriété de SKPaint l’objet est affectée à la SKShader valeur de retour de la méthode statique SKShader.CreateLinearGradient . Les cinq arguments sont les suivants :

  • Point de départ du dégradé, défini ici sur le coin supérieur gauche du rectangle
  • Point de fin du dégradé, défini ici sur le coin inférieur droit du rectangle
  • Tableau de deux couleurs ou plus qui contribuent au dégradé
  • Tableau de float valeurs indiquant la position relative des couleurs dans la ligne de dégradé
  • Membre de l’énumération SKShaderTileMode indiquant comment le dégradé se comporte au-delà des extrémités de la ligne de dégradé

Une fois l’objet dégradé créé, la DrawRect méthode dessine le rectangle carré de 300 pixels à l’aide de l’objet SKPaint qui inclut le nuanceur. Ici, il s’exécute sur iOS, Android et le plateforme Windows universelle (UWP) :

Dégradé d’angle à coin

La ligne de dégradé est définie par les deux points spécifiés comme les deux premiers arguments. Notez que ces points sont relatifs au canevas et non à l’objet graphique affiché avec le dégradé. Le long de la ligne dégradée, la couleur passe progressivement du rouge en haut à gauche au bleu en bas à droite. Toute ligne perpendiculaire à la ligne dégradée a une couleur constante.

Le tableau de float valeurs spécifiées comme quatrième argument a une correspondance un-à-un avec le tableau de couleurs. Les valeurs indiquent la position relative le long de la ligne de dégradé où ces couleurs se produisent. Ici, le 0 signifie qu’il Red se produit au début de la ligne de dégradé, et 1 signifie qu’il Blue se produit à la fin de la ligne. Les nombres doivent être croissants et doivent être compris entre 0 et 1. S’ils ne se trouvent pas dans cette plage, ils seront ajustés pour être dans cette plage.

Les deux valeurs du tableau peuvent être définies sur une valeur autre que 0 et 1. Procédez comme suit :

new float[] { 0.25f, 0.75f }

Maintenant, le premier trimestre de la ligne de dégradé est rouge pur, et le dernier trimestre est bleu pur. Le mélange rouge et bleu est limité à la moitié centrale de la ligne de dégradé.

En règle générale, vous souhaiterez espacer ces valeurs de position de 0 à 1. Si c’est le cas, vous pouvez simplement fournir null le quatrième argument à CreateLinearGradient.

Bien que ce dégradé soit défini entre deux angles du rectangle carré de 300 pixels, il n’est pas limité au remplissage de ce rectangle. La page Dégradé d’angle à coin inclut un code supplémentaire qui répond aux appuis ou aux clics de souris sur la page. Le drawBackground champ est activé entre true et false avec chaque appui. Si la valeur est true, le PaintSurface gestionnaire utilise le même SKPaint objet pour remplir l’intégralité du canevas, puis dessine un rectangle noir indiquant le plus petit rectangle :

public class CornerToCornerGradientPage : ContentPage
{
    bool drawBackground;

    public CornerToCornerGradientPage ()
    {
        ···
        TapGestureRecognizer tap = new TapGestureRecognizer();
        tap.Tapped += (sender, args) =>
        {
            drawBackground ^= true;
            canvasView.InvalidateSurface();
        };
        canvasView.GestureRecognizers.Add(tap);
    }

    void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
    {
        ···
        using (SKPaint paint = new SKPaint())
        {
            ···
            if (drawBackground)
            {
                // Draw the gradient on the whole canvas
                canvas.DrawRect(info.Rect, paint);

                // Outline the smaller rectangle
                paint.Shader = null;
                paint.Style = SKPaintStyle.Stroke;
                paint.Color = SKColors.Black;
                canvas.DrawRect(rect, paint);
            }
        }
    }
}

Voici ce que vous verrez après avoir appuyé sur l’écran :

Dégradé d’angle à coin complet

Notez que le dégradé se répète dans le même modèle au-delà des points définissant la ligne de dégradé. Cette répétition se produit parce que le dernier argument à CreateLinearGradient atteindre est SKShaderTileMode.Repeat. (Vous verrez bientôt les autres options.)

Notez également que les points que vous utilisez pour spécifier la ligne de dégradé ne sont pas uniques. Les lignes perpendiculaires à la ligne de dégradé ont la même couleur. Il existe donc un nombre infini de lignes de dégradé que vous pouvez spécifier pour le même effet. Par exemple, lors du remplissage d’un rectangle avec un dégradé horizontal, vous pouvez spécifier les coins supérieur gauche et supérieur droit, ou les coins inférieur gauche et inférieur droit, ou les deux points qui sont même avec et parallèles à ces lignes.

Expérience interactive

Vous pouvez expérimenter de manière interactive des dégradés linéaires avec la page Dégradé linéaire interactif. Cette page utilise la InteractivePage classe introduite dans l’article Trois façons de dessiner un arc. InteractivePage Gère les TouchEffect événements pour gérer une collection d’objets TouchPoint que vous pouvez déplacer avec vos doigts ou la souris.

Le fichier XAML joint à TouchEffect un parent du SKCanvasView fichier et inclut également un Picker élément qui vous permet de sélectionner l’un des trois membres de l’énumération SKShaderTileMode :

<local:InteractivePage xmlns="http://xamarin.com/schemas/2014/forms"
                       xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
                       xmlns:local="clr-namespace:SkiaSharpFormsDemos"
                       xmlns:skia="clr-namespace:SkiaSharp;assembly=SkiaSharp"
                       xmlns:skiaforms="clr-namespace:SkiaSharp.Views.Forms;assembly=SkiaSharp.Views.Forms"
                       xmlns:tt="clr-namespace:TouchTracking"
                       x:Class="SkiaSharpFormsDemos.Effects.InteractiveLinearGradientPage"
                       Title="Interactive Linear Gradient">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>

        <Grid BackgroundColor="White"
              Grid.Row="0">
            <skiaforms:SKCanvasView x:Name="canvasView"
                                    PaintSurface="OnCanvasViewPaintSurface" />
            <Grid.Effects>
                <tt:TouchEffect Capture="True"
                                TouchAction="OnTouchEffectAction" />
            </Grid.Effects>
        </Grid>

        <Picker x:Name="tileModePicker"
                Grid.Row="1"
                Title="Shader Tile Mode"
                Margin="10"
                SelectedIndexChanged="OnPickerSelectedIndexChanged">
            <Picker.ItemsSource>
                <x:Array Type="{x:Type skia:SKShaderTileMode}">
                    <x:Static Member="skia:SKShaderTileMode.Clamp" />
                    <x:Static Member="skia:SKShaderTileMode.Repeat" />
                    <x:Static Member="skia:SKShaderTileMode.Mirror" />
                </x:Array>
            </Picker.ItemsSource>

            <Picker.SelectedIndex>
                0
            </Picker.SelectedIndex>
        </Picker>
    </Grid>
</local:InteractivePage>

Le constructeur du fichier code-behind crée deux TouchPoint objets pour les points de début et de fin du dégradé linéaire. Le PaintSurface gestionnaire définit un tableau de trois couleurs (pour un dégradé du rouge au vert au bleu) et obtient le courant SKShaderTileMode à partir des Pickeréléments suivants :

public partial class InteractiveLinearGradientPage : InteractivePage
{
    public InteractiveLinearGradientPage ()
    {
        InitializeComponent ();

        touchPoints = new TouchPoint[2];

        for (int i = 0; i < 2; i++)
        {
            touchPoints[i] = new TouchPoint
            {
                Center = new SKPoint(100 + i * 200, 100 + i * 200)
            };
        }

        InitializeComponent();
        baseCanvasView = canvasView;
    }

    void OnPickerSelectedIndexChanged(object sender, EventArgs args)
    {
        canvasView.InvalidateSurface();
    }

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

        canvas.Clear();

        SKColor[] colors = { SKColors.Red, SKColors.Green, SKColors.Blue };
        SKShaderTileMode tileMode =
            (SKShaderTileMode)(tileModePicker.SelectedIndex == -1 ?
                                        0 : tileModePicker.SelectedItem);

        using (SKPaint paint = new SKPaint())
        {
            paint.Shader = SKShader.CreateLinearGradient(touchPoints[0].Center,
                                                         touchPoints[1].Center,
                                                         colors,
                                                         null,
                                                         tileMode);
            canvas.DrawRect(info.Rect, paint);
        }
        ···
    }
}

Le PaintSurface gestionnaire crée l’objet SKShader à partir de toutes ces informations et l’utilise pour colorer l’intégralité du canevas. Le tableau de float valeurs est défini sur null. Sinon, pour espacer de façon égale trois couleurs, vous devez définir ce paramètre sur un tableau avec les valeurs 0, 0,5 et 1.

L’essentiel du gestionnaire est consacré à l’affichage de plusieurs objets : les points tactiles sous forme de PaintSurface cercles de contour, de trait de dégradé et de lignes perpendiculaires aux lignes dégradées aux points tactiles :

public partial class InteractiveLinearGradientPage : InteractivePage
{
    ···
    void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
    {
        ···
        // Display the touch points here rather than by TouchPoint
        using (SKPaint paint = new SKPaint())
        {
            paint.Style = SKPaintStyle.Stroke;
            paint.Color = SKColors.Black;
            paint.StrokeWidth = 3;

            foreach (TouchPoint touchPoint in touchPoints)
            {
                canvas.DrawCircle(touchPoint.Center, touchPoint.Radius, paint);
            }

            // Draw gradient line connecting touchpoints
            canvas.DrawLine(touchPoints[0].Center, touchPoints[1].Center, paint);

            // Draw lines perpendicular to the gradient line
            SKPoint vector = touchPoints[1].Center - touchPoints[0].Center;
            float length = (float)Math.Sqrt(Math.Pow(vector.X, 2) +
                                            Math.Pow(vector.Y, 2));
            vector.X /= length;
            vector.Y /= length;
            SKPoint rotate90 = new SKPoint(-vector.Y, vector.X);
            rotate90.X *= 200;
            rotate90.Y *= 200;

            canvas.DrawLine(touchPoints[0].Center,
                            touchPoints[0].Center + rotate90,
                            paint);

            canvas.DrawLine(touchPoints[0].Center,
                            touchPoints[0].Center - rotate90,
                            paint);

            canvas.DrawLine(touchPoints[1].Center,
                            touchPoints[1].Center + rotate90,
                            paint);

            canvas.DrawLine(touchPoints[1].Center,
                            touchPoints[1].Center - rotate90,
                            paint);
        }
    }
}

La ligne dégradée reliant les deux points tactiles est facile à dessiner, mais les lignes perpendiculaires nécessitent un certain travail. La ligne de dégradé est convertie en vecteur, normalisée pour avoir une longueur d’une unité, puis pivotée de 90 degrés. Ce vecteur est ensuite donné une longueur de 200 pixels. Il est utilisé pour dessiner quatre lignes qui s’étendent des points tactiles à perpendiculairement à la ligne de dégradé.

Les lignes perpendiculaires coïncident avec le début et la fin du dégradé. Ce qui se passe au-delà de ces lignes dépend du paramètre de l’énumération SKShaderTileMode :

Dégradé linéaire interactif

Les trois captures d’écran montrent les résultats des trois valeurs différentes de SKShaderTileMode. La capture d’écran iOS montre SKShaderTileMode.Clamp, qui étend simplement les couleurs sur la bordure du dégradé. L’option SKShaderTileMode.Repeat dans la capture d’écran Android montre comment le modèle de dégradé est répété. L’option SKShaderTileMode.Mirror de la capture d’écran UWP répète également le modèle, mais le modèle est inversé chaque fois, ce qui n’entraîne aucune discontinuité de couleur.

Dégradés sur les dégradés

La SKShader classe ne définit aucune propriété ou méthode publique à l’exception Disposede . Les SKShader objets créés par ses méthodes statiques sont donc immuables. Même si vous utilisez le même dégradé pour deux objets différents, il est probable que vous souhaitiez varier légèrement le dégradé. Pour ce faire, vous devez créer un SKShader objet.

La page Texte dégradé affiche du texte et un premier plan d’accolades qui sont tous deux colorés avec des dégradés similaires :

Texte dégradé

Les seules différences dans les dégradés sont les points de début et de fin. Le dégradé utilisé pour l’affichage du texte est basé sur deux points sur les angles du rectangle englobant pour le texte. Pour l’arrière-plan, les deux points sont basés sur l’intégralité du canevas. Voici le code  :

public class GradientTextPage : ContentPage
{
    const string TEXT = "GRADIENT";

    public GradientTextPage ()
    {
        Title = "Gradient Text";

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

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

        canvas.Clear();

        using (SKPaint paint = new SKPaint())
        {
            // Create gradient for background
            paint.Shader = SKShader.CreateLinearGradient(
                                new SKPoint(0, 0),
                                new SKPoint(info.Width, info.Height),
                                new SKColor[] { new SKColor(0x40, 0x40, 0x40),
                                                new SKColor(0xC0, 0xC0, 0xC0) },
                                null,
                                SKShaderTileMode.Clamp);

            // Draw background
            canvas.DrawRect(info.Rect, paint);

            // Set TextSize to fill 90% of width
            paint.TextSize = 100;
            float width = paint.MeasureText(TEXT);
            float scale = 0.9f * info.Width / width;
            paint.TextSize *= scale;

            // Get text bounds
            SKRect textBounds = new SKRect();
            paint.MeasureText(TEXT, ref textBounds);

            // Calculate offsets to center the text on the screen
            float xText = info.Width / 2 - textBounds.MidX;
            float yText = info.Height / 2 - textBounds.MidY;

            // Shift textBounds by that amount
            textBounds.Offset(xText, yText);

            // Create gradient for text
            paint.Shader = SKShader.CreateLinearGradient(
                                new SKPoint(textBounds.Left, textBounds.Top),
                                new SKPoint(textBounds.Right, textBounds.Bottom),
                                new SKColor[] { new SKColor(0x40, 0x40, 0x40),
                                                new SKColor(0xC0, 0xC0, 0xC0) },
                                null,
                                SKShaderTileMode.Clamp);

            // Draw text
            canvas.DrawText(TEXT, xText, yText, paint);
        }
    }
}

La Shader propriété de l’objet SKPaint est définie en premier pour afficher un dégradé pour couvrir l’arrière-plan. Les points de dégradé sont définis sur les coins supérieur gauche et inférieur droit du canevas.

Le code définit la TextSize propriété de l’objet SKPaint afin que le texte soit affiché à 90 % de la largeur du canevas. Les limites de texte sont utilisées pour calculer xText et yText valeurs à passer à la DrawText méthode pour centrer le texte.

Toutefois, les points de dégradé du deuxième CreateLinearGradient appel doivent faire référence au coin supérieur gauche et inférieur droit du texte par rapport au canevas lorsqu’il est affiché. Pour ce faire, déplacez le textBounds rectangle par les mêmes valeurs et yText les mêmes xText valeurs :

textBounds.Offset(xText, yText);

À présent, les coins supérieur gauche et inférieur droit du rectangle peuvent être utilisés pour définir les points de début et de fin du dégradé.

Animation d’un dégradé

Il existe plusieurs façons d’animer un dégradé. Une approche consiste à animer les points de début et de fin. La page Animation dégradée déplace les deux points autour d’un cercle centré sur le canevas. Le rayon de ce cercle est la moitié de la largeur ou de la hauteur de la zone de dessin, selon ce qui est plus petit. Les points de début et de fin sont opposés sur ce cercle, et le dégradé passe du blanc au noir avec un Mirror mode mosaïque :

Animation de dégradé

Le constructeur crée le SKCanvasView. Les OnAppearing méthodes et OnDisappearing les méthodes gèrent la logique d’animation :

public class GradientAnimationPage : ContentPage
{
    SKCanvasView canvasView;
    bool isAnimating;
    double angle;
    Stopwatch stopwatch = new Stopwatch();

    public GradientAnimationPage()
    {
        Title = "Gradient Animation";

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

    protected override void OnAppearing()
    {
        base.OnAppearing();

        isAnimating = true;
        stopwatch.Start();
        Device.StartTimer(TimeSpan.FromMilliseconds(16), OnTimerTick);
    }

    protected override void OnDisappearing()
    {
        base.OnDisappearing();

        stopwatch.Stop();
        isAnimating = false;
    }

    bool OnTimerTick()
    {
        const int duration = 3000;
        angle = 2 * Math.PI * (stopwatch.ElapsedMilliseconds % duration) / duration;
        canvasView.InvalidateSurface();

        return isAnimating;
    }
    ···
}

La OnTimerTick méthode calcule une angle valeur animée de 0 à 2π toutes les 3 secondes.

Voici une façon de calculer les deux points de dégradé. Une SKPoint valeur nommée vector est calculée pour s’étendre du centre du canevas à un point sur le rayon du cercle. La direction de ce vecteur est basée sur les valeurs sinus et cosinus de l’angle. Les deux points de dégradé opposés sont ensuite calculés : un point est calculé en soustrayant ce vecteur du point central, et d’autres points sont calculés en ajoutant le vecteur au point central :

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

        canvas.Clear();

        using (SKPaint paint = new SKPaint())
        {
            SKPoint center = new SKPoint(info.Rect.MidX, info.Rect.MidY);
            int radius = Math.Min(info.Width, info.Height) / 2;
            SKPoint vector = new SKPoint((float)(radius * Math.Cos(angle)),
                                         (float)(radius * Math.Sin(angle)));

            paint.Shader = SKShader.CreateLinearGradient(
                                center - vector,
                                center + vector,
                                new SKColor[] { SKColors.White, SKColors.Black },
                                null,
                                SKShaderTileMode.Mirror);

            canvas.DrawRect(info.Rect, paint);
        }
    }
}

Une approche quelque peu différente nécessite moins de code. Cette approche utilise la SKShader.CreateLinearGradient méthode de surcharge avec une transformation de matrice comme dernier argument. Cette approche est la version de l’exemple :

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

        canvas.Clear();

        using (SKPaint paint = new SKPaint())
        {
            paint.Shader = SKShader.CreateLinearGradient(
                                new SKPoint(0, 0),
                                info.Width < info.Height ? new SKPoint(info.Width, 0) :
                                                           new SKPoint(0, info.Height),
                                new SKColor[] { SKColors.White, SKColors.Black },
                                new float[] { 0, 1 },
                                SKShaderTileMode.Mirror,
                                SKMatrix.MakeRotation((float)angle, info.Rect.MidX, info.Rect.MidY));

            canvas.DrawRect(info.Rect, paint);
        }
    }
}

Si la largeur du canevas est inférieure à la hauteur, les deux points de dégradé sont définis sur (0, 0) et (info.Width0). La transformation de rotation passée en tant que dernier argument pour CreateLinearGradient faire pivoter efficacement ces deux points autour du centre de l’écran.

Notez que si l’angle est 0, il n’y a pas de rotation et que les deux points de dégradé sont les angles supérieur gauche et supérieur droit du canevas. Ces points ne sont pas les mêmes points de dégradé calculés comme indiqué dans l’appel précédent CreateLinearGradient . Toutefois, ces points sont parallèles à la ligne de dégradé horizontale qui bisecte le centre de la zone de dessin, et elles entraînent un dégradé identique.

Dégradé arc-en-ciel

La page Gradient Arc-en-ciel dessine un arc-en-ciel du coin supérieur gauche du canevas vers le coin inférieur droit. Mais ce dégradé arc-en-ciel n’est pas comme un vrai arc-en-ciel. Elle est droite plutôt que courbée, mais elle est basée sur huit couleurs HSL (teinte-saturation-luminosité) qui sont déterminées par le cyclisme à travers les valeurs de teinte comprises entre 0 et 360 :

SKColor[] colors = new SKColor[8];

for (int i = 0; i < colors.Length; i++)
{
    colors[i] = SKColor.FromHsl(i * 360f / (colors.Length - 1), 100, 50);
}

Ce code fait partie du PaintSurface gestionnaire indiqué ci-dessous. Le gestionnaire commence par créer un chemin qui définit un polygone à six côtés qui s’étend du coin supérieur gauche du canevas au coin inférieur droit :

public class RainbowGradientPage : ContentPage
{
    public RainbowGradientPage ()
    {
        Title = "Rainbow Gradient";

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

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

        canvas.Clear();

        using (SKPath path = new SKPath())
        {
            float rainbowWidth = Math.Min(info.Width, info.Height) / 2f;

            // Create path from upper-left to lower-right corner
            path.MoveTo(0, 0);
            path.LineTo(rainbowWidth / 2, 0);
            path.LineTo(info.Width, info.Height - rainbowWidth / 2);
            path.LineTo(info.Width, info.Height);
            path.LineTo(info.Width - rainbowWidth / 2, info.Height);
            path.LineTo(0, rainbowWidth / 2);
            path.Close();

            using (SKPaint paint = new SKPaint())
            {
                SKColor[] colors = new SKColor[8];

                for (int i = 0; i < colors.Length; i++)
                {
                    colors[i] = SKColor.FromHsl(i * 360f / (colors.Length - 1), 100, 50);
                }

                paint.Shader = SKShader.CreateLinearGradient(
                                    new SKPoint(0, rainbowWidth / 2),
                                    new SKPoint(rainbowWidth / 2, 0),
                                    colors,
                                    null,
                                    SKShaderTileMode.Repeat);

                canvas.DrawPath(path, paint);
            }
        }
    }
}

Les deux points de dégradé de la CreateLinearGradient méthode sont basés sur deux des points qui définissent ce chemin : les deux points sont proches du coin supérieur gauche. La première se trouve sur le bord supérieur du canevas et la seconde se trouve sur le bord gauche du canevas. Voici le résultat :

Dégradé arc-en-ciel défectueux

C’est une image intéressante, mais ce n’est pas tout à fait l’intention. Le problème est que lors de la création d’un dégradé linéaire, les lignes de couleur constante sont perpendiculaires à la ligne de dégradé. La ligne dégradée est basée sur les points où la figure touche les côtés supérieur et gauche, et cette ligne n’est généralement pas perpendiculaire aux bords de la figure qui s’étendent au coin inférieur droit. Cette approche ne fonctionnerait que si le canevas était carré.

Pour créer un dégradé arc-en-ciel approprié, la ligne de dégradé doit être perpendiculaire au bord de l’arc-en-ciel. C’est un calcul plus impliqué. Un vecteur doit être défini parallèlement au long côté de la figure. Le vecteur est pivoté de 90 degrés pour qu’il soit perpendiculaire à ce côté. Il est ensuite allongé pour être la largeur de la figure en multipliant par rainbowWidth. Les deux points de dégradé sont calculés en fonction d’un point du côté de la figure, et de ce point plus le vecteur. Voici le code qui apparaît dans la page Gradient Arc-en-ciel dans l’exemple :

public class RainbowGradientPage : ContentPage
{
    ···
    void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
    {
        ···
        using (SKPath path = new SKPath())
        {
            ···
            using (SKPaint paint = new SKPaint())
            {
                ···
                // Vector on lower-left edge, from top to bottom
                SKPoint edgeVector = new SKPoint(info.Width - rainbowWidth / 2, info.Height) -
                                     new SKPoint(0, rainbowWidth / 2);

                // Rotate 90 degrees counter-clockwise:
                SKPoint gradientVector = new SKPoint(edgeVector.Y, -edgeVector.X);

                // Normalize
                float length = (float)Math.Sqrt(Math.Pow(gradientVector.X, 2) +
                                                Math.Pow(gradientVector.Y, 2));
                gradientVector.X /= length;
                gradientVector.Y /= length;

                // Make it the width of the rainbow
                gradientVector.X *= rainbowWidth;
                gradientVector.Y *= rainbowWidth;

                // Calculate the two points
                SKPoint point1 = new SKPoint(0, rainbowWidth / 2);
                SKPoint point2 = point1 + gradientVector;

                paint.Shader = SKShader.CreateLinearGradient(point1,
                                                             point2,
                                                             colors,
                                                             null,
                                                             SKShaderTileMode.Repeat);

                canvas.DrawPath(path, paint);
            }
        }
    }
}

Maintenant, les couleurs arc-en-ciel sont alignées avec la figure :

Dégradé arc-en-ciel

Couleurs infinis

Un dégradé arc-en-ciel est également utilisé dans la page Infinity Colors . Cette page dessine un signe infini à l’aide d’un objet path décrit dans l’article Trois types de courbes de Bézier. L’image est ensuite colorée avec un dégradé arc-en-ciel animé qui balaye continuellement l’image.

Le constructeur crée l’objet SKPath décrivant le signe infini. Une fois le chemin créé, le constructeur peut également obtenir les limites rectangulaires du chemin. Il calcule ensuite une valeur appelée gradientCycleLength. Si un dégradé est basé sur les angles supérieur gauche et inférieur droit du pathBounds rectangle, cette gradientCycleLength valeur correspond à la largeur horizontale totale du modèle de dégradé :

public class InfinityColorsPage : ContentPage
{
    ···
    SKCanvasView canvasView;

    // Path information
    SKPath infinityPath;
    SKRect pathBounds;
    float gradientCycleLength;

    // Gradient information
    SKColor[] colors = new SKColor[8];
    ···

    public InfinityColorsPage ()
    {
        Title = "Infinity Colors";

        // Create path for infinity sign
        infinityPath = new SKPath();
        infinityPath.MoveTo(0, 0);                                  // Center
        infinityPath.CubicTo(  50,  -50,   95, -100,  150, -100);   // To top of right loop
        infinityPath.CubicTo( 205, -100,  250,  -55,  250,    0);   // To far right of right loop
        infinityPath.CubicTo( 250,   55,  205,  100,  150,  100);   // To bottom of right loop
        infinityPath.CubicTo(  95,  100,   50,   50,    0,    0);   // Back to center  
        infinityPath.CubicTo( -50,  -50,  -95, -100, -150, -100);   // To top of left loop
        infinityPath.CubicTo(-205, -100, -250,  -55, -250,    0);   // To far left of left loop
        infinityPath.CubicTo(-250,   55, -205,  100, -150,  100);   // To bottom of left loop
        infinityPath.CubicTo( -95,  100, - 50,   50,    0,    0);   // Back to center
        infinityPath.Close();

        // Calculate path information
        pathBounds = infinityPath.Bounds;
        gradientCycleLength = pathBounds.Width +
            pathBounds.Height * pathBounds.Height / pathBounds.Width;

        // Create SKColor array for gradient
        for (int i = 0; i < colors.Length; i++)
        {
            colors[i] = SKColor.FromHsl(i * 360f / (colors.Length - 1), 100, 50);
        }

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

Le constructeur crée également le colors tableau pour l’arc-en-ciel et l’objet SKCanvasView .

Les remplacements des OnAppearing méthodes et OnDisappearing effectuent la surcharge pour l’animation. La OnTimerTick méthode anime le offset champ de 0 à gradientCycleLength toutes les deux secondes :

public class InfinityColorsPage : ContentPage
{
    ···
    // For animation
    bool isAnimating;
    float offset;
    Stopwatch stopwatch = new Stopwatch();
    ···

    protected override void OnAppearing()
    {
        base.OnAppearing();

        isAnimating = true;
        stopwatch.Start();
        Device.StartTimer(TimeSpan.FromMilliseconds(16), OnTimerTick);
    }

    protected override void OnDisappearing()
    {
        base.OnDisappearing();

        stopwatch.Stop();
        isAnimating = false;
    }

    bool OnTimerTick()
    {
        const int duration = 2;     // seconds
        double progress = stopwatch.Elapsed.TotalSeconds % duration / duration;
        offset = (float)(gradientCycleLength * progress);
        canvasView.InvalidateSurface();

        return isAnimating;
    }
    ···
}

Enfin, le PaintSurface gestionnaire restitue le signe infini. Étant donné que le chemin contient des coordonnées négatives et positives entourant un point central de (0, 0), une Translate transformation sur le canevas est utilisée pour la déplacer vers le centre. La transformation de traduction est suivie d’une Scale transformation qui applique un facteur de mise à l’échelle qui rend le signe infini aussi grand que possible tout en restant dans les 95 % de la largeur et de la hauteur du canevas.

Notez que la STROKE_WIDTH constante est ajoutée à la largeur et à la hauteur du rectangle englobant de chemin d’accès. Le chemin sera tracé avec une ligne de cette largeur, de sorte que la taille de l’infini rendu est augmentée de la moitié de cette largeur sur les quatre côtés :

public class InfinityColorsPage : ContentPage
{
    const int STROKE_WIDTH = 50;
    ···
    void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
    {
        SKImageInfo info = args.Info;
        SKSurface surface = args.Surface;
        SKCanvas canvas = surface.Canvas;

        canvas.Clear();

        // Set transforms to shift path to center and scale to canvas size
        canvas.Translate(info.Width / 2, info.Height / 2);
        canvas.Scale(0.95f *
            Math.Min(info.Width / (pathBounds.Width + STROKE_WIDTH),
                     info.Height / (pathBounds.Height + STROKE_WIDTH)));

        using (SKPaint paint = new SKPaint())
        {
            paint.Style = SKPaintStyle.Stroke;
            paint.StrokeWidth = STROKE_WIDTH;
            paint.Shader = SKShader.CreateLinearGradient(
                                new SKPoint(pathBounds.Left, pathBounds.Top),
                                new SKPoint(pathBounds.Right, pathBounds.Bottom),
                                colors,
                                null,
                                SKShaderTileMode.Repeat,
                                SKMatrix.MakeTranslation(offset, 0));

            canvas.DrawPath(infinityPath, paint);
        }
    }
}

Examinez les points passés comme les deux premiers arguments de SKShader.CreateLinearGradient. Ces points sont basés sur le rectangle englobant de chemin d’origine. Le premier point est (–250, –100) et le second est (250, 100). Internes à SkiaSharp, ces points sont soumis à la transformation de canevas actuelle afin qu’ils s’alignent correctement avec le signe infini affiché.

Sans le dernier argument à CreateLinearGradient, vous verrez un dégradé arc-en-ciel qui s’étend de l’angle supérieur gauche du signe infini vers le bas à droite. (En fait, le dégradé s’étend du coin supérieur gauche au coin inférieur droit du rectangle englobant. Le signe d’infini rendu est supérieur au rectangle englobant par la moitié de la STROKE_WIDTH valeur de tous les côtés. Étant donné que le dégradé est rouge à la fois au début et à la fin, et que le dégradé est créé avec SKShaderTileMode.Repeat, la différence n’est pas visible.)

Avec ce dernier argument, CreateLinearGradientle modèle dégradé balaye en permanence l’image :

Couleurs infinis

Transparence et dégradés

Les couleurs qui contribuent à un dégradé peuvent incorporer la transparence. Au lieu d’un dégradé qui disparaît d’une couleur à une autre, le dégradé peut disparaître d’une couleur à une couleur transparente.

Vous pouvez utiliser cette technique pour certains effets intéressants. L’un des exemples classiques montre un objet graphique avec sa réflexion :

dégradé d’Réflexions ion

Le texte qui est à l’envers est coloré avec un dégradé de 50 % transparent en haut pour être entièrement transparent au bas. Ces niveaux de transparence sont associés à des valeurs alpha de 0x80 et 0.

Le PaintSurface gestionnaire de la page dégradé Réflexions ion met à l’échelle la taille du texte à 90 % de la largeur du canevas. Il calcule et yText définit ensuite les xText valeurs pour positionner le texte de manière horizontale, mais assis sur une ligne de base correspondant au centre vertical de la page :

public class ReflectionGradientPage : ContentPage
{
    const string TEXT = "Reflection";

    public ReflectionGradientPage ()
    {
        Title = "Reflection Gradient";

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

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

        canvas.Clear();

        using (SKPaint paint = new SKPaint())
        {
            // Set text color to blue
            paint.Color = SKColors.Blue;

            // Set text size to fill 90% of width
            paint.TextSize = 100;
            float width = paint.MeasureText(TEXT);
            float scale = 0.9f * info.Width / width;
            paint.TextSize *= scale;

            // Get text bounds
            SKRect textBounds = new SKRect();
            paint.MeasureText(TEXT, ref textBounds);

            // Calculate offsets to position text above center
            float xText = info.Width / 2 - textBounds.MidX;
            float yText = info.Height / 2;

            // Draw unreflected text
            canvas.DrawText(TEXT, xText, yText, paint);

            // Shift textBounds to match displayed text
            textBounds.Offset(xText, yText);

            // Use those offsets to create a gradient for the reflected text
            paint.Shader = SKShader.CreateLinearGradient(
                                new SKPoint(0, textBounds.Top),
                                new SKPoint(0, textBounds.Bottom),
                                new SKColor[] { paint.Color.WithAlpha(0),
                                                paint.Color.WithAlpha(0x80) },
                                null,
                                SKShaderTileMode.Clamp);

            // Scale the canvas to flip upside-down around the vertical center
            canvas.Scale(1, -1, 0, yText);

            // Draw reflected text
            canvas.DrawText(TEXT, xText, yText, paint);
        }
    }
}

Ces xText valeurs sont yText les mêmes que celles utilisées pour afficher le texte réfléchi dans l’appel DrawText en bas du PaintSurface gestionnaire. Juste avant ce code, toutefois, vous verrez un appel à la Scale méthode de SKCanvas. Cette Scale méthode est mise à l’échelle horizontalement par 1 (ce qui ne fait rien), mais verticalement par –1, ce qui retourne efficacement tout à l’envers. Le centre de rotation est défini sur le point (0, yText), où yText est le centre vertical du canevas, initialement calculé comme info.Height divisé par 2.

N’oubliez pas que Skia utilise le dégradé pour colorer les objets graphiques avant les transformations de canevas. Une fois le texte non réflecté dessiné, le textBounds rectangle est décalé de sorte qu’il correspond au texte affiché :

textBounds.Offset(xText, yText);

L’appel CreateLinearGradient définit un dégradé du haut de ce rectangle au bas. Le dégradé est d’un bleu complètement transparent (paint.Color.WithAlpha(0)) à un bleu transparent de 50 % (paint.Color.WithAlpha(0x80)). La transformation de canevas retourne le texte à l’envers, de sorte que le bleu transparent de 50 % commence à la ligne de base et devient transparent en haut du texte.