在C#中,处理位图(Bitmap)图像数据时,理解像素格式(PixelFormat)、步幅(stride)和像素数据复制是非常重要的。
BitmapSource常用属性与方法
PixelFormat——Defines a pixel format for images and pixel-based surfaces.即定义图像和基于像素表面的像素格式。
Stride——(步幅)是图像数据中每一行像素所占用的字节数。这个值不一定等于图像宽度乘以每个像素的字节数,因为出于内存对齐的考虑,有些图像格式可能会在行尾添加额外的填充字节(padding bytes)。
PixelFormats——表示受支持像素格式的集合。PixelFormat
是一个静态类,描述了位图中每个像素的颜色深度和格式。例如,PixelFormats.
Bgra32表示每个像素占用32位,其中包含了透明度(Alpha)、红色(Red)、绿色(Green)和蓝色(Blue)四个分量。
CopyPixels——将位图像素数据复制到具有从指定偏移量开始的指定跨距的像素数组中。(C#中对于BitmapSource像素数据的复制,提供了此方法。)
应用示例
public static byte[] GetCorPixelDataFromCTData(CaseDTO currentCase, short[,,] ctData16ct, int curPhase, double frameIndex, out int width, out int height, double slope, double intercept, int imgWidth, int imgHeight)
{
Stopwatch sw = Stopwatch.StartNew();
//ctData
var ctDataCurrentPhrase = new short[currentCase.OnePhaseSlicesNumber, ctData16ct.GetLength(1), ctData16ct.GetLength(2)];
var start = (curPhase - 1) * currentCase.OnePhaseSlicesNumber;
var end = curPhase * currentCase.OnePhaseSlicesNumber;
//设XOZ为矢状面
width = ctDataCurrentPhrase.GetLength(2);
height = ctDataCurrentPhrase.GetLength(0);
//4个字节一个像素,参考ARGB
byte[] PixelData = new byte[width * height * 4];
try
{
//PixelData是按照height*width*4排列的数据
var rowStride = width * 4;
for (int y = 0; y < height; ++y)
{
for (int x = 0; x < width; ++x)
{
var pixelDataindex = y * rowStride + x * 4;
var y0 = start + y;
var gray = (byte)HUtoGraysale(ctData16ct[y0, (int)frameIndex, x], slope, intercept, imgHeight, imgWidth);
UpdatePixelData(PixelData, pixelDataindex, gray);
/* for (int i = 0; i < 4; ++i)
{
var pixelDataindex = y * rowStride + x * 4 + i;
if (i != 3)
{
var y0 = start + y;
PixelData[pixelDataindex] = (byte)HUtoGraysale(ctData16ct[y0, (int)frameIndex, x], slope, intercept, imgHeight, imgWidth);
}
else
PixelData[pixelDataindex] = 255;
}*/
}
}
Debug.WriteLine($"GetCorPixelDataFromCTData cost time: {sw.ElapsedMilliseconds}");
return PixelData;
}
catch (Exception ex)
{
return PixelData;
}
finally
{
sw.Stop();
}
}
internal static void UpdatePixelData(byte[] PixelData, int pixelDataindex, byte gray)
{
PixelData[pixelDataindex] = gray;
PixelData[pixelDataindex + 1] = gray;
PixelData[pixelDataindex + 2] = gray;
PixelData[pixelDataindex + 3] = 255;
}
public static int HUtoGraysale(int hu, double slope, double intercept, double windowCenter, double windowWidth)
{
//CT值转灰度值的关键公式pixel * slope + intercept
hu = (int)(hu * slope + intercept);
int low = (int)windowCenter - (int)windowWidth / 2;
int high = (int)windowCenter + (int)windowWidth / 2;
int grayscale = hu > high ? 255 : (hu < low ? 0 : (int)((hu - low) * 255 / windowWidth));
return grayscale;
}
注意事项
在上述示例中有一些关注点,在开发时需要注意:
- stride的求取要注意取整;示例中为了保证stride为整数操作为 (bitmap.PixelWidth * pixelFormat.BitsPerPixel + 7) / 8 ,因为1字节=8位,所以要将位数除以8来得到字节数。加7是为了实现向上取整的效果,比如如果每个像素占用3位,那么
(3 * width + 7) / 8
将确保每像素至少占用1个字节。 - RGBA的存储并不是按RGBA的先后顺序来的,而是B、G、R、A;这个一定要注意,上述代码中如下:
int offset = y * stride + x * 4; byte blue = pixels[offset]; byte green = pixels[offset + 1]; byte red = pixels[offset + 2]; //byte alpha = pixels[offset + 3];