web-dev-qa-db-ja.com

ピクセルデータのバイト配列からビットマップを作成する

この質問は、ビットマップのピクセルデータの読み取り/書き込み、割り当て、管理方法に関するものです。

以下は、ピクセルデータにバイト配列(管理メモリ)を割り当て、それを使用してビットマップを作成する方法の例です。

_Size size = new Size(800, 600);
PixelFormat pxFormat = PixelFormat.Format8bppIndexed;
//Get the stride, in this case it will have the same length of the width.
//Because the image Pixel format is 1 Byte/pixel.
//Usually stride = "ByterPerPixel"*Width
_

//しかし、常に真実とは限りません。 bobpowellの詳細

_int stride = GetStride(size.Width, pxFormat);
byte[] data = new byte[stride * size.Height];
GCHandle handle = GCHandle.Alloc(data, GCHandleType.Pinned);
Bitmap bmp = new Bitmap(size.Width, size.Height, stride,
             pxFormat, handle.AddrOfPinnedObject());

//After doing your stuff, free the Bitmap and unpin the array.
bmp.Dispose();
handle.Free();

public static int GetStride(int width, PixelFormat pxFormat)
{
    //float bitsPerPixel = System.Drawing.Image.GetPixelFormatSize(format);
    int bitsPerPixel = ((int)pxFormat >> 8) & 0xFF;
    //Number of bits used to store the image data per line (only the valid data)
    int validBitsPerLine = width * bitsPerPixel;
    //4 bytes for every int32 (32 bits)
    int stride = ((validBitsPerLine + 31) / 32) * 4;
    return stride;
}
_

ビットマップは配列データのコピーを作成すると思いましたが、実際には同じデータを指します。あなたは見ることができましたか:

_Color c;
c = bmp.GetPixel(0, 0);
Console.WriteLine("Color before: " + c.ToString());
//Prints: Color before: Color [A=255, R=0, G=0, B=0]
data[0] = 255;
c = bmp.GetPixel(0, 0);
Console.WriteLine("Color after: " + c.ToString());
//Prints: Color after: Color [A=255, R=255, G=255, B=255]
_

質問:

  1. Byte []配列(マネージメモリ)からビットマップを作成し、GCHandleをfree()しても安全ですか?安全でない場合、固定された配列を維持する必要がありますが、GC /パフォーマンスにとってどれほど悪いですか?

  2. データを変更しても安全ですか(例:data [0] = 255;)?

  3. Scan0のアドレスはGCによって変更できますか?つまり、ロックされたビットマップからScan0を取得してロックを解除し、しばらくしてから再度ロックすると、Scan0が異なる可能性があります。

  4. LockBitsメソッドのImageLockMode.UserInputBufferの目的は何ですか?それについての情報を見つけるのは非常に難しいです! MSDNはそれを明確に説明しません!

編集1:いくつかのフォローアップ

  1. 固定したままにする必要があります。 GCが遅くなりますか? ここで質問しました 。画像の数とサイズに依存します。誰も私に定量的な答えを与えていない。判断するのは難しいと思います。 Marshalを使用してメモリを割り当てることも、Bitmapによって割り当てられたアンマネージメモリを使用することもできます。

  2. 2つのスレッドを使用して多くのテストを行いました。ビットマップがロックされている限り、問題ありません。ビットマップがロック解除されている場合、安全ではありません! Scan0への直接読み取り/書き込みに関する私の関連記事 。 Boing's answer「ロックの外でscan0を使用できる幸運な理由については既に説明しました。元のbmp PixelFormatを使用し、その場合GDIはポインターを与えるために最適化されますコピーではありません。このポインタは、OSが解放するまで有効です。保証があるのは、LockBitsとUnLockBitsの間だけです。

  3. ええ、それは起こる可能性がありますが、大きなメモリ領域はGCによって異なって扱われ、この大きなオブジェクトを移動/解放する頻度は少なくなります。そのため、この配列をGCで移動するには時間がかかる場合があります。 MSDNから :「_85,000 bytes_以上の割り当てはlarge object heap (LOH)で行われます」...「LOHは第2世代の収集中にのみ収集されます」。 .NET 4.5では、LOHが改善されています。

  4. この質問には@​​Boingが回答しました。しかし、私は認めるつもりです。完全に理解できませんでした。 Boingまたは他の誰かが_please clarify it_をできたら、私はうれしいです。ところで、なぜ ロックせずにSca0に直接読み書きする ? => Scan0は、アンマネージメモリ(GDI内)によって作成されたビットマップデータのコピーを指すため、Scan0に直接書き込むべきではありません。ロック解除後、このメモリは他のものに再割り当てすることができます。これは、Scan0が実際のビットマップデータを指すことはもうありません。これは、Scan0をロック状態にして、ロック解除し、ロック解除ビットマップで回転フリットを行うことで再現できます。しばらくすると、Scan0は無効な領域をポイントし、そのメモリ位置への読み取り/書き込みを試みると例外が発生します。

37
Pedro77
  1. Scan0を(直接またはBitMap()のオーバーロードを介して)設定するのではなく、データをmarshal.copyする場合に安全です。管理対象オブジェクトを固定したままにしたくない場合、ガベージコレクターが制約されます。
  2. コピーすれば、完全に安全です。
  3. 入力配列は管理されており、GCによって移動できます。scan0は、配列が移動すると古くなるアンマネージポインターです。 Bitmapオブジェクト自体は管理されていますが、Windowsのハンドルを介してscan0ポインターを設定します。
  4. ImageLockMode.UserInputBufferは?どうやらLockBitsに渡すことができます。おそらくBitmap()に入力配列データをコピーするように伝えます。

配列からグレースケールビットマップを作成するサンプルコード:

    var b = new Bitmap(Width, Height, PixelFormat.Format8bppIndexed);

    ColorPalette ncp = b.Palette;
    for (int i = 0; i < 256; i++)
        ncp.Entries[i] = Color.FromArgb(255, i, i, i);
    b.Palette = ncp;

    var BoundsRect = new Rectangle(0, 0, Width, Height);
    BitmapData bmpData = b.LockBits(BoundsRect,
                                    ImageLockMode.WriteOnly,
                                    b.PixelFormat);

    IntPtr ptr = bmpData.Scan0;

    int bytes = bmpData.Stride*b.Height;
    var rgbValues = new byte[bytes];

    // fill in rgbValues, e.g. with a for loop over an input array

    Marshal.Copy(rgbValues, 0, ptr, bytes);
    b.UnlockBits(bmpData);
    return b;
24
Peter Wishart

質問4について:ImageLockMode.UserInputBufferは、BitmapDataオブジェクトに参照される可能性のある膨大な量のメモリの割り当てプロセスを制御できます。

BitmapDataオブジェクトを自分で作成することを選択した場合、Marshall.Copy。次に、このフラグを別のImageLockModeと組み合わせて使用​​する必要があります。

特にStrideとPixelFormatに関して、複雑なビジネスであることに注意してください。

ここに取得する例があります 一発で 24bbpバッファーの内容をBitMapに格納し、次に別のショットでそれを別のバッファーに読み込んで48bbpに戻します。

Size size = Image.Size;
Bitmap bitmap = Image;
// myPrewrittenBuff is allocated just like myReadingBuffer below (skipped for space sake)
// But with two differences: the buff would be byte [] (not ushort[]) and the Stride == 3 * size.Width (not 6 * ...) because we build a 24bpp not 48bpp
BitmapData writerBuff= bm.LockBits(new Rectangle(0, 0, size.Width, size.Height), ImageLockMode.UserInputBuffer | ImageLockMode.WriteOnly, PixelFormat.Format24bppRgb, myPrewrittenBuff);
// note here writerBuff and myPrewrittenBuff are the same reference
bitmap.UnlockBits(writerBuff);
// done. bitmap updated , no marshal needed to copy myPrewrittenBuff 

// Now lets read back the bitmap into another format...
BitmapData myReadingBuffer = new BitmapData();
ushort[] buff = new ushort[(3 * size.Width) * size.Height]; // ;Marshal.AllocHGlobal() if you want
GCHandle handle= GCHandle.Alloc(buff, GCHandleType.Pinned);
myReadingBuffer.Scan0 = Marshal.UnsafeAddrOfPinnedArrayElement(buff, 0);
myReadingBuffer.Height = size.Height;
myReadingBuffer.Width = size.Width;
myReadingBuffer.PixelFormat = PixelFormat.Format48bppRgb;
myReadingBuffer.Stride = 6 * size.Width;
// now read into that buff
BitmapData result = bitmap.LockBits(new Rectangle(0, 0, size.Width, size.Height), ImageLockMode.UserInputBuffer | ImageLockMode.ReadOnly, PixelFormat.Format48bppRgb, myReadingBuffer);
if (object.ReferenceEquals(result, myReadingBuffer)) {
    // Note: we pass here
    // and buff is filled
}
bitmap.UnlockBits(result);
handle.Free();
// use buff at will...

ILSpyを使用すると、このメソッドが GDI + にリンクし、これらのメソッドがより完全に役立つことがわかります。

独自のメモリスキームを使用することでパフォーマンスを向上させることができますが、最高のパフォーマンスを得るにはStrideが何らかの調整を必要とすることに注意してください。

その後、たとえば割り当てを行うことができます 巨大 仮想メモリはscan0をマップし、それらを非常に効率的にブリットしました。巨大な配列(特に数個)を固定することはGCの負担にならず、完全に安全な方法(または速度を求める場合は安全ではない)でバイト/ショートを操作できることに注意してください。

10
Boing

あなたがあなたのやり方でそれをしている理由があるかどうかはわかりません。たぶんあります。あなたはあなたがあなたの質問のタイトルが意味するものよりも高度な何かをしようとしているかもしれないほど十分にbeatられた道を外れているようです...

ただし、バイト配列からビットマップを作成する従来の方法は次のとおりです。

using (MemoryStream stream = new MemoryStream(byteArray))
{
     Bitmap bmp = new Bitmap(stream);
     // use bmp here....
}
6
Steve