Fast Video Display WPF - c#

I am working on a WPF application that needs to display several video streams at a fast frame rate (we would like 30 fps). The video streams are 1920x1080 raw (RGB24) frames (they are stored in a System.Drawing.Bitmap). Does anyone have any ideas on how to achieve this?
More details:
Our previous attempts have used a standard WPF Image control, changing its source for each frame. This worked well for a single stream but now that we have to render multiple streams, it is slowing down.
We have also tried using Direct2D to handle the drawing, using a D3D9 shared surface as the source for an Image control. While this was faster, we are still not able to get a stable 30fps out of it (it jumps between 24-32 fps as things back up).
The video stream is coming in on a background thread, then being marshaled (using the Dispatcher of the window) to the proper UI thread for drawing. All the drawing is then done on the UI thread. We have also tried giving each window its own thread.
I can provide code samples of what we have tried if anyone wants to see.
thanks!

Anyone looking for a solution, we wrote a custom winforms control using DirectX 11 with a highly optimized copy into graphics memory, then hosted the control in a WinformsHost, I can provide some code to anyone who is interested.
Optimized copy into gpu memory
// Using native memcpy for the fastest possible copy
[DllImport("msvcrt.dll", EntryPoint = "memcpy", CallingConvention = CallingConvention.Cdecl, SetLastError = false)]
private static extern IntPtr memcpy(IntPtr dest, IntPtr src, UIntPtr count);
/// <summary>
/// Copies a bitmap to gpu memory
/// </summary>
/// <param name="frame">The image to copy to the gpu</param>
/// <returns>A texture in gpu memory for the bitmap</returns>
public Texture2D CopyFrameToGpuMemory(Bitmap frame)
{
SampleDescription sampleDesc = new SampleDescription();
sampleDesc.Count = 1;
sampleDesc.Quality = 0;
Texture2DDescription texDesc = new Texture2DDescription()
{
ArraySize = 1,
MipLevels = 1,
SampleDescription = sampleDesc,
Format = Format.B8G8R8A8_UNorm,
CpuAccessFlags = CpuAccessFlags.Write,
BindFlags = BindFlags.ShaderResource,
Usage = ResourceUsage.Dynamic,
Height = frame.Height,
Width = frame.Width
};
Texture2D tex = new Texture2D(Device, texDesc);
Surface surface = tex.AsSurface();
DataRectangle mappedRect = surface.Map(SlimDX.DXGI.MapFlags.Write | SlimDX.DXGI.MapFlags.Discard);
BitmapData pixelData = frame.LockBits(new Rectangle(0, 0, frame.Width, frame.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppRgb);
unsafe //!!!
{
byte* pixelDataStart = (byte*)pixelData.Scan0.ToPointer();
byte* mappedRectDataStart = (byte*)mappedRect.Data.DataPointer.ToPointer();
for (int y = 0; y < texDesc.Height; y++)
{
byte* lineLocationDest = mappedRectDataStart + (y * mappedRect.Pitch);
byte* lineLocationSrc = pixelDataStart + (y * pixelData.Stride);
// Copy line by line for best performance
memcpy((IntPtr)lineLocationDest, (IntPtr)lineLocationSrc, (UIntPtr)(texDesc.Width * 4));
}
} //!!!
frame.UnlockBits(pixelData);
mappedRect.Data.Dispose();
surface.Unmap();
surface.Dispose();
return tex;
}

Related

Continous data pass between forms

I have a MainForm that receives images from a camera (15-20 FPS) and extracts data from those images. I want to be able to plot those data, thus I need a Chart object that would update on each frame received.
Unfortunately, the API from the camera (Vimba) I am using is, I think, strangely written, and I can't draw the Chart in the same event that handles the reception of the frames and the extraction of the data. It just doesn't work (in the API, any instruction that involves a graphic object but isn't simply putting the received image in a Bitmap, or putting said bitmap into a PictureBox, doesn't run, I don't know why).
I have been fighting with this issue for a few days already, and one thing I haven't tried yet is passing the data extracted from the image to another Form, and draw the chart there. My problem is that I have no clue how to get the data to be sent to the new form on each frame received. I have already used a simple method to send data to another Form ONCE, but not continuously. Could someone explain to me how to achieve this?
Here is the event that runs when the computer receives a frame from the camera (the data I want to draw is stored in PixelColorCOuntReady) :
private void OnFrameReceived(Frame frame)
{
Bitmap myBitmap = null;
if (true == m_Acquiring)
{
mycamera.QueueFrame(frame);
}
frame.Fill(ref myBitmap);
pictureBoxLiveCamera.Image = myBitmap;
SaveBitmap = myBitmap.Clone(cloneRect, myBitmap.PixelFormat);
Array.Clear(PixelColorCount, 0, 256);
unsafe
{
int width = SaveBitmap.Width;
int height = SaveBitmap.Height;
int bytesPerPixel = 1;
int maxPointerLength = width * height * bytesPerPixel;
int stride = width * bytesPerPixel;
System.Drawing.Imaging.BitmapData bData = SaveBitmap.LockBits(new System.Drawing.Rectangle(0, 0, SaveBitmap.Width, SaveBitmap.Height), System.Drawing.Imaging.ImageLockMode.ReadWrite, SaveBitmap.PixelFormat);
byte* scan0 = (byte*)bData.Scan0.ToPointer();
byte B;
for (int i = 0; i < maxPointerLength; i++)
{
B = scan0[i];
PixelColorCount[B] += 1;
}
SaveBitmap.UnlockBits(bData);
}
PixelColorCountReady = PixelColorCount.DeepClone();
}

Read and write directly to Unlocked Bitmap unmanaged memory (Scan0)

Is that ok to Write and Read directly from a unlocked Bitmap unmanaged memory?
Can I keep using the BitmapData after I UnlockBits of the Bitmap? I did a test app where I can read the pixel of the Bitmap of a PictureBox at mouse position while another thread is writing pixels to the same Bitmap.
EDIT 1: As Boing have pointed out in his answer: "Scan0 does not point to the actual pixel data of the Bitmap object; rather, it points to a temporary buffer that represents a portion of the pixel data in the Bitmap object." from MSDN.
But once I get the Scan0, I'm able to read/write to the Bitmap without the need of Lockbits or UnlockBits! I'm doing this a lot of times in a thread. Accordingly to MSDN, it should not happen, because Scan0 points to a COPY of the Bitmap data! Well, in C# all the test shows that it is not a copy. In C++ I don't know if it works as it should.
EDIT 2: Using the rotate method some times makes the OS to free the Bitmap pixel data copy. Conclusion, it is not safe to read/write an unlocked Bitmap Scan0. Thanks Boing for your answer and comments!
Below is how I get the BitmapData and read and write the pixel value.
/// <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;
}
}
}
No, you cannot. The official explanation is clear about that.
Scan0 does not point to the actual pixel data of the Bitmap object; rather, it points to a temporary buffer that represents a portion of the pixel data in the Bitmap object. The code writes the value 0xff00ff00 (green) to 1500 locations in the temporary buffer. Later, the call to Bitmap::UnlockBits copies those values to the Bitmap object itself.
I would agree that there is a "bug" in UnLockBits(), because every non ImageLockModeUserInputBuf BitmapData should have its field reset (especially scan0) after the 'release/unlock'.
Scan0 GDI managed buffers may still be accessible after UnLockBits, but this is pure luck you do not get a invalid memory reference hard fault. The graphic subsystem may need this memory space to backup another Bitmap, or the same Bitmap but for another rectangle or in another pixelformat.
Scan0 don't represent the internal data of the bitmap, but a COPY, writen to by GDI while LockBits(...| ImageLockModeRead...) and read from by GDI while UnLockBits() (.. if LockBitswith(.. | ImageLockModeWrite ..)
That is what BitmapData abstraction is. Now maybe if you use a rectangle equals to the bitmap size AND a pixelmode matching the one of your display card, GDI may return the actual pixel storage address of the bitmap into scan0 (and not a copy), but you should never rely on that (or make a program that only work on your own computer).
EDIT 1: I allready explained above why you are lucky to be able to use scan0 outside the lock. Because you use the original bmp PixelFormat and that GDI is optimized in that case to give you the pointer and not a copy. This pointer is valid until the OS will decide to free it. The only time there is a garantee is between LockBits and UnLockBits. Period.
Here is the code to add to yours, put it in a Form to test it somewhat seriously. I can make it crash with a kind-of "neutral" call with Rotate180FlipX by hammering the button.
The bitmap internals are private. Period.
The OS may decide any moment to change its representation without even you making "action" on it (like minimizing the window, and zillions other possibilities).
EDIT 2: Your question:is there any practical difference locking a bitmap using ReadOnly or WriteOnly mode when no user buffer is given?
With or without user buffer, there IS a difference. One copy on LockBits(if readonly) AND/OR one copy on UnlockBits(if writeonly). Choose carefully to not do unwanted copies. Hint: stop thinking you are working in the same pixelformat, logically you do not. A write only buffer in 64bpp is received totally filled with noise (or untouched if it is also user buffer). You had better completely fill it before the unlock. (not just poking at some pixels). The naming of enum is misleading because WriteOnly | ReadOnly == ReadWrite
Accessing one pixel at a time using LockBits is BAD. Nobody wants to do that. What you do is to create/modify many*many pixel (using pointer/scan0) and commit them in quazy ATOMIC operation (Lock/Marhsal.Copy/UnLock) to the bitmap (and Invalidate()/redraw if you want to see something)
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));
}
}

DrawingContext to Bitmap file

I have a DrawingContext (part of a Visual or a DrawingGroup), where I draw a bunch of rectangles and/or 1-bit images on top of each other. Think of it as a masking 1-bit image. I would like to convert this into a bitmap image file.
Using RenderTargetBitmap is not an option because it can only render at 32bit pixel format, so if I have to render a 20MB 1-bit image, I will end up with a 640MB (20*32) of memory on my heap. This of course creates magnificent LOH fragmentation, and the application runs out-of-memory on the second shot.
So, I basically need a way to write a 1-bit bitmap file from a drawing context efficiently. Any ideas/suggestions/alternate methods would be appreciated.
A number of ideas, some are a bit convoluted...
Print to XPS then extract Bitmap
You could print the Visual to an XPS Document.
If you're lucky then it will combine the 1bit images that you drew in in the DrawingContext and produce a single bitmap in the XPS file.
For the Rectangles it might keep the Rectangles as vector based information in the XPS (the "Shape" based Rectangle or DrawingContext DrawRectangle might both do this)....if that happens then create a bitmap into which your draw the rectangle part and draw that bitmap into the context.
Once you have the XPS files, you could then parse it and extract the "image" if you are lucky that's what it produced inside. (XPS is just a ZIP file that uses XAML to describe content and uses subdirectories to store bitmap image data files, fonts, etc).
(I think you can use the Package class to access the raw parts of an XPS document, or XpsDocument for a higher level interpretation).
Or just display the XPS in an XPS viewer if your intention is just to provide a way to allow your combined 1bit images to be viewed in a different context.
Convert WPF (XAML) Control to XPS Document
How to create BIG bitmaps with RenderTargetBitmap?
Printing a WPF BitmapImage
Use a Printer Driver that provides Print to Image functionality
This isn't ideal...not least because you have to install a Printer Driver on the machine.....but you might be able to use a "Print to Image" printer driver and then use that to produce your bitmap. Here are some suggestions:
http://sourceforge.net/projects/imageprinter/
http://www.zan1011.com/
Create a GDI+ HBITMAP and do the drawing using GDI+ calls...then wrap it for use by WPF...or save out to disk.
[1] First create a GDI+ Bitmap large enough to hold your composed rendering
(there are a few different ways to do this...one way is to use a WriteableBitmap to provide the backbuffer bits/store...that's if you wanted access to the bits in memory...in your case I don't think you do/need to).
var bmp = new System.Drawing.Bitmap( pixelwidth, pixelheight, System.Drawing.Imaging.Format1bppIndexed );
Or this way if you want WriteableBitmap access.
Getting a DrawingContext for a wpf WriteableBitmap
[2] Convert any WPF BitmapSources to GDI+ Bitmaps so you can draw them onto the GDI+ Graphics context using DrawImage.
You can use CopyPixels to do that on your BitmapSources.
Is there a good way to convert between BitmapSource and Bitmap?
For your rectangles you can just use the GDI+ DrawRectangle rendering command.
[3] Save the GDI+ Bitmap to disk
Use the .Save method on System.Image.Bitmap to save it as a Bitmap.
[4] Use the saved image as the Source to an Image element
(note this will probably use masses of memory when it loads your image even though it's 1bpp...because the WPF rendering path is all 32bpp).
[4] OR wrap the GDI+ Bitmap for use in WPF
You can use InteropBitmap to wrap the GDI+ based bitmap so you can use it as a BitmapSource in WPF. (note it may have to be a 32bpp one....if it has to...then you are back at square 1...but try anyway).
http://arbel.net/2008/10/22/improper-use-of-interopbitmap-can-cause-a-memory-leak/
http://arbel.net/2008/10/22/improper-use-of-interopbitmap-can-cause-a-memory-leak/
Create a Bitmap "Service"
Create another process which acts as a service (doesn't have to be an NT service...could just be a child process you start) to render your combined 1bpp images....there are various ways to communicate with it to give it the rendering commands.
When memory consumption gets too high/the LOH gets fragmented, then you could restart this service.
Other Ideas
You could render using OpenGL (e.g. use OpenTK or SharpGL), or render using DirectX...via the 3D path with D3DImage or Direct2D (whether this behaves the same as RenderTargetBitmap in terms of memory usage...that's to find out).
http://www.codeproject.com/Articles/265903/Using-OpenGL-in-a-WPF-Application
http://www.codeproject.com/Articles/113991/Using-Direct2D-with-WPF
http://www.codeproject.com/Articles/28526/Introduction-to-D3DImage
Try out NET 4.5 as there have been a number of improvements in the LOH Heap:
http://blogs.msdn.com/b/dotnet/archive/2011/10/04/large-object-heap-improvements-in-net-4-5.aspx
http://blogs.msdn.com/b/dotnet/archive/2012/07/20/the-net-framework-4-5-includes-new-garbage-collector-enhancements-for-client-and-server-apps.aspx
I dont think you can do anything better. Since RenderTargetBitmap uses MILcore which you can't access. And I don't think there is any other way to copy Visual. However I think there is one option more. It won't be one-line but I think it will be good enough.
Basically you will render visual block by block(PGBRA32) and convert it into BlackWhite on the fly, and then concat it with Blackwhite buffer. I've started a little example code but in halfway decided that it's not gonna be so easy, but you can finish it.
/// <summary>
/// Renders only part of the visual and returns byte[] array
/// which holds only black/white information.
/// </summary>
/// <param name="oVisual"></param>
/// <param name="x"></param>
/// <param name="y"></param>
/// <param name="width"></param>
/// <param name="height"></param>
/// <returns>black/white pixel information. one pixel=one bit. 1=white, 0=black</returns>
public static byte[] RenderPartially(Visual oVisual,
int x, int y, double width, double height)
{
int nWidth = (int)Math.Ceiling(width);
int nHeight = (int)Math.Ceiling(height);
RenderTargetBitmap oTargetBitmap = new RenderTargetBitmap(
nWidth,
nHeight,
96,
96,
PixelFormats.Pbgra32
);
DrawingVisual oDrawingVisual = new DrawingVisual();
using (DrawingContext oDrawingContext = oDrawingVisual.RenderOpen())
{
VisualBrush oVisualBrush = new VisualBrush(oVisual);
oDrawingContext.DrawRectangle(
oVisualBrush,
null,
new Rect(
new Point(x, y),
new Size(nWidth, nHeight)
)
);
oDrawingContext.Close();
oTargetBitmap.Render(oDrawingVisual);
}
//Pbgra32 == 32 bits ber pixel?!(4bytes)
// Calculate stride of source and copy it over into new array.
int bytesPerPixel = oTargetBitmap.Format.BitsPerPixel / 8;
int stride = oTargetBitmap.PixelWidth * bytesPerPixel;
byte[] data = new byte[stride * oTargetBitmap.PixelHeight];
oTargetBitmap.CopyPixels(data, stride, 0);
//assume pixels in byte[] are stored as PBGRA32 which means that 4 bytes form single PIXEL.
//so the layout is like:
// R1, G1, B1, A1, R2, G2, B2, A2, R3, G3, B3, A3, and so on.
byte [] bitBufferBlackWhite = new byte[oTargetBitmap.PixelWidth
* oTargetBitmap.PixelHeight / bytesPerPixel];
for(int row = 0; row < oTargetBitmap.PixelHeight; row++)
{
for(int col = 0; col < oTargetBitmap.PixelWidth; col++)
{
//calculate concrete pixel from PBGRA32
int index = stride * row + bytesPerPixel * col;
int r = data[index];
int g = data[index + 1];
int b = data[index + 2];
int transparency = data[index + 3];
//determine whenever pixel is black or white.
//note that I dont know the exact process how one converts
//from PBGRA32 to BlackWhite format. But you should get the idea.
bool isBlack = r + g + b + transparency == 0;
//calculate target index and USE bitwise operators in order to
//set right bits.
}
}
return bitBufferBlackWhite;
}
So essentially, set up new WriteableBitmap with BlackWhite format, and then call this function like this:
WriteableBitmap blackWhiteFullBuffer = new WriteableBItmap(....., PIxelFormats.BlackWhite);
for(int x = 0; x < Visual.Width; x += <some uniform amount that divides correctly>)
{
for(int y = 0; y < VIsual.Height; y+= <some uniform amount that divides correctly>)
{
byte[] partialBuffer = PartialRenderer.RenderPartially(Visual, x, y, <uniform amX>,
<uniform amY>);
//concat that partial blackWhite buffer with other blackWhite buffer.
//you do this as long as needed and memory wont grow too much
//hopefully it will be fast enough too.
PartialRenderer.ConcateBuffers(blackWhiteFullBuffer, partialBuffer);
}
}
//then save blackWhiteFullBuffer to HDD if needed.
What about PixelFormats
Edit: (thanks to Anders Gustafsson)
The lower is PixelFormats.BlackWhite, with 1bit per pixel.
So this way, you can convert any BitmapImage to a FormatConvertedBitmap, where you can modify the format to a lower bpp.

Write to buffered graphics surface via pointer manupilation

I need to render a 1027 * 768 bitmap to the client window (same size) and I don't have more than 10-15 ms to complete this task. I am using bufferedGraphics allocated from a bufferedGraphicsContect object and still notice huge performance issues.
I am using unsafe code to perform my copy operations and found unbelievable results. I know the Graphics / BufferedGraphics objects should have some sort of a drawing surface in memory. I was wondering if someone could point me in the right direction on how to write to this surface using Marshal or some other unsafe low level method.
I am in the process of porting an older c# graphics application. I know c# is not designed for heavy graphics and that there are better tools than GDI+ available, unfortunately I don't have those luxuries.
This is what I have come up with so far... Any insight what so ever is greatly apperciated.
byte[] _argbs = null;
static readonly Bitmap _bmUnderlay = Properties.Resources.bg;
static Bitmap _bmpRender = new Bitmap(1024, 768, System.Drawing.Imaging.PixelFormat.Format24bppRgb);
int bmpHeight = Properties.Resources.bg.Height;
int bmpWidth = Properties.Resources.bg.Width;
static BufferedGraphicsContext _bgc = new BufferedGraphicsContext();
internal unsafe void FillBackBuffer(Point cameraPos)
{
// lock up the parts of the original image to read (parts of it)
System.Drawing.Imaging.BitmapData bmd = _bmUnderlay.LockBits(
new Rectangle(cameraPos.X, cameraPos.Y, 1024, 768),
System.Drawing.Imaging.ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format24bppRgb);
// get the address of the first line.
IntPtr ptr = bmd.Scan0;
//if (_argbs == null || _argbs.Length != bmd.Stride * bmd.Height)
// _argbs = new byte[bmd.Stride * bmd.Height];
if (_argbs == null || _argbs.Length != 1024 * 3 * 768)
_argbs = new byte[1024 * 3 * 768];
// copy data out to a buffer
Marshal.Copy(ptr, _argbs, 0, 1024 * 3 * 768);
_bmUnderlay.UnlockBits(bmd);
// lock the new image to write to (all of it)
System.Drawing.Imaging.BitmapData bmdNew = _bmpRender.LockBits(
new Rectangle(0, 0, 1024, 768),
System.Drawing.Imaging.ImageLockMode.ReadWrite, System.Drawing.Imaging.PixelFormat.Format24bppRgb);
// copy data to new bitmap
Marshal.Copy(_argbs, 0, bmdNew.Scan0, 1024 * 3 * 768);
_bmpRender.UnlockBits(bmdNew);
}
private unsafe void _btnGo_Click(object sender, EventArgs e)
{
// less than 2 ms to complete!!!!!!!!
FillBackBuffer(new Point());
using (BufferedGraphics bg = _bgc.Allocate(CreateGraphics(), ClientRectangle))
{
System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
sw.Start();
/////
///
// This method takes over 17 ms to complete
bg.Graphics.DrawImageUnscaled(_bmpRender, new Point());
//
///
/////
sw.Start();
this.Text = sw.Elapsed.TotalMilliseconds.ToString();
bg.Render();
}
}
EDIT:
Forgot to mention I'm looking for a low level alternative to Graphics.DrawImage(), preferrably writing to Graphics surface memory, using pointers? Thanks again
Pay attention to the pixel format of the bitmap. On standard 32bpp video hardware, Format32bppPArgb renders ten times faster than any of the other ones. Because the pixels don't need any translation. The 24bpp format you use now needs to be widened to 32bpp and that doesn't come for free. Don't skip the P of PArgb and don't forget to set the alpha value to 255 in your code.
Using BufferedGraphics is fishy btw. You should always use the one you get for free in the OnPaint method. And you probably don't need one at all since you're blitting this fast. That's an automatic 2x speed-up.

Can I copy this array any faster?

Is this the absolute fastest I could possibly copy a Bitmap to a Byte[] in C#?
If there is a speedier way I am dying to know!
const int WIDTH = /* width */;
const int HEIGHT = /* height */;
Bitmap bitmap = new Bitmap(WIDTH, HEIGHT, PixelFormat.Format32bppRgb);
Byte[] bytes = new byte[WIDTH * HEIGHT * 4];
BitmapToByteArray(bitmap, bytes);
private unsafe void BitmapToByteArray(Bitmap bitmap, Byte[] bytes)
{
BitmapData bitmapData = bitmap.LockBits(new Rectangle(0, 0, WIDTH, HEIGHT), ImageLockMode.ReadOnly, PixelFormat.Format32bppRgb);
fixed(byte* pBytes = &bytes[0])
{
MoveMemory(pBytes, bitmapData.Scan0.ToPointer(), WIDTH * HEIGHT * 4);
}
bitmap.UnlockBits(bitmapData);
}
[DllImport("Kernel32.dll", EntryPoint = "RtlMoveMemory", SetLastError = false)]
private static unsafe extern void MoveMemory(void* dest, void* src, int size);
Well, using Marshal.Copy() would be wiser here, that at least cuts out on the (one time) cost of looking up the DLL export. But that's it, they both use the C runtime memcpy() function. Speed is entirely throttled by the RAM bus bandwidth, only buying a more expensive machine can speed it up.
Beware that profiling is tricky, accessing the bitmap data the first time causes page faults to get the pixel data into memory. How long that takes is critically dependent on what your hard disk is doing and the state of the file system cache.
I'm reading into this that you're looking to do pixel manipulation of a bitmap.
So logically, you're wanting to access the pixels as an array in order to do this.
The problem your facing is fundamentally flawed, and the other guys didn't pick up on this - and maybe this is something they can take away too?
Basically, you've gotten so far to be messing with pointers from bitmaps, I'm going to give you one of my hard earned "secrets".
YOU DON'T NEED TO COPY THE BITMAP INTO AN ARRAY. Just use it as it is.
Unsafe pointers are your friend in this case. When you hit "bitmapData.Scan0.ToPointer()" you missed the trick.
So long as you have a pointer to the first pixel, and the Stride, and you're aware of the number of bytes per pixel, then you're on to a winner.
I define a bitmap specifically with 32 bits per pixel (memory efficient for UInt32 access) and I get a pointer to first pixel. I then treat the pointer as an array, and can both read and write pixel data as UInt32 values.
Code speaks louder than words. have a look.
I salute you for making it this far!
This is untested code, but much is copied from my source.
public delegate void BitmapWork(UInt32* ptr, int stride);
/// <summary>
/// you don't want to forget to unlock a bitmap do you? I've made that mistake too many times...
/// </summary>
unsafe private void UnlockBitmapAndDoWork(Bitmap bmp, BitmapWork work)
{
var s = new Rectangle (0, 0, bmp.Width, bmp.Height);
var locker = bmp.LockBits(new Rectangle(0, 0, 320, 200), ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);
var ptr = (UInt32*)locker.Scan0.ToPointer();
// so many times I've forgotten the stride is expressed in bytes, but I'm accessing with UInt32's. So, divide by 4.
var stride = locker.Stride / 4;
work(ptr, stride);
bmp.UnlockBits(locker);
}
//using System.Drawing.Imaging;
unsafe private void randomPixels()
{
Random r = new Random(DateTime.Now.Millisecond);
// 32 bits per pixel. You might need to concern youself with the Alpha part depending on your use of the bitmap
Bitmap bmp = new Bitmap(300, 200, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
UnlockBitmapAndDoWork(bmp, (ptr, stride) =>
{
var calcLength = 300 * 200; // so we only have one loop, not two. It's quicker!
for (int i = 0; i < calcLength; i++)
{
// You can use the pointer like an array. But there's no bounds checking.
ptr[i] = (UInt32)r.Next();
}
});
}
I would also look at System.Buffer.BlockCopy. This function is also very fast and it might be competetive in this setup as you are copying from managed to managed in your case. Unfortunately I can not provide some performance tests.
I think this is faster (I actually used it):
// Bitmap bytes have to be created via a direct memory copy of the bitmap
private byte[] BmpToBytes_MemStream (Bitmap bmp)
{
MemoryStream ms = new MemoryStream();
// Save to memory using the Jpeg format
bmp.Save(ms, ImageFormat.Jpeg);
// read to end
byte[] bmpBytes = ms.GetBuffer();
bmp.Dispose();
ms.Close();
return bmpBytes;
}
Original Source

Categories