SkiaSharp bit eşlemlerini kırpma

SkiaSharp Bit Eşlemleri Oluşturma ve Çizme makalesinde, bir SKBitmap nesnenin bir oluşturucuya SKCanvas nasıl geçirilebileceği açıklanmıştır. Bu tuvalde çağrılan herhangi bir çizim yöntemi, bit eşlem üzerinde grafiklerin işlenmesine neden olur. Bu çizim yöntemleri içerir. DrawBitmapBu, bu tekniğin bir bit eşlem parçasının veya tamamının başka bir bit eşleme aktarılmasına izin verdiği anlamına gelir ve dönüşümler uygulanmış olabilir.

Yöntemini kaynak ve hedef dikdörtgenlerle çağırarak DrawBitmap bit eşlem kırpmak için bu tekniği kullanabilirsiniz:

canvas.DrawBitmap(bitmap, sourceRect, destRect);

Ancak kırpma uygulayan uygulamalar genellikle kullanıcının kırpma dikdörtgenini etkileşimli olarak seçmesi için bir arabirim sağlar:

Kırpma Örneği

Bu makale bu arabirime odaklanır.

Kırpma dikdörtgenini kapsülleme

adlı CroppingRectanglebir sınıftaki kırpma mantığının bazılarını yalıtmak yararlı olur. Oluşturucu parametreleri, genellikle kırpılmakta olan bit eşlem boyutunu ve isteğe bağlı en boy oranını içeren bir maksimum dikdörtgen içerir. Oluşturucu ilk olarak türünün SKRectözelliğinde Rect ortak hale getiren ilk kırpma dikdörtgenini tanımlar. Bu ilk kırpma dikdörtgeni bit eşlem dikdörtgeninin genişliğinin ve yüksekliğinin %80'idir, ancak en boy oranı belirtilirse ayarlanır:

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; }
    ···
}

Kullanılabilir hale getiren CroppingRectangle kullanışlı bilgilerden biri, kırpma dikdörtgeninin SKPoint dört köşesine karşılık gelen bir değer dizisidir ve sırasıyla sol üst, sağ üst, sağ alt ve sol alt:

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)
            };
        }
    }
    ···
}

Bu dizi, adlı HitTestaşağıdaki yöntemde kullanılır. SKPoint parametresi, parmakla dokunmaya veya fare tıklamasına karşılık gelen bir noktadır. yöntemi, parametre tarafından radius verilen uzaklık içinde, parmağın veya fare işaretçisinin dokunduğu köşeye karşılık gelen bir dizin (0, 1, 2 veya 3) döndürür:

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;
    }
    ···
}

Dokunma veya fare noktası herhangi bir köşenin birimleri içinde radius değilse, yöntem –1 döndürür.

içindeki CroppingRectangle MoveCornerson yöntem, dokunma veya fare hareketine yanıt olarak çağrılan olarak adlandırılır. İki parametre taşınmakta olan köşenin dizinini ve bu köşenin yeni konumunu gösterir. yöntemin ilk yarısı kırpma dikdörtgenini köşenin yeni konumuna göre ayarlar, ancak her zaman bit eşlem boyutu olan sınırları maxRectiçindedir. Bu mantık, kırpma dikdörtgeninin MINIMUM hiçbir şeye daraltılmasını önlemek için alanı da dikkate alır:

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;
    }
}

Yöntemin ikinci yarısı isteğe bağlı en boy oranına göre ayarlanır.

Bu sınıftaki her şeyin piksel cinsinden olduğunu unutmayın.

Yalnızca kırpma için tuval görünümü

CroppingRectangle Az önce gördüğünüz sınıf, öğesinden SKCanvasViewtüretilen sınıfı tarafından PhotoCropperCanvasView kullanılır. Bu sınıf bit eşlem ve kırpma dikdörtgenini görüntülemenin yanı sıra kırpma dikdörtgenini değiştirmek için dokunma veya fare olaylarını işlemekle sorumludur.

PhotoCropperCanvasView Oluşturucu bir bit eşlem gerektirir. En boy oranı isteğe bağlıdır. Oluşturucu, bu bit eşlem ve en boy oranına göre türdeki CroppingRectangle bir nesnenin örneğini oluşturur ve bunu alan olarak kaydeder:

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);
        ···
    }
    ···
}

Bu sınıf öğesinden SKCanvasViewtüretildiği için, olay için PaintSurface bir işleyici yüklemesi gerekmez. Bunun yerine yöntemini geçersiz kılabilir OnPaintSurface . yöntemi bit eşlemi görüntüler ve geçerli kırpma dikdörtgenini SKPaint çizmek için alan olarak kaydedilen birkaç nesne kullanır:

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);
    }
    ···
}

sınıfındaki CroppingRectangle kod, kırpma dikdörtgenini bit eşlem piksel boyutuna göre temel alır. Ancak, bit eşleminin sınıfı tarafından PhotoCropperCanvasView görüntülenmesi, görüntüleme alanının boyutuna göre ölçeklendirilir. Geçersiz bitmapScaleMatrix kılmada OnPaintSurface hesaplanan, bit eşlem piksellerinden, görüntülendiği şekilde bit eşlem piksellerinin boyutuna ve konumuna eşler. Bu matris daha sonra kırpma dikdörtgenini bit eşleme göre görüntülenecek şekilde dönüştürmek için kullanılır.

Geçersiz kılmanın OnPaintSurface son satırı, öğesinin tersini bitmapScaleMatrix alır ve alan olarak inverseBitmapMatrix kaydeder. Bu, dokunma işleme için kullanılır.

Bir TouchEffect nesne bir alan olarak örneği oluşturulur ve oluşturucu olaya bir işleyici TouchAction ekler, ancak TouchEffect türevin üst SKCanvasView öğesinin koleksiyonuna Effects eklenmesi gerekir, böylece geçersiz kılmada OnParentSet yapılır:

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));
    }
}

İşleyici tarafından işlenen dokunma olayları cihazdan TouchAction bağımsız birimlerdedir. Bunların önce sınıfının en altındaki yöntemi kullanılarak ConvertToPixel piksellere dönüştürülmesi ve ardından kullanılarak inverseBitmapMatrixbirimlere CroppingRectangle dönüştürülmesi gerekir.

İşleyici olaylar TouchAction için Pressed yöntemini CroppingRectangleçağırırHitTest. Bu işlem –1 dışında bir dizin döndürürse, kırpma dikdörtgeninin köşelerinden biri değiştirilir. Bu dizin ve gerçek dokunma noktasının köşeden uzaklığı bir TouchPoint nesnede depolanır ve sözlüğe eklenir touchPoints .

Olay için Moved , MoveCorner en boy oranı için olası ayarlamalarla köşeyi taşımak için yöntemi CroppingRectangle çağrılır.

herhangi bir zamanda, kullanan PhotoCropperCanvasView bir program özelliğine CroppedBitmap erişebilir. Bu özellik kırpılan boyutta yeni bir bit eşlem oluşturmak için özelliğini CroppingRectangle kullanırRect. Hedef ve kaynak dikdörtgenlerle sürümü DrawBitmap , özgün bit eşleminin bir alt kümesini ayıklar:

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;
        }
    }
    ···
}

Fotoğraf kırpıcı tuval görünümünü barındırma

Bu iki sınıf kırpma mantığını işlerken, örnek uygulamadaki Fotoğraf Kırpma sayfasının yapacak çok az işi vardır. XAML dosyası, ve Bitti düğmesini barındırmak PhotoCropperCanvasView için bir örneği Grid oluşturur:

<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>

PhotoCropperCanvasView türünde SKBitmapbir parametre gerektirdiğinden XAML dosyasında örneği oluşturulamıyor.

Bunun yerine, PhotoCropperCanvasView kaynak bit eşlemlerinden biri kullanılarak arka planda kod dosyasının oluşturucusunda örneği oluşturulur:

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);
    }
}

Kullanıcı daha sonra kırpma dikdörtgenini işleyebilir:

Fotoğraf Kırpıcı 1

İyi bir kırpma dikdörtgeni tanımlandığında Bitti düğmesine tıklayın. İşleyici Clicked kırpılan bit eşlemi özelliğinden CroppedBitmap PhotoCropperCanvasViewalır ve sayfanın tüm içeriğini bu kırpılmış bit eşlemi görüntüleyen yeni SKCanvasView bir nesneyle değiştirir:

Fotoğraf Kırpıcı 2

İkinci bağımsız değişkenini PhotoCropperCanvasView 1,78f olarak ayarlamayı deneyin (örneğin):

photoCropper = new PhotoCropperCanvasView(bitmap, 1.78f);

Kırpma dikdörtgeninin yüksek tanımlı televizyonun 16-9 en boy oranı özelliğiyle sınırlı olduğunu göreceksiniz.

Bit eşlemi kutucuklara bölme

Ünlü 14-15 bulmacanın bir Xamarin.Forms sürümü ile Xamarin.FormsMobil Uygulama Oluşturma kitabının 22. Bölümünde ortaya çıktı ve XamagonXuzzle olarak indirilebilir. Ancak, bulmaca kendi fotoğraf kitaplığınızdan bir görüntüye dayandığında daha eğlenceli (ve genellikle daha zorlayıcı) hale gelir.

14-15 bulmacanın bu sürümü örnek uygulamanın bir parçasıdır ve Photo Puzzle adlı bir dizi sayfadan oluşur.

PhotoPuzzlePage1.xaml dosyası bir Buttonoluşur:

<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>

Arka planda kod dosyası, kullanıcının fotoğraf kitaplığından bir Clicked fotoğraf seçmesine izin vermek için bağımlılık hizmetini kullanan IPhotoLibrary bir işleyici uygular:

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));
            }
        }
    }
}

Yöntemi daha sonra öğesine gider PhotoPuzzlePage2ve seçili bit eşlemciyi constuctor'a geçirir.

Kitaplıktan seçilen fotoğraf, fotoğraf kitaplığında göründüğü gibi yönlendirilmemiş olabilir, ancak döndürülmüş veya baş aşağı döndürülmüş olabilir. (Bu özellikle iOS cihazlarıyla ilgili bir sorundur.) Bu nedenle, PhotoPuzzlePage2 görüntüyü istediğiniz yönde döndürmenize olanak tanır. XAML dosyası 90° Sağ (saat yönünde anlamına gelir), 90° Sol (saat yönünün tersine) ve Bitti etiketli üç düğme içerir.

Arka planda kod dosyası, SkiaSharp Bit Eşlemlerinde Oluşturma ve Çizme makalesinde gösterilen bit eşlem döndürme mantığını uygular. Kullanıcı görüntüyü herhangi bir sayıda saat yönünde veya saat yönünün tersine 90 derece döndürebilir:

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));
    }
}

Kullanıcı Bitti düğmesine tıkladığında, Clicked işleyici sayfasına gider PhotoPuzzlePage3ve sayfanın oluşturucusunda son döndürülmüş bit eşlemi geçirir.

PhotoPuzzlePage3 fotoğrafın kırpılmasına izin verir. Program, 4'e 4 kutucuk kılavuzuna bölmek için kare bit eşlem gerektirir.

PhotoPuzzlePage3.xaml dosyası, öğesini barındırmak PhotoCropperCanvasViewiçin bir Grid Label, ve başka bir Bitti düğmesi içerir:

<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>

Arka planda kod dosyası, oluşturucusunun PhotoCropperCanvasView bit eşlemi geçirilirken örneğini oluşturur. İkinci bağımsız değişken olarak 1'in geçirildiğini fark edin PhotoCropperCanvasView. 1'in bu en boy oranı, kırpma dikdörtgenini kareye zorlar:

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));
    }
}

Bitti düğmesi işleyicisi kırpılan bit eşlem genişliğini ve yüksekliğini alır (bu iki değer aynı olmalıdır) ve ardından her biri özgün bit eşlem genişliği ve yüksekliği 1/4 olan 15 ayrı bit eşlem içine böler. (Olası 16 bit eşlemlerin sonuncusu oluşturulmaz.) DrawBitmap Kaynak ve hedef dikdörtgen içeren yöntem, daha büyük bir bit eşlem alt kümesine göre bir bit eşlem oluşturulmasını sağlar.

Bit eşlemlere Xamarin.Forms dönüştürme

yönteminde OnDoneButtonClicked , 15 bit eşlem için oluşturulan dizi türündedir ImageSource:

ImageSource[] imgSources = new ImageSource[15];

ImageSourceXamarin.Forms bit eşlemi kapsülleyen temel türdür. Neyse ki SkiaSharp, SkiaSharp bit eşlemlerinden bit eşlemlere dönüştürmeye Xamarin.Forms izin verir. SkiaSharp.Views.Forms derlemesi, bir SkiaSharp SKBitmap nesnesine göre türetilen ImageSource ancak oluşturulabilen bir sınıf tanımlarSKBitmapImageSource. SKBitmapImageSource ve arasındaki SKBitmapImageSource SKBitmapdönüştürmeleri tanımlar ve nesneler dizide bit eşlem olarak bu şekilde SKBitmap Xamarin.Forms depolanır:

imgSources[4 * row + col] = (SKBitmapImageSource)bitmap;

Bu bit eşlem dizisi öğesine bir oluşturucu PhotoPuzzlePage4olarak geçirilir. Bu sayfa tamamen Xamarin.Forms ve skiaSharp kullanmaz. XamagonXuzzle'a çok benzer, bu nedenle burada açıklanamaz, ancak seçtiğiniz fotoğrafı 15 kare kutucuk olarak görüntüler:

Fotoğraf Bulmaca 1

Rastgele seç düğmesine basıldığında tüm kutucuklar karıştırılır:

Fotoğraf Bulmaca 2

Artık bunları doğru sıraya koyabilirsiniz. Boş kareyle aynı satır veya sütundaki kutucuklar, boş kareye taşımak için dokunabilir.