文章目录
- SKBitmap
- 与Bitmap性能对比
- 对比结果
- 构造函数
- SKBitmap()
- SKBitmap(SKImageInfo)
- SKBitmap(Int32, Int32, SKColorType, SKAlphaType, SKColorSpace)
- SKBitmap属性
- AlphaType
- ByteCount
- Bytes
- BytesPerPixel
- ColorSpace
- ColorType
- DrawsNothing
- Info
- IsEmpty
- IsImmutable
- IsNull
- Pixels
- ReadyToDraw
- RowBytes
- Width、Height
- 示例
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与指针访问的性能。
- 分别加载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");
}
- 逐像素分别访问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
- 分别显示耗时
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;
- 使用指针方式逐像素访问
#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
- 分别显示耗时
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各属性的值。
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属性
AlphaType
public SkiaSharp.SKAlphaType AlphaType { get; }
定义了位图像素的透明度处理方式。透明度(或Alpha通道)在图形处理和图像合成中起着关键作用,因为它决定了像素的透明度或不透明度。
不同枚举值的意义:
-
Unknown:
- 描述:Alpha 通道的类型未知。这通常用于未明确指定 alpha 类型的图像。
- 应用场景:较少使用,因为不明确的 alpha 类型会导致不确定的渲染结果。
-
Opaque:
- 描述:图像是完全不透明的,没有透明度信息。Alpha 通道的值被忽略。
- 应用场景:用于不需要处理透明度的图像,提高渲染效率。
-
Premul (Pre-multiplied alpha):
- 描述:RGB 值已经被 alpha 通道值预乘,即每个颜色分量已经乘以其对应的 alpha 值。例如,如果 alpha 为 0.5(半透明),则红色分量被乘以 0.5。
- 应用场景:常用于图像合成,因为这种方式可以简化混合操作,减少计算开销。
-
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);
}
}
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);
}
}