43

Bitmap の各ピクセルにアクセスして操作し、Bitmap に保存する必要があります。

とを使用するBitmap.GetPixel()Bitmap.SetPixel()、プログラムの実行が遅くなります。

Bitmapにすばやく変換するにはどうすればよいbyte[]ですか?

各ピクセルの RGBA データを含むbyte[]withが必要です。length = (4 * width * height)

4

7 に答える 7

90

いくつかの異なる方法でそれを行うことができます。を使用unsafeしてデータに直接アクセスしたり、マーシャリングを使用してデータを前後にコピーしたりできます。安全でないコードの方が高速ですが、マーシャリングには安全でないコードは必要ありません。これは私がしばらく前に行ったパフォーマンス比較です。

ロックビットを使用した完全なサンプルを次に示します。

/*Note unsafe keyword*/
public unsafe Image ThresholdUA(float thresh)
{
    Bitmap b = new Bitmap(_image);//note this has several overloads, including a path to an image

    BitmapData bData = b.LockBits(new Rectangle(0, 0, _image.Width, _image.Height), ImageLockMode.ReadWrite, b.PixelFormat);

    byte bitsPerPixel = GetBitsPerPixel(bData.PixelFormat);

    /*This time we convert the IntPtr to a ptr*/
    byte* scan0 = (byte*)bData.Scan0.ToPointer();

    for (int i = 0; i < bData.Height; ++i)
    {
        for (int j = 0; j < bData.Width; ++j)
        {
            byte* data = scan0 + i * bData.Stride + j * bitsPerPixel / 8;

            //data is a pointer to the first byte of the 3-byte color data
            //data[0] = blueComponent;
            //data[1] = greenComponent;
            //data[2] = redComponent;
        }
    }

    b.UnlockBits(bData);

    return b;
}

これは同じことですが、マーシャリングを使用しています。

/*No unsafe keyword!*/
public Image ThresholdMA(float thresh)
{
    Bitmap b = new Bitmap(_image);

    BitmapData bData = b.LockBits(new Rectangle(0, 0, _image.Width, _image.Height), ImageLockMode.ReadWrite, b.PixelFormat);

    /* GetBitsPerPixel just does a switch on the PixelFormat and returns the number */
    byte bitsPerPixel = GetBitsPerPixel(bData.PixelFormat);

    /*the size of the image in bytes */
    int size = bData.Stride * bData.Height;

    /*Allocate buffer for image*/
    byte[] data = new byte[size];

    /*This overload copies data of /size/ into /data/ from location specified (/Scan0/)*/
    System.Runtime.InteropServices.Marshal.Copy(bData.Scan0, data, 0, size);

    for (int i = 0; i < size; i += bitsPerPixel / 8 )
    {
        double magnitude = 1/3d*(data[i] +data[i + 1] +data[i + 2]);

        //data[i] is the first of 3 bytes of color

    }

    /* This override copies the data back into the location specified */
    System.Runtime.InteropServices.Marshal.Copy(data, 0, bData.Scan0, data.Length);

    b.UnlockBits(bData);

    return b;
}
于 2009-10-13T21:59:37.927 に答える
6

Bitmap.LockBits メソッドを使用できます。また、タスクの並列実行を使用する場合は、System.Threading.Tasks 名前空間の Parallel クラスを使用できます。次のリンクには、いくつかのサンプルと説明があります。

于 2014-09-03T11:39:49.383 に答える
4

LockBitsが必要です。その後、BitmapData オブジェクトから必要なバイトを抽出できます。

于 2009-10-13T21:39:04.567 に答える
1

はるかに高速で便利な別の方法があります。Bitmap コンストラクターを見ると、IntPtr を最後のパラメーターとして使用するコンストラクターが見つかります。その IntPtr は、ピクセル データを保持するためのものです。では、どのように使用しますか?

Dim imageWidth As Integer = 1920
Dim imageHeight As Integer = 1080

Dim fmt As PixelFormat = PixelFormat.Format32bppRgb
Dim pixelFormatSize As Integer = Image.GetPixelFormatSize(fmt)

Dim stride As Integer = imageWidth * pixelFormatSize
Dim padding = 32 - (stride Mod 32)
If padding < 32 Then stride += padding

Dim pixels((stride \ 32) * imageHeight) As Integer
Dim handle As GCHandle = GCHandle.Alloc(pixels, GCHandleType.Pinned)
Dim addr As IntPtr = Marshal.UnsafeAddrOfPinnedArrayElement(pixels, 0)

Dim bitmap As New Bitmap(imageWidth, imageHeight, stride \ 8, fmt, addr)

これで、同じメモリを参照する単純な整数配列とビットマップができました。整数配列に加えた変更は、ビットマップに直接影響します。単純な輝度変換でこれを試してみましょう。

Public Sub Brightness(ByRef pixels() As Integer, ByVal scale As Single)
    Dim r, g, b As Integer
    Dim mult As Integer = CInt(1024.0f * scale)
    Dim pixel As Integer

    For i As Integer = 0 To pixels.Length - 1
        pixel = pixels(i)
        r = pixel And 255
        g = (pixel >> 8) And 255
        b = (pixel >> 16) And 255

        'brightness calculation
        'shift right by 10 <=> divide by 1024
        r = (r * mult) >> 10
        g = (g * mult) >> 10
        b = (b * mult) >> 10

        'clamp to between 0 and 255
        If r < 0 Then r = 0
        If g < 0 Then g = 0
        If b < 0 Then b = 0
        r = (r And 255)
        g = (g And 255)
        b = (b And 255)

        pixels(i) = r Or (g << 8) Or (b << 16) Or &HFF000000
    Next
End Sub

ループ内で浮動小数点演算を実行しないように、ちょっとしたトリックを使用していることに気付くかもしれません。これにより、パフォーマンスがかなり向上します。そして、終わったら、もちろん少し片付ける必要があります...

addr = IntPtr.Zero
If handle.IsAllocated Then
    handle.Free()
    handle = Nothing
End If
bitmap.Dispose()
bitmap = Nothing
pixels = Nothing

ここではアルファ成分を無視していますが、それも自由に使用できます。このようにして、多くのビットマップ編集ツールをまとめました。これは、Bitmap.LockBits() よりもはるかに高速で信頼性が高く、何よりも、ビットマップの編集を開始するためにメモリ コピーが不要です。

于 2014-05-13T17:51:48.373 に答える
1

@notJim の回答に基づいて (およびhttps://web.archive.org/web/20141229164101/http://bobpowell.net/lockingbits.aspxの助けを借りて)、私は次のことを開発しました。最終的にはxy座標によってピクセルにジャンプできる配列の配列になります。もちろん、xピクセルあたりのバイト数によって座標を修正する必要がありますが、それは簡単な拡張です。

Dim bitmapData As Imaging.BitmapData = myBitmap.LockBits(New Rectangle(0, 0, myBitmap.Width, myBitmap.Height), Imaging.ImageLockMode.ReadOnly, myBitmap.PixelFormat)

Dim size As Integer = Math.Abs(bitmapData.Stride) * bitmapData.Height
Dim data(size - 1) As Byte

Marshal.Copy(bitmapData.Scan0, data, 0, size)

Dim pixelArray(myBitmap.Height)() As Byte

'we have to load all the opacity pixels into an array for later scanning by column
'the data comes in rows
For y = myBitmap.Height - 1 To 0 Step -1
    Dim rowArray(bitmapData.Stride) As Byte
    Array.Copy(data, y * bitmapData.Stride, rowArray, 0, bitmapData.Stride)
    'For x = myBitmap.Width - 1 To 0 Step -1
    '   Dim i = (y * bitmapData.Stride) + (x * 4)
    '   Dim B = data(i)
    '   Dim G = data(i + 1)
    '   Dim R = data(i + 2)
    '   Dim A = data(i + 3)
    'Next
    pixelArray(y) = rowArray
Next
于 2013-02-28T22:54:45.670 に答える
0

この C# ソリューションを試してください。

テスト用の winforms アプリを作成します。

Button と PictureBox を追加し、クリック イベントとフォームを閉じるイベントを追加します。

フォームに次のコードを使用します。

public partial class Form1 : Form
{
    uint[] _Pixels { get; set; }

    Bitmap _Bitmap { get; set; }

    GCHandle _Handle { get; set; }

    IntPtr _Addr { get; set; }


    public Form1()
    {
        InitializeComponent();

        int imageWidth = 100; //1920;

        int imageHeight = 100; // 1080;

        PixelFormat fmt = PixelFormat.Format32bppRgb;

        int pixelFormatSize = Image.GetPixelFormatSize(fmt);

        int stride = imageWidth * pixelFormatSize;

        int padding = 32 - (stride % 32);

        if (padding < 32)
        {
            stride += padding;
        }

        _Pixels = new uint[(stride / 32) * imageHeight + 1];

         _Handle = GCHandle.Alloc(_Pixels, GCHandleType.Pinned);

        _Addr = Marshal.UnsafeAddrOfPinnedArrayElement(_Pixels, 0);

        _Bitmap = new Bitmap(imageWidth, imageHeight, stride / 8, fmt, _Addr);

        pictureBox1.Image = _Bitmap;

    }

    private void button1_Click(object sender, EventArgs e)
    {
        for (int i = 0; i < _Pixels.Length; i++)
        {
            _Pixels[i] = ((uint)(255 | (255 << 8) | (255 << 16) | 0xff000000));

        }

    }

    private void Form1_FormClosing(object sender, FormClosingEventArgs e)
    {
        _Addr = IntPtr.Zero;

        if (_Handle.IsAllocated)
        {
            _Handle.Free();

        }

        _Bitmap.Dispose();

        _Bitmap = null;

        _Pixels = null;

    }

}

これで、配列を編集すると、ビットマップが自動的に更新されます。

これらの変更を表示するには、picturebox で refresh メソッドを呼び出す必要があります。

于 2016-09-08T10:11:36.210 に答える