文章目录
- 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);}}