TL; DR: I have byte[]
. I want Bgra32Pixel[]
. Don't want to copy. If a copy is necessary, I want fastest possible optimized copy, no copying of single bytes. Is it even possible?
Full description:
Here's the struct:
/// <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;
}
I have a byte array, lets call it data
. I want to access it as Bgra32Pixel[]
.
The same bytes in memory. Is it necessary to copy bytes for it?
I wish something like this worked:
var pixels = data as Bgra32Pixel[];
But it doesn't. What is the fastest way to do it?
My guess is to make custom type with indexer returning Bgra32Pixel directly from original byte[] reference. But it wouldn't be very fast. No copying is required for that, but each access would actually create a new struct from 4 bytes. No, seems unnecessary slow. There must be a way to trick C# into thinking byte[] is somehow Bgra32Pixel[].
So here's my solution I found after reading all the answers:
TL;DR: No need for struct.
Conversion to struct would require unsafe context and fixed statement. This is no good for performance. Here's the code for removing background from a bitmap, which assumes the pixel in the left top corner has the background color. This code calls special "color to alpha" voodoo on each pixel:
/// <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;
}
}
If you are very curious, what is the voodoo class used, here:
/// <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);
}
I'm giving back to the community. I found all necessary math on StackOverflow and Github. I guess GIMP is using something very similar for "color to alpha" effect.
The question still remains open: is there a faster way to do that?