2013-11-24 2 views
6

WriteableBitmap의 픽셀 데이터를 직접 읽고 쓸 수 있습니까? 현재 WriteableBitmapEx의 SetPixel()을 사용하고 있지만 속도가 느리고 오버 헤드없이 픽셀에 직접 액세스하려고합니다.WriteableBitmap의 원시 픽셀 데이터를 편집 하시겠습니까?

나는 한 동안 HTML5의 캔버스를 사용하지 않은, 그러나 나는 올바르게 기억 경우 숫자의 단일 배열로의 이미지 데이터를 얻을 수와 그 종류의 내가 미리

감사를 찾고 있어요

답변

22

이 더 직접 Lock를 사용하여 쓰기 가능한 비트 맵의 ​​데이터에 액세스 할 수 있습니다, 귀하의 질문에 대답하기 위해,하지만은 다음과 같습니다 간단한 예를 들어 , 당신이 좀 걸릴 수 있습니다 일반적으로 그림의 내용을 기반으로 도면을 작성하지 않는 한 필요하지 않습니다. 더 일반적으로 새로운 버퍼를 생성하여 비트 맵으로 만들 수 있습니다.

즉, 픽셀 조작을 사용하지 않고도 혁신적인 드로잉을 수행 할 수있는 WPF에는 많은 확장 점이 있습니다. 대부분의 컨트롤에서 기존의 WPF primitives (테두리, 선, 사각형, 이미지 등)이 충분합니다. 많은 요소를 사용하는 것에 대해 염려하지 말고 사용하기에 저렴합니다. 복잡한 컨트롤의 경우 DrawingContext을 사용하여 D3D 프리미티브를 그릴 수 있습니다. 이미지 효과의 경우 을 Effect class으로 사용하거나 내장 효과 (흐림 및 그림자)를 사용할 수 있습니다.

그러나 상황에 직접적인 픽셀 액세스가 필요한 경우 pixel format을 선택하고 글을 시작하십시오. BGRA32는 이해하기 쉽기 때문에 BGRA32를 제안하고 아마도 가장 공통적 인 논의 일 것입니다.

BGRA32는 픽셀 데이터가 이미지의 파랑, 녹색, 빨강 및 알파 채널을이 순서로 나타내는 4 바이트로 메모리에 저장된다는 의미입니다. 이는 각 픽셀이 32 비트 정수로 저장하기 위해 4 바이트 경계로 끝나기 때문에 편리합니다. 32 비트 정수를 처리 할 때는 대부분의 플랫폼에서 순서가 바뀌므로 염두에 두십시오 (복수 플랫폼을 지원해야하는 경우 런타임시 올바른 바이트 순서를 확인하려면 BitConverter.IsLittleEndian을 확인하십시오. x86 및 x86_64는 모두 리틀 엔디안입니다)

이미지 데이터는 하나의 행을 이미지의 너비로 구성하는 하나의 너비 인 stride 인 가로 스트립에 저장됩니다. stride 너비는 항상 선택한 형식의 픽셀 당 바이트 수를 곱한 이미지의 픽셀 너비보다 크거나 같습니다. 특정 상황에서는 스트라이드가 특정 아키텍처에만 해당하는 width * bytesPerPixel보다 길어질 수 있으므로 폭을 곱하는 대신 스트라이드 너비를 사용하여 행의 시작을 계산해야합니다. 우리가 4 바이트의 와이드 픽셀 포맷을 사용하고 있기 때문에, 우리의 보폭은 width * 4이 되겠지만, 그것에 의존해서는 안됩니다.언급 한 바와 같이

, 난 당신이 기존 이미지에 액세스하는 경우 WritableBitmap가 사용하는 것이 좋습니다 것입니다 경우에만, 그 아래의 예 그래서 :

전/후 :

image run through below algorithm

// must be compiled with /UNSAFE 
// get an image to draw on and convert it to our chosen format 
BitmapSource srcImage = JpegBitmapDecoder.Create(File.Open("img13.jpg", FileMode.Open), 
    BitmapCreateOptions.None, BitmapCacheOption.OnLoad).Frames[0]; 

if (srcImage.Format != PixelFormats.Bgra32) 
    srcImage = new FormatConvertedBitmap(srcImage, PixelFormats.Bgra32, null, 0); 

// get a writable bitmap of that image 
var wbitmap = new WriteableBitmap(srcImage); 

int width = wbitmap.PixelWidth; 
int height = wbitmap.PixelHeight; 
int stride = wbitmap.BackBufferStride; 
int bytesPerPixel = (wbitmap.Format.BitsPerPixel + 7)/8; 

wbitmap.Lock(); 
byte* pImgData = (byte*)wbitmap.BackBuffer; 

// set alpha to transparent for any pixel with red < 0x88 and invert others 
int cRowStart = 0; 
int cColStart = 0; 
for (int row = 0; row < height; row++) 
{ 
    cColStart = cRowStart; 
    for (int col = 0; col < width; col++) 
    { 
     byte* bPixel = pImgData + cColStart; 
     UInt32* iPixel = (UInt32*)bPixel; 

     if (bPixel[2 /* bgRa */] < 0x44) 
     { 
      // set to 50% transparent 
      bPixel[3 /* bgrA */] = 0x7f; 
     } 
     else 
     { 
      // invert but maintain alpha 
      *iPixel = *iPixel^0x00ffffff; 
     } 

     cColStart += bytesPerPixel; 
    } 
    cRowStart += stride; 
} 
wbitmap.Unlock(); 
// if you are going across threads, you will need to additionally freeze the source 
wbitmap.Freeze(); 

그러나 기존 이미지를 수정하지 않는 경우에는 실제로 필요하지 않습니다. 예를 들어, 모든 안전 코드를 사용하여 바둑판 패턴을 그릴 수 있습니다 :

출력 :

checkerboard pattern at 25 percent zoom

// draw rectangles 
int width = 640, height = 480, bytesperpixel = 4; 
int stride = width * bytesperpixel; 
byte[] imgdata = new byte[width * height * bytesperpixel]; 

int rectDim = 40; 
UInt32 darkcolorPixel = 0xffaaaaaa; 
UInt32 lightColorPixel = 0xffeeeeee; 
UInt32[] intPixelData = new UInt32[width * height]; 
for (int row = 0; row < height; row++) 
{ 
    for (int col = 0; col < width; col++) 
    { 
     intPixelData[row * width + col] = ((col/rectDim) % 2) != ((row/rectDim) % 2) ? 
      lightColorPixel : darkcolorPixel; 
    } 
} 
Buffer.BlockCopy(intPixelData, 0, imgdata, 0, imgdata.Length); 

// compose the BitmapImage 
var bsCheckerboard = BitmapSource.Create(width, height, 96, 96, PixelFormats.Bgra32, null, imgdata, stride); 

그리고 당신은 바이트로 작성하는 경우 당신은 정말도, Int32 형식 중간 필요하지 않습니다 배열 직접.

출력 :

Gradient produced from below

// draw using byte array 
int width = 640, height = 480, bytesperpixel = 4; 
int stride = width * bytesperpixel; 
byte[] imgdata = new byte[width * height * bytesperpixel]; 

// draw a gradient from red to green from top to bottom (R00 -> ff; Gff -> 00) 
// draw a gradient of alpha from left to right 
// Blue constant at 00 
for (int row = 0; row < height; row++) 
{ 
    for (int col = 0; col < width; col++) 
    { 
     // BGRA 
     imgdata[row * stride + col * 4 + 0] = 0; 
     imgdata[row * stride + col * 4 + 1] = Convert.ToByte((1 - (col/(float)width)) * 0xff); 
     imgdata[row * stride + col * 4 + 2] = Convert.ToByte((col/(float)width) * 0xff); 
     imgdata[row * stride + col * 4 + 3] = Convert.ToByte((row/(float)height) * 0xff); 
    } 
} 
var gradient = BitmapSource.Create(width, height, 96, 96, PixelFormats.Bgra32, null, imgdata, stride); 

편집 : 분명히, 당신은 이미지 편집기의 어떤 종류를 만들기 위해 WPF를 사용하려고합니다. 난 여전히 셰이프 및 소스 비트 맵에 대한 WPF 기본 형식을 사용하고 RenderTransform의 비트 맵 효과 Effect과 같은 WPF 모델 내에서 모든 것을 유지하고 크기 변환, 회전을 구현합니다. 그러나 그것이 당신을 위해 작동하지 않는 경우, 우리는 다른 많은 옵션이 있습니다. 당신은 아래 WritableBitmap와 함께 사용할 PixelFormat을 선택 한 RenderTargetBitmap에 렌더링 WPF 프리미티브를 사용할 수

:

Canvas cvRoot = new Canvas(); 
// position primitives on canvas 

var rtb = new RenderTargetBitmap(width, height, dpix, dpiy, PixelFormats.Bgra32); 
var wb = new WritableBitmap(rtb); 

그런 다음 비트 맵으로로 렌더링 GDI 스타일의 명령을 실행하는 WPF DrawingVisual을 사용할 수 있습니다 RenderTargetBitmap 페이지의 샘플에서 시연했습니다.

당신은 InteropBitmapBitmap.GetHBitmap 방법에서 검색된 HBITMAP에서 System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap를 사용하여 생성하여 GDI를 사용할 수 있습니다. 그래도 HBITMAP을 유출하지 않도록하십시오.

+0

답변 주셔서 감사합니다. 귀하의 코드를 테스트 해보려고했지만 Pbgra32 형식을 사용해야합니다. 전환 코드가 작동하도록 노력했지만 광산은 BitmapSource가 아닌 WriteableBitmap으로 저장되므로 얻을 수 없습니다. 그것은 작동합니다. WriteableBitmap을 Bgra32로 변환 할 수 있습니까? – Oztaco

+0

@ leaf68,'WritableBitmap'은'BitmapSource'에서 상속 받고'FormatConvertedBitmap' 클래스를 사용하여 pixelformats간에 변환 할 수 있지만 다시는 필요하지 않습니다. 당신이하려는 일에 대해 설명해 주시겠습니까? PBGRA는 일반적으로 편집이나 생성이 아닌 이미지 저장에만 사용됩니다. – Mitch

+0

셰이프를 그리기 위해 WriteableBitmapEx 라이브러리를 사용하고 있으며 Pbgra만을 입력으로 허용한다는 사실을 알았습니다. 흠, Pbgra의 원시 데이터를 Bgra와 비슷하게 편집 할 수 있습니까? 알파 값이 색상에 미리 혼합 되었기 때문입니까? – Oztaco

1

Lock() 메서드를 호출하고 나중에 BackBuffer 속성을 사용하여 원시 픽셀 데이터에 액세스 할 수 있습니다. 작업이 끝나면 AddDirtyRectUnlock에 전화하는 것을 잊지 마십시오. 아래 있듯이,,, Unlock 패턴을 쓰기 http://cscore.codeplex.com/SourceControl/latest#CSCore.Visualization/WPF/Utils/PixelManipulationBitmap.cs

+0

감사합니다! 귀하의 링크 (다른 코드와 함께)에서'* ((int *) pBackBuffer) = color_data;'를 사용하여 설정했습니다. 그러나 한 가지 질문 : 어떻게 그것을 똑같이 읽을 수 있습니까? – Oztaco

+0

또한 어떻게 알파 값을 설정할 수 있습니까? – Oztaco

+0

@ leaf68, 포인터 산술을 사용하여 직접 비트 맵에 액세스하므로, 쓰기와 같은 방식으로 읽을 수 있습니다 ('color_data = * ((int *) pBackBuffer)'). ARGB 형식에서 MSB는 8 비트 알파 값을 보유합니다. 비트 산술을 수행하여이를 설정할 수 있습니다. 예 :'currentValue & 0x00ffffff | (newAValue << 24)는 임의의 바이트로 설정합니다. MSDN (영문) 안전하지 않은 코드에 대한 자세한 내용은 http://msdn.microsoft.com/en-us/library/aa664774(v=vs.71).aspx를 참조하십시오. – Mitch

1

는 좋은 긴 두통 후에, 나는 비트 연산을 사용하지 않고 그것을 할 수있는 방법을 설명합니다 this article을 발견하고, 나를 대신 배열로 취급 할 수 있습니다 :

unsafe 
{ 
    IntPtr pBackBuffer = bitmap.BackBuffer; 

    byte* pBuff = (byte*)pBackBuffer.ToPointer(); 

    pBuff[4 * x + (y * bitmap.BackBufferStride)] = 255; 
    pBuff[4 * x + (y * bitmap.BackBufferStride) + 1] = 255; 
    pBuff[4 * x + (y * bitmap.BackBufferStride) + 2] = 255; 
    pBuff[4 * x + (y * bitmap.BackBufferStride) + 3] = 255; 
}