2016-11-26 5 views
0

TL; DR:私はbyte[]です。私はBgra32Pixel[]が欲しいです。コピーしたくない。コピーが必要な場合は、できるだけ高速にコピーを最適化し、1バイトをコピーしないでください。それも可能ですか?byte []をC#のstruct FASTの配列に変換する方法は?

詳しい説明:ここでは

が構造です:

/// <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への変換には、安全でないコンテキストと固定ステートメントが必要です。これはパフォーマンスには悪いことです。ビットマップから背景を削除するコードは、左上隅のピクセルが背景色であることを前提としています。このコードは、各ピクセルに特別な「色アルファへの」ブードゥー教を呼び出します。

/// <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は "color to alpha"エフェクトと非常によく似たものを使用していると思います。

質問はまだ開かれています:それを行うより速い方法がありますか?

+0

http://stackoverflow.com/questions/31045358/how-do-i-copy-bytes-into-a-struct-variable- in-c –

+0

バイト配列の形式は何ですか?それはシリアル化されているBgra32Pixelですか?あるいは、特定の順序でB/G/R/Aフィールドにメッシュ化する値を持つバイトの配列ですか?または、バイト配列はBgra32Pixelデータを含むメモリ内のアドレスへのポインタですか?または...? – Abion47

+0

サンプルデータの中には、適切な答えを得るために長い道のりがあります。 – Abion47

答えて

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; 
} 
+0

これは最速のソリューションのようです。あるビットマップを別のピクセル形式にコピーする必要があるため、1回のコピー操作は避けられません。したがって、同じステップでアルファを計算するのが最善です。 – Harry

1

byteの配列をBgra32Pixelの配列に変換するには、次のコードを使用します。

CopyBytesWithPlain: 00:00:00.1701410 
CopyBytesWithGCHandle: 00:00:02.8298880 
CopyBytesWithMarshal: 00:00:05.5448466 
CopyBytesWithLinq:  00:00:33.5987996 

次のようにコードを使用することができる:

を次のようにスピードテストの結果である

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(); 
} 

:私はまた、以下の方法を行った速度試験用

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; 
} 

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); 

gabrielで指摘したようにTaha Paksuによっておよびhttps://stackoverflow.com/a/2887/637425に指摘したようにコードがhttps://stackoverflow.com/a/31047345/637425に基づいています。

+1

これは洗練されたソリューションですが、適時にBitmapSourceからピクセルデータにアクセスしているOPの中核的な問題には対応していません。 – Abion47

+0

@ Abion47私はほとんどの文章から働いた:私はこのようなものがうまくいきたいと思います: 'var pixels = data as Bgra32Pixel [];' – SynerCoder

+0

@ Abion47と彼の最初の文:TL; DR:私はバイト[]を持っています。 Bgra32Pixel [] – SynerCoder

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 
    } 
} 
+0

安全でない固定されたものをすべてスキップすると、さらに高速になります。更新された質問を参照してください。 'uint'の管理された配列を取得し、ビット単位のロジックを使用してマネージコードでそれらを処理することができます。 – Harry

関連する問題