Poznámka:
Přístup k této stránce vyžaduje autorizaci. Můžete se zkusit přihlásit nebo změnit adresáře.
Přístup k této stránce vyžaduje autorizaci. Můžete zkusit změnit adresáře.
Získání informací o cestách a vytvoření výčtu obsahu
Třída SKPath definuje několik vlastností a metod, které umožňují získat informace o cestě. Vlastnosti Bounds a TightBounds vlastnosti (a související metody) získávají metrikové dimenze cesty. Metoda Contains umožňuje určit, jestli je konkrétní bod v cestě.
Někdy je užitečné určit celkovou délku všech čar a křivek, které tvoří cestu. Výpočet této délky není algoritmicky jednoduchý úkol, takže se jí věnuje celá třída PathMeasure .
Je také někdy užitečné získat všechny operace kreslení a body, které tvoří cestu. Zpočátku se může zdát, že toto zařízení není nutné: Pokud program vytvořil cestu, program už ví obsah. Viděli jste ale, že cesty se dají vytvořit také efekty cesty a převodem textových řetězců na cesty. Můžete také získat všechny operace výkresu a body, které tvoří tyto cesty. Jednou z možností je použít algoritmusovou transformaci na všechny body, například k obtékání textu kolem polokoule:

Získání délky cesty
V článku Cesty a text jste viděli, jak pomocí DrawTextOnPath metody nakreslit textový řetězec, jehož směrný plán sleduje průběh cesty. Ale co když chcete upravit velikost textu tak, aby přesně odpovídal cestě? Kreslení textu kolem kruhu je snadné, protože obvod kruhu je jednoduchý k výpočtu. Ale obvod tří teček nebo délky Bézierovy křivky není tak jednoduché.
Třída SKPathMeasure může pomoct. Konstruktor přijímá SKPath argument a vlastnost odhalí jeho délkuLength.
Tato třída je ukázaná ve vzorku Délka cesty, která je založena na stránce Bezier Curve . Soubor PathLengthPage.xaml je odvozen z InteractivePage a obsahuje dotykové rozhraní:
<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.Views.Forms;assembly=SkiaSharp.Views.Forms"
xmlns:tt="clr-namespace:TouchTracking"
x:Class="SkiaSharpFormsDemos.Curves.PathLengthPage"
Title="Path Length">
<Grid BackgroundColor="White">
<skia:SKCanvasView x:Name="canvasView"
PaintSurface="OnCanvasViewPaintSurface" />
<Grid.Effects>
<tt:TouchEffect Capture="True"
TouchAction="OnTouchEffectAction" />
</Grid.Effects>
</Grid>
</local:InteractivePage>
Soubor PathLengthPage.xaml.cs kódu umožňuje přesunout čtyři dotykové body k definování koncových bodů a kontrolních bodů krychlové Bézierovy křivky. Tři pole definují textový řetězec, SKPaint objekt a počítanou šířku textu:
public partial class PathLengthPage : InteractivePage
{
const string text = "Compute length of path";
static SKPaint textPaint = new SKPaint
{
Style = SKPaintStyle.Fill,
Color = SKColors.Black,
TextSize = 10,
};
static readonly float baseTextWidth = textPaint.MeasureText(text);
...
}
Pole baseTextWidth je šířka textu založená na TextSize nastavení 10.
Obslužná PaintSurface rutina nakreslí bézierovou křivku a pak ztěžuje text tak, aby se vešel po celé délce:
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
SKImageInfo info = args.Info;
SKSurface surface = args.Surface;
SKCanvas canvas = surface.Canvas;
canvas.Clear();
// Draw path with cubic Bezier curve
using (SKPath path = new SKPath())
{
path.MoveTo(touchPoints[0].Center);
path.CubicTo(touchPoints[1].Center,
touchPoints[2].Center,
touchPoints[3].Center);
canvas.DrawPath(path, strokePaint);
// Get path length
SKPathMeasure pathMeasure = new SKPathMeasure(path, false, 1);
// Find new text size
textPaint.TextSize = pathMeasure.Length / baseTextWidth * 10;
// Draw text on path
canvas.DrawTextOnPath(text, path, 0, 0, textPaint);
}
...
}
Vlastnost Length nově vytvořeného SKPathMeasure objektu získá délku cesty. Délka cesty je dělena baseTextWidth hodnotou (což je šířka textu na základě velikosti textu 10) a poté vynásobena základní velikostí textu 10. Výsledkem je nová velikost textu pro zobrazení textu v této cestě:
Vzhledem k tomu, že Bézierova křivka je delší nebo kratší, můžete vidět změnu velikosti textu.
Procházení cesty
SKPathMeasure může provádět více než jen měření délky cesty. Pro libovolnou hodnotu mezi nulou a délkou SKPathMeasure cesty může objekt získat pozici na cestě a tangens křivky cesty v tomto bodě. Tangens je k dispozici jako vektor ve formě objektu SKPoint nebo jako rotace zapouzdřená v objektu SKMatrix . Tady jsou metody SKPathMeasure , které tyto informace získají různými a flexibilními způsoby:
Boolean GetPosition (Single distance, out SKPoint position)
Boolean GetTangent (Single distance, out SKPoint tangent)
Boolean GetPositionAndTangent (Single distance, out SKPoint position, out SKPoint tangent)
Boolean GetMatrix (Single distance, out SKMatrix matrix, SKPathMeasureMatrixFlags flag)
Členy výčtu SKPathMeasureMatrixFlags jsou:
GetPositionGetTangentGetPositionAndTangent
Stránka s polovičním potrubím unicycle animuje hůlovou postavu na jednokolce, která se zdá být vyjíždět zpět podél krychlové Bézierovy křivky:
Objekt SKPaint použitý pro stroking napůl potrubí i jednocykl je definován jako pole ve UnicycleHalfPipePage třídě. Definuje se SKPath také objekt pro jednocykl:
public class UnicycleHalfPipePage : ContentPage
{
...
SKPaint strokePaint = new SKPaint
{
Style = SKPaintStyle.Stroke,
StrokeWidth = 3,
Color = SKColors.Black
};
SKPath unicyclePath = SKPath.ParseSvgPathData(
"M 0 0" +
"A 25 25 0 0 0 0 -50" +
"A 25 25 0 0 0 0 0 Z" +
"M 0 -25 L 0 -100" +
"A 15 15 0 0 0 0 -130" +
"A 15 15 0 0 0 0 -100 Z" +
"M -25 -85 L 25 -85");
...
}
Třída obsahuje standardní přepsání OnAppearing a OnDisappearing metody animace. Obslužná rutina PaintSurface vytvoří cestu pro poloviční potrubí a pak ji nakreslí. Objekt SKPathMeasure se pak vytvoří na základě této cesty:
public class UnicycleHalfPipePage : ContentPage
{
...
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
SKImageInfo info = args.Info;
SKSurface surface = args.Surface;
SKCanvas canvas = surface.Canvas;
canvas.Clear();
using (SKPath pipePath = new SKPath())
{
pipePath.MoveTo(50, 50);
pipePath.CubicTo(0, 1.25f * info.Height,
info.Width - 0, 1.25f * info.Height,
info.Width - 50, 50);
canvas.DrawPath(pipePath, strokePaint);
using (SKPathMeasure pathMeasure = new SKPathMeasure(pipePath))
{
float length = pathMeasure.Length;
// Animate t from 0 to 1 every three seconds
TimeSpan timeSpan = new TimeSpan(DateTime.Now.Ticks);
float t = (float)(timeSpan.TotalSeconds % 5 / 5);
// t from 0 to 1 to 0 but slower at beginning and end
t = (float)((1 - Math.Cos(t * 2 * Math.PI)) / 2);
SKMatrix matrix;
pathMeasure.GetMatrix(t * length, out matrix,
SKPathMeasureMatrixFlags.GetPositionAndTangent);
canvas.SetMatrix(matrix);
canvas.DrawPath(unicyclePath, strokePaint);
}
}
}
}
Obslužná PaintSurface rutina vypočítá hodnotu t , která se každých pět sekund dostane od 0 do 1. Potom pomocí Math.Cos funkce převede hodnotu, která se pohybuje od 0 do t 1 a zpět na 0, kde 0 odpovídá jednocyklu na začátku vlevo nahoře, zatímco 1 odpovídá jednocyklu v pravém horním rohu. Kosinus funkce způsobí, že rychlost bude nejpomalejší v horní části potrubí a nejrychlejší v dolní části.
Všimněte si, že tato hodnota t musí být vynásobená délkou cesty prvního argumentu na GetMatrix. Matice se pak použije na SKCanvas objekt pro nakreslení jednosměrné cesty.
Vytvoření výčtu cesty
Dvě vložené třídy SKPath umožňují vytvořit výčet obsahu cesty. Tyto třídy jsou SKPath.Iterator a SKPath.RawIterator. Tyto dvě třídy jsou velmi podobné, ale SKPath.Iterator mohou eliminovat prvky v cestě s nulovou délkou nebo blízko nulové délky. Používá se RawIterator v následujícím příkladu.
Objekt typu SKPath.RawIterator lze získat voláním CreateRawIterator metody SKPath. Výčet prostřednictvím cesty se provádí opakovaným voláním Next metody. Předejte mu pole čtyř SKPoint hodnot:
SKPoint[] points = new SKPoint[4];
...
SKPathVerb pathVerb = rawIterator.Next(points);
Metoda Next vrátí člen typu výčtu SKPathVerb . Tyto hodnoty označují konkrétní příkaz výkresu v cestě. Počet platných bodů vložených do pole závisí na tomto slovesu:
Moves jedním bodemLinese dvěma bodyCubicse čtyřmi bodyQuadse třemi bodyConicse třemi body (a také metoduConicWeightpro váhu)Closes jedním bodemDone
Sloveso Done označuje, že výčet cesty je dokončený.
Všimněte si, že neexistují žádné Arc příkazy. To znamená, že všechny oblouky se při přidání do cesty převedou na Bézierovy křivky.
Některé informace v SKPoint poli jsou redundantní. Pokud je například Move sloveso následované slovesem Line , pak první ze dvou bodů, které doprovází Line , je stejné jako bod Move . V praxi je tato redundance velmi užitečná. Když získáte Cubic sloveso, je doprovázeno všemi čtyřmi body, které definují krychlovou Bézierovou křivku. Aktuální pozici vytvořenou předchozím slovesem nemusíte uchovávat.
Problematické sloveso však je Close. Tento příkaz nakreslí přímku z aktuální pozice na začátek obrysu vytvořeného dříve příkazem Move . V Close ideálním případě by příkaz měl místo jednoho bodu poskytnout tyto dva body. Horší je, že bod, který doprovází Close sloveso, je vždy (0, 0). Při vytváření výčtu cesty budete pravděpodobně muset zachovat Move bod a aktuální pozici.
Vytváření výčtů, zploštění a malformování
Někdy je žádoucí použít algoritmusovou transformaci na cestu, která ho nějakým způsobem ztěžuje:

Většina těchto písmen se skládá z rovných čar, ale tyto rovné čáry byly zřejmě zkroucené do křivek. Jak je to možné?
Klíčem je, že původní rovné čáry jsou rozděleny do řady menších rovných čar. Tyto jednotlivé menší rovné čáry je pak možné manipulovat různými způsoby, jak vytvořit křivku.
Pro pomoc s tímto procesem obsahuje ukázka statickou PathExtensions třídu s metodou Interpolate , která rozdělí přímku na mnoho krátkých řádků, které mají délku pouze jednu jednotku. Kromě toho třída obsahuje několik metod, které převádějí tři typy Bézierových křivek na řadu malých rovných čar, které se blíží křivkě. (Parametrické vzorce byly prezentovány v článku .Tři typy Bézierových křivek.) Tento proces se nazývá zploštění křivky:
static class PathExtensions
{
...
static SKPoint[] Interpolate(SKPoint pt0, SKPoint pt1)
{
int count = (int)Math.Max(1, Length(pt0, pt1));
SKPoint[] points = new SKPoint[count];
for (int i = 0; i < count; i++)
{
float t = (i + 1f) / count;
float x = (1 - t) * pt0.X + t * pt1.X;
float y = (1 - t) * pt0.Y + t * pt1.Y;
points[i] = new SKPoint(x, y);
}
return points;
}
static SKPoint[] FlattenCubic(SKPoint pt0, SKPoint pt1, SKPoint pt2, SKPoint pt3)
{
int count = (int)Math.Max(1, Length(pt0, pt1) + Length(pt1, pt2) + Length(pt2, pt3));
SKPoint[] points = new SKPoint[count];
for (int i = 0; i < count; i++)
{
float t = (i + 1f) / count;
float x = (1 - t) * (1 - t) * (1 - t) * pt0.X +
3 * t * (1 - t) * (1 - t) * pt1.X +
3 * t * t * (1 - t) * pt2.X +
t * t * t * pt3.X;
float y = (1 - t) * (1 - t) * (1 - t) * pt0.Y +
3 * t * (1 - t) * (1 - t) * pt1.Y +
3 * t * t * (1 - t) * pt2.Y +
t * t * t * pt3.Y;
points[i] = new SKPoint(x, y);
}
return points;
}
static SKPoint[] FlattenQuadratic(SKPoint pt0, SKPoint pt1, SKPoint pt2)
{
int count = (int)Math.Max(1, Length(pt0, pt1) + Length(pt1, pt2));
SKPoint[] points = new SKPoint[count];
for (int i = 0; i < count; i++)
{
float t = (i + 1f) / count;
float x = (1 - t) * (1 - t) * pt0.X + 2 * t * (1 - t) * pt1.X + t * t * pt2.X;
float y = (1 - t) * (1 - t) * pt0.Y + 2 * t * (1 - t) * pt1.Y + t * t * pt2.Y;
points[i] = new SKPoint(x, y);
}
return points;
}
static SKPoint[] FlattenConic(SKPoint pt0, SKPoint pt1, SKPoint pt2, float weight)
{
int count = (int)Math.Max(1, Length(pt0, pt1) + Length(pt1, pt2));
SKPoint[] points = new SKPoint[count];
for (int i = 0; i < count; i++)
{
float t = (i + 1f) / count;
float denominator = (1 - t) * (1 - t) + 2 * weight * t * (1 - t) + t * t;
float x = (1 - t) * (1 - t) * pt0.X + 2 * weight * t * (1 - t) * pt1.X + t * t * pt2.X;
float y = (1 - t) * (1 - t) * pt0.Y + 2 * weight * t * (1 - t) * pt1.Y + t * t * pt2.Y;
x /= denominator;
y /= denominator;
points[i] = new SKPoint(x, y);
}
return points;
}
static double Length(SKPoint pt0, SKPoint pt1)
{
return Math.Sqrt(Math.Pow(pt1.X - pt0.X, 2) + Math.Pow(pt1.Y - pt0.Y, 2));
}
}
Všechny tyto metody jsou odkazovány z rozšiřující metody CloneWithTransform také zahrnuté v této třídě a uvedeny níže. Tato metoda naklonuje cestu vytvořením výčtu příkazů cesty a vytvořením nové cesty na základě dat. Nová cesta se ale skládá jenom z MoveTo volání a LineTo volání. Všechny křivky a rovné čáry se zmenší na řadu malých čar.
Při volání CloneWithTransform, předáváte metodě Func<SKPoint, SKPoint>, což je funkce s parametrem SKPaint , který vrací SKPoint hodnotu. Tato funkce se volá pro každý bod, který použije vlastní algoritmusovou transformaci:
static class PathExtensions
{
public static SKPath CloneWithTransform(this SKPath pathIn, Func<SKPoint, SKPoint> transform)
{
SKPath pathOut = new SKPath();
using (SKPath.RawIterator iterator = pathIn.CreateRawIterator())
{
SKPoint[] points = new SKPoint[4];
SKPathVerb pathVerb = SKPathVerb.Move;
SKPoint firstPoint = new SKPoint();
SKPoint lastPoint = new SKPoint();
while ((pathVerb = iterator.Next(points)) != SKPathVerb.Done)
{
switch (pathVerb)
{
case SKPathVerb.Move:
pathOut.MoveTo(transform(points[0]));
firstPoint = lastPoint = points[0];
break;
case SKPathVerb.Line:
SKPoint[] linePoints = Interpolate(points[0], points[1]);
foreach (SKPoint pt in linePoints)
{
pathOut.LineTo(transform(pt));
}
lastPoint = points[1];
break;
case SKPathVerb.Cubic:
SKPoint[] cubicPoints = FlattenCubic(points[0], points[1], points[2], points[3]);
foreach (SKPoint pt in cubicPoints)
{
pathOut.LineTo(transform(pt));
}
lastPoint = points[3];
break;
case SKPathVerb.Quad:
SKPoint[] quadPoints = FlattenQuadratic(points[0], points[1], points[2]);
foreach (SKPoint pt in quadPoints)
{
pathOut.LineTo(transform(pt));
}
lastPoint = points[2];
break;
case SKPathVerb.Conic:
SKPoint[] conicPoints = FlattenConic(points[0], points[1], points[2], iterator.ConicWeight());
foreach (SKPoint pt in conicPoints)
{
pathOut.LineTo(transform(pt));
}
lastPoint = points[2];
break;
case SKPathVerb.Close:
SKPoint[] closePoints = Interpolate(lastPoint, firstPoint);
foreach (SKPoint pt in closePoints)
{
pathOut.LineTo(transform(pt));
}
firstPoint = lastPoint = new SKPoint(0, 0);
pathOut.Close();
break;
}
}
}
return pathOut;
}
...
}
Vzhledem k tomu, že klonovaná cesta se zmenší na malé rovné čáry, funkce transformace má schopnost převádět rovné čáry na křivky.
Všimněte si, že metoda zachovává první bod každého obrysu v proměnné a firstPoint aktuální pozici za každým příkazem výkresu v proměnné lastPoint. Tyto proměnné jsou nezbytné k vytvoření konečného koncového Close řádku při zjištěných slovesech.
Ukázka GlobularText používá tuto rozšiřující metodu k zdánlivě obtékání textu kolem polokoule v 3D efektu:
Konstruktor GlobularTextPage třídy provede tuto transformaci. Vytvoří objekt SKPaint pro text a poté získá SKPath objekt z GetTextPath metody. Toto je cesta předaná CloneWithTransform metodě rozšíření spolu s transformační funkcí:
public class GlobularTextPage : ContentPage
{
SKPath globePath;
public GlobularTextPage()
{
Title = "Globular Text";
SKCanvasView canvasView = new SKCanvasView();
canvasView.PaintSurface += OnCanvasViewPaintSurface;
Content = canvasView;
using (SKPaint textPaint = new SKPaint())
{
textPaint.Typeface = SKTypeface.FromFamilyName("Times New Roman");
textPaint.TextSize = 100;
using (SKPath textPath = textPaint.GetTextPath("HELLO", 0, 0))
{
SKRect textPathBounds;
textPath.GetBounds(out textPathBounds);
globePath = textPath.CloneWithTransform((SKPoint pt) =>
{
double longitude = (Math.PI / textPathBounds.Width) *
(pt.X - textPathBounds.Left) - Math.PI / 2;
double latitude = (Math.PI / textPathBounds.Height) *
(pt.Y - textPathBounds.Top) - Math.PI / 2;
longitude *= 0.75;
latitude *= 0.75;
float x = (float)(Math.Cos(latitude) * Math.Sin(longitude));
float y = (float)Math.Sin(latitude);
return new SKPoint(x, y);
});
}
}
}
...
}
Transformační funkce nejprve vypočítá dvě pojmenované longitude hodnoty a latitude rozsah od –π/2 v horní a levé části textu až po π/2 v pravém a dolním rohu textu. Rozsah těchto hodnot není vizuálně uspokojivý, takže se zmenší vynásobením hodnotou 0,75. (Vyzkoušejte kód bez těchto úprav. Text se příliš zakrývá na severních a jižních pólech a je příliš tenký na stranách.) Tyto trojrozměrné kulové souřadnice se převedou na dvojrozměrné x a y souřadnice standardními vzorci.
Nová cesta se uloží jako pole. Obslužná PaintSurface rutina pak potřebuje pouze na střed a škálovat cestu, aby se zobrazila na obrazovce:
public class GlobularTextPage : ContentPage
{
SKPath globePath;
...
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
SKImageInfo info = args.Info;
SKSurface surface = args.Surface;
SKCanvas canvas = surface.Canvas;
canvas.Clear();
using (SKPaint pathPaint = new SKPaint())
{
pathPaint.Style = SKPaintStyle.Fill;
pathPaint.Color = SKColors.Blue;
pathPaint.StrokeWidth = 3;
pathPaint.IsAntialias = true;
canvas.Translate(info.Width / 2, info.Height / 2);
canvas.Scale(0.45f * Math.Min(info.Width, info.Height)); // radius
canvas.DrawPath(globePath, pathPaint);
}
}
}
To je velmi všestranná technika. Pokud pole efektů cesty popsané v článku Efekty cesty neobsahuje něco, co byste měli zahrnout, je to způsob, jak vyplnit mezery.


