Not
Bu sayfaya erişim yetkilendirme gerektiriyor. Oturum açmayı veya dizinleri değiştirmeyi deneyebilirsiniz.
Bu sayfaya erişim yetkilendirme gerektiriyor. Dizinleri değiştirmeyi deneyebilirsiniz.
Yollar hakkında bilgi alma ve içeriği listeleme
sınıfı, SKPath yol hakkında bilgi edinmenizi sağlayan çeşitli özellikler ve yöntemler tanımlar. Bounds ve TightBounds özellikleri (ve ilgili yöntemler), bir yolun ölçüm boyutlarını alır. yöntemi, Contains belirli bir noktanın bir yol içinde olup olmadığını belirlemenizi sağlar.
Bazen bir yolu oluşturan tüm çizgilerin ve eğrilerin toplam uzunluğunu belirlemek yararlıdır. Bu uzunluğu hesaplamak algoritmik olarak basit bir görev olmadığından, adlı PathMeasure sınıfın tamamı buna ayrılmıştır.
Ayrıca bazen bir yolu oluşturan tüm çizim işlemlerini ve noktaları elde etmek de yararlıdır. İlk başta, bu tesis gereksiz görünebilir: Programınız yolu oluşturduysa, program içeriği zaten biliyordur. Ancak, yolların yol efektleri tarafından ve metin dizeleri yollara dönüştürülerek de oluşturulabileceğini gördünüz. Ayrıca, bu yolları oluşturan tüm çizim işlemlerini ve noktaları da elde edebilirsiniz. Bir olasılık, tüm noktalara algoritmik bir dönüşüm uygulamaktır, örneğin, metni yarım kürenin etrafında kaydırmak için:

Yol Uzunluğunu Alma
Yollar ve Metin makalesinde, temeli bir yolun yolunu izleyen bir metin dizesi çizmek için yönteminin nasıl kullanılacağını DrawTextOnPath gördünüz. Peki ya metni yola tam olarak sığacak şekilde boyutlandırmak istiyorsanız? Dairenin çevresini hesaplamak basit olduğundan, dairenin çevresine metin çizmek kolaydır. Ancak üç noktanın çevresi veya Bézier eğrisinin uzunluğu o kadar basit değildir.
SKPathMeasure Sınıf yardımcı olabilir. Oluşturucu bir SKPath bağımsız değişken kabul eder ve Length özelliği uzunluğunu gösterir.
Bu sınıf, Bezier Eğrisi sayfasını temel alan Yol Uzunluğu örneğinde gösterilmiştir. PathLengthPage.xaml dosyası bir dokunmatik arabirimden InteractivePage türetilir ve şunları içerir:
<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>
Arka planda kod PathLengthPage.xaml.cs dosyası, Bézier küp eğrisinin uç noktalarını ve kontrol noktalarını tanımlamak için dört dokunma noktası taşımanızı sağlar. Üç alan metin dizesini, nesneyi SKPaint ve metnin hesaplanan genişliğini tanımlar:
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);
...
}
Alan baseTextWidth , 10 ayarına TextSize göre metnin genişliğidir.
İşleyici PaintSurface Bézier eğrisini çizer ve metni tam uzunluğu boyunca sığacak şekilde boyutlandırıyor:
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);
}
...
}
Length Yeni oluşturulan SKPathMeasure nesnenin özelliği yolun uzunluğunu alır. Yol uzunluğu değere baseTextWidth (10 metin boyutuna göre metnin genişliğidir) bölünür ve ardından 10'un temel metin boyutuyla çarpılır. Sonuç, metni bu yol boyunca görüntülemek için yeni bir metin boyutudur:
Bézier eğrisi daha uzun veya kısaldıkça metin boyutunun değiştiğini görebilirsiniz.
Yolu Geçirme
SKPathMeasure yalnızca yolun uzunluğunu ölçmekten fazlasını yapabilir. Sıfır ile yol uzunluğu arasındaki herhangi bir değer için, nesne SKPathMeasure yoldaki konumu ve bu noktada yol eğrisinin tanjantını alabilir. Tanjant, nesne biçiminde vektör olarak veya nesne SKPoint içinde SKMatrix kapsüllenmiş bir döndürme olarak kullanılabilir. Bu bilgileri çeşitli ve esnek yollarla elde etmenin yöntemleri SKPathMeasure şunlardır:
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)
Numaralandırmanın SKPathMeasureMatrixFlags üyeleri şunlardır:
GetPositionGetTangentGetPositionAndTangent
Unicycle Yarım Boru sayfası, Bézier küp eğrisi boyunca ileri geri gidiyor gibi görünen tek tekerlekli bisiklet üzerindeki bir çubuk figüre animasyon oluşturur:
Hem SKPaint yarım boruyu hem de tek tekerlekli bisikleti çakarken kullanılan nesne, sınıfında bir alan UnicycleHalfPipePage olarak tanımlanır. Ayrıca, tek tekerlekli bisiklet için nesne de tanımlanır SKPath :
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");
...
}
sınıfı, animasyon için ve OnDisappearing yöntemlerinin OnAppearing standart geçersiz kılmalarını içerir. İşleyici yarım PaintSurface borunun yolunu oluşturur ve sonra çizer. Ardından bu yola göre bir SKPathMeasure nesne oluşturulur:
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);
}
}
}
}
İşleyici, PaintSurface beş saniyede bir 0'dan 1'e kadar olan değerini t hesaplar. Ardından işlevini kullanarak Math.Cos bunu 0 ile 1 arasında bir değere t ve 0'a geri dönüştürür; burada 0 sol üstteki tek tekerlekli bisiklete karşılık gelirken, 1 sağ üstteki tek tekerlekli bisiklete karşılık gelir. Kosinüs işlevi, hızın borunun en üstünde en yavaş, en altta ise en hızlı olmasını sağlar.
değerinin t , ilk bağımsız değişkeninin GetMatrixyol uzunluğuyla çarpılması gerektiğine dikkat edin. Matris daha sonra tek bisiklet yolunu çizmek için nesnesine uygulanır SKCanvas .
Yolu Numaralandırma
öğesinin SKPath iki katıştırılmış sınıfı, yolun içeriğini listelemenize olanak sağlar. Bu sınıflar ve SKPath.RawIteratorşeklindedirSKPath.Iterator. İki sınıf birbirine çok benzer, ancak SKPath.Iterator sıfır uzunlukta veya sıfır uzunluğa yakın olan yoldaki öğeleri ortadan kaldırabilir. RawIterator aşağıdaki örnekte kullanılmıştır.
yöntemini SKPathçağırarak CreateRawIterator türünde SKPath.RawIterator bir nesnesi elde edebilirsiniz. Yol üzerinden numaralandırma yöntemi tekrar tekrar çağrılarak Next gerçekleştirilir. Dört değerden oluşan SKPoint bir dizi geçirin:
SKPoint[] points = new SKPoint[4];
...
SKPathVerb pathVerb = rawIterator.Next(points);
yöntemi, Next numaralandırma türünün bir üyesini SKPathVerb döndürür. Bu değerler, yoldaki belirli çizim komutunu gösterir. Diziye eklenen geçerli noktaların sayısı şu fiile bağlıdır:
Movetek bir noktaylaLineiki puanlaCubicdört puanlaQuadüç puanlaConicüç nokta ile (ve ayrıca ağırlık için yöntemini çağırınConicWeight)Closebir nokta ileDone
Fiil, Done yol numaralandırmasının tamamlandığını gösterir.
Fiil olmadığına Arc dikkat edin. Bu, yola eklendiğinde tüm yayların Bézier eğrilerine dönüştürüldüğünü gösterir.
Dizideki SKPoint bazı bilgiler yedeklidir. Örneğin, bir Move fiilin ardından bir Line fiil gelirse, ile birlikte Line gelen iki noktanın ilki noktayla Move aynıdır. Pratikte, bu yedeklilik çok yararlıdır. Bir Cubic fiil aldığınızda, Bézier küp eğrisini tanımlayan dört noktanın hepsi eşlik eder. Önceki fiil tarafından oluşturulan geçerli konumu korumanız gerekmez.
Ancak sorunlu fiil şudur Close: . Bu komut, geçerli konumdan daha önce komut tarafından Move oluşturulan dağılımın başına doğru düz bir çizgi çizer. İdeal olarak fiilin Close yalnızca bir nokta yerine bu iki noktayı sağlaması gerekir. Daha da kötüsü fiilin eşlik eden noktasının Close her zaman olmasıdır (0, 0). Bir yol boyunca numaralandırdığınızda, büyük olasılıkla noktayı ve geçerli konumu korumanız Move gerekir.
Numaralandırma, Düzleştirme ve Malforming
Bazen bir şekilde yanlış biçimlendirilmiş bir yola algoritmik dönüşüm uygulamak istenir:

Bu harflerin çoğu düz çizgilerden oluşuyor, ancak bu düz çizgiler görünüşe göre eğrilere çevrilmiş. Bu nasıl mümkün olabilir?
Asıl önemli olan, özgün düz çizgilerin daha küçük düz çizgilerden oluşan bir diziye bölünmesidir. Bu küçük düz çizgiler daha sonra eğri oluşturmak için farklı şekillerde değiştirilebilir.
Bu işleme yardımcı olmak için örnek, düz bir çizgiyi yalnızca bir Interpolate birim uzunluğundaki çok sayıda kısa satıra bölen bir yönteme sahip statik PathExtensions bir sınıf içerir. Buna ek olarak, sınıfı üç tür Bézier eğrisini eğriye yakın olan küçük düz çizgiler dizisine dönüştüren çeşitli yöntemler içerir. (Parametrik formüller makalede sunulmuşturÜç Tür Bézier Eğrisi.) Bu işleme eğriyi düzleştirme denir:
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));
}
}
Tüm bu yöntemlere bu sınıfa da dahil edilen uzantı yönteminden CloneWithTransform başvurulur ve aşağıda gösterilmiştir. Bu yöntem, yol komutlarını numaralandırarak ve verileri temel alan yeni bir yol oluşturarak yolu klonlar. Ancak, yeni yol yalnızca ve LineTo çağrılarından MoveTo oluşur. Tüm eğriler ve düz çizgiler bir dizi küçük çizgiye indirgenmiş.
çağrısı CloneWithTransformyaparken, bir değer döndüren bir parametreye sahip SKPaint bir işlev olan yöntemine Func<SKPoint, SKPoint>SKPoint geçirirsiniz. Bu işlev, özel bir algoritmik dönüşüm uygulamak için her nokta için çağrılır:
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;
}
...
}
Kopyalanan yol küçük düz çizgilere indirgendiğinden, dönüştürme işlevi düz çizgileri eğrilere dönüştürme özelliğine sahiptir.
yönteminin adlı firstPoint değişkendeki her konturun ilk noktasını ve değişkendeki her çizim komutundan sonraki geçerli konumu koruduğuna lastPointdikkat edin. Bu değişkenler, bir Close fiil ile karşılaşıldığında son kapanış satırını oluşturmak için gereklidir.
GlobularText örneği, metni 3B efektli bir yarım küre etrafında kaydırmak için bu uzantı yöntemini kullanır:
Sınıf GlobularTextPage oluşturucu bu dönüşümü gerçekleştirir. Metin için bir SKPaint nesne oluşturur ve ardından yönteminden GetTextPath bir SKPath nesne alır. Bu, bir dönüştürme işleviyle birlikte uzantı yöntemine CloneWithTransform geçirilen yoldur:
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);
});
}
}
}
...
}
Transform işlevi önce adlı longitude ve metnin üst ve latitude sol tarafındaki –π/2 ile metnin sağ ve alt kısmındaki π/2 arasında değişen iki değeri hesaplar. Bu değerlerin aralığı görsel olarak tatmin edici olmadığından 0,75 ile çarpılarak azaltılır. (Kodu bu ayarlamalar olmadan deneyin. Metin, kuzey ve güney kutuplarında çok belirsiz ve kenarlarda çok ince olur.) Bu üç boyutlu küresel koordinatlar, standart formüller tarafından iki boyutlu x ve y koordinatlara dönüştürülür.
Yeni yol bir alan olarak depolanır. Ardından işleyicinin PaintSurface ekranda görüntülemek için yolu ortalaması ve ölçeklendirmesi gerekir:
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);
}
}
}
Bu çok yönlü bir tekniktir. Yol Efektleri makalesinde açıklanan yol efektleri dizisi, dahil edilmesi gerektiğini hissettiğiniz bir şeyi tam olarak kapsıyorsa, bu boşlukları doldurmanın bir yoludur.


