TL; DR:我有byte[]
。我想要Bgra32Pixel[]
。不想复制。如果需要复制,我想要最快的优化复制,不要复制单个字节。它甚至可能吗?
完整说明:
这是结构:
/// <summary>
/// Represents a PixelFormats.Bgra32 format pixel
/// </summary>
[StructLayout(LayoutKind.Explicit)]
public struct Bgra32Pixel {
[FieldOffset(0)]
public readonly int Value;
[FieldOffset(0)]
public byte B;
[FieldOffset(1)]
public byte G;
[FieldOffset(2)]
public byte R;
[FieldOffset(3)]
public byte A;
}
我有一个字节数组,我们称之为data
。我希望以Bgra32Pixel[]
的形式访问它。
内存中的字节相同。是否有必要为其复制字节?
我希望这样的事情有效:
var pixels = data as Bgra32Pixel[];
但事实并非如此。最快的方法是什么?
我的猜测是使用索引器自定义类型直接从原始byte []引用返回Bgra32Pixel。但它不会很快。不需要复制,但每次访问实际上将创建一个4字节的新结构。不,似乎不必要的慢。必须有一种方法来欺骗C#,认为byte []以某种方式Bgra32Pixel []。
所以这是我在阅读完所有答案后找到的解决方案:
TL; DR:不需要结构。
转换为struct需要不安全的上下文和固定的语句。这对性能没有好处。这是从位图中删除背景的代码,它假设左上角的像素具有背景颜色。此代码在每个像素上调用特殊的“颜色到alpha”voodoo:
/// <summary>
/// Extensions for bitmap manipulations.
/// </summary>
static class BitmapSourceExtensions {
/// <summary>
/// Removes the background from the bitmap assuming the first pixel is background color.
/// </summary>
/// <param name="source">Opaque bitmap.</param>
/// <returns>Bitmap with background removed.</returns>
public static BitmapSource RemoveBackground(this BitmapSource source) {
if (source.Format != PixelFormats.Bgr32) throw new NotImplementedException("Pixel format not implemented.");
var target = new WriteableBitmap(source.PixelWidth, source.PixelHeight, source.DpiX, source.DpiY, PixelFormats.Bgra32, null);
var pixelSize = source.Format.BitsPerPixel / 8;
var pixelCount = source.PixelWidth * source.PixelHeight;
var pixels = new uint[pixelCount];
var stride = source.PixelWidth * pixelSize;
source.CopyPixels(pixels, stride, 0);
var background = new LABColor(pixels[0]);
for (int i = 0; i < pixelCount; i++) pixels[i] &= background.ColorToAlpha(pixels[i]);
var bounds = new Int32Rect(0, 0, source.PixelWidth, source.PixelHeight);
target.WritePixels(bounds, pixels, stride, 0);
return target;
}
}
如果你很好奇,那么使用的伏都教课程是什么:
/// <summary>
/// CIE LAB color space structure with BGRA pixel support.
/// </summary>
public struct LABColor {
/// <summary>
/// Lightness (0..100).
/// </summary>
public readonly double L;
/// <summary>
/// A component (0..100)
/// </summary>
public readonly double A;
/// <summary>
/// B component (0..100)
/// </summary>
public readonly double B;
/// <summary>
/// Creates CIE LAB color from BGRA pixel.
/// </summary>
/// <param name="bgra">Pixel.</param>
public LABColor(uint bgra) {
const double t = 1d / 3d;
double r = ((bgra & 0x00ff0000u) >> 16) / 255d;
double g = ((bgra & 0x0000ff00u) >> 8) / 255d;
double b = (bgra & 0x000000ffu) / 255d;
r = (r > 0.04045 ? Math.Pow((r + 0.055) / 1.055, 2.4) : r / 12.92) * 100d;
g = (g > 0.04045 ? Math.Pow((g + 0.055) / 1.055, 2.4) : g / 12.92) * 100d;
b = (b > 0.04045 ? Math.Pow((b + 0.055) / 1.055, 2.4) : b / 12.92) * 100d;
double x = (r * 0.4124 + g * 0.3576 + b * 0.1805) / 95.047;
double y = (r * 0.2126 + g * 0.7152 + b * 0.0722) / 100.000;
double z = (r * 0.0193 + g * 0.1192 + b * 0.9505) / 108.883;
x = x > 0.0088564516790356311 ? Math.Pow(x, t) : (903.2962962962963 * x + 16d) / 116d;
y = y > 0.0088564516790356311 ? Math.Pow(y, t) : (903.2962962962963 * y + 16d) / 116d;
z = z > 0.0088564516790356311 ? Math.Pow(z, t) : (903.2962962962963 * z + 16d) / 116d;
L = Math.Max(0d, 116d * y - 16d);
A = 500d * (x - y);
B = 200d * (y - z);
}
/// <summary>
/// Calculates color space distance between 2 CIE LAB colors.
/// </summary>
/// <param name="c">CIE LAB color.</param>
/// <returns>A color space distance between 2 colors from 0 (same colors) to 100 (black and white)</returns>
public double Distance(LABColor c) {
double dl = L - c.L;
double da = A - c.A;
double db = B - c.B;
return Math.Sqrt(dl * dl + da * da + db * db);
}
/// <summary>
/// Calculates bit mask for alpha calculated from difference between this color and another BGRA color.
/// </summary>
/// <param name="bgra">Pixel.</param>
/// <returns>Bit mask for alpha in BGRA pixel format.</returns>
public uint ColorToAlpha(uint bgra) => 0xffffffu | ((uint)(Distance(new LABColor(bgra)) * 2.55d) << 24);
}
我正在回馈社区。我在StackOverflow和Github上找到了所有必要的数学。 我猜GIMP正在使用非常类似的“颜色到alpha”效果。
问题仍然存在:是否有更快的方法可以做到这一点?
答案 0 :(得分:2)
不需要将字节数组转换为Bgra32Pixel
个对象,这样做只会损害您的性能。要从WriteableBitmap
读取像素数据,您可以执行以下操作:
unsafe public static BitmapSource GetBgra32(this BitmapSource bmp)
{
if (bmp.Format != PixelFormats.Bgr32)
throw new NotImplementedException("Pixel format not implemented.");
var source = new WriteableBitmap(bmp);
var target = new WriteableBitmap(bmp.PixelWidth, bmp.PixelHeight, bmp.DpiX, bmp.DpiY, PixelFormats.Bgra32, null);
source.Lock();
target.Lock();
var srcPtr = (byte*) source.BackBuffer;
var trgPtr = (byte*) source.BackBuffer;
int sIdx,sCol,tIdx,tCol;
for (int y = 0; y < bmp.PixelHeight; y++)
{
sCol = y * source.BackBufferStride;
tCol = y * target.BackBufferStride;
for (int x = 0; x < bmp.PixelWidth; x++)
{
sIdx = sCol + (x * 3); // Bpp = 3
tIdx = tCol + (x * 4); // Bpp = 4
byte b = srcPtr[sIdx];
byte g = srcPtr[sIdx + 1];
byte r = srcPtr[sIdx + 2];
// Do some processing
trgPtr[tIdx] = bVal;
trgPtr[tIdx + 1] = gVal;
trgPtr[tIdx + 2] = rVal;
trgPtr[tIdx + 3] = aVal;
}
}
source.Unlock();
target.Unlock();
return target;
}
答案 1 :(得分:1)
以下代码可用于将byte
数组转换为Bgra32Pixel
数组。
static T[] ConvertBytesWithGCHandle<T>(byte[] data) where T : struct
{
int size = Marshal.SizeOf(typeof(T));
if (data.Length % size != 0) throw new ArgumentOutOfRangeException("data", "Data length must be divisable by " + size + " (struct size).");
GCHandle handle = GCHandle.Alloc(data, GCHandleType.Pinned);
IntPtr ptr = handle.AddrOfPinnedObject();
T[] returnData = new T[data.Length / size];
for (int i = 0; i < returnData.Length; i++)
returnData[i] = (T)Marshal.PtrToStructure(ptr + i * size, typeof(T));
handle.Free();
return returnData;
}
static T[] ConvertBytesWithMarshal<T>(byte[] data) where T : struct
{
int size = Marshal.SizeOf(typeof(T));
if (data.Length % size != 0) throw new ArgumentOutOfRangeException("data", "Data length must be divisable by " + size + " (struct size).");
T[] returnData = new T[data.Length / size];
for (int i = 0; i < returnData.Length; i++)
{
IntPtr ptr = Marshal.AllocHGlobal(size);
Marshal.Copy(data, i * size, ptr, size);
returnData[i] = (T)Marshal.PtrToStructure(ptr, typeof(T));
Marshal.FreeHGlobal(ptr);
}
return returnData;
}
对于速度测试,我还做了以下方法:
static Bgra32Pixel[] CopyBytesWithPlain(byte[] data)
{
if (data.Length % 4 != 0) throw new ArgumentOutOfRangeException("data", "Data length must be divisable by 4 (struct size).");
Bgra32Pixel[] returnData = new Bgra32Pixel[data.Length / 4];
for (int i = 0; i < returnData.Length; i++)
returnData[i] = new Bgra32Pixel()
{
B = data[i * 4 + 0],
G = data[i * 4 + 1],
R = data[i * 4 + 2],
A = data[i * 4 + 3]
};
return returnData;
}
static Bgra32Pixel[] CopyBytesWithLinq(byte[] data)
{
if (data.Length % 4 != 0) throw new ArgumentOutOfRangeException("data", "Data length must be divisable by 4 (struct size).");
return data
.Select((b, i) => new { Byte = b, Index = i })
.GroupBy(g => g.Index / 4)
.Select(g => g.Select(b => b.Byte).ToArray())
.Select(a => new Bgra32Pixel()
{
B = a[0],
G = a[1],
R = a[2],
A = a[3]
})
.ToArray();
}
速度测试的结果如下:
CopyBytesWithPlain: 00:00:00.1701410
CopyBytesWithGCHandle: 00:00:02.8298880
CopyBytesWithMarshal: 00:00:05.5448466
CopyBytesWithLinq: 00:00:33.5987996
代码可以按如下方式使用:
var bytes = new byte[] { 0, 1, 2, 3, 4, 5, 6, 7 };
var pixels = ConvertBytesWithMarshal<Bgra32Pixel>(bytes);
foreach (var pixel in pixels)
Console.WriteLine(pixel.Value);
或者
var bytes = new byte[] { 0, 1, 2, 3, 4, 5, 6, 7 };
var pixels = ConvertBytesWithGCHandle<Bgra32Pixel>(bytes);
foreach (var pixel in pixels)
Console.WriteLine(pixel.Value);
代码基于https://stackoverflow.com/a/31047345/637425,Taha Paksu和https://stackoverflow.com/a/2887/637425指出,正如 gabriel
答案 2 :(得分:1)
您不需要将byte
数组转换为Bgra32Pixel
数组,您只需访问字节数组,因为它是Bgra32Pixel
数组。
以下代码创建一个缓冲区,用于保存32个像素并将其颜色设置为半透明红色:
const int numberOfPixels = 32;
var buffer = new byte[numberOfPixels * sizeof(Bgra32Pixel)];
fixed(void* p = buffer)
{
var pixels = (Bgra32Pixel*)p;
for (int i= 0; i < numberOfPixels; i++)
{
pixels[i].A = 128;
pixels[i].R = 255;
}
}
但是,如果您将像素视为整个uint
的
以下代码执行相同操作,但速度更快:
const int numberOfPixels = 32;
var buffer = new byte[numberOfPixels * sizeof(uint)];
fixed(void* p = buffer)
{
var pixels = (uint*)p;
for (int i= 0; i < numberOfPixels; i++)
{
pixels[i] = 0x80FF0000; // A: 0x80, R: 0xFF, G: 0x00, B: 0x00
}
}