Is Graphics.DrawImage too slow for bigger images? - c#

I'm currently working on a game and I wish to have a main menu with background image.
However, I find the method Graphics.DrawImage() really slow. I have made some measurement. Let's assume that MenuBackground is my resource image with resolution 800 x 1200 pixels. I will draw it onto another 800 x 1200 bitmap (I render everything to a buffer bitmap first, then I scale it and finally draw it onto screen - that's how I deal with the possibility of multiple players' resolutions. But it shouldn't affect it in any way, see the next paragraph).
So I've measured the following code:
Stopwatch SW = new Stopwatch();
SW.Start();
// First let's render background image into original-sized bitmap:
OriginalRenderGraphics.DrawImage(Properties.Resources.MenuBackground,
new Rectangle(0, 0, Globals.OriginalScreenWidth, Globals.OriginalScreenHeight));
SW.Stop();
System.Windows.Forms.MessageBox.Show(SW.ElapsedMilliseconds + " milliseconds");
The result is quiet surprising to me - the Stopwatch measures something between 40 - 50 milliseconds. And because the background image is not the only thing to be drawn, the whole menu takes about over 100 ms to display, which implicates observable lag.
I have tried to draw it to Graphics object given by Paint event, but the result was 30 - 40 milliseconds - not much changed.
So, does it mean, that Graphics.DrawImage() is unusable for drawing bigger images? If so, what should I do to improve the performance of my game?

Yes, it is too slow.
I ran into this problem several years ago while developing Paint.NET (right from the start, actually, and it was rather frustrating!). Rendering performance was abysmal, as it was always proportional to the size of the bitmap and not the size of the area that it was told to redraw. That is, framerate went down as the size of the bitmap went up, and framerate never went up as the size of the invalid/redraw area went down when implementing OnPaint() and calling Graphics.DrawImage(). A small bitmap, say 800x600, always worked fine, but larger images (e.g. 2400x1800) were very slow. (You can assume, for the preceding paragraph anyway, that nothing extra was going on, such as scaling with some expensive Bicubic filter, which would have adversely affected performance.)
It is possible to force WinForms into using GDI instead of GDI+ and avoid even the creation of a Graphics object behind the scenes, at which point you can layer another rendering toolkit on top of that (e.g. Direct2D). However, it's not simple. I do this in Paint.NET, and you can see what's required by using something like Reflector on the class called GdiPaintControl in the SystemLayer DLL, but for what you're doing I'd consider it a last resort.
However, the bitmap size you're using (800x1200) should still work OK enough in GDI+ without having to resort to advanced interop, unless you're targeting something as low as a 300MHz Pentium II. Here are some tips that might help out:
If you are using an opaque bitmap (no alpha/transparency) in the call to Graphics.DrawImage(), and especially if it's a 32-bit bitmap with an alpha channel (but you know it's opaque, or you don't care), then set Graphics.CompositingMode to CompositingMode.SourceCopy before calling DrawImage() (be sure to set it back to the original value after, otherwise regular drawing primitives will look very ugly). This skips a lot of extra blending math per-pixel.
Make sure Graphics.InterpolationMode isn't set to something like InterpolationMode.HighQualityBicubic. Using NearestNeighbor will be the fastest, although if there's any stretching it may not look very good (unless it's stretching by exactly 2x, 3x, 4x, etc.) Bilinear is usually a good compromise. You should never use anything but NearestNeighbor if the bitmap size matches the area you're drawing to, in pixels.
Always draw into the Graphics object given to you in OnPaint().
Always do your drawing in OnPaint. If you need to redraw an area, call Invalidate(). If you need the drawing to happen right now, call Update() after Invalidate(). This is a reasonable approach since WM_PAINT messages (which results in a call to OnPaint()) are "low priority" messages. Any other processing by the window manager will be done first, and thus you could end up with lots of frame skipping and hitching otherwise.
Using a System.Windows.Forms.Timer as a framerate/tick timer won't work very well. These are implemented using Win32's SetTimer and result in WM_TIMER messages which then result in the Timer.Tick event being raised, and WM_TIMER is another low priority message which is sent only when the message queue is empty. You're better off using System.Threading.Timer and then using Control.Invoke() (to make sure you're on the right thread!) and calling Control.Update().
In general, do not use Control.CreateGraphics(). (corollary to 'always draw in OnPaint()' and 'always use the Graphics given to you by OnPaint()')
I recommend not using the Paint event handler. Instead, implement OnPaint() in the class you're writing which should be derived from Control. Deriving from another class, e.g. PictureBox or UserControl, will either not add any value for you or will add additional overhead. (BTW PictureBox is often misunderstood. You will probably almost never want to use it.)
Hope that helps.

Although this is an ancient question and WinForms is an ancient Framework, I would like to share what I have just discovered by accident: drawing a Bitmap into a BufferedGraphics and rendering it afterwards to the graphics context provided by OnPaint is way faster than drawing the Bitmap directly to OnPaint's graphics context - at least on my Windows 10 machine.
That's surprising because intuitively I had assumed that it would be slightly slower to copy data twice (and so I thought that this is usually only justified when one wants to do double-buffering manually). But obviously there is something more sophisticated going on with the BufferedGraphics object.
So create a BufferedGraphics in the constructor of the Control that shall host the Bitmap (in my case I wanted to draw a fullscreen bitmap 1920x1080):
using (Graphics graphics = CreateGraphics())
{
graphicsBuffer = BufferedGraphicsManager.Current.Allocate(graphics, new Rectangle(0,0,Screen.PrimaryScreen.Bounds.Width,Screen.PrimaryScreen.Bounds.Height));
}
and use it in OnPaint (while voiding OnPaintBackground)
protected override void OnPaintBackground(PaintEventArgs e) {/* just rely on the bitmap to fill the screen */}
protected override void OnPaint(PaintEventArgs e)
{
Graphics g = graphicsBuffer.Graphics;
g.DrawImage(someBitmap,0,0,bitmap.Width, bitmap.Height);
graphicsBuffer.Render(e.Graphics);
}
instead of naively defining
protected override void OnPaintBackground(PaintEventArgs e) {/* just rely on the bitmap to fill the screen */}
protected override void OnPaint(PaintEventArgs e)
{
e.Graphics.DrawImage(someBitmap,0,0,bitmap.Width, bitmap.Height);
}
See the following screenshots for a comparison of the resulting MouseMove event frequency (I am implementing a very simple bitmap sketching control). At the top is the version where the Bitmap is drawn directly, at the bottom BufferedGraphics is used. I moved the mouse at about the same speed in both cases.

GDI+ is probably not the best choice for games. DirectX/XNA or OpenGL should be preferred as they utilize whatever graphics acceleration is possible and are very fast.

GDI+ is not a speed demon by any means. Any serious image manipulation usually has to go into the native side of things (pInvoke calls and/or manipulation via a pointer obtained by calling LockBits.)
Have you looked into XNA/DirectX/OpenGL?. These are frameworks designed for game development and will be orders of magnitude more efficient and flexible than using a UI framework like WinForms or WPF. The libraries all offer C# bindings.
You could pInvoke into native code, using functions like BitBlt, but there is overhead associated with crossing the managed code boundary as well.

Related

Should Double Buffering be done on a panel where I'm already drawing from a Bitmap, to minimize flicker?

I understand double buffering a panel basically means all the drawing is done to a buffer and then the buffer is copied directly to the drawing surface.
I am drawing to a Bitmap, and then drawing that Bitmap to my panel, using Graphics.DrawImage, so that I have the Bitmap saved and can make updates to it without having to re-do all the draw operations.
Is this the same as Double Buffering the panel, as far as the flickering of the panel is concerned?
Or, should I be double buffering and re-doing all my drawing, if that means I will get lower flicker?
Edit:
I am plotting points with DrawLine, the points are generated dynamically and I don't store a point once I am done plotting it. If I do Double Buffer, I will also have to incur the memory overhead of storing all the points I plot.
The optimal solution will mostly depend on what you draw, how many operations, how many pixels involved and the total size of the drawing surface.
One common example is a drawing program, where the user piles strokes upon strokes, each consisting of hundreds of points..
In general it is recommended to let the system take care of double-buffering the control you draw on. It will do a better job than you can hope for..
So you should re-draw in the Paint event and not try to implement your own buffered bitmap drawing in order to get rid of flicker.
The flicker will go away but with a huge number of drawing operations this will be slow and cause lags.
To avoid lags you can combine the best of both methods:
draw the graphics until they get too many; it depends on the drawing calls (basically the number of pixels involved) and the speed of your system whether you can afford a few hundred or tens of thousands of drawing calls before you notice lags, say after N calls.
Then cache the first N drawing calls by drawing into a bitmap which you then set as the Panel's BackgroundImage. All drawing from N+1 will still be drawn onto the panel's surface in the Paint event. When you reach 2*N you create another version of the caching image, start surface drawing at 2*N+1 and so forth..
Double buffer should work a bit differently. Lets say while you modify buffer A, it should draw using buffer B; then when you start modifying buffer B, it should read from A.
So, the idea is to not write the buffer being read. Therefore, using external buffer and copying it to drawing buffer doesnt seem to be same as double buffer. Actually, it is possible to write buffer by copying external one while it is being read.

How to show an image in PictureBox if the picture can be from 10x10 to 500x500

Now I used next code to download picture from desctop to PictureBox
OpenFileDialog openFileDialog = new OpenFileDialog();
openFileDialog.Filter = "BMP|*.bmp";
if (openFileDialog.ShowDialog() != DialogResult.OK)
return;
pictureBox.Load(openFileDialog.FileName);
pictureBox.SizeMode=PictureBoxSizeMode.StretchImage;
pictureBox.BorderStyle = BorderStyle.Fixed3D;
If I used a normal picture(100x100) it looks nice(unfuzzy).
If I used a smal picture(15x15) it looks fuzzy.
I want to know how to make them unfuzzy( they can look like bricks, but they need to be unfuzzy);
Waiting result for small picture need to look like this
Waiting result for normal picture need to look like this
The fuzziness comes from image interpolation. To have your desired blocky look, you need to use nearest neighbour interpolation.
Thankfully, .NET has this build into. You can set to the interpolation mode to the GDI Graphics object which renders your PictureBox.
To do so I'd recommend exteding the PictureBox user control and adding an InterpolationMode parameter. Then, you can override the OnPaint method, and use your interpolation mode.
public class InterpolatingPictureBox : PictureBox
{
public InterpolationMode InterpolationMode { get; set; }
protected override void OnPaint(PaintEventArgs eventArgs)
{
eventArgs.Graphics.InterpolationMode = InterpolationMode;
base.OnPaint(eventArgs);
}
}
Here is a 16x16 image: , upscaled to 256x256 using
NearestNeighbour (left)
Bilinear (which is default, middle)
HighQualityBilinear (right)
All are rendered on the InterpolatingPictureBox control.
You have a SERIOUS problem. There is no easy solution.
I want to know how to make them unfuzzy
Impossible. Sharpening (which is what you want) of images to larger resolution is possible within reason, but this takes seriously advanced programs that run for quite some time. Not talking as programmer here - but this is a standard step in printing fine art photography. Photos have a relatively low resolution (mine come at up to 43 megapixel) and printers a much higher one.
There are some AA approaches that are easy, but 10x10 sized up t0 100x100 is going to challenge even the most advanced programs - 10x10 is nothing to work with. You talk of 10x starting with no data on the beginning.
You CAN turn off anti aliasing, resize the image in code (easy to do) with no anti aliasing - but this will turn real pictures looking UGLY. .NET has no real high level sharpening functionality and again, 10x10 is not enough to start working with.
The best you can do is not load them into a picture box. Load them into a bitmap, then draw that onto a new bitmap of optimal size (100x100). This at least gives you full control over the resizing.
The code for this you can find at
Scaling a System.Drawing.Bitmap to a given size while maintaining aspect ratio
while
Image resizing in .Net with Antialiasing
tells you how to select anti aliasing.
But again: expect quite little. Those anti aliasing modes are purely technical and not anything that you use for photos. Plus selecting the right one depends on the input image - so you can use a default or give the user a choice. I am probably overcomplicating things, but outside programming I actually DO deal with images that need to be scaled up with as little image loss as possible for large printing.

What is the fastest way to draw thousands of lines in WinForms application

I have WinForms application. I made an user control, which draws a map from coordinates of ca 10k lines. Actualy, not all lines are straight ones, but when the map is zoomed out fully - Bezier curves are irrelevant and are replaced with straight lines.
When the map is zoomed, I have smaller number of lines and curves, so the drawing is fast enough (below 15ms). But when it's zoomed out fully - I need to draw all lines (because all fit into viewport). This is painfully slow. On my very fast machine it takes about 1000ms, so on slower machines it would be an overkill.
Is there a simple way to speed up the drawing?
I use Graphics object for drawing and I set Graphics.Scale property to my map fit into my control.
Does this slow things down?
I use Graphics.TranslateTransform() to ensure the whole map is visible.
Both scale and translate is set only once in OnPaint() event handler.
Then there is a loop which draws ca 10k lines. And I just see them drawing on the screen.
Maybe WPF container would help?
Well, I could probably simplify the map to merge some lines, but I wonder if it's worth the effort. It would complicate the code greatly, would introduce much more calculations, use extra memory and I don't know if at the end of the day it would be considerably faster.
BTW, I tested that processing of all lines (converting from one structure to another with some aditional calculations) takes ca 10ms on my machine. So - the drawing alone costs 100x more time.
EDIT:
Now here's the new problem. I've turned double buffering on with:
SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint | ControlStyles.OptimizedDoubleBuffer, true);
Here's my messy OnPaint() handler:
protected override void OnPaint(PaintEventArgs e) {
base.OnPaint(e);
if (Splines == null) return;
var pens = new[] {
new Pen(TrackColor),
new Pen(TrackColor),
new Pen(RoadColor),
new Pen(RiverColor),
new Pen(CrossColor)
};
var b = Splines.Bounds;
Graphics g = e.Graphics;
g.PageScale = _CurrentScale;
g.TranslateTransform(-b.Left, -b.Top);
int i = 0;
foreach (var s in Splines) {
g.DrawLine(pens[s.T], s.A, s.D);
if (++i > 100) break;
//if (s.L) g.DrawLine(pens[s.T], s.A, s.D);
//else g.DrawBezier(pens[s.T], s.A, s.B, s.C, s.D);
}
foreach (var p in pens) p.Dispose();
}
Take my word the code works, if I only remove OptimizedDoubleBuffer from styles. When double buffering is on the handler executes properly, each DrawLine is executed with correct params. But the graphics is not displayed. CPU usage during resizing is next to zero. Like all DrawLine calls were ignored. What's happening here?
In a related post I've seen recently but can't find, the OP claimed to have seen a large speed-up when switching his control to use double-buffering. Apparently there's a substantial hit for drawing stuff to the screen.
Another thing you could try is decimating the point lists in the lines you draw when zoomed out. Instead of doing the decimation each frame, you could do it only once each time the zoom is changed.
Try double buffering as a possible solution or try to reduce the number of lines. Only testing will give you an answer for your application.
Winforms Double Buffering
Double buffering with Panel
The feasibility of this really depends on if you're using anti-aliasing, if the thing can rotate, if the thickness has to be very accurate, etc.
However you can always draw all the lines into a bitmap, then simply redraw the bitmap unless the map data itself has actually changed. Of course then you get into having different bitmaps for different zoom levels, hiding and showing them, multiple bitmaps in a grid for the high details etc.
It's definitely not ideal, but if you really do need to draw thousands of lines on a 20ms refresh though.. it might be your only real option.
Or you could use lower level of drawing, outside GDI+. one such example is SlimDX. This wrapper allows you to create a directX device write from your windows controls and forms. Once DirectX is in action, the speed can increase up to several times.
2ndly, when drawing on win panel even with DoubleBuffered enabled, you always have to Invalidate the panel which asks the Environment to call the OnPaint event which actual draws using the system provided Graphics object. This invalidation usually requires a timer with fire rate more than 30 to five you a feeling of smooth playback. Now, when the load increases, the subsequent timer event is delayed since everything is happening under a single thread. And the timer must Yield the thread for around 25ms after every fire (windows OS limitation). Cross Thread access ia not allowed, using which a System.Threading.Timer could have prevent this jitter.
See this link for an example where I have tried to transfer my existing GDI code to DirectX. The code uses a lot of graphics attributes which i have incorporated in the wrapper which can draw on both GDI and DirectX.
https://drive.google.com/file/d/1DsoQl62x2YeZIKFxf252OTH4HCyEorsO/view?usp=drivesdk

How to prevent PictureBox internally refreshes itself?

I have a very fast loop which renders animation in a Bitmap buffer and adds filter to it (by using LockBits/UnlockBits to access to the raw data and Marshaling changes to it.) in an independent thread.
I wanted to figure out a way to display the render on the Form, real-time, so I created a PictureBox and linked its Image to the bitmap I created. Everytime immediately after the bitmap is unlocked, I refreshed the PictureBox (using delegate, to do cross-threading) so that the rendering is updated properly.
It's totally fine and works very fast, but one big problem came out as I tried dragging the form to the border of the screen, to see if any bug would appear, and oops, the app collapse..saying 'the bitmap is being locked' This happens when either there's other window moving above the PictureBox or the PictureBox is dragged partially out of the screen. I suspice it because PictureBox can refresh itself when redraw is neccessary, and it does when the bitmap is still being locked. So...any way to sovle this problem? Or anyother ways to render the my animation better?
One of possible solutions could be is create your custom MyPictureBox : PictureBox (say) class which override OnPaintBackground, like this:
protected override OnPaintBackground(...)
{
// nothing, an empty method
}
But I'm not very sure that this will work, you should to check this by yourself.
What I would do, personally, considering your comment:
I have a very fast loop which renders animation in a Bitmap buffer and
adds filter to it (by using LockBits/UnlockBits to access to the raw
data and Marshaling changes to it.) in an independent thread
just forget about PictureBox, cause I found it, personally, too generic and non suitable for high performance rendering. Just write a simple class that handles the drawing of specified bitmap on specified surface.
Imo, this is a best choice.
You can't do that.
Instead, you should copy the image (on the background thread) and put the copy in the PictureBox.
For better performance, you can swap between two images to avoid creating too many images.

WPF - ScreenSaver graphics performance improvements

I took this WPF-VS2008 ScreenSaver template and started to make a new screen saver. I have some experience with winForms-platform (GDI+) screen savers, so i am little bit lost with WPF.
Background-element for my screen saver is Canvas.
A DispatcherTimer tick is set to 33 msec, which is ~ 30 FPS.
Background-color is just one huge LinearGradientBrush.
On the screen I have (per available screen, on my local computer i have 2) n-Ellipses drawn with randomly-calculated (Initialization) Background colors + Alpha channel. They are all in Canvas's Children collection.
I'm moving those Ellipses around the screen with some logic (every DispatcherTimer tick). I make a move per-ellipse, and then just call Canvas.SetLeft(...) and Canvas.SetTop(...) for each Ellipse.
If N (number of Ellipses) is higher > 70-80, i begin to notice graphics slow-downs.
Now, i wonder, if there is anything i could do to improve the graphic-smoothness when choosing higher N-values ? Can I "freeze" "something" before moving my Ellipses and "un-freeze" "something" when i'm finished ? Or is there any other trick i could do?
Not that i would be too picky about mentioned performance drop downs - becouse when N==50, everything works smooth as it should. Even if Ellipses are ALL in the SAME place (loads of transparency stuff), there are no problems at all.
Have you tried rendering in the CompositionTarget.Rendering event, rather than in a timer? I've gotten impressive performance in a 3D screen saver when using the Rendering event and doing my own double buffering. (See http://stuff.seans.com/2008/08/21/simple-water-animation-in-wpf/ , http://stuff.seans.com/2008/08/24/raindrop-animation-in-wpf/ , and http://stuff.seans.com/2008/09/01/writing-a-screen-saver-in-wpf/ )
You will improve performance if you call the Freeze method on objects that inherit from Freezable - brushes for example.
The reason is that Freezable supports extra change notifications that have to be handled by the graphics system, when you call Freeze the object can no longer change and so there are no more change notifications.
For an example of this notification system, if you create a brush, use it to paint a rectangle (for example) and then change the brush the on-screen rectangle will change color.
It is not possible to unfreeze something once it has been frozen (although a copy of the object is unfozen by default). Double buffering is also enabled by default in WPF so you cannot gain here.
Once way to improve performance if not already done is to use geometry objects such as Ellipse Geometry rather than shapes if you do not need to the all of the events as these are lighter weight.
I also have found this MSDN Article Optimizing Performance: 2D Graphics and Imaging that suggests a CachingHint may help along with some other tips.
Finally ensure that you are using the latest service pack one as it has many performance improvements outlined here

Categories

Resources