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 :
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) :
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 :
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
:
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 Dispose
de . 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 :
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 :
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.Width
0). 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 :
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 :
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, CreateLinearGradient
le modèle dégradé balaye en permanence l’image :
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 :
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.