2013-06-10 39 views
5

잠금 해제 된 Bitmap 관리되지 않는 메모리에서 직접 쓰고 읽을 수 있습니까?Unlocked Bitmap 관리되지 않는 메모리 (Scan0)에 직접 읽고 씁니다.

비트 맵의 ​​UnlockBits 이후에 BitmapData를 계속 사용할 수 있습니까? 나는 다른 스레드가 동일한 비트 맵에 픽셀을 쓰는 동안 마우스 위치에서 PictureBox의 비트 맵 픽셀을 읽을 수있는 테스트 응용 프로그램을 만들었습니다.

EDIT 1 Boing로서 그의 대답 지적 "Scan0 비트 맵 객체의 실제 픽셀 데이터를 가리 키지 않으며, 오히려 화소 데이터의 부분을 대표하는 임시 버퍼를 가리키는 비트 맵 개체. " MSDN.

그러나 일단 Scan0을 얻으면 Lockbit 또는 UnlockBits가 필요없이 비트 맵을 읽고 쓸 수 있습니다! 나는 스레드에서 많은 시간을 보내고 있습니다. Scan0이 Bitmap 데이터의 COPY를 가리 키기 때문에 MSDN에 따라, 이런 일이 일어나서는 안됩니다! 글쎄, C#에서 모든 테스트는 복사본이 아니라는 것을 보여줍니다. C++에서는 그것이 제대로 작동하는지 모르겠습니다.

EDIT 2 : 회전 방법을 사용하면 OS가 비트 맵 픽셀 데이터 복사본을 해제하게됩니다. 결론, it is not safe to read/write an unlocked Bitmap Scan0. 귀하의 답변과 의견을 주셔서 감사합니다!

다음은 BitmapData를 가져 와서 픽셀 값을 읽고 쓰는 방법입니다.

/// <summary> 
    /// Locks and unlocks the Bitmap to get the BitmapData. 
    /// </summary> 
    /// <param name="bmp">Bitmap</param> 
    /// <returns>BitmapData</returns> 
    public static BitmapData GetBitmapData(Bitmap bmp) 
    { 
     BitmapData bmpData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadOnly, bmp.PixelFormat); 
     bmp.UnlockBits(bmpData); 
     return bmpData; 
    } 

    /// <summary> 
    /// Get pixel directly from unamanged pixel data based on the Scan0 pointer. 
    /// </summary> 
    /// <param name="bmpData">BitmapData of the Bitmap to get the pixel</param> 
    /// <param name="p">Pixel position</param> 
    /// <param name="channel">Channel</param> 
    /// <returns>Pixel value</returns> 
    public static byte GetPixel(BitmapData bmpData, Point p, int channel) 
    { 
     if ((p.X > bmpData.Width - 1) || (p.Y > bmpData.Height - 1)) 
      throw new ArgumentException("GetPixel Point p is outside image bounds!"); 

     int bitsPerPixel = ((int)bmpData.PixelFormat >> 8) & 0xFF; 
     int bpp = bitsPerPixel/8; 
     byte data; 
     int id = p.Y * bmpData.Stride + p.X * bpp; 
     unsafe 
     { 
      byte* pData = (byte*)bmpData.Scan0; 
      data = pData[id + channel]; 
     } 
     return data; 
    } 

    //Non UI Thread 
    private void DrawtoBitmapLoop() 
    { 
     while (_drawBitmap) 
     { 
      _drawPoint = new Point(_drawPoint.X + 10, _drawPoint.Y + 10); 
      if (_drawPoint.X > _backImageData.Width - 20) 
       _drawPoint.X = 0; 
      if (_drawPoint.Y > _backImageData.Height - 20) 
       _drawPoint.Y = 0; 

      DrawToScan0(_backImageData, _drawPoint, 1); 

      Thread.Sleep(10); 
     } 
    } 

    private static void DrawToScan0(BitmapData bmpData, Point start, int channel = 0) 
    { 
     int x = start.X; 
     int y = start.Y; 
     int bitsPerPixel = ((int)bmpData.PixelFormat >> 8) & 0xFF; 
     int bpp = bitsPerPixel/8; 
     for (int i = 0; i < 10; i++) 
     { 
      unsafe 
      { 
       byte* p = (byte*)bmpData.Scan0; 
       int id = bmpData.Stride * y + channel + (x + i) * bpp; 
       p[id] = 255; 
      } 
     } 
    } 
+1

는, 그래,이 픽셀을 얻고 설정하는 매우 빠른 방법입니다,하지만 당신은'GetPixel'를 사용하여 작은 이미지 (* 500 <500), 작업하는 경우 'SetPixel'은 훨씬 쉬울 것입니다. – Mehran

+0

@Mehran, 제 편집과 Boings 대답을보세요. 내가 잠겨 있지 않은 비트 맵을 읽고 쓰고 있다는 것을 알고 있니? 따라서 MSDN에 따라 이것이 작동해서는 안됩니다. – Pedro77

+0

나는 당신이 말한 것을 시도했지만, 비트 잠금을 풀지 않고서는 제 'picturebox'와 흰색 바탕에 흰색 바탕에 적십자가 있습니다! , 당신은 이미 언급했듯이 이것이 효과가 없어야하지만, 나는 그것이 당신의 경우에 왜하는지 잘 모른다! – Mehran

답변

7

아니요. official explanation은 그것에 대해 명확합니다.

Scan0 은 Bitmap 객체의 실제 픽셀 데이터를 가리 킵니다. 오히려 Bitmap 객체에서 픽셀 데이터의 일부를 나타내는 임시 버퍼를 가리 킵니다. 이 코드는 임시 버퍼의 1500 위치에 값 0xff00ff00 (녹색)을 씁니다. 나중에 Bitmap :: UnlockBits를 호출하면 값이 Bitmap 객체 자체에 복사됩니다. 모든 비 ImageLockModeUserInputBuf의 BitmapData가 '릴리스/잠금 해제'후 해당 분야 리셋 (특히 scan0)을해야하기 때문에

나는 UnLockBits()에서 "버그"가 동의하지 않았다.

Scan0 GDI 관리 버퍼는 UnLockBits 이후에도 계속 액세스 할 수 있지만 잘못된 메모리 참조 하드 오류는 발생하지 않습니다. 그래픽 서브 시스템은 다른 비트 맵 또는 다른 비트 맵을 백업하기 위해이 메모리 공간을 필요로하지만 다른 사각형이나 다른 픽셀 포맷을 백업 할 수 있습니다.

Scan0 비트 맵의 ​​내부 데이터를 나타내고 있지 않지만, 및 LockBits(...| ImageLockModeRead...) 동안 GDI 의해 writen COPY는 추상화의 BitmapData가 무엇 UnLockBits() (.. if LockBitswith(.. | ImageLockModeWrite ..)

동안 GDI하여 읽을. 이제 비트 맵 크기와 동일한 사각형과 디스플레이 카드 중 하나와 일치하는 픽셀 모드를 사용하는 경우 GDI 은 비트 맵의 ​​실제 픽셀 저장 주소를 scan0 (사본이 아닌)으로 되 돌리지 만, (또는 자신의 컴퓨터에서만 작동하는 프로그램을 만드십시오).

EDIT 1 : 나는 자물쇠 밖에서 scan0을 사용할 수있어서 운 좋게도 위의 모든 내용을 이미 설명했습니다.원래 bmp PixelFormat을 사용하고이 경우 GDI가 포인터가 아닌 복사본을 제공하도록 최적화되어 있기 때문입니다. 이 포인터는 OS가 해제 할 때까지 유효합니다. 시간이 인 경우 보증인은LockBitsUnLockBits 사이에 입니다. 기간. 여기에 코드를 추가하여 Form에 입력하면 다소 심각하게 테스트 할 수 있습니다. 버튼을 두들 기면서 Rotate180FlipX으로 일종의 '중립적 인'전화를 걸어 충돌을 일으킬 수 있습니다. 비트 맵 내부은 개인입니다. 기간. 운영 체제는 "동작"을 설정하지 않고도 (창을 최소화하고 다른 옵션을 사용할 수 없음) 표현을 변경하는 순간을 결정할 수 있습니다.

EDIT 2 : 질문 : 사용자 버퍼가 지정되지 않은 경우 ReadOnly 또는 WriteOnly 모드를 사용하여 비트 맵을 잠그는 실용적인 차이점이 있습니까?

사용자 버퍼가 있거나 없으면 차이가 있습니다. LockBits (읽기 전용 인 경우) 및/또는 한 세트는 UnlockBits에 1 부 (writeonly 인 경우)입니다. 원하지 않는 사본을 만들지 않도록 신중하게 선택하십시오. 힌트 : 동일한 픽셀 포맷으로 작업하고 있다고 생각하지 마십시오. 논리적으로는 이 아닙니다. 의 경우 버퍼가 64 바이트 버퍼에 모두 수신되면 노이즈 (또는 사용자 버퍼 일 경우 손대지 않음)으로 채워집니다. 잠금을 해제하기 전에 완전히 채우는 것이 좋습니다. (일부 픽셀에서는 파고가 아님). 열거 형의 이름 지정은 잘못된 것입니다. 왜냐하면 WriteOnly | ReadOnly == ReadWrite

LockBits를 사용하여 한 번에 하나의 픽셀에 액세스하는 것은 나쁜 것입니다. 아무도 그렇게하고 싶지 않습니다. 당신이 할 일은 many * many 픽셀 (pointer/scan0 사용)을 만들고/수정하고 (비트 맵에) 잠금 장치를 사용하고 싶다면 Quargey Atomic 연산 (Lock/Marhsal.Copy/UnLock)에서 비트 맵으로 복사하고 (Invalidate()/Redraw) C#에서 큰 이미지 처리를위한 참조 뭔가)

public MainForm() 
{ 
InitializeComponent(); 

pictureBox.SizeMode = PictureBoxSizeMode.StretchImage; 
// use a .gif for 8bpp 
Bitmap bmp = (Bitmap)Bitmap.FromFile(@"C:\Users\Public\Pictures\Sample Pictures\Forest Flowers.jpg"); 
pictureBox.Image = bmp; 
_backImageData = GetBitmapData(bmp); 
_drawBitmap = true; 
_thread= new Thread(DrawtoBitmapLoop); 
_thread.IsBackground= true; 
_thread.Start(); 

button.Text = "Let's get real"; 
button.Click += (object sender, EventArgs e) => 
    { 
     // OK on my system, it does not rreallocate but ... 
     bmp.RotateFlip(RotateFlipType.Rotate180FlipX); 
     // ** FAIL with Rotate180FlipY on my system**  
    }; 
} 
Thread _thread; 
bool _drawBitmap; 
BitmapData _backImageData; 

//Non UI Thread 
private void DrawtoBitmapLoop() 
{ 
    while (_drawBitmap) 
    { 
     ScrollColors(_backImageData); 

     this.Invoke((ThreadStart)(() => 
     { 
      if (!this.IsDisposed) 
       this.pictureBox.Invalidate(); 
     }));     
     Thread.Sleep(100); 
    } 
} 

private unsafe static void ScrollColors(BitmapData bmpData) 
{ 
    byte* ptr = (byte*)bmpData.Scan0; 
    ptr--; 
    byte* last = &ptr[(bmpData.Stride) * bmpData.Height]; 
    while (++ptr <= last) 
    { 
     *ptr = (byte)((*ptr << 7) | (*ptr >> 1)); 
    } 
} 
+1

글쎄,이 기사는 분명히 당신이 말하는 것을 지원합니다. "Scan0은 Bitmap 객체의 실제 픽셀 데이터를 가리키는 것이 아니라 Bitmap 객체의 픽셀 데이터 부분을 나타내는 임시 버퍼를 가리 킵니다." 그러나 Scan0에 쓸 때 표시된 비트 맵은 변경되지만 기사에서는 UnLockBits 이후에만 발생해야한다고 말합니다! 어쩌면 C++에서 작동해야합니다. 누군가 이것을 시도 할 수 있습니까? 실제 Bitmap 데이터에 대해 Scan0을 사용하게 된 것은 운이 좋았다고 말하고 있지만, 나는 한 대 이상의 컴퓨터에서 항상 "운이 좋았습니다". 실제로 일어나는 일을 이해하고 싶습니다. – Pedro77

+3

@ Pedro77 이것은 C++ 문제가 아닙니다. .NET/C++ GDI 방식으로 직접 전달 (ILSpy 사용). 두 경우 모두 작동해야합니다. 편집을 참조하십시오. 요청 된 PixelFormat이 동일한 경우 복사/변환이 필요하지 않습니다. – Boing

+0

"GDI는이 경우 포인터가 아닌 복사본을 제공하기 위해 최적화되었습니다"라고 말합니다. 당신 말이 맞습니다. 로테 플립은 Scan0 변경을 몇 번합니다! 관리되지 않는 버퍼를 사용하여 비트 맵을 만들면 어떻게됩니까? 나는이 경우에 Scan0 wount가 변화한다고 생각한다. 나는 그것을 시험 할 것이다. – Pedro77