Ořezy cestami a oblastmi
Použití cest k oříznutí grafiky do konkrétních oblastí a vytváření oblastí
Někdy je nutné omezit vykreslování grafiky na konkrétní oblast. To se označuje jako výřez. Výřez můžete použít pro speciální efekty, jako je například tento obrázek opice, kterou vidíte prostřednictvím klíčového otvoru:
Oblast výřezu je oblast obrazovky, ve které se vykreslují grafické objekty. Všechno, co se zobrazí mimo oblast výřezu, se nevykreslí. Oblast výřezu je obvykle definována obdélníkem SKPath
nebo objektem, ale můžete alternativně definovat oblast výřezu pomocí objektu SKRegion
. Tyto dva typy objektů se zpočátku zdají být související, protože můžete vytvořit oblast z cesty. Nemůžete však vytvořit cestu z oblasti a jsou velmi odlišné interně: Cesta se skládá z řady čar a křivek, zatímco oblast je definována řadou vodorovných čar skenování.
Výše uvedený obrázek vytvořila stránka Monkey through Keyhole . Třída MonkeyThroughKeyholePage
definuje cestu pomocí dat SVG a pomocí konstruktoru načte rastrový obrázek z programových prostředků:
public class MonkeyThroughKeyholePage : ContentPage
{
SKBitmap bitmap;
SKPath keyholePath = SKPath.ParseSvgPathData(
"M 300 130 L 250 350 L 450 350 L 400 130 A 70 70 0 1 0 300 130 Z");
public MonkeyThroughKeyholePage()
{
Title = "Monkey through Keyhole";
SKCanvasView canvasView = new SKCanvasView();
canvasView.PaintSurface += OnCanvasViewPaintSurface;
Content = canvasView;
string resourceID = "SkiaSharpFormsDemos.Media.SeatedMonkey.jpg";
Assembly assembly = GetType().GetTypeInfo().Assembly;
using (Stream stream = assembly.GetManifestResourceStream(resourceID))
{
bitmap = SKBitmap.Decode(stream);
}
}
...
}
I když objekt keyholePath
popisuje obrys klíčového tlačítka, souřadnice jsou zcela libovolné a odrážejí, co bylo vhodné, když byla data cesty navržena. Z tohoto důvodu PaintSurface
obslužná rutina získá hranice této cesty a volání Translate
a Scale
přesune cestu do středu obrazovky a aby byla téměř tak vysoká jako obrazovka:
public class MonkeyThroughKeyholePage : ContentPage
{
...
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
SKImageInfo info = args.Info;
SKSurface surface = args.Surface;
SKCanvas canvas = surface.Canvas;
canvas.Clear();
// Set transform to center and enlarge clip path to window height
SKRect bounds;
keyholePath.GetTightBounds(out bounds);
canvas.Translate(info.Width / 2, info.Height / 2);
canvas.Scale(0.98f * info.Height / bounds.Height);
canvas.Translate(-bounds.MidX, -bounds.MidY);
// Set the clip path
canvas.ClipPath(keyholePath);
// Reset transforms
canvas.ResetMatrix();
// Display monkey to fill height of window but maintain aspect ratio
canvas.DrawBitmap(bitmap,
new SKRect((info.Width - info.Height) / 2, 0,
(info.Width + info.Height) / 2, info.Height));
}
}
Cesta se ale nevykreslí. Místo toho se cesta použije k nastavení oblasti výřezu pomocí tohoto příkazu:
canvas.ClipPath(keyholePath);
Obslužná rutina PaintSurface
pak transformuje voláním ResetMatrix
a nakreslí rastrový obrázek tak, aby se rozšířil na celou výšku obrazovky. Tento kód předpokládá, že rastrový obrázek je čtvercový, což je tento konkrétní rastrový obrázek. Rastrový obrázek se vykreslí pouze v oblasti definované cestou výřezu:
Cesta výřezu podléhá transformacím, pokud ClipPath
je volána metoda, a nikoli transformací, pokud se zobrazí grafický objekt (například rastrový obrázek). Cesta výřezu je součástí stavu plátna, který je uložen s metodou Save
a obnoven pomocí Restore
metody.
Kombinování cest výřezů
Přesněji řečeno, oblast výřezu není "nastavena" metodou ClipPath
. Místo toho se zkombinuje s existující cestou výřezu, která začíná jako obdélník se stejnou velikostí jako plátno. Obdélníkové hranice oblasti výřezu můžete získat pomocí LocalClipBounds
vlastnosti nebo DeviceClipBounds
vlastnosti. Vlastnost LocalClipBounds
vrátí SKRect
hodnotu, která odráží všechny transformace, které mohou být účinné. Vlastnost DeviceClipBounds
vrátí RectI
hodnotu. Toto je obdélník s celočíselnou dimenzí a popisuje oblast výřezu ve skutečných rozměrech pixelů.
Jakékoli volání ClipPath
pro zmenšení oblasti výřezu zkombinováním oblasti výřezu s novou oblastí. Úplná syntaxe ClipPath
metody, která kombinuje oblast výřezu s obdélníkem:
public Void ClipRect(SKRect rect, SKClipOperation operation = SKClipOperation.Intersect, Boolean antialias = false);
Výsledná oblast výřezu je ve výchozím nastavení průsečíkem existující oblasti výřezu SKPath
a oblastí, SKRect
která je zadána ClipPath
v metodě nebo ClipRect
metodě. To je znázorněno na stránce Čtyř kruhů protínající klip . Obslužná rutina PaintSurface
ve FourCircleInteresectClipPage
třídě znovu použije stejný SKPath
objekt k vytvoření čtyř překrývajících se kruhů, z nichž každá z nich zmenšuje oblast výřezu prostřednictvím následných volání ClipPath
:
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
SKImageInfo info = args.Info;
SKSurface surface = args.Surface;
SKCanvas canvas = surface.Canvas;
canvas.Clear();
float size = Math.Min(info.Width, info.Height);
float radius = 0.4f * size;
float offset = size / 2 - radius;
// Translate to center
canvas.Translate(info.Width / 2, info.Height / 2);
using (SKPath path = new SKPath())
{
path.AddCircle(-offset, -offset, radius);
canvas.ClipPath(path, SKClipOperation.Intersect);
path.Reset();
path.AddCircle(-offset, offset, radius);
canvas.ClipPath(path, SKClipOperation.Intersect);
path.Reset();
path.AddCircle(offset, -offset, radius);
canvas.ClipPath(path, SKClipOperation.Intersect);
path.Reset();
path.AddCircle(offset, offset, radius);
canvas.ClipPath(path, SKClipOperation.Intersect);
using (SKPaint paint = new SKPaint())
{
paint.Style = SKPaintStyle.Fill;
paint.Color = SKColors.Blue;
canvas.DrawPaint(paint);
}
}
}
Vlevo je průsečík těchto čtyř kruhů:
Výčet SKClipOperation
má pouze dva členy:
Difference
odebere zadanou cestu nebo obdélník z existující oblasti výřezu.Intersect
protíná zadanou cestu nebo obdélník s existující oblastí výřezu.
Pokud nahradíte čtyři SKClipOperation.Intersect
argumenty ve FourCircleIntersectClipPage
třídě SKClipOperation.Difference
, zobrazí se následující:
Z oblasti výřezu byly odebrány čtyři překrývající se kruhy.
Stránka Operace klipů znázorňuje rozdíl mezi těmito dvěma operacemi jenom párem kruhů. Do oblasti výřezu se přidá první kruh vlevo s výchozí operací klipartu Intersect
, zatímco druhý kruh vpravo se přidá do oblasti výřezu pomocí operace klipartu označené textovým popiskem:
Třída ClipOperationsPage
definuje dva SKPaint
objekty jako pole a pak rozdělí obrazovku nahoru do dvou obdélníkových oblastí. Tyto oblasti se liší v závislosti na tom, jestli je telefon v režimu na výšku nebo na šířku. Třída DisplayClipOp
pak zobrazí text a volání ClipPath
se dvěma kruhovými cestami k ilustraci každé operace klipu:
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
SKImageInfo info = args.Info;
SKSurface surface = args.Surface;
SKCanvas canvas = surface.Canvas;
canvas.Clear();
float x = 0;
float y = 0;
foreach (SKClipOperation clipOp in Enum.GetValues(typeof(SKClipOperation)))
{
// Portrait mode
if (info.Height > info.Width)
{
DisplayClipOp(canvas, new SKRect(x, y, x + info.Width, y + info.Height / 2), clipOp);
y += info.Height / 2;
}
// Landscape mode
else
{
DisplayClipOp(canvas, new SKRect(x, y, x + info.Width / 2, y + info.Height), clipOp);
x += info.Width / 2;
}
}
}
void DisplayClipOp(SKCanvas canvas, SKRect rect, SKClipOperation clipOp)
{
float textSize = textPaint.TextSize;
canvas.DrawText(clipOp.ToString(), rect.MidX, rect.Top + textSize, textPaint);
rect.Top += textSize;
float radius = 0.9f * Math.Min(rect.Width / 3, rect.Height / 2);
float xCenter = rect.MidX;
float yCenter = rect.MidY;
canvas.Save();
using (SKPath path1 = new SKPath())
{
path1.AddCircle(xCenter - radius / 2, yCenter, radius);
canvas.ClipPath(path1);
using (SKPath path2 = new SKPath())
{
path2.AddCircle(xCenter + radius / 2, yCenter, radius);
canvas.ClipPath(path2, clipOp);
canvas.DrawPaint(fillPaint);
}
}
canvas.Restore();
}
Volání DrawPaint
obvykle způsobí, že celé plátno bude vyplněno tímto SKPaint
objektem, ale v tomto případě metoda pouze maluje v oblasti výřezu.
Prozkoumání oblastí
Oblast výřezu můžete také definovat z hlediska objektu SKRegion
.
Nově vytvořený SKRegion
objekt popisuje prázdnou oblast. Prvním voláním objektu je SetRect
obvykle to, že oblast popisuje obdélníkovou oblast. Parametr SetRect
je SKRectI
hodnota – obdélník se souřadnicemi celého čísla, protože určuje obdélník z hlediska pixelů. Potom můžete volat SetPath
s objektem SKPath
. Tím se vytvoří oblast, která je stejná jako vnitřní část cesty, ale oříznutá na počáteční obdélníkovou oblast.
Oblast lze také upravit voláním jednoho z Op
přetížení metody, například tímto:
public Boolean Op(SKRegion region, SKRegionOperation op)
Výčet SKRegionOperation
se podobá SKClipOperation
výčtu, ale má více členů:
Difference
Intersect
Union
XOR
ReverseDifference
Replace
Oblast, na které voláte Op
, se zkombinuje s oblastí zadanou jako parametr na základě člena SKRegionOperation
. Když nakonec získáte oblast vhodná pro výřez, můžete ji nastavit jako oblast výřezu plátna pomocí ClipRegion
metody SKCanvas
:
public void ClipRegion(SKRegion region, SKClipOperation operation = SKClipOperation.Intersect)
Následující snímek obrazovky ukazuje výřez oblastí založených na šesti operacích oblasti. Levý kruh je oblast, na Op
které je metoda volána, a pravý kruh je oblast předaná Op
metodě:
Jsou tyto všechny možnosti kombinování těchto dvou kruhů? Představte si výslednou image jako kombinaci tří komponent, které jsou samy o sobě vidět v Difference
, Intersect
a ReverseDifference
operacích. Celkový početkombinacích Chybějící dvě oblasti jsou původní oblastí (což má za následek nevolání Op
vůbec) a úplně prázdnou oblast.
Při výřezu je obtížnější použít oblasti, protože je potřeba nejprve vytvořit cestu a pak oblast z této cesty a pak zkombinovat více oblastí. Celková struktura stránky Operace v oblasti je velmi podobná operacím klipů , ale RegionOperationsPage
třída rozdělí obrazovku nahoru do šesti oblastí a zobrazuje nadbytečnou práci potřebnou k použití oblastí pro tuto úlohu:
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
SKImageInfo info = args.Info;
SKSurface surface = args.Surface;
SKCanvas canvas = surface.Canvas;
canvas.Clear();
float x = 0;
float y = 0;
float width = info.Height > info.Width ? info.Width / 2 : info.Width / 3;
float height = info.Height > info.Width ? info.Height / 3 : info.Height / 2;
foreach (SKRegionOperation regionOp in Enum.GetValues(typeof(SKRegionOperation)))
{
DisplayClipOp(canvas, new SKRect(x, y, x + width, y + height), regionOp);
if ((x += width) >= info.Width)
{
x = 0;
y += height;
}
}
}
void DisplayClipOp(SKCanvas canvas, SKRect rect, SKRegionOperation regionOp)
{
float textSize = textPaint.TextSize;
canvas.DrawText(regionOp.ToString(), rect.MidX, rect.Top + textSize, textPaint);
rect.Top += textSize;
float radius = 0.9f * Math.Min(rect.Width / 3, rect.Height / 2);
float xCenter = rect.MidX;
float yCenter = rect.MidY;
SKRectI recti = new SKRectI((int)rect.Left, (int)rect.Top,
(int)rect.Right, (int)rect.Bottom);
using (SKRegion wholeRectRegion = new SKRegion())
{
wholeRectRegion.SetRect(recti);
using (SKRegion region1 = new SKRegion(wholeRectRegion))
using (SKRegion region2 = new SKRegion(wholeRectRegion))
{
using (SKPath path1 = new SKPath())
{
path1.AddCircle(xCenter - radius / 2, yCenter, radius);
region1.SetPath(path1);
}
using (SKPath path2 = new SKPath())
{
path2.AddCircle(xCenter + radius / 2, yCenter, radius);
region2.SetPath(path2);
}
region1.Op(region2, regionOp);
canvas.Save();
canvas.ClipRegion(region1);
canvas.DrawPaint(fillPaint);
canvas.Restore();
}
}
}
Tady je velký rozdíl mezi metodou ClipPath
a metodou ClipRegion
:
Důležité
ClipPath
Na rozdíl od metody ClipRegion
není metoda ovlivněna transformací.
Abyste pochopili odůvodnění tohoto rozdílu, je užitečné pochopit, co je oblast. Pokud jste přemýšleli o tom, jak se operace klipů nebo oblasti můžou implementovat interně, pravděpodobně se zdá být velmi složité. Zkombinuje se několik potenciálně složitých cest a osnova výsledné cesty je pravděpodobně algoritmickou noční můrou.
Tato úloha je značně zjednodušená, pokud je každá cesta omezena na řadu vodorovných skenových čar, jako jsou ty ve starých vakuových trubicích. Každá čára skenování je jednoduše vodorovná čára s počátečním bodem a koncovým bodem. Například kruh s poloměrem 10 pixelů lze rozdělit do 20 vodorovných čar skenování, z nichž každá začíná v levé části kruhu a končí v pravé části. Kombinace dvou kruhů s libovolnou operací oblasti se stává velmi jednoduchou, protože je to jednoduše otázka zkoumání počátečních a koncových souřadnic každé dvojice odpovídajících čar skenování.
To je oblast: Řada vodorovných čar skenování, které definují oblast.
Pokud se ale oblast zmenší na řadu skenovaných čar, jsou tyto čáry skenování založeny na konkrétní dimenzi pixelu. Přesněji řečeno, oblast není vektorový grafický objekt. Je v přírodě blíže komprimovanému monochromatickému rastru než k cestě. V důsledku toho nelze oblasti škálovat ani otáčet bez ztráty věrnosti, a proto se při použití pro výřezy neproměňují.
Transformace však můžete použít pro oblasti pro účely obrazu. Oblast Malování program živě ukazuje vnitřní povahu oblastí. Třída RegionPaintPage
vytvoří SKRegion
objekt založený na kruhu SKPath
poloměru 10 jednotek. Transformace pak tento kruh rozbalí, aby vyplnil stránku:
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
SKImageInfo info = args.Info;
SKSurface surface = args.Surface;
SKCanvas canvas = surface.Canvas;
canvas.Clear();
int radius = 10;
// Create circular path
using (SKPath circlePath = new SKPath())
{
circlePath.AddCircle(0, 0, radius);
// Create circular region
using (SKRegion circleRegion = new SKRegion())
{
circleRegion.SetRect(new SKRectI(-radius, -radius, radius, radius));
circleRegion.SetPath(circlePath);
// Set transform to move it to center and scale up
canvas.Translate(info.Width / 2, info.Height / 2);
canvas.Scale(Math.Min(info.Width / 2, info.Height / 2) / radius);
// Fill region
using (SKPaint fillPaint = new SKPaint())
{
fillPaint.Style = SKPaintStyle.Fill;
fillPaint.Color = SKColors.Orange;
canvas.DrawRegion(circleRegion, fillPaint);
}
// Stroke path for comparison
using (SKPaint strokePaint = new SKPaint())
{
strokePaint.Style = SKPaintStyle.Stroke;
strokePaint.Color = SKColors.Blue;
strokePaint.StrokeWidth = 0.1f;
canvas.DrawPath(circlePath, strokePaint);
}
}
}
}
Volání DrawRegion
vyplní oblast oranžovou barvou, zatímco DrawPath
volání pro porovnání tahá původní cestu modře:
Oblast je jasně řadou samostatných souřadnic.
Pokud v souvislosti s oblastmi výřezu nepotřebujete používat transformace, můžete k výřezu použít oblasti, jak ukazuje stránka Four-Leaf Clover . Třída FourLeafCloverPage
vytvoří složenou oblast ze čtyř kruhových oblastí, nastaví danou složenou oblast jako oblast výřezu a pak nakreslí řadu 360 rovných čar pocházejících ze středu stránky:
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
SKImageInfo info = args.Info;
SKSurface surface = args.Surface;
SKCanvas canvas = surface.Canvas;
canvas.Clear();
float xCenter = info.Width / 2;
float yCenter = info.Height / 2;
float radius = 0.24f * Math.Min(info.Width, info.Height);
using (SKRegion wholeScreenRegion = new SKRegion())
{
wholeScreenRegion.SetRect(new SKRectI(0, 0, info.Width, info.Height));
using (SKRegion leftRegion = new SKRegion(wholeScreenRegion))
using (SKRegion rightRegion = new SKRegion(wholeScreenRegion))
using (SKRegion topRegion = new SKRegion(wholeScreenRegion))
using (SKRegion bottomRegion = new SKRegion(wholeScreenRegion))
{
using (SKPath circlePath = new SKPath())
{
// Make basic circle path
circlePath.AddCircle(xCenter, yCenter, radius);
// Left leaf
circlePath.Transform(SKMatrix.MakeTranslation(-radius, 0));
leftRegion.SetPath(circlePath);
// Right leaf
circlePath.Transform(SKMatrix.MakeTranslation(2 * radius, 0));
rightRegion.SetPath(circlePath);
// Make union of right with left
leftRegion.Op(rightRegion, SKRegionOperation.Union);
// Top leaf
circlePath.Transform(SKMatrix.MakeTranslation(-radius, -radius));
topRegion.SetPath(circlePath);
// Combine with bottom leaf
circlePath.Transform(SKMatrix.MakeTranslation(0, 2 * radius));
bottomRegion.SetPath(circlePath);
// Make union of top with bottom
bottomRegion.Op(topRegion, SKRegionOperation.Union);
// Exclusive-OR left and right with top and bottom
leftRegion.Op(bottomRegion, SKRegionOperation.XOR);
// Set that as clip region
canvas.ClipRegion(leftRegion);
// Set transform for drawing lines from center
canvas.Translate(xCenter, yCenter);
// Draw 360 lines
for (double angle = 0; angle < 360; angle++)
{
float x = 2 * radius * (float)Math.Cos(Math.PI * angle / 180);
float y = 2 * radius * (float)Math.Sin(Math.PI * angle / 180);
using (SKPaint strokePaint = new SKPaint())
{
strokePaint.Color = SKColors.Green;
strokePaint.StrokeWidth = 2;
canvas.DrawLine(0, 0, x, y, strokePaint);
}
}
}
}
}
}
Ve skutečnosti nevypadá jako čtyřlistý clover, ale je to obrázek, který by jinak mohl být těžké vykreslit bez výřezu: