How can I draw a big image in WinForms? - c#

I'm trying to make a floor for a top down style game, where I used to use pictureboxes
I was told that instead of using a Picturebox, I should be using the Graphics.DrawImage(); method, which seems to slightly help but still is very laggy.
My paint function looks like this to draw the background looks like this:
How should I make the drawing less laggy? The image is 2256 by 1504
private void Form1_Paint(object sender, PaintEventArgs e)
{
Graphics g = e.Graphics;
g.DrawImage(PeanutToTheDungeon.Properties.Resources.tutorialFloor, 0, 0);
}

There are two points that I would make here.
Firstly, DO NOT use Properties.Resources like that. Every time you use a property of that object, the data for that resource gets extracted from the assembly. That means that you are creating a new Image object every time the Paint event is raised, which is often. You should use that resource property once and once only, assign the value to a field and then use that field repeatedly. That means that you will save the time of extracting that data and creating the Image object every time.
The second point may or may not be applicable but, if you are forcing the Paint event to be raised by calling Invalidate or Refresh then you need to make sure that you are invalidating the minimum area possible. If you call Refresh or Invalidate with no argument then you are invalidating the whole for, which means that the whole form will be repainted. The repainting of pixels on screen is actually the slow part of the process, so that's the part that you need to keep to a minimum. That means that, when something changes on your game board, you need to calculate the smallest area possible that will contain all the possible changes and specify that when calling Invalidate. That will reduce the amount of repainting and speed up the process.
Note that you don't need to change your drawing code. You always DRAW everything, then the system will PAINT the part of the form that you previously invalidated.

Related

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

Is Graphics.DrawImage too slow for bigger images?

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.

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.

How to manually get instance of Graphics object in WinForms?

I know how to work with object of type Graphics (at least I am able to render images) but I always do that by passing graphics object retrieved from OnPaint method.
I would like to display an image when the app is opened (ie in Form_Load method) but have no clue how to obtain the instance of Graphics object I could use?
Thanks
Using the e.Graphics object that OnPaint() supplies to you is the correct way of doing it. It will run right after the OnLoad() method. The form isn't visible yet in OnLoad.
Getting a Graphics object from Control.CreateGraphics() is supported. However, whatever you draw with this will be wiped out as soon as the form repaints itself. Which happens when the user moves another window across yours (pre-Aero) or when she minimizes and restores or otherwise resizes the window. Use CreateGraphics only ever when animating at a high rate.
If you're attempting to create a graphics object from the surface of your form, you can use this.CreateGraphics
If you are attempting to create a new Image, you can always initialize an Image and then call Graphics.CreateGraphics.FromImage(YourImage) e.g.
Bitmap b = new Bitmap(100,100);
var g = Graphics.CreateGraphics.FromImage(b);
At this point, any drawing performed to your Graphics object will be drawn onto your image.
None of the preceding answers worked for me. I found Rajnikant Rajwadi solution effective (see https://social.msdn.microsoft.com/Forums/vstudio/en-US/ce90eb80-3faf-4266-b6e3-0082191793f7/creation-of-graphics-object-in-wpf-user-control?forum=wpf)
Here is a horribly condensed call to Graphics.MeasureString(). (please code more responsibly)
SizeF sf = System.Drawing.Graphics.FromHwnd(new System.Windows.Interop.WindowInteropHelper(this).Handle).MeasureString("w", new Font(TheControl.FontFamily.ToString(), (float)TheControl.FontSize));
And how do you plan to use the Graphics object you got in the Load event?
If you want to paint something on the screen, you have to be in the Paint event, or it will be cleared on the next paint.
What you can do: load another (simple) form, with just a picture, and hide it when your main form is loaded.
Since your Load event will probably run on the UI thread. Call DoEvents to make the other form appear.
form.CreateGraphics();
http://msdn.microsoft.com/en-us/library/system.windows.forms.control.creategraphics.aspx
http://msdn.microsoft.com/en-us/library/5y289054.aspx

Winforms: SuspendLayout/ResumeLayout is not enough?

I have a library of a few "custom controls". Essentially we have our own buttons, rounder corner panels, and a few groupboxes with some custom paint. Despite the "math" in the OnPaint methods, the controls are pretty standard. Most of the time, all we do is draw the rounded corners and add gradient to the background. We use GDI+ for all that.
These controls are ok (and very nice looking according to our customers), however and despite the DoubleBuffer, you can see some redrawing, especially when there are 20++ buttons (for example) on the same form. On form load you see the buttons drawing… which is annoying.
I'm pretty sure that our buttons are not the fastest thing on earth but my question is: if double buffer is "on", shouldn't all that redraw happen in background and the Windows subsystem should show the results "instantly" ?
On the other hand, if there's "complex" foreach loop that will create labels, add them to a panel (double buffered) and change their properties, if we suspendlayout of the panel before the loop and resume layout of the panel when the loop is over, shouldn't all these controls (labels and buttons) appear "almost instantly"? This doesn't happen like that, you can see the panel being filled.
Any idea why this is not happening? I know it's hard to evaluate without sample code but that's hard to replicate too. I could make a video with a camera, but trust me on this one, it's not fast :)
We've seen this problem too.
One way we've seen to "fix" it is to completely suspend drawing of the control until we're ready to go. To accomplish this, we send the WM_SETREDRAW message to the control:
// Note that WM_SetRedraw = 0XB
// Suspend drawing.
UnsafeSharedNativeMethods.SendMessage(handle, WindowMessages.WM_SETREDRAW, IntPtr.Zero, IntPtr.Zero);
...
// Resume drawing.
UnsafeSharedNativeMethods.SendMessage(handle, WindowMessages.WM_SETREDRAW, new IntPtr(1), IntPtr.Zero);
One of the things you should look at is whether you have set BackColor=Transparent on any of the child controls of your panels. The BackColor=Transparent will significantly degrade rendering performance especially if parent panels are using gradients.
Windows Forms does not use real transparency, rather it is uses "fake" one. Each child control paint call generates paint call on parent so parent can paint its background over which the child control paints its content so it appears transparent.
So if you have 50 child controls that will generate additional 50 paint calls on parent control for background painting. And since gradients are generally slower you will see performance degradation.
Hope this helps.
I'll approach your problem from a performance angle.
foreach loop that will create labels,
add them to a panel (double buffered)
and change their properties
If that's the order things are done, there's room for improvement. First create all your labels, change their properties, and when they are all ready, add them to the panel: Panel.Controls.AddRange(Control[])
Most of the time, all we do is draw
the rounded corners and add gradient
to the background
Are you doing the same thing over and over again? How are your gradients generated? Writing an image can't be that slow. I once had to create a 1680x1050 gradient in-memory, and it was really fast, like, too fast for Stopwatch, so drawing a gradient can't be so hard.
My advice would be to try and cache some stuff. Open Paint, draw your corners and save to disk, or generate an image in-memory just once. Then load (and resize) as needed. Same for the gradient.
Even if different buttons have different colors, but the same motif, you can create a bitmap with Paint or whatever and at runtime load it and multiply the Color values by another Color.
EDIT:
if we suspendlayout of the panel before the
loop and resume layout of the panel when the loop is over
That's not what SuspendLayout and ResumeLayout are for. They suspend the layout logic, that is, the automatic positioning of the controls. Most relevant with FlowLayoutPanel and TableLayoutPanel.
As for doublebuffering, I'm not sure it applies to custom draw code (haven't tried). I guess you should implement your own.
Doublebuffering in a nutshell:
It's very simple, a couple lines of code. On the paint event, render to a bitmap instead of rendering to the Graphics object, and then draw that bitmap to the Graphics object.
In addition to the DoubleBuffered property, also try adding this to your control's constructor:
SetStyle(ControlStyles.OptimizedDoubleBuffer |
ControlStyles.AllPaintingInWmPaint, true);
And if that ends up not being enough (which I'm gonna go out on a limb and say it isn't), consider having a look at my answer to this question and suspend/resume the redraw of the panel or Form. This would let your layout operations complete, then do all of the drawing once that's done.
You may want to look at the answer to my question, How do I suspend painting for a control and its children? for a better Suspend/Resume.
It sounds like what you are looking for is a "composited" display, where the entire application is drawn all at once, almost like one big bitmap. This is what happens with WPF applications, except the "chrome" surrounding the application (things like the title bar, resize handles and scrollbars).
Note that normally, unless you've messed with some of the window styles, each Windows Form control is responsible for painting itself. That is, every control gets a crack at the WM_ PAINT, WM_ NCPAINT, WM_ERASEBKGND, etc painting related messages and handles these message independently. What this means for you is that double buffering only applies to the single control you are dealing with. To get somewhat close to a clean, composited effect, you need to concern yourself not just with your custom controls that you are drawing, but also the container controls on which they are placed. For example, if you have a Form that contains a GroupBox which in turn contains a number of custom drawn buttons, each of these controls should have there DoubleBuffered property set to True. Note that this property is protected, so this means you either end up inheriting for the various controls (just to set the double buffering property) or you use reflection to set the protected property. Also, not all Windows Form controls respect the DoubleBuffered property, as internally some of them are just wrappers around the native "common" controls.
There is a way to set a composited flag if you are targeting Windows XP (and presumably later). There is the WS_ EX_ COMPOSITED window style. I have used it before to mix results. It doesn't work well with WPF/WinForm hybrid applications and also does not play well with the DataGridView control. If you go this route, be sure you do lots of testing on different machines because I've seen strange results. In the end, I abandoned used of this approach.
Maybe first draw on a control-only 'visible' (private) buffer and then render it:
In your control
BufferedGraphicsContext gfxManager;
BufferedGraphics gfxBuffer;
Graphics gfx;
A function to install graphics
private void InstallGFX(bool forceInstall)
{
if (forceInstall || gfxManager == null)
{
gfxManager = BufferedGraphicsManager.Current;
gfxBuffer = gfxManager.Allocate(this.CreateGraphics(), new Rectangle(0, 0, Width, Height));
gfx = gfxBuffer.Graphics;
}
}
In its paint method
protected override void OnPaint(PaintEventArgs e)
{
InstallGFX(false);
// .. use GFX to draw
gfxBuffer.Render(e.Graphics);
}
In its resize method
protected override void OnSizeChanged(EventArgs e)
{
base.OnSizeChanged(e);
InstallGFX(true); // To reallocate drawing space of new size
}
The code above has been somewhat tested.
I had the same problem with a tablelayoutpanel when switching usercontrols that I wanted displayed.
I completely got rid of the flicker by creating a class that inherited the table, then enabled doublebuffering.
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
namespace myNameSpace.Forms.UserControls
{
public class TableLayoutPanelNoFlicker : TableLayoutPanel
{
public TableLayoutPanelNoFlicker()
{
this.DoubleBuffered = true;
}
}
}
I've had a lot of similar issues in the past, and the way I resolved it was to use a third-party UI suite (that is, DevExpress) rather than the standard Microsoft controls.
I started out using the Microsoft standard controls, but I found that I was constantly debugging issues which were caused by their controls. The problem is made worse by the fact that Microsoft generally does not fix any of the issues which are identified and they do very little to provide suitable workarounds.
I switched to DevExpress, and I have nothing but good things to say. The product is solid, they provide great support and documentation and yes they actually listen to their customers. Any time I had a question or an issue, I got a friendly response within 24 hours. In a couple of cases, I did find a bug and in both instances, they implemented a fix for the next service release.
I have seen bad winforms flicker on forms where the controls referred to a missing font.
This is probably not common, but it's worth looking into if you've tried everything else.

Categories

Resources