Oříznutí rastrových obrázků SkiaSharp
Článek Vytváření a kreslení SkiaSharp Bitmaps popisuje, jak SKBitmap
lze objekt předat konstruktoru SKCanvas
. Jakákoli metoda kreslení volaná na plátně způsobí vykreslení grafiky na rastrovém obrázku. Tyto metody kreslení zahrnují DrawBitmap
, což znamená, že tato technika umožňuje přenést část nebo všechny rastrové obrázky do jiného rastrového obrázku, například s použitými transformacemi.
Tuto techniku můžete použít k oříznutí bitmapy zavoláním DrawBitmap
metody se zdrojovými a cílovými obdélníky:
canvas.DrawBitmap(bitmap, sourceRect, destRect);
Aplikace, které implementují oříznutí, ale často poskytují rozhraní pro uživatele, aby interaktivně vybral obdélník oříznutí:
Tento článek se zaměřuje na toto rozhraní.
Zapouzdření obdélníku oříznutí
Je užitečné izolovat některé logiky oříznutí ve třídě s názvem CroppingRectangle
. Parametry konstruktoru obsahují maximální obdélník, což je obecně velikost rastrového obrázku oříznutého obrázku a volitelný poměr stran. Konstruktor nejprve definuje počáteční obdélník oříznutí, který zpřístupňuje ve Rect
vlastnosti typu SKRect
. Tento počáteční obdélník oříznutí je 80 % šířky a výšky rastrového obdélníku, ale je upraven, pokud je zadán poměr stran:
class CroppingRectangle
{
···
SKRect maxRect; // generally the size of the bitmap
float? aspectRatio;
public CroppingRectangle(SKRect maxRect, float? aspectRatio = null)
{
this.maxRect = maxRect;
this.aspectRatio = aspectRatio;
// Set initial cropping rectangle
Rect = new SKRect(0.9f * maxRect.Left + 0.1f * maxRect.Right,
0.9f * maxRect.Top + 0.1f * maxRect.Bottom,
0.1f * maxRect.Left + 0.9f * maxRect.Right,
0.1f * maxRect.Top + 0.9f * maxRect.Bottom);
// Adjust for aspect ratio
if (aspectRatio.HasValue)
{
SKRect rect = Rect;
float aspect = aspectRatio.Value;
if (rect.Width > aspect * rect.Height)
{
float width = aspect * rect.Height;
rect.Left = (maxRect.Width - width) / 2;
rect.Right = rect.Left + width;
}
else
{
float height = rect.Width / aspect;
rect.Top = (maxRect.Height - height) / 2;
rect.Bottom = rect.Top + height;
}
Rect = rect;
}
}
public SKRect Rect { set; get; }
···
}
Jednou z užitečných informací, které CroppingRectangle
jsou k dispozici, je matice SKPoint
hodnot odpovídající čtyřem rohům obdélníku oříznutí v pravém horním rohu, vpravo nahoře, vpravo dole a v levém dolním rohu:
class CroppingRectangle
{
···
public SKPoint[] Corners
{
get
{
return new SKPoint[]
{
new SKPoint(Rect.Left, Rect.Top),
new SKPoint(Rect.Right, Rect.Top),
new SKPoint(Rect.Right, Rect.Bottom),
new SKPoint(Rect.Left, Rect.Bottom)
};
}
}
···
}
Toto pole se používá v následující metodě, která se nazývá HitTest
. Parametr SKPoint
je bod odpovídající dotyku prstem nebo kliknutí myší. Metoda vrátí index (0, 1, 2 nebo 3) odpovídající rohu, na který se prst nebo ukazatel myši dotýkal, ve vzdálenosti zadané parametrem radius
:
class CroppingRectangle
{
···
public int HitTest(SKPoint point, float radius)
{
SKPoint[] corners = Corners;
for (int index = 0; index < corners.Length; index++)
{
SKPoint diff = point - corners[index];
if ((float)Math.Sqrt(diff.X * diff.X + diff.Y * diff.Y) < radius)
{
return index;
}
}
return -1;
}
···
}
Pokud touch nebo myš nebyl v radius
jednotkách žádného rohu, vrátí metoda –1.
Poslední metoda je CroppingRectangle
volána MoveCorner
, která je volána v reakci na pohyb dotyku nebo myši. Dva parametry označují index přesunu rohu a nové umístění tohoto rohu. První polovina metody upraví obdélník oříznutí na základě nového umístění rohu, ale vždy v mezích maxRect
, což je velikost rastrového obrázku. Tato logika také bere v úvahu MINIMUM
pole, aby se zabránilo sbalení obdélníku oříznutí do nic:
class CroppingRectangle
{
const float MINIMUM = 10; // pixels width or height
···
public void MoveCorner(int index, SKPoint point)
{
SKRect rect = Rect;
switch (index)
{
case 0: // upper-left
rect.Left = Math.Min(Math.Max(point.X, maxRect.Left), rect.Right - MINIMUM);
rect.Top = Math.Min(Math.Max(point.Y, maxRect.Top), rect.Bottom - MINIMUM);
break;
case 1: // upper-right
rect.Right = Math.Max(Math.Min(point.X, maxRect.Right), rect.Left + MINIMUM);
rect.Top = Math.Min(Math.Max(point.Y, maxRect.Top), rect.Bottom - MINIMUM);
break;
case 2: // lower-right
rect.Right = Math.Max(Math.Min(point.X, maxRect.Right), rect.Left + MINIMUM);
rect.Bottom = Math.Max(Math.Min(point.Y, maxRect.Bottom), rect.Top + MINIMUM);
break;
case 3: // lower-left
rect.Left = Math.Min(Math.Max(point.X, maxRect.Left), rect.Right - MINIMUM);
rect.Bottom = Math.Max(Math.Min(point.Y, maxRect.Bottom), rect.Top + MINIMUM);
break;
}
// Adjust for aspect ratio
if (aspectRatio.HasValue)
{
float aspect = aspectRatio.Value;
if (rect.Width > aspect * rect.Height)
{
float width = aspect * rect.Height;
switch (index)
{
case 0:
case 3: rect.Left = rect.Right - width; break;
case 1:
case 2: rect.Right = rect.Left + width; break;
}
}
else
{
float height = rect.Width / aspect;
switch (index)
{
case 0:
case 1: rect.Top = rect.Bottom - height; break;
case 2:
case 3: rect.Bottom = rect.Top + height; break;
}
}
}
Rect = rect;
}
}
Druhá polovina metody se přizpůsobí volitelnému poměru stran.
Mějte na paměti, že všechno v této třídě je v jednotkách pixelů.
Zobrazení plátna jen pro oříznutí
Třída CroppingRectangle
, kterou jste právě viděli, je používána PhotoCropperCanvasView
třídou, která je odvozena od SKCanvasView
. Tato třída zodpovídá za zobrazení rastrového obrázku a obdélníku oříznutí a také zpracování událostí dotykového ovládání nebo myši za změnu obdélníku oříznutí.
Konstruktor PhotoCropperCanvasView
vyžaduje rastrový obrázek. Poměr stran je volitelný. Konstruktor vytvoří instanci objektu typu CroppingRectangle
na základě tohoto rastrového obrázku a poměru stran a uloží ho jako pole:
class PhotoCropperCanvasView : SKCanvasView
{
···
SKBitmap bitmap;
CroppingRectangle croppingRect;
···
public PhotoCropperCanvasView(SKBitmap bitmap, float? aspectRatio = null)
{
this.bitmap = bitmap;
SKRect bitmapRect = new SKRect(0, 0, bitmap.Width, bitmap.Height);
croppingRect = new CroppingRectangle(bitmapRect, aspectRatio);
···
}
···
}
Vzhledem k tomu, že tato třída je odvozena od SKCanvasView
, nemusí instalovat obslužnou rutinu PaintSurface
události. Místo toho může přepsat svou OnPaintSurface
metodu. Metoda zobrazí rastrový obrázek a používá několik objektů uložených SKPaint
jako pole k vykreslení aktuálního obdélníku oříznutí:
class PhotoCropperCanvasView : SKCanvasView
{
const int CORNER = 50; // pixel length of cropper corner
···
SKBitmap bitmap;
CroppingRectangle croppingRect;
SKMatrix inverseBitmapMatrix;
···
// Drawing objects
SKPaint cornerStroke = new SKPaint
{
Style = SKPaintStyle.Stroke,
Color = SKColors.White,
StrokeWidth = 10
};
SKPaint edgeStroke = new SKPaint
{
Style = SKPaintStyle.Stroke,
Color = SKColors.White,
StrokeWidth = 2
};
···
protected override void OnPaintSurface(SKPaintSurfaceEventArgs args)
{
base.OnPaintSurface(args);
SKImageInfo info = args.Info;
SKSurface surface = args.Surface;
SKCanvas canvas = surface.Canvas;
canvas.Clear(SKColors.Gray);
// Calculate rectangle for displaying bitmap
float scale = Math.Min((float)info.Width / bitmap.Width, (float)info.Height / bitmap.Height);
float x = (info.Width - scale * bitmap.Width) / 2;
float y = (info.Height - scale * bitmap.Height) / 2;
SKRect bitmapRect = new SKRect(x, y, x + scale * bitmap.Width, y + scale * bitmap.Height);
canvas.DrawBitmap(bitmap, bitmapRect);
// Calculate a matrix transform for displaying the cropping rectangle
SKMatrix bitmapScaleMatrix = SKMatrix.MakeIdentity();
bitmapScaleMatrix.SetScaleTranslate(scale, scale, x, y);
// Display rectangle
SKRect scaledCropRect = bitmapScaleMatrix.MapRect(croppingRect.Rect);
canvas.DrawRect(scaledCropRect, edgeStroke);
// Display heavier corners
using (SKPath path = new SKPath())
{
path.MoveTo(scaledCropRect.Left, scaledCropRect.Top + CORNER);
path.LineTo(scaledCropRect.Left, scaledCropRect.Top);
path.LineTo(scaledCropRect.Left + CORNER, scaledCropRect.Top);
path.MoveTo(scaledCropRect.Right - CORNER, scaledCropRect.Top);
path.LineTo(scaledCropRect.Right, scaledCropRect.Top);
path.LineTo(scaledCropRect.Right, scaledCropRect.Top + CORNER);
path.MoveTo(scaledCropRect.Right, scaledCropRect.Bottom - CORNER);
path.LineTo(scaledCropRect.Right, scaledCropRect.Bottom);
path.LineTo(scaledCropRect.Right - CORNER, scaledCropRect.Bottom);
path.MoveTo(scaledCropRect.Left + CORNER, scaledCropRect.Bottom);
path.LineTo(scaledCropRect.Left, scaledCropRect.Bottom);
path.LineTo(scaledCropRect.Left, scaledCropRect.Bottom - CORNER);
canvas.DrawPath(path, cornerStroke);
}
// Invert the transform for touch tracking
bitmapScaleMatrix.TryInvert(out inverseBitmapMatrix);
}
···
}
Kód ve CroppingRectangle
třídě zakřižuje obdélník oříznutí na velikosti pixelu rastrového obrázku. Zobrazení rastrového obrázku podle PhotoCropperCanvasView
třídy se však škáluje na základě velikosti oblasti zobrazení. Počítané bitmapScaleMatrix
v OnPaintSurface
mapách přepsání z rastrových pixelů na velikost a pozici rastrového obrázku, jak je zobrazen. Tato matice se pak použije k transformaci obdélníku oříznutí, aby bylo možné jej zobrazit vzhledem k bitmapě.
Poslední řádek OnPaintSurface
přepsání převezme inverzní funkci bitmapScaleMatrix
a uloží ho jako inverseBitmapMatrix
pole. Používá se k dotykovému zpracování.
Objekt TouchEffect
se vytvoří instance jako pole a konstruktor připojí obslužnou rutinu TouchAction
k události, ale TouchEffect
je nutné ho přidat do Effects
kolekce nadřazeného objektu derivátu SKCanvasView
, aby to bylo provedeno v OnParentSet
přepsání:
class PhotoCropperCanvasView : SKCanvasView
{
···
const int RADIUS = 100; // pixel radius of touch hit-test
···
CroppingRectangle croppingRect;
SKMatrix inverseBitmapMatrix;
// Touch tracking
TouchEffect touchEffect = new TouchEffect();
struct TouchPoint
{
public int CornerIndex { set; get; }
public SKPoint Offset { set; get; }
}
Dictionary<long, TouchPoint> touchPoints = new Dictionary<long, TouchPoint>();
···
public PhotoCropperCanvasView(SKBitmap bitmap, float? aspectRatio = null)
{
···
touchEffect.TouchAction += OnTouchEffectTouchAction;
}
···
protected override void OnParentSet()
{
base.OnParentSet();
// Attach TouchEffect to parent view
Parent.Effects.Add(touchEffect);
}
···
void OnTouchEffectTouchAction(object sender, TouchActionEventArgs args)
{
SKPoint pixelLocation = ConvertToPixel(args.Location);
SKPoint bitmapLocation = inverseBitmapMatrix.MapPoint(pixelLocation);
switch (args.Type)
{
case TouchActionType.Pressed:
// Convert radius to bitmap/cropping scale
float radius = inverseBitmapMatrix.ScaleX * RADIUS;
// Find corner that the finger is touching
int cornerIndex = croppingRect.HitTest(bitmapLocation, radius);
if (cornerIndex != -1 && !touchPoints.ContainsKey(args.Id))
{
TouchPoint touchPoint = new TouchPoint
{
CornerIndex = cornerIndex,
Offset = bitmapLocation - croppingRect.Corners[cornerIndex]
};
touchPoints.Add(args.Id, touchPoint);
}
break;
case TouchActionType.Moved:
if (touchPoints.ContainsKey(args.Id))
{
TouchPoint touchPoint = touchPoints[args.Id];
croppingRect.MoveCorner(touchPoint.CornerIndex,
bitmapLocation - touchPoint.Offset);
InvalidateSurface();
}
break;
case TouchActionType.Released:
case TouchActionType.Cancelled:
if (touchPoints.ContainsKey(args.Id))
{
touchPoints.Remove(args.Id);
}
break;
}
}
SKPoint ConvertToPixel(Xamarin.Forms.Point pt)
{
return new SKPoint((float)(CanvasSize.Width * pt.X / Width),
(float)(CanvasSize.Height * pt.Y / Height));
}
}
Dotykové události zpracovávané obslužnou rutinou TouchAction
jsou v jednotkách nezávislých na zařízení. Nejprve je potřeba převést na pixely pomocí ConvertToPixel
metody v dolní části třídy a pak je převést na CroppingRectangle
jednotky pomocí inverseBitmapMatrix
.
U Pressed
událostí obslužná rutina TouchAction
volá metodu CroppingRectangle
HitTest
. Pokud se vrátí jiný index než -1, pak se pracuje s jedním z rohů obdélníku oříznutí. Tento index a posun skutečného dotykového bodu z rohu jsou uloženy v objektu TouchPoint
a přidány do slovníku touchPoints
.
Moved
U události MoveCorner
je volána metoda CroppingRectangle
přesunutí rohu s možnými úpravami poměru stran.
Program, který používá PhotoCropperCanvasView
tuto vlastnost, má kdykoli přístup k CroppedBitmap
vlastnosti. Tato vlastnost používá Rect
vlastnost objektu CroppingRectangle
k vytvoření nového rastrového obrázku oříznuté velikosti. Verze DrawBitmap
s cílovými a zdrojovými obdélníky pak extrahuje podmnožinu původního rastrového obrázku:
class PhotoCropperCanvasView : SKCanvasView
{
···
SKBitmap bitmap;
CroppingRectangle croppingRect;
···
public SKBitmap CroppedBitmap
{
get
{
SKRect cropRect = croppingRect.Rect;
SKBitmap croppedBitmap = new SKBitmap((int)cropRect.Width,
(int)cropRect.Height);
SKRect dest = new SKRect(0, 0, cropRect.Width, cropRect.Height);
SKRect source = new SKRect(cropRect.Left, cropRect.Top,
cropRect.Right, cropRect.Bottom);
using (SKCanvas canvas = new SKCanvas(croppedBitmap))
{
canvas.DrawBitmap(bitmap, source, dest);
}
return croppedBitmap;
}
}
···
}
Hostování zobrazení plátna pro oříznutí fotek
S těmito dvěma třídami, které zpracovávají logiku oříznutí, má stránka Oříznutí fotografie v aplikaci SkiaSharpFormsDemos velmi málo práce. Soubor XAML vytvoří Grid
instanci pro hostování PhotoCropperCanvasView
a tlačítko Hotovo :
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="SkiaSharpFormsDemos.Bitmaps.PhotoCroppingPage"
Title="Photo Cropping">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid x:Name="canvasViewHost"
Grid.Row="0"
BackgroundColor="Gray"
Padding="5" />
<Button Text="Done"
Grid.Row="1"
HorizontalOptions="Center"
Margin="5"
Clicked="OnDoneButtonClicked" />
</Grid>
</ContentPage>
V PhotoCropperCanvasView
souboru XAML nelze vytvořit instanci, protože vyžaduje parametr typu SKBitmap
.
Místo toho se vytvoří PhotoCropperCanvasView
instance v konstruktoru souboru kódu za použitím některého z rastrových obrázků prostředků:
public partial class PhotoCroppingPage : ContentPage
{
PhotoCropperCanvasView photoCropper;
SKBitmap croppedBitmap;
public PhotoCroppingPage ()
{
InitializeComponent ();
SKBitmap bitmap = BitmapExtensions.LoadBitmapResource(GetType(),
"SkiaSharpFormsDemos.Media.MountainClimbers.jpg");
photoCropper = new PhotoCropperCanvasView(bitmap);
canvasViewHost.Children.Add(photoCropper);
}
void OnDoneButtonClicked(object sender, EventArgs args)
{
croppedBitmap = photoCropper.CroppedBitmap;
SKCanvasView canvasView = new SKCanvasView();
canvasView.PaintSurface += OnCanvasViewPaintSurface;
Content = canvasView;
}
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
SKImageInfo info = args.Info;
SKSurface surface = args.Surface;
SKCanvas canvas = surface.Canvas;
canvas.Clear();
canvas.DrawBitmap(croppedBitmap, info.Rect, BitmapStretch.Uniform);
}
}
Uživatel pak může manipulovat s obdélníkem oříznutí:
Po definování dobrého obdélníku oříznutí klikněte na tlačítko Hotovo . Obslužná Clicked
rutina získá oříznutý rastrový obrázek z CroppedBitmap
vlastnosti PhotoCropperCanvasView
a nahradí veškerý obsah stránky novým SKCanvasView
objektem, který zobrazí tento oříznutý rastrový obrázek:
Zkuste nastavit druhý argument PhotoCropperCanvasView
1,78f (například):
photoCropper = new PhotoCropperCanvasView(bitmap, 1.78f);
Zobrazí se obdélník oříznutí omezený na poměr stran 16 až 9, který je charakteristické pro televizi s vysokým rozlišením.
Rozdělení rastrového obrázku na dlaždice
Verze Xamarin.Forms slavné 14-15 puzzle se objevila v kapitole 22 knihy Vytváření mobilních aplikací sXamarin.Formsa lze stáhnout jako XamagonXuzzle. Puzzle se ale stává zábavnější (a často náročnější), když je založen na obrázku z vlastní knihovny fotografií.
Tato verze 14-15 puzzle je součástí aplikace SkiaSharpFormsDemos a skládá se z řady stránek s názvem Photo Puzzle.
Soubor PhotoPuzzlePage1.xaml se skládá z Button
:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="SkiaSharpFormsDemos.Bitmaps.PhotoPuzzlePage1"
Title="Photo Puzzle">
<Button Text="Pick a photo from your library"
VerticalOptions="CenterAndExpand"
HorizontalOptions="CenterAndExpand"
Clicked="OnPickButtonClicked"/>
</ContentPage>
Soubor s kódem implementuje obslužnou rutinu Clicked
, která používá IPhotoLibrary
službu závislostí, aby uživatel vybral fotku z knihovny fotografií:
public partial class PhotoPuzzlePage1 : ContentPage
{
public PhotoPuzzlePage1 ()
{
InitializeComponent ();
}
async void OnPickButtonClicked(object sender, EventArgs args)
{
IPhotoLibrary photoLibrary = DependencyService.Get<IPhotoLibrary>();
using (Stream stream = await photoLibrary.PickPhotoAsync())
{
if (stream != null)
{
SKBitmap bitmap = SKBitmap.Decode(stream);
await Navigation.PushAsync(new PhotoPuzzlePage2(bitmap));
}
}
}
}
Metoda pak přejde na PhotoPuzzlePage2
, předat constuctor vybrané bitmapy.
Je možné, že fotka vybraná z knihovny není orientována tak, jak se zobrazuje v knihovně fotek, ale otočí se nebo vzhůru nohama. (Jedná se zejména o problém se zařízeními s iOSem.) Z tohoto důvodu PhotoPuzzlePage2
můžete obrázek otočit na požadovanou orientaci. Soubor XAML obsahuje tři tlačítka označená jako 90° vpravo (tj. ve směru hodinových ručiček), 90° vlevo (proti směru hodinových ručiček) a Hotovo.
Soubor s kódem implementuje logiku rastrového otočení znázorněnou v článku Vytváření a kreslení na skiaSharp Bitmaps. Uživatel může obrázek otočit o 90 stupňů po směru hodinových ručiček nebo proti směru hodinových ručiček libovolný počet:
public partial class PhotoPuzzlePage2 : ContentPage
{
SKBitmap bitmap;
public PhotoPuzzlePage2 (SKBitmap bitmap)
{
this.bitmap = bitmap;
InitializeComponent ();
}
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
SKImageInfo info = args.Info;
SKSurface surface = args.Surface;
SKCanvas canvas = surface.Canvas;
canvas.Clear();
canvas.DrawBitmap(bitmap, info.Rect, BitmapStretch.Uniform);
}
void OnRotateRightButtonClicked(object sender, EventArgs args)
{
SKBitmap rotatedBitmap = new SKBitmap(bitmap.Height, bitmap.Width);
using (SKCanvas canvas = new SKCanvas(rotatedBitmap))
{
canvas.Clear();
canvas.Translate(bitmap.Height, 0);
canvas.RotateDegrees(90);
canvas.DrawBitmap(bitmap, new SKPoint());
}
bitmap = rotatedBitmap;
canvasView.InvalidateSurface();
}
void OnRotateLeftButtonClicked(object sender, EventArgs args)
{
SKBitmap rotatedBitmap = new SKBitmap(bitmap.Height, bitmap.Width);
using (SKCanvas canvas = new SKCanvas(rotatedBitmap))
{
canvas.Clear();
canvas.Translate(0, bitmap.Width);
canvas.RotateDegrees(-90);
canvas.DrawBitmap(bitmap, new SKPoint());
}
bitmap = rotatedBitmap;
canvasView.InvalidateSurface();
}
async void OnDoneButtonClicked(object sender, EventArgs args)
{
await Navigation.PushAsync(new PhotoPuzzlePage3(bitmap));
}
}
Když uživatel klikne na tlačítko Hotovo , Clicked
obslužná rutina přejde na PhotoPuzzlePage3
, předá konečný otočený rastrový obrázek v konstruktoru stránky.
PhotoPuzzlePage3
umožňuje oříznout fotku. Program vyžaduje čtvercový rastrový obrázek, který se rozdělí na mřížku 4 po 4 dlaždicích.
Soubor PhotoPuzzlePage3.xaml obsahuje soubor , který Grid
hostuje Label
PhotoCropperCanvasView
a další tlačítko Hotovo:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="SkiaSharpFormsDemos.Bitmaps.PhotoPuzzlePage3"
Title="Photo Puzzle">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Label Text="Crop the photo to a square"
Grid.Row="0"
FontSize="Large"
HorizontalTextAlignment="Center"
Margin="5" />
<Grid x:Name="canvasViewHost"
Grid.Row="1"
BackgroundColor="Gray"
Padding="5" />
<Button Text="Done"
Grid.Row="2"
HorizontalOptions="Center"
Margin="5"
Clicked="OnDoneButtonClicked" />
</Grid>
</ContentPage>
Soubor kódu vytvoří instanci PhotoCropperCanvasView
rastrového obrázku předaného jeho konstruktoru. Všimněte si, že hodnota 1 se předává jako druhý argument PhotoCropperCanvasView
. Tento poměr stran 1 vynutí obdélník oříznutí jako čtverec:
public partial class PhotoPuzzlePage3 : ContentPage
{
PhotoCropperCanvasView photoCropper;
public PhotoPuzzlePage3(SKBitmap bitmap)
{
InitializeComponent ();
photoCropper = new PhotoCropperCanvasView(bitmap, 1f);
canvasViewHost.Children.Add(photoCropper);
}
async void OnDoneButtonClicked(object sender, EventArgs args)
{
SKBitmap croppedBitmap = photoCropper.CroppedBitmap;
int width = croppedBitmap.Width / 4;
int height = croppedBitmap.Height / 4;
ImageSource[] imgSources = new ImageSource[15];
for (int row = 0; row < 4; row++)
{
for (int col = 0; col < 4; col++)
{
// Skip the last one!
if (row == 3 && col == 3)
break;
// Create a bitmap 1/4 the width and height of the original
SKBitmap bitmap = new SKBitmap(width, height);
SKRect dest = new SKRect(0, 0, width, height);
SKRect source = new SKRect(col * width, row * height, (col + 1) * width, (row + 1) * height);
// Copy 1/16 of the original into that bitmap
using (SKCanvas canvas = new SKCanvas(bitmap))
{
canvas.DrawBitmap(croppedBitmap, source, dest);
}
imgSources[4 * row + col] = (SKBitmapImageSource)bitmap;
}
}
await Navigation.PushAsync(new PhotoPuzzlePage4(imgSources));
}
}
Obslužná rutina tlačítka Hotovo získá šířku a výšku oříznutého rastrového obrázku (tyto dvě hodnoty by měly být stejné) a pak ji rozdělí na 15 samostatných rastrových obrázků, z nichž každá je 1/4 šířky a výšky původního obrázku. (Poslední z možných 16 rastrových obrázků není vytvořen.) Metoda DrawBitmap
se zdrojovým a cílovým obdélníkem umožňuje vytvořit rastrový obrázek na základě podmnožina většího rastrového obrázku.
Převod na Xamarin.Forms rastrové obrázky
OnDoneButtonClicked
V metodě je pole vytvořené pro 15 rastrových obrázků typuImageSource
:
ImageSource[] imgSources = new ImageSource[15];
ImageSource
Xamarin.Forms je základní typ, který zapouzdřuje rastrový obrázek. Naštěstí SkiaSharp umožňuje převádět z rastrových obrázků SkiaSharp na Xamarin.Forms rastrové obrázky. Sestavení SkiaSharp.Views.Forms definuje SKBitmapImageSource
třídu, která je odvozena, ImageSource
ale lze ji vytvořit na základě objektu SkiaSharp SKBitmap
. SKBitmapImageSource
dokonce definuje převody mezi SKBitmapImageSource
objekty a SKBitmap
, a to je způsob, jakým SKBitmap
jsou objekty uloženy v poli jako Xamarin.Forms rastrové obrázky:
imgSources[4 * row + col] = (SKBitmapImageSource)bitmap;
Toto pole rastrových obrázků je předáno jako konstruktor do PhotoPuzzlePage4
. Tato stránka je zcela Xamarin.Forms a nepoužívá žádné SkiaSharp. Je velmi podobný XamagonXuzzle, takže se zde nebude popisovat, ale zobrazí vybranou fotku rozdělenou na 15 čtvercových dlaždic:
Stisknutím tlačítka Randomize se všechny dlaždice promíchají:
Teď je můžete vrátit do správného pořadí. Všechny dlaždice ve stejném řádku nebo sloupci jako prázdný čtverec je možné klepnout a přesunout je do prázdného čtverečku.