SkiaSharp'ta Yol Efektleri
Yolların vuruş ve doldurma için kullanılmasına izin veren çeşitli yol efektlerini keşfedin
Yol efekti, sınıfı tarafından tanımlanan sekiz statik oluşturma yönteminden biriyle oluşturulan sınıfın bir örneğidirSKPathEffect
. Nesne SKPathEffect
daha sonra çeşitli ilginç efektler için bir SKPaint
nesnenin özelliğine ayarlanırPathEffect
, örneğin, küçük bir çoğaltılmış yol ile bir çizgi vuruşu:
Yol efektleri şunları yapmanızı sağlar:
- Bir çizgiyi nokta ve tirelerle konturla
- Herhangi bir doldurulmuş yola sahip bir çizgiyi konturla
- Bir alanı tarama çizgileri ile doldurma
- Bir alanı kutucuklu bir yolla doldurma
- Keskin köşeleri yuvarlatılmış hale getirme
- Çizgilere ve eğrilere rastgele "değişim" ekleme
Ayrıca, iki veya daha fazla yol efektini birleştirebilirsiniz.
Bu makalede ayrıca ve PathEffect
dahil olmak üzere StrokeWidth
özelliklerini uygulayarak bir yolu başka bir yola dönüştürmek için yönteminin SKPaint
nasıl kullanılacağı GetFillPath
gösterilmektedirSKPaint
. Bu, başka bir yolun ana hattı olan bir yol elde etme gibi bazı ilginç tekniklerle sonuçlanır. GetFillPath
, yol efektleriyle bağlantılı olarak da yararlıdır.
Noktalar ve Tireler
yönteminin PathEffect.CreateDash
kullanımı Dots and Dashes makalesinde açıklanmıştır. yönteminin ilk bağımsız değişkeni, tire uzunlukları ile tireler arasındaki boşluk uzunlukları arasında değişen çift sayıda iki veya daha fazla değer içeren bir dizidir:
public static SKPathEffect CreateDash (Single[] intervals, Single phase)
Bu değerler vuruş genişliğine göre değildir . Örneğin, vuruş genişliği 10 ise ve kare tirelerden ve kare boşluklardan oluşan bir çizgi istiyorsanız, diziyi intervals
{ 10, 10 } olarak ayarlayın. Bağımsız değişken, phase
çizgi deseninin içinde çizginin nerede başladığını gösterir. Bu örnekte, çizginin kare boşlukla başlamasını istiyorsanız 10 olarak ayarlayın phase
.
Tirelerin uçları özelliğinden StrokeCap
SKPaint
etkilenir. Geniş vuruş genişlikleri için, bu özelliğin tirelerin uçlarını yuvarlatacak şekilde SKStrokeCap.Round
ayarlanması çok yaygındır. Bu durumda, dizideki intervals
değerler yuvarlamadan kaynaklanan fazladan uzunluğu içermez. Bu durum, döngüsel noktanın sıfır genişliği belirtmeyi gerektirdiği anlamına gelir. 10 vuruş genişliği için, aynı çapta noktalar arasında dairesel noktalar ve boşluklar içeren bir intervals
çizgi oluşturmak için { 0, 20 } dizisi kullanın.
Animasyonlu Noktalı Metin sayfası, içindeki Metin ve Grafikleri Tümleştirme makalesinde açıklanan Ana Hatlı Metin sayfasına benzer ve nesnenin SKPaint
SKPaintStyle.Stroke
özelliğini olarak ayarlayarak Style
ana hatlı metin karakterleri görüntüler. Buna ek olarak, Animasyonlu Noktalı Metin , bu ana hattı noktalı bir görünüm vermek için kullanır SKPathEffect.CreateDash
ve program ayrıca phase
, noktaların metin karakterleri arasında geziniyor gibi görünmesini sağlamak için yöntemin bağımsız değişkenine SKPathEffect.CreateDash
animasyon ekler. Sayfa şu şekilde yatay moddadır:
AnimatedDottedTextPage
sınıfı, bazı sabitleri tanımlayarak başlar ve ayrıca animasyon için ve OnDisappearing
yöntemlerini geçersiz kılarOnAppearing
:
public class AnimatedDottedTextPage : ContentPage
{
const string text = "DOTTED";
const float strokeWidth = 10;
static readonly float[] dashArray = { 0, 2 * strokeWidth };
SKCanvasView canvasView;
bool pageIsActive;
public AnimatedDottedTextPage()
{
Title = "Animated Dotted Text";
canvasView = new SKCanvasView();
canvasView.PaintSurface += OnCanvasViewPaintSurface;
Content = canvasView;
}
protected override void OnAppearing()
{
base.OnAppearing();
pageIsActive = true;
Device.StartTimer(TimeSpan.FromSeconds(1f / 60), () =>
{
canvasView.InvalidateSurface();
return pageIsActive;
});
}
protected override void OnDisappearing()
{
base.OnDisappearing();
pageIsActive = false;
}
...
}
İşleyici, PaintSurface
metni görüntülemek için bir SKPaint
nesne oluşturarak başlar. TextSize
özelliği, ekranın genişliğine göre ayarlanır:
public class AnimatedDottedTextPage : ContentPage
{
...
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
SKImageInfo info = args.Info;
SKSurface surface = args.Surface;
SKCanvas canvas = surface.Canvas;
canvas.Clear();
// Create an SKPaint object to display the text
using (SKPaint textPaint = new SKPaint
{
Style = SKPaintStyle.Stroke,
StrokeWidth = strokeWidth,
StrokeCap = SKStrokeCap.Round,
Color = SKColors.Blue,
})
{
// Adjust TextSize property so text is 95% of screen width
float textWidth = textPaint.MeasureText(text);
textPaint.TextSize *= 0.95f * info.Width / textWidth;
// Find the text bounds
SKRect textBounds = new SKRect();
textPaint.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;
// Animate the phase; t is 0 to 1 every second
TimeSpan timeSpan = new TimeSpan(DateTime.Now.Ticks);
float t = (float)(timeSpan.TotalSeconds % 1 / 1);
float phase = -t * 2 * strokeWidth;
// Create dotted line effect based on dash array and phase
using (SKPathEffect dashEffect = SKPathEffect.CreateDash(dashArray, phase))
{
// Set it to the paint object
textPaint.PathEffect = dashEffect;
// And draw the text
canvas.DrawText(text, xText, yText, textPaint);
}
}
}
}
Yöntemin sonuna doğru, SKPathEffect.CreateDash
alan olarak tanımlanan ve animasyonlu phase
değer kullanılarak dashArray
yöntemi çağrılır. ÖrnekSKPathEffect
, metni görüntülemek için nesnesinin SKPaint
özelliğine ayarlanırPathEffect
.
Alternatif olarak, metni SKPaint
ölçmeden ve sayfada ortalamadan önce nesneyi nesneye ayarlayabilirsinizSKPathEffect
. Ancak bu durumda, animasyonlu noktalar ve tireler işlenen metnin boyutunda bazı farklılıklara neden olur ve metin biraz titreşme eğilimindedir. (Deneyin!)
Ayrıca, animasyonlu noktalar metin karakterlerinin çevresinde daire içine aldıkça, her kapalı eğride noktaların ortaya çıkıp çıktığı belirli bir nokta olduğunu da fark edeceksiniz. Bu, karakter ana hattını tanımlayan yolun başladığı ve sona erdiği yerdir. Yol uzunluğu, tire deseninin uzunluğunun tam sayı katı değilse (bu örnekte 20 piksel), bu desenin yalnızca bir kısmı yolun sonuna sığabilir.
Tire deseninin uzunluğunu yolun uzunluğuna uyacak şekilde ayarlamak mümkündür, ancak bunun için Yol Bilgileri ve Numaralandırma makalesinde ele alınan bir teknik olan yolun uzunluğunun belirlenmesi gerekir.
Nokta / Tire Dönüşüm programı tire deseninin kendisine animasyon ekler, böylece tireler tekrar tire oluşturmak için bir araya gelen noktalara ayrılmış gibi görünür:
sınıfı, DotDashMorphPage
önceki programın yaptığı gibi ve OnDisappearing
yöntemlerini geçersiz kılarOnAppearing
, ancak sınıfı nesneyi bir alan olarak tanımlarSKPaint
:
public class DotDashMorphPage : ContentPage
{
const float strokeWidth = 30;
static readonly float[] dashArray = new float[4];
SKCanvasView canvasView;
bool pageIsActive = false;
SKPaint ellipsePaint = new SKPaint
{
Style = SKPaintStyle.Stroke,
StrokeWidth = strokeWidth,
StrokeCap = SKStrokeCap.Round,
Color = SKColors.Blue
};
...
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
SKImageInfo info = args.Info;
SKSurface surface = args.Surface;
SKCanvas canvas = surface.Canvas;
canvas.Clear();
// Create elliptical path
using (SKPath ellipsePath = new SKPath())
{
ellipsePath.AddOval(new SKRect(50, 50, info.Width - 50, info.Height - 50));
// Create animated path effect
TimeSpan timeSpan = new TimeSpan(DateTime.Now.Ticks);
float t = (float)(timeSpan.TotalSeconds % 3 / 3);
float phase = 0;
if (t < 0.25f) // 1, 0, 1, 2 --> 0, 2, 0, 2
{
float tsub = 4 * t;
dashArray[0] = strokeWidth * (1 - tsub);
dashArray[1] = strokeWidth * 2 * tsub;
dashArray[2] = strokeWidth * (1 - tsub);
dashArray[3] = strokeWidth * 2;
}
else if (t < 0.5f) // 0, 2, 0, 2 --> 1, 2, 1, 0
{
float tsub = 4 * (t - 0.25f);
dashArray[0] = strokeWidth * tsub;
dashArray[1] = strokeWidth * 2;
dashArray[2] = strokeWidth * tsub;
dashArray[3] = strokeWidth * 2 * (1 - tsub);
phase = strokeWidth * tsub;
}
else if (t < 0.75f) // 1, 2, 1, 0 --> 0, 2, 0, 2
{
float tsub = 4 * (t - 0.5f);
dashArray[0] = strokeWidth * (1 - tsub);
dashArray[1] = strokeWidth * 2;
dashArray[2] = strokeWidth * (1 - tsub);
dashArray[3] = strokeWidth * 2 * tsub;
phase = strokeWidth * (1 - tsub);
}
else // 0, 2, 0, 2 --> 1, 0, 1, 2
{
float tsub = 4 * (t - 0.75f);
dashArray[0] = strokeWidth * tsub;
dashArray[1] = strokeWidth * 2 * (1 - tsub);
dashArray[2] = strokeWidth * tsub;
dashArray[3] = strokeWidth * 2;
}
using (SKPathEffect pathEffect = SKPathEffect.CreateDash(dashArray, phase))
{
ellipsePaint.PathEffect = pathEffect;
canvas.DrawPath(ellipsePath, ellipsePaint);
}
}
}
}
İşleyici, PaintSurface
sayfanın boyutuna göre eliptik bir yol oluşturur ve kodun ve phase
değişkenlerini ayarlayan dashArray
uzun bir bölümünü yürütür. Animasyonlu değişken t
0 ile 1 arasında olduğundan bloklar if
bu süreyi dört çeyreğe ayırır ve bu üç aylık dönemlerin tsub
her birinde de 0 ile 1 arasında değişir. En sonunda, program öğesini oluşturur SKPathEffect
ve çizim için nesnesine SKPaint
ayarlar.
Yoldan Yola
yöntemiSKPaint
, GetFillPath
nesnedeki SKPaint
ayarlara göre bir yolu başka bir yola dönüştürür. Bunun nasıl çalıştığını görmek için önceki programdaki çağrıyı canvas.DrawPath
aşağıdaki kodla değiştirin:
SKPath newPath = new SKPath();
bool fill = ellipsePaint.GetFillPath(ellipsePath, newPath);
SKPaint newPaint = new SKPaint
{
Style = fill ? SKPaintStyle.Fill : SKPaintStyle.Stroke
};
canvas.DrawPath(newPath, newPaint);
Bu yeni kodda GetFillPath
çağrı , (yalnızca oval olan) newPath
öğesini içine dönüştürür ellipsePath
ve ardından ile newPaint
görüntülenir. newPaint
nesnesi, özelliğin öğesinden GetFillPath
Boole dönüş değerine göre ayarlanması dışında Style
tüm varsayılan özellik ayarlarıyla oluşturulur.
içinde ayarlanmış ancak ayarlanmayan ellipsePaint
newPaint
renk dışında görseller aynıdır. içinde ellipsePath
newPath
tanımlanan basit üç nokta yerine, nokta ve tire dizisini tanımlayan çok sayıda yol konturu içerir. Bu, çeşitli özelliklerini ellipsePaint
(özellikle , StrokeCap
StrokeWidth
ve ) ellipsePath
uygulaması ve PathEffect
sonuç yolunu içine newPath
koymanın sonucudur. yöntemi, GetFillPath
hedef yolun doldurulup doldurulmayacağını belirten bir Boole değeri döndürür; bu örnekte, dönüş değeri true
yolu doldurmak içindir.
ayarını newPaint
SKPaintStyle.Stroke
olarak değiştirmeyi Style
denediğinizde tek bir piksel genişlikli çizgiyle ana hatlarıyla tek tek yol dağılımlarını görürsünüz.
Bir Yol ile Vuruş
SKPathEffect.Create1DPath
yöntemi kavramsal olarak şuna benzerSKPathEffect.CreateDash
, ancak tire ve boşluk deseni yerine bir yol belirtirsiniz. Bu yol, çizgiye veya eğriye vuruş yapmak için birden çok kez çoğaltılır.
Söz dizimi aşağıdaki gibidir:
public static SKPathEffect Create1DPath (SKPath path, Single advance,
Single phase, SKPath1DPathEffectStyle style)
Genel olarak, geçirdiğiniz Create1DPath
yol küçük olur ve nokta etrafında ortalanır (0, 0). parametresi, advance
yol satırda çoğaltılırken yolun merkezleri arasındaki uzaklığı gösterir. Bu bağımsız değişkeni genellikle yolun yaklaşık genişliğine ayarlarsınız. bağımsız phase
değişkeni burada yönteminde CreateDash
olduğu gibi aynı rolü oynar.
üç SKPath1DPathEffectStyle
üyeye sahiptir:
Translate
Rotate
Morph
Üye, Translate
yolun bir çizgi veya eğri boyunca çoğaltıldığı yönle aynı yönde kalmasına neden olur. için Rotate
yol, eğrinin tanjantını temel alarak döndürülür. Yolun yatay çizgiler için normal yönü vardır. Morph
, yolun kendisi de konturlanan çizginin eğriliğiyle eşleşecek şekilde eğri olması dışında benzerdir Rotate
.
1B Yol Efekti sayfasında bu üç seçenek gösterilir. OneDimensionalPathEffectPage.xaml dosyası, numaralandırmanın üç üyesine karşılık gelen üç öğe içeren bir seçici tanımlar:
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:skia="clr-namespace:SkiaSharp.Views.Forms;assembly=SkiaSharp.Views.Forms"
x:Class="SkiaSharpFormsDemos.Curves.OneDimensionalPathEffectPage"
Title="1D Path Effect">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Picker x:Name="effectStylePicker"
Title="Effect Style"
Grid.Row="0"
SelectedIndexChanged="OnPickerSelectedIndexChanged">
<Picker.ItemsSource>
<x:Array Type="{x:Type x:String}">
<x:String>Translate</x:String>
<x:String>Rotate</x:String>
<x:String>Morph</x:String>
</x:Array>
</Picker.ItemsSource>
<Picker.SelectedIndex>
0
</Picker.SelectedIndex>
</Picker>
<skia:SKCanvasView x:Name="canvasView"
PaintSurface="OnCanvasViewPaintSurface"
Grid.Row="1" />
</Grid>
</ContentPage>
arka planda kod OneDimensionalPathEffectPage.xaml.cs dosyası üç SKPathEffect
nesneyi alan olarak tanımlar. Bunların tümü kullanılarak, kullanılarak SKPathEffect.Create1DPath
SKPath.ParseSvgPathData
oluşturulan nesnelerle SKPath
oluşturulur. Birincisi basit bir kutu, ikincisi baklava şeklinde, üçüncüsü ise dikdörtgendir. Bunlar üç efekt stilini göstermek için kullanılır:
public partial class OneDimensionalPathEffectPage : ContentPage
{
SKPathEffect translatePathEffect =
SKPathEffect.Create1DPath(SKPath.ParseSvgPathData("M -10 -10 L 10 -10, 10 10, -10 10 Z"),
24, 0, SKPath1DPathEffectStyle.Translate);
SKPathEffect rotatePathEffect =
SKPathEffect.Create1DPath(SKPath.ParseSvgPathData("M -10 0 L 0 -10, 10 0, 0 10 Z"),
20, 0, SKPath1DPathEffectStyle.Rotate);
SKPathEffect morphPathEffect =
SKPathEffect.Create1DPath(SKPath.ParseSvgPathData("M -25 -10 L 25 -10, 25 10, -25 10 Z"),
55, 0, SKPath1DPathEffectStyle.Morph);
SKPaint pathPaint = new SKPaint
{
Color = SKColors.Blue
};
public OneDimensionalPathEffectPage()
{
InitializeComponent();
}
void OnPickerSelectedIndexChanged(object sender, EventArgs args)
{
if (canvasView != null)
{
canvasView.InvalidateSurface();
}
}
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())
{
path.MoveTo(new SKPoint(0, 0));
path.CubicTo(new SKPoint(2 * info.Width, info.Height),
new SKPoint(-info.Width, info.Height),
new SKPoint(info.Width, 0));
switch ((string)effectStylePicker.SelectedItem))
{
case "Translate":
pathPaint.PathEffect = translatePathEffect;
break;
case "Rotate":
pathPaint.PathEffect = rotatePathEffect;
break;
case "Morph":
pathPaint.PathEffect = morphPathEffect;
break;
}
canvas.DrawPath(path, pathPaint);
}
}
}
İşleyici PaintSurface
, kendi etrafında döngü yapan bir Bézier eğrisi oluşturur ve bunu vuruş yapmak için hangilerinin PathEffect
kullanılması gerektiğini belirlemek için seçiciye erişir. Üç seçenek ( Translate
, Rotate
ve Morph
) soldan sağa gösterilir:
yönteminde SKPathEffect.Create1DPath
belirtilen yol her zaman doldurulur. Nesnenin PathEffect
özelliği 1B yol efektine ayarlanmışsa yönteminde SKPaint
DrawPath
belirtilen yol her zaman konturlanır. Nesnenin pathPaint
ayarı olmadığına Style
ve normalde varsayılan değer olduğuna ancak yol ne olursa olsun konturlandığına Fill
dikkat edin.
Örnekte kullanılan Translate
kutu 20 piksel karedir ve advance
bağımsız değişken 24 olarak ayarlanmıştır. Bu fark, çizgi kabaca yatay veya dikey olduğunda kutular arasında bir boşluk olmasına neden olur, ancak kutunun köşegeni 28,3 piksel olduğundan çizgi çapraz olduğunda kutular biraz çakışıyor.
Örnekteki Rotate
baklava şekli de 20 piksel genişliğindedir. advance
20 olarak ayarlanır, böylece elmas çizginin eğriliğiyle birlikte döndürülürken noktalar dokunmaya devam ediyor.
Örnekteki Morph
dikdörtgen şekli, Bézier eğrisi etrafında bükülürken dikdörtgenler arasında küçük bir boşluk oluşturmak için 55 ayarıyla 50 piksel genişliğindedir advance
.
advance
Bağımsız değişken yolun boyutundan küçükse çoğaltılan yollar çakışabilir. Bu, bazı ilginç etkilere neden olabilir. Bağlı Zincir sayfasında, bir katenaryin ayırt edici şeklinde asılı olan, bağlantılı bir zincire benzeyen çakışan bir dizi daire görüntülenir:
Çok yakından baktığınızda, bunların aslında daire olmadığını göreceksiniz. Zincirdeki her bağlantı iki yaydır, boyutlandırılır ve konumlandırılır, böylece birbirine bitişik bağlantılarla bağlanır gibi görünürler.
Tekdüzen ağırlık dağılımının bir zinciri veya kablosu katenary şeklinde askıda kalıyor. Ters katenary biçiminde inşa edilmiş bir kemer, bir kemerin ağırlığından eşit basınç dağılımından yararlanır. Katenaryin görünüşte basit bir matematiksel açıklaması vardır:
y = a · cosh(x / a)
Cosh, hiperbolik kosinüs işlevidir. 0'a eşit x için cosh sıfır, y ise a'ya eşittir. Burası katenaryanın merkezi. Kosinüs işlevinde olduğu gibi, cosh'un eşit olduğu söylenir; bu da cosh(–x) öğesinin cosh(x) değerine eşit olduğu ve pozitif veya negatif bağımsız değişkenlerin artırılması için değerlerin arttığı anlamına gelir. Bu değerler, katenaryin kenarlarını oluşturan eğrileri açıklar.
Katenaryanın telefon sayfasının boyutlarına sığacak şekilde uygun değerini bulmak doğrudan bir hesaplama değildir. w ve h bir dikdörtgenin genişliği ve yüksekliğiyse, en uygun değeriaşağıdaki denklemi karşılar:
cosh(w / 2 / a) = 1 + h / a
sınıfındaki LinkedChainPage
aşağıdaki yöntem, ve olarak left
right
eşittir işaretinin sol ve sağındaki iki ifadeye başvurarak bu eşitliği birleştirir. küçük değerleri için , değerinden right
büyüktür; büyük değerleri için, left
değerinden right
küçüktür. left
Döngü, while
aşağıdakilerin en uygun değerini daraltıyor:
float FindOptimumA(float width, float height)
{
Func<float, float> left = (float a) => (float)Math.Cosh(width / 2 / a);
Func<float, float> right = (float a) => 1 + height / a;
float gtA = 1; // starting value for left > right
float ltA = 10000; // starting value for left < right
while (Math.Abs(gtA - ltA) > 0.1f)
{
float avgA = (gtA + ltA) / 2;
if (left(avgA) < right(avgA))
{
ltA = avgA;
}
else
{
gtA = avgA;
}
}
return (gtA + ltA) / 2;
}
SKPath
Bağlantıların nesnesi sınıfın oluşturucusunda oluşturulur ve sonuç SKPathEffect
nesnesi, alan olarak depolanan nesnenin SKPaint
özelliğine PathEffect
ayarlanır:
public class LinkedChainPage : ContentPage
{
const float linkRadius = 30;
const float linkThickness = 5;
Func<float, float, float> catenary = (float a, float x) => (float)(a * Math.Cosh(x / a));
SKPaint linksPaint = new SKPaint
{
Color = SKColors.Silver
};
public LinkedChainPage()
{
Title = "Linked Chain";
SKCanvasView canvasView = new SKCanvasView();
canvasView.PaintSurface += OnCanvasViewPaintSurface;
Content = canvasView;
// Create the path for the individual links
SKRect outer = new SKRect(-linkRadius, -linkRadius, linkRadius, linkRadius);
SKRect inner = outer;
inner.Inflate(-linkThickness, -linkThickness);
using (SKPath linkPath = new SKPath())
{
linkPath.AddArc(outer, 55, 160);
linkPath.ArcTo(inner, 215, -160, false);
linkPath.Close();
linkPath.AddArc(outer, 235, 160);
linkPath.ArcTo(inner, 395, -160, false);
linkPath.Close();
// Set that path as the 1D path effect for linksPaint
linksPaint.PathEffect =
SKPathEffect.Create1DPath(linkPath, 1.3f * linkRadius, 0,
SKPath1DPathEffectStyle.Rotate);
}
}
...
}
İşleyicinin PaintSurface
ana işi, katenaryin kendisi için bir yol oluşturmaktır. En uygun a değerini belirledikten ve değişkende optA
depoladıktan sonra pencerenin en üstünden bir uzaklık hesaplaması gerekir. Ardından, katenary için bir değer koleksiyonu SKPoint
birikebilir, bunu bir yola dönüştürebilir ve önceden oluşturulmuş SKPaint
nesneyle yolu çizebilir:
public class LinkedChainPage : ContentPage
{
...
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
SKImageInfo info = args.Info;
SKSurface surface = args.Surface;
SKCanvas canvas = surface.Canvas;
canvas.Clear(SKColors.Black);
// Width and height of catenary
int width = info.Width;
float height = info.Height - linkRadius;
// Find the optimum 'a' for this width and height
float optA = FindOptimumA(width, height);
// Calculate the vertical offset for that value of 'a'
float yOffset = catenary(optA, -width / 2);
// Create a path for the catenary
SKPoint[] points = new SKPoint[width];
for (int x = 0; x < width; x++)
{
points[x] = new SKPoint(x, yOffset - catenary(optA, x - width / 2));
}
using (SKPath path = new SKPath())
{
path.AddPoly(points, false);
// And render that path with the linksPaint object
canvas.DrawPath(path, linksPaint);
}
}
...
}
Bu program, içinde (0, 0) noktasının merkezde olması için kullanılan Create1DPath
yolu tanımlar. Yolun (0, 0) noktası, süslü olduğu çizgi veya eğriyle hizalandığından bu mantıklı görünür. Ancak bazı özel efektler için ortalanmamış (0, 0) nokta kullanabilirsiniz.
Taşıyıcı Bant sayfası, pencerenin boyutlarına göre boyutlandırılmış kavisli bir üst ve alta sahip dikdörtgen taşıyıcı banda benzeyen bir yol oluşturur. Bu yol, 20 piksel genişliğinde ve gri renkli basit SKPaint
bir nesneyle konturlanır ve sonra küçük bir demete benzeyen bir yola başvuran bir SKPathEffect
nesneyle başka SKPaint
bir nesneyle yeniden konturlanır:
Demet yolunun (0, 0) noktası tutamaçtır, bu nedenle bağımsız değişken animasyonlu olduğunda phase
, kovalar taşıyıcı bandın etrafında dönüyor gibi görünüyor, belki de en alttaki suyu alıp en üste boşaltıyor.
sınıfı, ConveyorBeltPage
ve OnDisappearing
yöntemlerinin OnAppearing
geçersiz kılmalarıyla animasyon uygular. Demetin yolu, sayfanın oluşturucusunda tanımlanır:
public class ConveyorBeltPage : ContentPage
{
SKCanvasView canvasView;
bool pageIsActive = false;
SKPaint conveyerPaint = new SKPaint
{
Style = SKPaintStyle.Stroke,
StrokeWidth = 20,
Color = SKColors.DarkGray
};
SKPath bucketPath = new SKPath();
SKPaint bucketsPaint = new SKPaint
{
Color = SKColors.BurlyWood,
};
public ConveyorBeltPage()
{
Title = "Conveyor Belt";
canvasView = new SKCanvasView();
canvasView.PaintSurface += OnCanvasViewPaintSurface;
Content = canvasView;
// Create the path for the bucket starting with the handle
bucketPath.AddRect(new SKRect(-5, -3, 25, 3));
// Sides
bucketPath.AddRoundedRect(new SKRect(25, -19, 27, 18), 10, 10,
SKPathDirection.CounterClockwise);
bucketPath.AddRoundedRect(new SKRect(63, -19, 65, 18), 10, 10,
SKPathDirection.CounterClockwise);
// Five slats
for (int i = 0; i < 5; i++)
{
bucketPath.MoveTo(25, -19 + 8 * i);
bucketPath.LineTo(25, -13 + 8 * i);
bucketPath.ArcTo(50, 50, 0, SKPathArcSize.Small,
SKPathDirection.CounterClockwise, 65, -13 + 8 * i);
bucketPath.LineTo(65, -19 + 8 * i);
bucketPath.ArcTo(50, 50, 0, SKPathArcSize.Small,
SKPathDirection.Clockwise, 25, -19 + 8 * i);
bucketPath.Close();
}
// Arc to suggest the hidden side
bucketPath.MoveTo(25, -17);
bucketPath.ArcTo(50, 50, 0, SKPathArcSize.Small,
SKPathDirection.Clockwise, 65, -17);
bucketPath.LineTo(65, -19);
bucketPath.ArcTo(50, 50, 0, SKPathArcSize.Small,
SKPathDirection.CounterClockwise, 25, -19);
bucketPath.Close();
// Make it a little bigger and correct the orientation
bucketPath.Transform(SKMatrix.MakeScale(-2, 2));
bucketPath.Transform(SKMatrix.MakeRotationDegrees(90));
}
...
Demet oluşturma kodu, demeti biraz daha büyük hale getiren ve yanlara çeviren iki dönüşümle tamamlar. Bu dönüşümleri uygulamak, önceki koddaki tüm koordinatları ayarlamaktan daha kolaydı.
İşleyici, PaintSurface
taşıyıcı bandın kendisi için bir yol tanımlayarak başlar. Bu yalnızca 20 piksel genişliğinde koyu-gri çizgiyle çizilmiş bir çift çizgi ve bir çift yarı dairedir:
public class ConveyorBeltPage : ContentPage
{
...
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
SKImageInfo info = args.Info;
SKSurface surface = args.Surface;
SKCanvas canvas = surface.Canvas;
canvas.Clear();
float width = info.Width / 3;
float verticalMargin = width / 2 + 150;
using (SKPath conveyerPath = new SKPath())
{
// Straight verticals capped by semicircles on top and bottom
conveyerPath.MoveTo(width, verticalMargin);
conveyerPath.ArcTo(width / 2, width / 2, 0, SKPathArcSize.Large,
SKPathDirection.Clockwise, 2 * width, verticalMargin);
conveyerPath.LineTo(2 * width, info.Height - verticalMargin);
conveyerPath.ArcTo(width / 2, width / 2, 0, SKPathArcSize.Large,
SKPathDirection.Clockwise, width, info.Height - verticalMargin);
conveyerPath.Close();
// Draw the conveyor belt itself
canvas.DrawPath(conveyerPath, conveyerPaint);
// Calculate spacing based on length of conveyer path
float length = 2 * (info.Height - 2 * verticalMargin) +
2 * ((float)Math.PI * width / 2);
// Value will be somewhere around 200
float spacing = length / (float)Math.Round(length / 200);
// Now animate the phase; t is 0 to 1 every 2 seconds
TimeSpan timeSpan = new TimeSpan(DateTime.Now.Ticks);
float t = (float)(timeSpan.TotalSeconds % 2 / 2);
float phase = -t * spacing;
// Create the buckets PathEffect
using (SKPathEffect bucketsPathEffect =
SKPathEffect.Create1DPath(bucketPath, spacing, phase,
SKPath1DPathEffectStyle.Rotate))
{
// Set it to the Paint object and draw the path again
bucketsPaint.PathEffect = bucketsPathEffect;
canvas.DrawPath(conveyerPath, bucketsPaint);
}
}
}
}
Taşıyıcı bandın çizilme mantığı yatay modda çalışmaz.
Kovalar taşıyıcı bantta yaklaşık 200 piksel aralanmalıdır. Bununla birlikte, taşıyıcı bant büyük olasılıkla 200 piksel uzunluğunda bir kat değildir, yani bağımsız değişkeni SKPathEffect.Create1DPath
animasyonlu olduğundan phase
kovalar varoluşun içine ve dışına çıkar.
Bu nedenle, program ilk olarak taşıyıcı bandın uzunluğu olan adlı length
bir değeri hesaplar. Taşıyıcı bant düz çizgilerden ve yarı dairelerden oluştuğundan, bu basit bir hesaplamadır. Ardından, demet sayısı 200'e bölünerek length
hesaplanır. Bu, en yakın tamsayıya yuvarlanıp bu sayıya length
bölünür. Sonuç, demetlerin tamsayı sayısı için bir aralıktır. Bağımsız phase
değişken bunun yalnızca bir bölümüdür.
Yoldan Yola Yeniden
Taşıyıcı Bant'taki işleyicinin DrawSurface
en altında, çağrıyı canvas.DrawPath
açıklama satırı yapın ve aşağıdaki kodla değiştirin:
SKPath newPath = new SKPath();
bool fill = bucketsPaint.GetFillPath(conveyerPath, newPath);
SKPaint newPaint = new SKPaint
{
Style = fill ? SKPaintStyle.Fill : SKPaintStyle.Stroke
};
canvas.DrawPath(newPath, newPaint);
Önceki örneğinde GetFillPath
olduğu gibi, renk dışında sonuçların aynı olduğunu göreceksiniz. yürütüldikten GetFillPath
newPath
sonra nesnesi, demet yolunun birden çok kopyasını içerir ve her biri animasyonun çağrı sırasında yerleştirmiş olduğu noktaya konumlandırılır.
Bir Alanı Tarama
SKPathEffect.Create2DLines
yöntemi, bir alanı genellikle tarama çizgileri olarak adlandırılan paralel çizgilerle doldurur. yöntemi aşağıdaki söz dizimine sahiptir:
public static SKPathEffect Create2DLine (Single width, SKMatrix matrix)
bağımsız değişkeni, width
tarama çizgilerinin vuruş genişliğini belirtir. matrix
parametresi, ölçeklendirme ve isteğe bağlı döndürmenin bir bileşimidir. Ölçeklendirme faktörü, Skia'nın tarama çizgilerini aralarken kullandığı piksel artışını gösterir. Satırlar arasındaki ayrım, ölçeklendirme faktörü eksi bağımsız değişkenidir width
. Ölçeklendirme faktörü değerden küçük veya buna eşitse width
, tarama çizgileri arasında boşluk kalmaz ve alan doldurulmuş gibi görünür. Yatay ve dikey ölçeklendirme için aynı değeri belirtin.
Varsayılan olarak, tarama çizgileri yataydır. matrix
Parametre döndürme içeriyorsa, tarama çizgileri saat yönünde döndürülür.
Tarama Dolgusu sayfasında bu yol efekti gösterilir. HatchFillPage
sınıfı, üç yol efektini alan olarak tanımlar ve ilki 3 piksel genişliğinde yatay tarama çizgileri ve bunların birbirinden 6 piksel aralıklı olduğunu belirten bir ölçeklendirme faktörü tanımlar. Bu nedenle çizgiler arasındaki ayrım üç pikseldir. İkinci yol efekti, birbirinden 24 piksel aralıklı altı piksel genişliğinde dikey tarama çizgileridir (bu nedenle ayırma 18 pikseldir) ve üçüncüsü 36 piksel aralıklı 12 piksel genişliğinde çapraz tarama çizgileri içindir.
public class HatchFillPage : ContentPage
{
SKPaint fillPaint = new SKPaint();
SKPathEffect horzLinesPath = SKPathEffect.Create2DLine(3, SKMatrix.MakeScale(6, 6));
SKPathEffect vertLinesPath = SKPathEffect.Create2DLine(6,
Multiply(SKMatrix.MakeRotationDegrees(90), SKMatrix.MakeScale(24, 24)));
SKPathEffect diagLinesPath = SKPathEffect.Create2DLine(12,
Multiply(SKMatrix.MakeScale(36, 36), SKMatrix.MakeRotationDegrees(45)));
SKPaint strokePaint = new SKPaint
{
Style = SKPaintStyle.Stroke,
StrokeWidth = 3,
Color = SKColors.Black
};
...
static SKMatrix Multiply(SKMatrix first, SKMatrix second)
{
SKMatrix target = SKMatrix.MakeIdentity();
SKMatrix.Concat(ref target, first, second);
return target;
}
}
Matris Multiply
yöntemine dikkat edin. Yatay ve dikey ölçeklendirme faktörleri aynı olduğundan, ölçeklendirme ve döndürme matrislerinin çarpıldığı sıra önemli değildir.
İşleyici, PaintSurface
sayfaya sığacak şekilde boyutlandırılmış yuvarlatılmış dikdörtgeni doldurmak için birlikte üç farklı renk içeren fillPaint
bu üç yol efektini kullanır. Style
üzerinde fillPaint
ayarlanan özellik yoksayılır; SKPaint
nesne öğesinden SKPathEffect.Create2DLine
oluşturulan bir yol efekti içerdiğinde, alan ne olursa olsun doldurulur:
public class HatchFillPage : ContentPage
{
...
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
SKImageInfo info = args.Info;
SKSurface surface = args.Surface;
SKCanvas canvas = surface.Canvas;
canvas.Clear();
using (SKPath roundRectPath = new SKPath())
{
// Create a path
roundRectPath.AddRoundedRect(
new SKRect(50, 50, info.Width - 50, info.Height - 50), 100, 100);
// Horizontal hatch marks
fillPaint.PathEffect = horzLinesPath;
fillPaint.Color = SKColors.Red;
canvas.DrawPath(roundRectPath, fillPaint);
// Vertical hatch marks
fillPaint.PathEffect = vertLinesPath;
fillPaint.Color = SKColors.Blue;
canvas.DrawPath(roundRectPath, fillPaint);
// Diagonal hatch marks -- use clipping
fillPaint.PathEffect = diagLinesPath;
fillPaint.Color = SKColors.Green;
canvas.Save();
canvas.ClipPath(roundRectPath);
canvas.DrawRect(new SKRect(0, 0, info.Width, info.Height), fillPaint);
canvas.Restore();
// Outline the path
canvas.DrawPath(roundRectPath, strokePaint);
}
}
...
}
Sonuçlara dikkatlice bakarsanız, kırmızı ve mavi tarama çizgilerinin yuvarlatılmış dikdörtgenle tam olarak sınırlı olmadığını görürsünüz. (Bu, temel alınan Skia kodunun bir özelliğidir.) Bu yetersizse, çapraz tarama çizgileri için yeşil renkli alternatif bir yaklaşım gösterilir: Yuvarlatılmış dikdörtgen kırpma yolu olarak kullanılır ve tarama çizgileri sayfanın tamamına çizilir.
İşleyici PaintSurface
, yuvarlatılmış dikdörtgeni konturlama çağrısıyla sonuçlanır, böylece kırmızı ve mavi tarama çizgileriyle tutarsızlığı görebilirsiniz:
Android ekranı gerçekten böyle görünmüyor: Ekran görüntüsünün ölçeklenmesi, ince kırmızı çizgilerin ve ince boşlukların daha geniş kırmızı çizgilerde ve daha geniş alanlarda birleştirilmesine neden oldu.
Bir Yol ile Doldurma
, SKPathEffect.Create2DPath
bir alanı yatay ve dikey olarak çoğaltılan bir yolla doldurmanıza olanak tanır ve bu da alanı kaplar:
public static SKPathEffect Create2DPath (SKMatrix matrix, SKPath path)
SKMatrix
Ölçeklendirme faktörleri çoğaltılan yolun yatay ve dikey aralığını gösterir. Ancak bu matrix
bağımsız değişkeni kullanarak yolu döndüremezsiniz; yolun döndürülmesini istiyorsanız, tarafından SKPath
tanımlanan yöntemi kullanarak Transform
yolun kendisini döndürün.
Çoğaltılan yol normalde doldurulan alan yerine ekranın sol ve üst kenarlarıyla hizalanır. Sol ve üst kenarlardan yatay ve dikey uzaklıkları belirtmek için 0 ile ölçeklendirme faktörleri arasında çeviri faktörleri sağlayarak bu davranışı geçersiz kılabilirsiniz.
Yol Kutucuğu Dolgu sayfası bu yol efektini gösterir. Alanı döşemek için kullanılan yol, sınıfında bir alan PathTileFillPage
olarak tanımlanır. Yatay ve dikey koordinatlar –40 ile 40 arasında değişir ve bu da bu yolun 80 piksel kare olduğu anlamına gelir:
public class PathTileFillPage : ContentPage
{
SKPath tilePath = SKPath.ParseSvgPathData(
"M -20 -20 L 2 -20, 2 -40, 18 -40, 18 -20, 40 -20, " +
"40 -12, 20 -12, 20 12, 40 12, 40 40, 22 40, 22 20, " +
"-2 20, -2 40, -20 40, -20 8, -40 8, -40 -8, -20 -8 Z");
...
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.Color = SKColors.Red;
using (SKPathEffect pathEffect =
SKPathEffect.Create2DPath(SKMatrix.MakeScale(64, 64), tilePath))
{
paint.PathEffect = pathEffect;
canvas.DrawRoundRect(
new SKRect(50, 50, info.Width - 50, info.Height - 50),
100, 100, paint);
}
}
}
}
İşleyicide PaintSurface
çağrılar SKPathEffect.Create2DPath
, 80 piksel kare kutucuklarının çakışmasına neden olmak için yatay ve dikey aralığı 64 olarak ayarlar. Neyse ki, yol bir bulmaca parçasına benziyor, bitişik kutucuklarla güzel bir şekilde meshleniyor:
Özgün ekran görüntüsünden ölçeklendirme, özellikle Android ekranında bazı bozulmalara neden olur.
Bu kutucukların her zaman bütün olarak göründüğüne ve hiçbir zaman kesilmediğini göreceksiniz. İlk iki ekran görüntüsünde, doldurulan alanın yuvarlak bir dikdörtgen olduğu bile belli değildir. Bu kutucukları belirli bir alana kırpmak istiyorsanız, bir kırpma yolu kullanın.
nesnesinin Style
Stroke
özelliğini olarak ayarlamayı SKPaint
deneyin; tek tek kutucukların doldurulması yerine ana hatlarıyla özetlendiğini görürsünüz.
SkiaSharp bit eşlem döşemesi makalesinde gösterildiği gibi bir alanı kutucuklu bit eşlem ile doldurmak da mümkündür.
Keskin Köşeleri Yuvarlama
Bir Yay Çizmenin Üç Yolu makalesinde sunulan Yuvarlatılmış Heptagon programı, yedi taraflı bir şeklin noktalarını eğmek için tanjant yay kullandı. Başka Bir Yuvarlatılmış Heptagon sayfası, yöntemden SKPathEffect.CreateCorner
oluşturulan yol efektini kullanan çok daha kolay bir yaklaşım gösterir:
public static SKPathEffect CreateCorner (Single radius)
Tek bağımsız değişken olarak adlandırılmış radius
olsa da, bunu istenen köşe yarıçapını yarıya ayarlamanız gerekir. (Bu, temel alınan Skia kodunun bir özelliğidir.)
sınıfındaki PaintSurface
işleyici aşağıdadır AnotherRoundedHeptagonPage
:
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
SKImageInfo info = args.Info;
SKSurface surface = args.Surface;
SKCanvas canvas = surface.Canvas;
canvas.Clear();
int numVertices = 7;
float radius = 0.45f * Math.Min(info.Width, info.Height);
SKPoint[] vertices = new SKPoint[numVertices];
double vertexAngle = -0.5f * Math.PI; // straight up
// Coordinates of the vertices of the polygon
for (int vertex = 0; vertex < numVertices; vertex++)
{
vertices[vertex] = new SKPoint(radius * (float)Math.Cos(vertexAngle),
radius * (float)Math.Sin(vertexAngle));
vertexAngle += 2 * Math.PI / numVertices;
}
float cornerRadius = 100;
// Create the path
using (SKPath path = new SKPath())
{
path.AddPoly(vertices, true);
// Render the path in the center of the screen
using (SKPaint paint = new SKPaint())
{
paint.Style = SKPaintStyle.Stroke;
paint.Color = SKColors.Blue;
paint.StrokeWidth = 10;
// Set argument to half the desired corner radius!
paint.PathEffect = SKPathEffect.CreateCorner(cornerRadius / 2);
canvas.Translate(info.Width / 2, info.Height / 2);
canvas.DrawPath(path, paint);
// Uncomment DrawCircle call to verify corner radius
float offset = cornerRadius / (float)Math.Sin(Math.PI * (numVertices - 2) / numVertices / 2);
paint.Color = SKColors.Green;
// canvas.DrawCircle(vertices[0].X, vertices[0].Y + offset, cornerRadius, paint);
}
}
}
Bu etkiyi, nesnenin Style
özelliğine SKPaint
göre stroking veya dolgu ile kullanabilirsiniz. Burada çalışıyor:
Bu yuvarlatılmış heptagonun önceki programla aynı olduğunu göreceksiniz. Çağrıda SKPathEffect.CreateCorner
belirtilen 50 yerine köşe yarıçapı gerçekten 100 olduğuna daha ikna edici bir şekilde ihtiyacınız varsa, programdaki son deyimin açıklamasını açabilir ve köşede 100 yarıçaplı dairenin üst üste bindiğini görebilirsiniz.
Rastgele Değişim
Bazen bilgisayar grafiklerinin kusursuz düz çizgileri tam olarak istediğiniz gibi değildir ve biraz rastgelelik istenir. Bu durumda yöntemini denemek SKPathEffect.CreateDiscrete
istersiniz:
public static SKPathEffect CreateDiscrete (Single segLength, Single deviation, UInt32 seedAssist)
Bu yol efektini, vuruş veya doldurma için kullanabilirsiniz. Çizgiler, yaklaşık uzunluğu tarafından segLength
belirtilen bağlı segmentlere ayrılır ve farklı yönlerde genişletilir. Özgün satırdan sapmanın kapsamı tarafından deviation
belirtilir.
Son bağımsız değişken, etki için kullanılan sahte rastgele diziyi oluşturmak için kullanılan bir tohumdur. Değişim etkisi farklı tohumlar için biraz farklı görünecektir. Bağımsız değişkenin varsayılan değeri sıfırdır ve bu da programı her çalıştırdığınızda efektin aynı olduğu anlamına gelir. Ekran her yeniden boyanırsa farklı bir değişim istiyorsanız, tohumu bir DataTime.Now
değerin Millisecond
özelliğine ayarlayabilirsiniz (örneğin).
Değişim Denemesi sayfası, bir dikdörtgeni okşayarak farklı değerlerle denemeler yapmanızı sağlar:
Program basittir. JitterExperimentPage.xaml dosyası iki Slider
öğe ve bir SKCanvasView
örneği oluşturur:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:skia="clr-namespace:SkiaSharp.Views.Forms;assembly=SkiaSharp.Views.Forms"
x:Class="SkiaSharpFormsDemos.Curves.JitterExperimentPage"
Title="Jitter Experiment">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.Resources>
<ResourceDictionary>
<Style TargetType="Label">
<Setter Property="HorizontalTextAlignment" Value="Center" />
</Style>
<Style TargetType="Slider">
<Setter Property="Margin" Value="20, 0" />
<Setter Property="Minimum" Value="0" />
<Setter Property="Maximum" Value="100" />
</Style>
</ResourceDictionary>
</Grid.Resources>
<Slider x:Name="segLengthSlider"
Grid.Row="0"
ValueChanged="sliderValueChanged" />
<Label Text="{Binding Source={x:Reference segLengthSlider},
Path=Value,
StringFormat='Segment Length = {0:F0}'}"
Grid.Row="1" />
<Slider x:Name="deviationSlider"
Grid.Row="2"
ValueChanged="sliderValueChanged" />
<Label Text="{Binding Source={x:Reference deviationSlider},
Path=Value,
StringFormat='Deviation = {0:F0}'}"
Grid.Row="3" />
<skia:SKCanvasView x:Name="canvasView"
Grid.Row="4"
PaintSurface="OnCanvasViewPaintSurface" />
</Grid>
</ContentPage>
PaintSurface
JitterExperimentPage.xaml.cs arka planda kod dosyasındaki işleyici, bir Slider
değer değiştiğinde çağrılır. İki Slider
değeri kullanarak çağırır SKPathEffect.CreateDiscrete
ve bunu kullanarak bir dikdörtgeni konturlar:
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
SKImageInfo info = args.Info;
SKSurface surface = args.Surface;
SKCanvas canvas = surface.Canvas;
canvas.Clear();
float segLength = (float)segLengthSlider.Value;
float deviation = (float)deviationSlider.Value;
using (SKPaint paint = new SKPaint())
{
paint.Style = SKPaintStyle.Stroke;
paint.StrokeWidth = 5;
paint.Color = SKColors.Blue;
using (SKPathEffect pathEffect = SKPathEffect.CreateDiscrete(segLength, deviation))
{
paint.PathEffect = pathEffect;
SKRect rect = new SKRect(100, 100, info.Width - 100, info.Height - 100);
canvas.DrawRect(rect, paint);
}
}
}
Bu efekti doldurma için de kullanabilirsiniz. Bu durumda, doldurulan alanın ana hattı bu rastgele sapmalara tabidir. Metni Değiştirme sayfası, metni görüntülemek için bu yol efektinin kullanılmasını gösterir. Sınıfın işleyicisindeki PaintSurface
kodun JitterTextPage
çoğu metni boyutlandırmaya ve ortalamaya ayrılmıştır:
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
SKImageInfo info = args.Info;
SKSurface surface = args.Surface;
SKCanvas canvas = surface.Canvas;
canvas.Clear();
string text = "FUZZY";
using (SKPaint textPaint = new SKPaint())
{
textPaint.Color = SKColors.Purple;
textPaint.PathEffect = SKPathEffect.CreateDiscrete(3f, 10f);
// Adjust TextSize property so text is 95% of screen width
float textWidth = textPaint.MeasureText(text);
textPaint.TextSize *= 0.95f * info.Width / textWidth;
// Find the text bounds
SKRect textBounds = new SKRect();
textPaint.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;
canvas.DrawText(text, xText, yText, textPaint);
}
}
Burada yatay modda çalışıyor:
Yol Ana Hat Oluşturma
yönteminin GetFillPath
SKPaint
iki küçük örneğini zaten gördünüz ve iki sürümü vardır:
public Boolean GetFillPath (SKPath src, SKPath dst, Single resScale = 1)
public Boolean GetFillPath (SKPath src, SKPath dst, SKRect cullRect, Single resScale = 1)
Yalnızca ilk iki bağımsız değişken gereklidir. yöntemi bağımsız değişken tarafından src
başvuruda bulunan yola erişir, nesnedeki SKPaint
vuruş özelliklerine (özelliği dahil) PathEffect
göre yol verilerini değiştirir ve ardından sonuçları dst
yola yazar. resScale
parametresi, daha küçük bir hedef yol oluşturmak için duyarlığı azaltmaya olanak tanır ve cullRect
bağımsız değişken dikdörtgen dışındaki dağılımları ortadan kaldırabilir.
Bu yöntemin temel kullanımlarından biri yol efektleri içermez: Nesnenin SKPaint
Style
özelliği olarak ayarlanmışsa SKPaintStyle.Stroke
ve kümesi yoksaPathEffect
, GetFillPath
boya özellikleri tarafından konturlanmış gibi kaynak yolun ana hattını temsil eden bir yol oluşturur.
Örneğin, yol 500 yarıçaplı basit bir daireyse ve SKPaint
nesne 100 vuruş genişliği belirtiyorsa src
dst
, yol biri 450 yarıçaplı, diğeri 550 yarıçaplı iki eşmerkezli daireye dönüşür. Bu dst
yolu doldurmak yolu vuruş ile aynı olduğundan yöntemi çağrılır.GetFillPath
src
Ancak yol ana hatlarını görmek için yolu da konturlayabilirsiniz dst
.
Yol Ana Hatlarına Dokun seçeneği bunu gösterir. ve örneği TapToOutlineThePathPage.xaml dosyasında oluşturulur.TapGestureRecognizer
SKCanvasView
Arka planda kod TapToOutlineThePathPage.xaml.cs dosyası üç SKPaint
nesneyi alan olarak tanımlar; ikisi 100 ve 20 vuruş genişliklerine ve üçüncüsü doldurmaya yöneliktir:
public partial class TapToOutlineThePathPage : ContentPage
{
bool outlineThePath = false;
SKPaint redThickStroke = new SKPaint
{
Style = SKPaintStyle.Stroke,
Color = SKColors.Red,
StrokeWidth = 100
};
SKPaint redThinStroke = new SKPaint
{
Style = SKPaintStyle.Stroke,
Color = SKColors.Red,
StrokeWidth = 20
};
SKPaint blueFill = new SKPaint
{
Style = SKPaintStyle.Fill,
Color = SKColors.Blue
};
public TapToOutlineThePathPage()
{
InitializeComponent();
}
void OnCanvasViewTapped(object sender, EventArgs args)
{
outlineThePath ^= true;
(sender as SKCanvasView).InvalidateSurface();
}
...
}
Ekrana dokunmadıysa, PaintSurface
işleyici döngüsel bir yol işlemek için ve redThickStroke
boya nesnelerini kullanırblueFill
:
public partial class TapToOutlineThePathPage : ContentPage
{
...
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
SKImageInfo info = args.Info;
SKSurface surface = args.Surface;
SKCanvas canvas = surface.Canvas;
canvas.Clear();
using (SKPath circlePath = new SKPath())
{
circlePath.AddCircle(info.Width / 2, info.Height / 2,
Math.Min(info.Width / 2, info.Height / 2) -
redThickStroke.StrokeWidth);
if (!outlineThePath)
{
canvas.DrawPath(circlePath, blueFill);
canvas.DrawPath(circlePath, redThickStroke);
}
else
{
using (SKPath outlinePath = new SKPath())
{
redThickStroke.GetFillPath(circlePath, outlinePath);
canvas.DrawPath(outlinePath, blueFill);
canvas.DrawPath(outlinePath, redThinStroke);
}
}
}
}
}
Daire beklediğiniz gibi doldurulur ve konturlanır:
Ekrana dokunduğunuzda, outlineThePath
olarak ayarlanır true
ve PaintSurface
işleyici yeni SKPath
bir nesne oluşturur ve bunu paint nesnesi üzerindeki redThickStroke
bir çağrıda GetFillPath
hedef yol olarak kullanır. Bu hedef yol daha sonra ile redThinStroke
doldurulur ve konturlanır ve sonuç olarak aşağıdakiler elde edilir:
İki kırmızı daire, orijinal dairesel yolun iki dairesel kontura dönüştürüldüğünü açıkça gösterir.
Bu yöntem, yöntem için SKPathEffect.Create1DPath
kullanılacak yollar geliştirmede çok yararlı olabilir. Bu yöntemlerde belirttiğiniz yollar, yollar çoğaltıldığında her zaman doldurulur. Yolun tamamının doldurulmasını istemiyorsanız, ana hatları dikkatlice tanımlamanız gerekir.
Örneğin, Bağlı Zincir örneğinde bağlantılar, her biri doldurulacak yolun alanını ana hatlarıyla özetleyen iki yarıçapı temel alan dört yaylı bir diziyle tanımlanmıştır. Bunu biraz farklı bir şekilde yapmak için sınıftaki LinkedChainPage
kodu değiştirmek mümkündür.
İlk olarak, sabiti linkRadius
yeniden tanımlamak istersiniz:
const float linkRadius = 27.5f;
const float linkThickness = 5;
linkPath
artık istenen başlangıç açıları ve süpürme açıları ile bu tek yarıçapı temel alan iki yaydır:
using (SKPath linkPath = new SKPath())
{
SKRect rect = new SKRect(-linkRadius, -linkRadius, linkRadius, linkRadius);
linkPath.AddArc(rect, 55, 160);
linkPath.AddArc(rect, 235, 160);
using (SKPaint strokePaint = new SKPaint())
{
strokePaint.Style = SKPaintStyle.Stroke;
strokePaint.StrokeWidth = linkThickness;
using (SKPath outlinePath = new SKPath())
{
strokePaint.GetFillPath(linkPath, outlinePath);
// Set that path as the 1D path effect for linksPaint
linksPaint.PathEffect =
SKPathEffect.Create1DPath(outlinePath, 1.3f * linkRadius, 0,
SKPath1DPathEffectStyle.Rotate);
}
}
}
Nesne outlinePath
daha sonra içinde belirtilen strokePaint
özelliklerle konturlandığında ana hattının linkPath
alıcısı olur.
Bu tekniği kullanan başka bir örnek, bir yöntemde kullanılan yol için bir sonraki adımdır.
Yol Efektlerini Birleştirme
öğesinin son iki statik oluşturma yöntemi SKPathEffect
şunlardırSKPathEffect.CreateSum
:SKPathEffect.CreateCompose
public static SKPathEffect CreateSum (SKPathEffect first, SKPathEffect second)
public static SKPathEffect CreateCompose (SKPathEffect outer, SKPathEffect inner)
Bu yöntemlerin her ikisi de bileşik yol efekti oluşturmak için iki yol efektini birleştirir. yöntemi, CreateSum
ayrı olarak uygulanan iki yol efektine benzer bir yol efekti oluştururken CreateCompose
, bir yol efekti () inner
uygular ve ardından bunu uygular outer
.
yönteminin GetFillPath
SKPaint
özelliklere (dahilPathEffect
) göre SKPaint
bir yolu başka bir yola nasıl dönüştürebildiğini zaten gördünüz, bu nedenle bir SKPaint
nesnenin veya CreateCompose
yöntemlerinde CreateSum
belirtilen iki yol efektiyle bu işlemi iki kez gerçekleştirebilmesi çok gizemli olmamalıdır.
bir yolu bir yol efektiyle dolduran ve yolu başka bir SKPaint
yol efektiyle konturlayan bir nesne tanımlamak, bunun belirgin bir kullanımıdırCreateSum
. Bu, taraklı kenarlara sahip bir çerçeve içinde bir dizi kedi görüntüleyen Çerçevedeki Kediler örneğinde gösterilmiştir:
sınıfı birkaç CatsInFramePage
alan tanımlayarak başlar. SVG Yol Verileri makalesinden sınıfından ilk alanı PathDataCatPage
tanıyabilirsiniz. İkinci yol, çerçevenin tarak deseni için bir çizgi ve yay temel alır:
public class CatsInFramePage : ContentPage
{
// From PathDataCatPage.cs
SKPath catPath = SKPath.ParseSvgPathData(
"M 160 140 L 150 50 220 103" + // Left ear
"M 320 140 L 330 50 260 103" + // Right ear
"M 215 230 L 40 200" + // Left whiskers
"M 215 240 L 40 240" +
"M 215 250 L 40 280" +
"M 265 230 L 440 200" + // Right whiskers
"M 265 240 L 440 240" +
"M 265 250 L 440 280" +
"M 240 100" + // Head
"A 100 100 0 0 1 240 300" +
"A 100 100 0 0 1 240 100 Z" +
"M 180 170" + // Left eye
"A 40 40 0 0 1 220 170" +
"A 40 40 0 0 1 180 170 Z" +
"M 300 170" + // Right eye
"A 40 40 0 0 1 260 170" +
"A 40 40 0 0 1 300 170 Z");
SKPaint catStroke = new SKPaint
{
Style = SKPaintStyle.Stroke,
StrokeWidth = 5
};
SKPath scallopPath =
SKPath.ParseSvgPathData("M 0 0 L 50 0 A 60 60 0 0 1 -50 0 Z");
SKPaint framePaint = new SKPaint
{
Color = SKColors.Black
};
...
}
catPath
nesnesi Style
özelliği olarak ayarlandıysa Stroke
yönteminde SKPathEffect.Create2DPath
SKPaint
kullanılabilir. Ancak, doğrudan bu programda kullanılırsa catPath
, kedinin tüm başı doldurulur ve bıyıklar bile görünmez. (Deneyin!) Bu yolun ana hattını almak ve yönteminde SKPathEffect.Create2DPath
bu ana hattı kullanmak gerekir.
Oluşturucu bu işi yapar. İlk olarak (0, 0) noktasını ortaya taşımak ve boyutunu küçültmek için iki dönüşüm catPath
uygular. GetFillPath
içindeki konturların tüm ana hatlarını outlinedCatPath
alır ve bu nesne çağrıda SKPathEffect.Create2DPath
kullanılır. Değerdeki SKMatrix
ölçeklendirme faktörleri, kutucuklar arasında küçük bir arabellek sağlamak için kedinin yatay ve dikey boyutundan biraz daha büyüktür, çeviri faktörleri ise çerçevenin sol üst köşesinde tam bir kedi görünecek şekilde kısmen ampirik olarak türetilmiştir:
public class CatsInFramePage : ContentPage
{
...
public CatsInFramePage()
{
Title = "Cats in Frame";
SKCanvasView canvasView = new SKCanvasView();
canvasView.PaintSurface += OnCanvasViewPaintSurface;
Content = canvasView;
// Move (0, 0) point to center of cat path
catPath.Transform(SKMatrix.MakeTranslation(-240, -175));
// Now catPath is 400 by 250
// Scale it down to 160 by 100
catPath.Transform(SKMatrix.MakeScale(0.40f, 0.40f));
// Get the outlines of the contours of the cat path
SKPath outlinedCatPath = new SKPath();
catStroke.GetFillPath(catPath, outlinedCatPath);
// Create a 2D path effect from those outlines
SKPathEffect fillEffect = SKPathEffect.Create2DPath(
new SKMatrix { ScaleX = 170, ScaleY = 110,
TransX = 75, TransY = 80,
Persp2 = 1 },
outlinedCatPath);
// Create a 1D path effect from the scallop path
SKPathEffect strokeEffect =
SKPathEffect.Create1DPath(scallopPath, 75, 0, SKPath1DPathEffectStyle.Rotate);
// Set the sum the effects to frame paint
framePaint.PathEffect = SKPathEffect.CreateSum(fillEffect, strokeEffect);
}
...
}
Oluşturucu daha sonra taraklı çerçeveyi çağırır SKPathEffect.Create1DPath
. Yolun genişliğinin 100 piksel olduğuna, ancak çoğaltılan yolun çerçevenin etrafında çakışması için ilerlemenin 75 piksel olduğuna dikkat edin. Oluşturucunun son deyimi, iki yol efektini birleştirmek ve sonucu nesnesine ayarlamak için SKPaint
çağırırSKPathEffect.CreateSum
.
Tüm bu çalışmalar işleyicinin PaintSurface
oldukça basit olmasını sağlar. Yalnızca kullanarak bir dikdörtgen tanımlaması ve çizmesi framePaint
gerekir:
public class CatsInFramePage : ContentPage
{
...
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
SKImageInfo info = args.Info;
SKSurface surface = args.Surface;
SKCanvas canvas = surface.Canvas;
canvas.Clear();
SKRect rect = new SKRect(50, 50, info.Width - 50, info.Height - 50);
canvas.ClipRect(rect);
canvas.DrawRect(rect, framePaint);
}
}
Yol efektlerinin arkasındaki algoritmalar her zaman, vuruş veya doldurma için kullanılan tüm yolun görüntülenmesine neden olur ve bu da bazı görsellerin dikdörtgenin dışında görünmesine neden olabilir. Aramadan ClipRect
önceki DrawRect
arama, görsellerin oldukça temiz olmasını sağlar. (Kırpmadan deneyin!)
Başka bir yol efektine biraz değişim eklemek için yaygın olarak kullanılır SKPathEffect.CreateCompose
. Kesinlikle kendi başınıza denemeler yapabilirsiniz, ancak aşağıda biraz farklı bir örnek verilmiştir:
Kesikli Tarama Çizgileri, üç noktayı kesikli tarama çizgileri ile doldurur. Sınıfındaki çalışmanın DashedHatchLinesPage
çoğu doğrudan alan tanımlarında gerçekleştirilir. Bu alanlar bir tire efekti ve bir tarama efekti tanımlar. Bunlar, tanımdaki bir SKPathEffect.CreateCompose
çağrıda SKPaint
başvurulacağından olarak static
tanımlanır:
public class DashedHatchLinesPage : ContentPage
{
static SKPathEffect dashEffect =
SKPathEffect.CreateDash(new float[] { 30, 30 }, 0);
static SKPathEffect hatchEffect = SKPathEffect.Create2DLine(20,
Multiply(SKMatrix.MakeScale(60, 60),
SKMatrix.MakeRotationDegrees(45)));
SKPaint paint = new SKPaint()
{
PathEffect = SKPathEffect.CreateCompose(dashEffect, hatchEffect),
StrokeCap = SKStrokeCap.Round,
Color = SKColors.Blue
};
...
static SKMatrix Multiply(SKMatrix first, SKMatrix second)
{
SKMatrix target = SKMatrix.MakeIdentity();
SKMatrix.Concat(ref target, first, second);
return target;
}
}
İşleyicinin PaintSurface
yalnızca standart ek yükü ve bir çağrısı içermesi DrawOval
gerekir:
public class DashedHatchLinesPage : ContentPage
{
...
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
SKImageInfo info = args.Info;
SKSurface surface = args.Surface;
SKCanvas canvas = surface.Canvas;
canvas.Clear();
canvas.DrawOval(info.Width / 2, info.Height / 2,
0.45f * info.Width, 0.45f * info.Height,
paint);
}
...
}
Daha önce keşfettiğiniz gibi, tarama çizgileri tam olarak alanın iç kısmıyla sınırlı değildir ve bu örnekte, her zaman soldan bir tireyle başlarlar:
Basit nokta ve tirelerden garip kombinasyonlara kadar uzanan yol efektleri gördüğünüze göre, hayal gücünüzü kullanın ve neler oluşturabileceğinizi görün.