SkiaSharp 位图的分段显示

SkiaSharp SKCanvas 对象定义一个名为 DrawBitmapNinePatch 的方法,以及两个非常相似的名为 DrawBitmapLattice 的方法。 这两个方法都将位图渲染为目标矩形的大小,但它们不是均匀拉伸位图,而是以其像素大小显示位图的一部分,并拉伸位图的其他部分以使其适合矩形:

分段示例

这些方法通常用于渲染构成用户界面对象(例如按钮)一部分的位图。 设计按钮时,通常你希望按钮的大小基于按钮的内容,但可能希望按钮的边框宽度相同,而不管按钮的内容如何。 这是 DrawBitmapNinePatch 的理想应用方案。

DrawBitmapNinePatchDrawBitmapLattice 的一种特例,但它是两个方法中更容易使用和理解的一个。

九宫格显示

从概念上讲,DrawBitmapNinePatch 将位图划分为九个矩形:

Nine Patch

四个角处的矩形以其像素大小显示。 如箭头所示,位图边缘的其他区域将水平或垂直拉伸到目标矩形的区域。 中心的矩形同时朝水平和垂直方向拉伸。

如果目标矩形中没有足够的空间来显示其像素大小的四个角,则它们会缩小到可用大小,并且只显示四个角。

若要将位图划分为这九个矩形,只需指定位于中心的矩形即可。 这是 DrawBitmapNinePatch 方法的语法:

canvas.DrawBitmapNinePatch(bitmap, centerRectangle, destRectangle, paint);

中心矩形相对于位图。 它是一个 SKRectI 值(SKRect 的整数版本),所有坐标和大小均以像素为单位。 目标矩形相对于显示表面。 paint 参数是可选的。

示例中的“Nine Patch 显示”页首先使用静态构造函数创建类型为 SKBitmap 的公共静态属性:

public partial class NinePatchDisplayPage : ContentPage
{
    static NinePatchDisplayPage()
    {
        using (SKCanvas canvas = new SKCanvas(FiveByFiveBitmap))
        using (SKPaint paint = new SKPaint
        {
            Style = SKPaintStyle.Stroke,
            Color = SKColors.Red,
            StrokeWidth = 10
        })
        {
            for (int x = 50; x < 500; x += 100)
                for (int y = 50; y < 500; y += 100)
                {
                    canvas.DrawCircle(x, y, 40, paint);
                }
        }
    }

    public static SKBitmap FiveByFiveBitmap { get; } = new SKBitmap(500, 500);
    ···
}

本文中的其他两个页使用相同的位图。 位图是 500 像素的正方形,由 25 个圆的数组构成,所有圆大小相同,每个圆占据 100 像素的正方形区域:

圆形网格

程序的实例构造函数创建一个带有 PaintSurface 处理程序的 SKCanvasView,该处理程序使用 DrawBitmapNinePatch 显示拉伸到其整个显示表面的位图:

public class NinePatchDisplayPage : ContentPage
{
    ···
    public NinePatchDisplayPage()
    {
        Title = "Nine-Patch Display";

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

        SKRectI centerRect = new SKRectI(100, 100, 400, 400);
        canvas.DrawBitmapNinePatch(FiveByFiveBitmap, centerRect, info.Rect);
    }
}

centerRect 矩形包含 16 个圆的中心数组。 位于角处的圆以其像素大小显示,其他所有内容会相应地拉伸:

Nine-Patch 显示

UWP 页的宽度恰好为 500 像素,因此将顶行和底行显示为一系列相同大小的圆。 否则,所有不在角处的圆都会拉伸以形成椭圆。

如果由圆和椭圆的组合所构成的对象的显示比较奇怪,请尝试定义中心矩形,使其与圆的行和列重叠:

SKRectI centerRect = new SKRectI(150, 150, 350, 350);

格子显示

两个 DrawBitmapLattice 方法与 DrawBitmapNinePatch 类似,但它们已针对任意数量的水平或垂直区格进行通用化。 这些区格由与像素对应的整数数组定义。

具有这些整数数组参数的 DrawBitmapLattice 方法似乎不起作用。 具有类型为 SKLattice 的参数的 DrawBitmapLattice 方法确实有效,这就是下面所示示例中使用的方法。

SKLattice 结构定义四个属性:

  • XDivs,整数数组
  • YDivs,整数数组
  • FlagsSKLatticeFlags 的数组,枚举类型
  • Nullable<SKRectI> 类型的 Bounds,用于指定位图中的可选源矩形

XDivs 数组将位图的宽度划分为垂直条带。 第一个条带从左侧的像素 0 延伸到 XDivs[0]。 该条带以其像素宽度进行渲染。 第二个条带从 XDivs[0] 延伸到 XDivs[1],并被拉伸。 第三个条带从 XDivs[1] 延伸到 XDivs[2],并以其像素宽度进行渲染。 最后一个条带从数组的最后一个元素延伸到位图的右边缘。 如果数组有偶数个元素,则以其像素宽度显示。 否则将其拉伸。 垂直条带的总数比数组中的元素数量多 1 个。

YDivs 数组类似。 它将数组的高度划分为水平条带。

XDivsYDivs 数组一起将位图划分为矩形。 矩形数量等于水平条带数量和垂直条带数量的乘积。

根据 Skia 文档,Flags 数组包含每个矩形的一个元素,首先是矩形的顶行,然后是第二行,依此类推。 Flags 数组的类型为 SKLatticeFlags,它是一个具有以下成员的枚举:

  • Default,值为 0
  • Transparent,值为 1

但是,这些标志似乎并没有按预期工作,最好忽略它们。 但不要将 Flags 属性设置为 null。 请将其设置为足以包含矩形总数的 SKLatticeFlags 值数组即可。

“九宫格”页使用 DrawBitmapLattice 来模仿 DrawBitmapNinePatch。 它使用在 NinePatchDisplayPage 中创建的相同位图:

public class LatticeNinePatchPage : ContentPage
{
    SKBitmap bitmap = NinePatchDisplayPage.FiveByFiveBitmap;

    public LatticeNinePatchPage ()
    {
        Title = "Lattice Nine-Patch";

        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;

        SKLattice lattice = new SKLattice();
        lattice.XDivs = new int[] { 100, 400 };
        lattice.YDivs = new int[] { 100, 400 };
        lattice.Flags = new SKLatticeFlags[9];

        canvas.DrawBitmapLattice(bitmap, lattice, info.Rect);
    }
}

XDivsYDivs 属性均设置为仅包含两个整数的数组,将位图水平和垂直划分为三个条带:从像素 0 到像素 100(以像素大小渲染)、从像素 100 到像素 400(拉伸),从像素 400 到像素 500(像素大小)。 XDivsYDivs 一起定义了总共 9 个矩形,这是 Flags 数组的大小。 只需创建该数组就足以创建 SKLatticeFlags.Default 值的数组。

显示内容与之前的程序相同:

Lattice Nine-Patch

“格子显示”页将位图划分为 16 个矩形

public class LatticeDisplayPage : ContentPage
{
    SKBitmap bitmap = NinePatchDisplayPage.FiveByFiveBitmap;

    public LatticeDisplayPage()
    {
        Title = "Lattice Display";

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

        SKLattice lattice = new SKLattice();
        lattice.XDivs = new int[] { 100, 200, 400 };
        lattice.YDivs = new int[] { 100, 300, 400 };

        int count = (lattice.XDivs.Length + 1) * (lattice.YDivs.Length + 1);
        lattice.Flags = new SKLatticeFlags[count];

        canvas.DrawBitmapLattice(bitmap, lattice, info.Rect);
    }
}

XDivsYDivs 数组有些不同,导致显示内容不像以上示例那样对称:

Lattice 显示

在左侧的 iOS 和 Android 图像中,仅以像素大小渲染较小的圆。 其他所有内容都拉伸了。

“格子显示”页通用化了 Flags 数组的创建,使你可以更轻松地使用 XDivsYDivs 进行试验。 具体而言,需要了解将 XDivsYDivs 数组的第一个元素设置为 0 时会发生什么情况。