【SkiaSharp绘图09】SKBitmap属性详解

SKBitmap

  • 光栅位图,整数的宽度、高度,格式(颜色类型)以及指向实际像素的指针。
  • SkBitmap 构建于 SkImageInfo 之上,包含整数宽度和高度、描述像素格式的 SkColorType 和 SkAlphaType 以及描述颜色范围的 SkColorSpace。 SkBitmap指向SkPixelRef,它描述了像素的物理数组。 SkImageInfo 边界可以位于完全位于 SkPixelRef 边界内的任何位置。
  • SkBitmap可以使用SkCanvas来绘制。 SkBitmap 可以是 SkCanvas 绘制成员函数的绘制目标。 SkBitmap 作为像素容器的灵活性限制了目标平台可用的一些优化。
  • 如果像素数组主要是只读的,请使用 SkImage 以获得更好的性能。如果主要写入像素数组,请使用 SkSurface 以获得更好的性能。
  • SkBitmap 不是线程安全的。尽管线程可以共享底层像素数组,但每个线程都必须拥有自己的 SkBitmap 字段副本。
  • 使用GetPixels()获取位图的像素地址。(注意,1.60.0版本之后不再需要调用LockPixels和UnlockPixels方法)。

与Bitmap性能对比

分别测试,单像素GetPixel与指针访问的性能。

  1. 分别加载SKBitmap和Bitmap图像
var canvas = e.Surface.Canvas;
var info = e.Info;
canvas.Clear(SKColors.White);

if (skBmp == null)
{
    skBmp = SKBitmap.Decode(@"Images\AIWoman.png");
}
if (bitmap == null)
{
    bitmap = new System.Drawing.Bitmap(@"Images\AIWoman.png");
}
  1. 逐像素分别访问SKBitmap与Bitmap对象
#region GetPixel对比
long sumR = 0;
long sumG = 0;
long sumB = 0;

var sw = Stopwatch.StartNew();
for (var row = 0; row < skBmp.Height; row++)
{
    for (var col = 0; col < skBmp.Width; col++)
    {
        var color = skBmp.GetPixel(col, row);
        sumR += color.Red;
        sumG += color.Green;
        sumB += color.Blue;
    }
}
sw.Stop();

long totalR = 0;
long totalG = 0;
long totalB = 0;

var sw1 = Stopwatch.StartNew();

for (var row = 0; row < bitmap.Height; row++) 
{
    for (var col = 0; col < bitmap.Width; col++)
    {
        var color = bitmap.GetPixel(col, row);
        totalR += color.R;
        totalG += color.G;
        totalB += color.B;
    }
}
sw1.Stop();
#endregion
  1. 分别显示耗时
var xOffset = 20F;
var yOffset = 50F;
var paint = new SKPaint();
paint.IsAntialias = true;
paint.TextSize = 24;
paint.Typeface = SKTypeface.FromFamilyName("微软雅黑");
canvas.DrawText($"图像大小:{skBmp.Width}x{skBmp.Height}", xOffset, yOffset, paint);
yOffset += paint.FontSpacing;
canvas.DrawText("GetPixel效率对比:", xOffset, yOffset, paint);
yOffset += paint.FontSpacing;
canvas.DrawText($"SKBitmap GetPixel耗时:{sw.ElapsedMilliseconds}ms,R:{sumR},G:{sumG},B:{sumB}", xOffset, yOffset, paint);
yOffset += paint.FontSpacing;
canvas.DrawText($"Bitmap GetPixel耗时:{sw1.ElapsedMilliseconds}ms,R:{totalR},G:{totalG},B:{totalB}", xOffset, yOffset, paint);
yOffset += paint.FontSpacing;
  1. 使用指针方式逐像素访问
#region  指针访问

sumR = 0;
sumG = 0;
sumB = 0;

sw = Stopwatch.StartNew();
// 获取指向像素数据的指针
IntPtr pixelsPtr = skBmp.GetPixels();
int width = bitmap.Width;
int height = bitmap.Height;
int bytesPerPixel = skBmp.Info.BytesPerPixel;
// 使用不安全代码块访问指针数据
unsafe
{
    byte* ptrP = (byte*)pixelsPtr.ToPointer();
    for (int y = 0; y < height; y++)
    {
        var offset = y * width;
        for (int x = 0; x < width; x++)
        {
            // 计算当前像素的指针位置
            byte* pixel = ptrP + (offset + x) * bytesPerPixel;

            // 累加各通道的值
            sumB += pixel[0];   // 蓝色通道
            sumG += pixel[1];  // 绿色通道
            sumR += pixel[2];    // 红色通道
        }
    }
}
sw.Stop();

sw1 = Stopwatch.StartNew();
// 定义锁定区域
Rectangle rect = new Rectangle(0, 0, bitmap.Width, bitmap.Height);
// 锁定位图的指定区域
BitmapData bmpData = bitmap.LockBits(rect, ImageLockMode.ReadOnly, bitmap.PixelFormat);

// 获取指向第一个像素数据的指针
IntPtr ptr = bmpData.Scan0;

// 判断像素格式,以确保正确处理像素数据
int pixelSize = Image.GetPixelFormatSize(bitmap.PixelFormat) / 8;

totalB = 0;
totalG = 0;
totalR = 0;

// 使用不安全代码块访问指针数据
unsafe
{
    byte* ptrP = (byte*)ptr.ToPointer();
    for (int y = 0; y < height; y++)
    {
        var offset = y * width;
        for (int x = 0; x < width; x++)
        {
            // 计算当前像素的指针位置
            byte* pixel = ptrP + (offset + x) * bytesPerPixel;

            // 累加各通道的值
            totalB+= pixel[0];   // 蓝色通道
            totalG+= pixel[1];  // 绿色通道
            totalR += pixel[2];    // 红色通道
        }
    }
}            
// 解锁位图
bitmap.UnlockBits(bmpData);
sw.Stop();
#endregion
  1. 分别显示耗时
canvas.DrawText("指针访问 对比:", xOffset, yOffset, paint);
yOffset += paint.FontSpacing;
canvas.DrawText($"SKBitmap 指针访问耗时:{sw.ElapsedMilliseconds}ms,R:{sumR},G:{sumG},B:{sumB}", xOffset, yOffset, paint);
yOffset += paint.FontSpacing;
canvas.DrawText($"Bitmap 指针访问耗时:{sw1.ElapsedMilliseconds}ms,R:{totalR},G:{totalG},B:{totalB}", xOffset, yOffset, paint);
yOffset += paint.FontSpacing;

canvas.DrawBitmap(skBmp,new SKRect(xOffset,yOffset,xOffset+400,yOffset+400), paint);
paint.Dispose();

效率对比

对比结果

SKBitmap的GetPixel对Bitmap的GetPixel快3倍左右,两个对象的指针访问方式性能一样,比GetPixel方法快几十、上百倍。

构造函数

SKBitmap()

public SKBitmap ();

构造一个0x0的图像,ColorType为Unknown,未分配内存。

SKBitmap(SKImageInfo)

public SKBitmap (SkiaSharp.SKImageInfo info);

根据指定的SKImageInfo构造位图对象。

var canvas = e.Surface.Canvas;
var info = e.Info;
canvas.Clear(SKColors.White);

using (var paint = new SKPaint())
{
    paint.IsAntialias = true;
    paint.TextSize = 18F;
    paint.Typeface = SKTypeface.FromFamilyName("微软雅黑");                

    var colorTypes = new SKColorType[] { SKColorType.Rgba8888, SKColorType.Gray8 };

    foreach(var colorType in colorTypes)
    {
        paint.IsStroke = true;
        var skImgInfo = new SKImageInfo(200, 100, colorType);

        var pt = new SKPoint(20F, 20F);
        using (var sBmp = new SKBitmap(skImgInfo))
        {
            canvas.DrawBitmap(sBmp, pt, paint);
            canvas.DrawRect(new SKRect(pt.X, pt.Y, pt.X + sBmp.Width, pt.Y + sBmp.Height), paint);

            paint.IsStroke = false;
            var yOffset = 20 + sBmp.Height + paint.FontSpacing;

            canvas.DrawText($"SKImageInfo的属性与值", 20, yOffset, paint);
            // 获取对象的类型信息
            Type type = skImgInfo.GetType();

            // 获取所有公共属性
            PropertyInfo[] properties = type.GetProperties();

            // 遍历并打印每个属性的名称和值
            foreach (PropertyInfo property in properties)
            {
                yOffset += paint.FontSpacing;
                string name = property.Name;
                object value = property.GetValue(skImgInfo);
                if (value == null) value = "null";
                canvas.DrawText($"{name}:{value}", 20, yOffset, paint);
            }
        }

        canvas.Translate(400, 0);
    }                
}

分别以SKColorType.Rgba8888和 SKColorType.Gray8创建两幅图像,查看其SKImageInfo各属性的值。
SKImageInfo值

SKBitmap(Int32, Int32, SKColorType, SKAlphaType, SKColorSpace)

public SKBitmap (int width, int height, SkiaSharp.SKColorType colorType, SkiaSharp.SKAlphaType alphaType, SkiaSharp.SKColorSpace colorspace);

创建一个SKBitmap,在SKBitmap上绘制一个圆,再将这个SKBitmap绘制到界面上。

var canvas = e.Surface.Canvas;
var info = e.Info;
canvas.Clear(SKColors.White);

using (var paint = new SKPaint())
{
    paint.IsAntialias = true;
    paint.TextSize = 18F;
    paint.Typeface = SKTypeface.FromFamilyName("微软雅黑");

    // 定义位图的宽度和高度
    int width = 600;
    int height = 600;

    // 定义颜色类型、透明度类型和颜色空间
    SKColorType colorType = SKColorType.Rgba8888;
    SKAlphaType alphaType = SKAlphaType.Premul;
    SKColorSpace colorSpace = SKColorSpace.CreateSrgb();
    using (var skBmp = new SKBitmap(width, height, colorType, alphaType, colorSpace))
    {
        using (var sCanvas = new SKCanvas(skBmp))
        {
            paint.Color = SKColors.LightGreen;
            sCanvas.DrawCircle(skBmp.Width / 2, skBmp.Height / 2, skBmp.Width / 4, paint);
        }
        canvas.Translate(100, 100);
        canvas.DrawBitmap(skBmp, 0, 0, paint);
        paint.IsStroke = true;
        canvas.DrawRect(skBmp.Info.Rect, paint);
    }                   
}

SKBitmap

SKBitmap属性

AlphaType

public SkiaSharp.SKAlphaType AlphaType { get; }

定义了位图像素的透明度处理方式。透明度(或Alpha通道)在图形处理和图像合成中起着关键作用,因为它决定了像素的透明度或不透明度。
不同枚举值的意义:

  1. Unknown:

    • 描述:Alpha 通道的类型未知。这通常用于未明确指定 alpha 类型的图像。
    • 应用场景:较少使用,因为不明确的 alpha 类型会导致不确定的渲染结果。
  2. Opaque:

    • 描述:图像是完全不透明的,没有透明度信息。Alpha 通道的值被忽略。
    • 应用场景:用于不需要处理透明度的图像,提高渲染效率。
  3. Premul (Pre-multiplied alpha):

    • 描述:RGB 值已经被 alpha 通道值预乘,即每个颜色分量已经乘以其对应的 alpha 值。例如,如果 alpha 为 0.5(半透明),则红色分量被乘以 0.5。
    • 应用场景:常用于图像合成,因为这种方式可以简化混合操作,减少计算开销。
  4. Unpremul (Unpremultiplied alpha):

    • 描述:RGB 值没有被 alpha 通道值预乘。颜色和 alpha 是独立存储的。
    • 应用场景:在图像编辑和处理时使用,因为它保留了原始颜色值,适合需要精细调整颜色和透明度的操作。
var canvas = e.Surface.Canvas;
var info = e.Info;
canvas.Clear(SKColors.White);

using (var paint = new SKPaint())
{
    paint.IsAntialias = true;
    paint.TextSize = 18F;
    paint.Typeface = SKTypeface.FromFamilyName("微软雅黑");

    // 定义位图的宽度和高度
    int width = 150;
    int height = 150;

    // 定义颜色类型、透明度类型和颜色空间
    SKColorType colorType = SKColorType.Rgba8888;

    var skColors = new SKColor[] { new SKColor(255, 0, 0, 128), new SKColor(0, 255, 0, 128), new SKColor(0, 0, 255, 128) };
    var skAlphaTypes = new SKAlphaType[] { SKAlphaType.Opaque, SKAlphaType.Premul, SKAlphaType.Unpremul };

    var pts = new SKPoint[] { new SKPoint(100, 30), new SKPoint(200, 30), new SKPoint(150, 90) };

    var bmps = new List<SKBitmap>();

    foreach (var alphaType in skAlphaTypes)
    {
        var index = 0;
        var y = 40F;
        canvas.DrawText($"AlphaType:{alphaType}", 400, y += paint.FontSpacing, paint);
        foreach (var skColor in skColors)
        {
            using (var skBmp = new SKBitmap(width, height, colorType, alphaType))
            {
                using (var sCanvas = new SKCanvas(skBmp))
                {
                    paint.Color = skColor;
                    sCanvas.DrawCircle(skBmp.Width / 2, skBmp.Height / 2, skBmp.Width / 2, paint);
                }
                canvas.DrawBitmap(skBmp, pts[index++], paint);

                var color = skBmp.GetPixel(width / 2, height / 2);

                canvas.DrawText($"R:{color.Red},G:{color.Green},B:{color.Blue},A:{color.Alpha}", 400, y += paint.FontSpacing, paint);
            }
        }
        canvas.Translate(0, 240);
    }
}

AlphaType

ByteCount

public int ByteCount { get; }

根据RowBytes*Height,返回像素的字节大小。

Bytes

public byte[] Bytes { get; }

获取所有像素数据的副本。

BytesPerPixel

public int BytesPerPixel { get; }

获取每个像素的字节数。如果ColorType为Unknown,则该值为0。

ColorSpace

public SkiaSharp.SKColorSpace ColorSpace { get; }

获取和设置位图的色彩空间。

ColorType

public SkiaSharp.SKColorType ColorType { get; }

获取位图的颜色类型。

DrawsNothing

public bool DrawsNothing { get; }

判断位图是否有影响绘制效果。无效果,返回true,若则返回false。

Info

public SkiaSharp.SKImageInfo Info { get; }

获取具有位图所有属性的 SKImageInfo 实例。

IsEmpty

public bool IsEmpty { get; }

判断位图的尺寸是否为0。
DrawsNothing除了判断尺寸,还判断其它,而IsEmpty只判断尺寸。

IsImmutable

public bool IsImmutable { get; }

判断位图内容是否不可变。
不可变的位图性能更优,可通过SKBitmap对的的SetImmutable()方法设置。

IsNull

public bool IsNull { get; }

直接new SKBitmap()返回true,new SKBitmap(0,0)返回的是false。

Pixels

public SkiaSharp.SKColor[] Pixels { get; set; }

返回所有像素的颜色数组。

ReadyToDraw

public bool ReadyToDraw { get; }

获取位图是否可以有效绘制。

RowBytes

public int RowBytes { get; }

返回每行的字节数。

Width、Height

public int Width { get; }
public int Height { get; }

获取位图的宽与高。

示例

 var canvas = e.Surface.Canvas;
 var info = e.Info;
 canvas.Clear(SKColors.White);

 using (var paint = new SKPaint())
 {
     paint.IsAntialias = true;
     paint.TextSize = 18F;
     paint.Typeface = SKTypeface.FromFamilyName("微软雅黑");

     var yOffset = 50F;

     using (var skBmp0x0 = new SKBitmap(0, 0))
     using (var skBmp1x1 = new SKBitmap(1, 1))
     {
         
         canvas.DrawText($"0x0SKBitmap,DrawsNothing:{skBmp0x0.DrawsNothing}", 20, yOffset += paint.FontSpacing, paint);
         canvas.DrawText($"1x1SKBitmap,DrawsNothing:{skBmp1x1.DrawsNothing}", 20, yOffset += paint.FontSpacing, paint);

         canvas.DrawText($"0x0SKBitmap,IsEmpty:{skBmp0x0.IsEmpty}", 20, yOffset += paint.FontSpacing, paint);
         canvas.DrawText($"1x1SKBitmap,IsEmpty:{skBmp1x1.IsEmpty}", 20, yOffset += paint.FontSpacing, paint);

         canvas.DrawText($"0x0SKBitmap,IsNull:{skBmp0x0.IsNull}", 20, yOffset += paint.FontSpacing, paint);
         canvas.DrawText($"1x1SKBitmap,IsNull:{skBmp1x1.IsNull}", 20, yOffset += paint.FontSpacing, paint);

         canvas.DrawText($"0x0SKBitmap,ByteCount:{skBmp0x0.ByteCount}", 20, yOffset += paint.FontSpacing, paint);
         canvas.DrawText($"1x1SKBitmap,ByteCount:{skBmp1x1.ByteCount}", 20, yOffset += paint.FontSpacing, paint);

         canvas.DrawText($"0x0SKBitmap,BytesPerPixel:{skBmp0x0.BytesPerPixel}", 20, yOffset += paint.FontSpacing, paint);
         canvas.DrawText($"1x1SKBitmap,BytesPerPixel:{skBmp1x1.BytesPerPixel}", 20, yOffset += paint.FontSpacing, paint);

         canvas.DrawText($"0x0SKBitmap,IsImmutable:{skBmp0x0.IsImmutable}", 20, yOffset += paint.FontSpacing, paint);
         canvas.DrawText($"1x1SKBitmap,IsImmutable:{skBmp1x1.IsImmutable}", 20, yOffset += paint.FontSpacing, paint);
         skBmp1x1.SetImmutable();
         canvas.DrawText($"1x1SKBitmap,IsImmutable:{skBmp1x1.IsImmutable}", 20, yOffset += paint.FontSpacing, paint);

         canvas.DrawText($"0x0SKBitmap,RowBytes:{skBmp0x0.RowBytes}", 20, yOffset += paint.FontSpacing, paint);
         canvas.DrawText($"1x1SKBitmap,RowBytes:{skBmp1x1.RowBytes}", 20, yOffset += paint.FontSpacing, paint);

         canvas.DrawText($"0x0SKBitmap,Width:{skBmp0x0.Width},Height:{skBmp0x0.Height}", 20, yOffset += paint.FontSpacing, paint);
         canvas.DrawText($"1x1SKBitmap,Width:{skBmp1x1.Width},Height:{skBmp1x1.Height}", 20, yOffset += paint.FontSpacing, paint);
     }
 }

SKBitmap属性相关

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

图南科技

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值