2

編集:返信に深く感謝します。ここで何よりも必要なのは、ネストされたループ内の数行のコードで行うことのサンプル コードです。これは、GetPixel/SetPixel で正しく機能するものですが、Lockbits を使用しても正しく機能しないものでもあるためです。ありがとうございました

処理時間を改善するために、画像処理フィルターを GetPixel / SetPixel から Lockbits に変換しようとしています。 ここの Stack Overflow、MSDN、およびその他のサイトでも Lockbits のチュートリアルを見てきましたが、何か間違ったことをしています。 緑を減らして赤と紫の効果を作成する、非常に単純なフィルターから始めます。これが私のコードです:

   private void redsAndPurplesToolStripMenuItem_Click(object sender, EventArgs e)
    {
        // Get bitmap from picturebox
        Bitmap bmpMain = (Bitmap)pictureBoxMain.Image.Clone();

        // search through each pixel via x, y coordinates, examine and make changes. Dont let values exceed 255 or fall under 0.  
        for (int y = 0; y < bmpMain.Height; y++)
            for (int x = 0; x < bmpMain.Width; x++)
            {
                bmpMain.GetPixel(x, y);
                Color c = bmpMain.GetPixel(x, y);
                int myRed = c.R, myGreen = c.G, myBlue = c.B;
                myGreen -= 128;
                if (myGreen < 0) myGreen = 0; 
                bmpMain.SetPixel(x, y, Color.FromArgb(255, myRed, myGreen, myBlue));
            }

        // assign the new bitmap to the picturebox
        pictureBoxMain.Image = (Bitmap)bmpMain;

        // Save a copy to the HD for undo / redo.
        string myString = Environment.GetEnvironmentVariable("temp", EnvironmentVariableTarget.Machine);
        pictureBoxMain.Image.Save(myString + "\\ColorAppRedo.png", System.Drawing.Imaging.ImageFormat.Png);
    }

そのため、GetPixel / SetPixel コードは正常に動作しますが、遅いです。だから私はこれを試しました:

    private void redsAndPurplesToolStripMenuItem_Click(object sender, EventArgs e)
    {
        // Get bitmap from picturebox
        Bitmap bmpMain = (Bitmap)pictureBoxMain.Image.Clone();

        Rectangle rect = new Rectangle(Point.Empty, bmpMain.Size); 
        BitmapData bmpData = bmpMain.LockBits(rect, ImageLockMode.ReadOnly, bmpMain.PixelFormat); 

        // search through each pixel via x, y coordinates, examine and make changes. Dont let values exceed 255 or fall under 0.  
        for (int y = 0; y < bmpMain.Height; y++)
            for (int x = 0; x < bmpMain.Width; x++)
            {
                bmpMain.GetPixel(x, y);
                Color c = new Color(); 
                int myRed = c.R, myGreen = c.G, myBlue = c.B;
                myGreen -= 128;
                if (myGreen < 0) myGreen = 0; 
                bmpMain.SetPixel(x, y, Color.FromArgb(255, myRed, myGreen, myBlue));

            }

        bmpMain.UnlockBits(bmpData); 

        // assign the new bitmap to the picturebox
        pictureBoxMain.Image = (Bitmap)bmpMain;

        // Save a copy to the HD for undo / redo.
        string myString = Environment.GetEnvironmentVariable("temp", EnvironmentVariableTarget.Machine);
        pictureBoxMain.Image.Save(myString + "\\ColorAppRedo.png", System.Drawing.Imaging.ImageFormat.Png);
    } 

ネストされたループの最初の行に到達すると、「 「System.InvalidOperationException」タイプの未処理の例外が System.Drawing.dll で発生しました追加情報:ビットマップ領域は既にロックされています」というエラーがスローされます。

これは初心者のエラーに違いないと思います。誰かがこの非常に単純なフィルターをロックビットに変換する正しい方法を示してくれれば幸いです。どうもありがとうございました

4

2 に答える 2

4

追加情報: ビットマップ領域は既にロックされています"

GetPixel() が遅い理由がわかりました。内部で Un/LockBits も使用しています。しかし、個々のピクセルごとにそうすると、オーバーヘッドが CPU サイクルを奪います。ビットマップは一度しかロックできないため、例外が発生しました。また、複数のスレッドで同時にビットマップにアクセスできないという基本的な理由もあります。

LockBits のポイントは、ビットマップ ピクセルが占めるメモリに直接アクセスできることです。BitmapData.Scan0 メンバーは、メモリ アドレスを提供します。メモリの直接アドレス指定は非常に高速です。ただしIntPtr、ポインターまたは Marshal.Copy() を使用する必要がある Scan0 の型を使用する必要があります。ポインターを使用するのが最適な方法です。これを行う方法については、既存の例が多数あります。ここでは繰り返しません。

 ... = bmpMain.LockBits(rect, ImageLockMode.ReadOnly, bmpMain.PixelFormat); 

渡す最後の引数は非常重要です。これはデータのピクセル形式を選択し、それはあなたが書くコードに影響を与えます。bmpMain.PixelFormat を使用するのが最速のロック方法ですが、非常に不便でもあります。そのため、コードを特定のピクセル形式に適合させる必要があります。たくさんあります。 PixelFormat enumをよく見てください。これらは、各ピクセルに使用されるバイト数と、ビットでの色のエンコード方法が異なります。

唯一の便利なピクセル形式は Format32bppArgb です。各ピクセルは 4 バイトを取り、色/アルファは 1 バイトでエンコードされ、非常に簡単かつ迅速にピクセルをuint*. Format24bppRgb を扱うことはできますが、今は が必要ですbyte*。これはかなり遅くなります。名前に P が含まれているものは、事前に乗算された形式で、表示は非常に高速ですが、扱いが非常に厄介です。したがって、LockBits() を強制的にピクセル形式に変換するというパフォーマンス ヒットを取得することで、はるかに有利になる可能性があります。この種の損失を回避するには、事前にピクセル形式に注意を払うことが重要です。

于 2015-05-30T11:07:47.413 に答える
4

scan0 によって返される配列は、BGRA BGRA BGRA BGRA ... などの形式で、B = 青、G = 緑、R = 赤、A = アルファです。

幅 4 ピクセル、高さ 3 ピクセルの非常に小さなビットマップの例。

BGRA BGRA BGRA BGRA
BGRA BGRA BGRA BGRA
BGRA BGRA BGRA BGRA 

stride = width*bytesPerPixel = 4*4 = 16 bytes
height = 3
maxLenght = stride*height= 16*3 = 48 bytes

画像内の特定のピクセル (x, y) に到達するには、次の式を使用します

int certainPixel = bytesPerPixel*x + stride * y;
B = scan0[certainPixel + 0];
G = scan0[certainPixel + 1];
R = scan0[certainPixel + 2];
A = scan0[certainPixel + 3];

    public unsafe void Test(Bitmap bmp)
    {
        int width = bmp.Width;
        int height = bmp.Height;
        //TODO determine bytes per pixel
        int bytesPerPixel = 4; // we assume that image is Format32bppArgb
        int maxPointerLenght = width * height * bytesPerPixel;
        int stride = width * bytesPerPixel;
        byte R, G, B, A;

        BitmapData bData = bmp.LockBits(
            new System.Drawing.Rectangle(0, 0, bmp.Width, bmp.Height),
            ImageLockMode.ReadWrite, bmp.PixelFormat);


        byte* scan0 = (byte*)bData.Scan0.ToPointer();

        for (int i = 0; i < maxPointerLenght; i += 4)
        {
            B = scan0[i + 0];
            G = scan0[i + 1];
            R = scan0[i + 2];
            A = scan0[i + 3];

            // do anything with the colors
            // Set the green component to 0
            G = 0;
            // do something with red
            R = R < 54 ? (byte)(R + 127) : R;
            R = R > 255 ? 255 : R;
        }


        bmp.UnlockBits(bData);
    }

自分でテストできます。ペイントまたはその他のプログラムで非常に小さなビットマップ (幅/高さ数ピクセル) を作成し、メソッドの先頭にブレークポイントを配置します。

于 2015-05-30T09:45:32.297 に答える