Render Large Canvases in UserControl - c#

I've been having trouble trying to implement this for a couple of days now. I've searched extensively on similar questions in regards to what I'm trying to do but I haven't come across a question that helps my issues directly.
Basically I'm rendering tiles onto a grid on my UserControl class. This is for my Tile Engine based world editor I'm developing. Here is a screenshot of an open world document and some tiles brushed on.
Initially, I was going to use a Bitmap in my control that would be the world's preview canvas. Using a brush tool for example, when you move your mouse and have the left button down, it sets the nearest tile beneath your cursor to the brush's tile, and paints it on the layer bitmap. The control's OnPaint method is overridden to where the layer bitmap is draw with respect to the paint event's clipping rectangle.
The issue with this method is that when dealing with large worlds, the bitmap will be extremely large. I need this application to be versatile with world sizes, and it's quite obvious there are performance issues when rendering large bitmaps onto the control each time it's invalidated.
Currently, I'm drawing the tiles onto the control directly in my control's overridden OnPaint event. This is great because it doesn't require a lot of memory. For example, a (1000, 1000) world at (20, 20) per tile (total canvas size is (20000, 20000)) runs at about 18mb of memory for the whole application. While not memory intensive, it's pretty processor intensive because every time the control is invalidated it iterates through every tile in the viewport. This produces a very annoying flicker.
What I want to accomplish is a way to meet in the middle as far as memory usage and performance. Essentially double buffer the world so that there isn't flickering when the control is redrawn (form resize, focus and blur, scrolling, etc). Take Photoshop for example - how does it render the open document when it overflows the container viewport?
For reference, here's my control's OnPaint override that is using the direct draw method mentioned above.
getRenderBounds returns a rectangle relative to PaintEventArgs.ClipRectangle that is used to render visible tiles, instead of looping through all the tiles in the world and checking if it's visible.
protected override void OnPaint(PaintEventArgs e)
{
WorldSettings settings = worldSettings();
Rectangle bounds = getRenderBounds(e.ClipRectangle),
drawLocation = new Rectangle(Point.Empty, settings.TileSize);
e.Graphics.InterpolationMode =
System.Drawing.Drawing2D.InterpolationMode.NearestNeighbor;
e.Graphics.SmoothingMode =
System.Drawing.Drawing2D.SmoothingMode.None;
e.Graphics.PixelOffsetMode =
System.Drawing.Drawing2D.PixelOffsetMode.None;
e.Graphics.CompositingQuality =
System.Drawing.Drawing2D.CompositingQuality.HighSpeed;
for (int x = bounds.X; x < bounds.Width; x++)
{
for (int y = bounds.Y; y < bounds.Height; y++)
{
if (!inWorld(x, y))
continue;
Tile tile = getTile(x, y);
if (tile == null)
continue;
drawLocation.X = x * settings.TileSize.Width;
drawLocation.Y = y * settings.TileSize.Height;
e.Graphics.DrawImage(img,
drawLocation,
tileRectangle,
GraphicsUnit.Pixel);
}
}
}
Just comment if you need some more context from my code.

The trick is to not use a big bitmap for this at all. You only need a bitmap covering the visible area. Then you draw whatever is visible.
To achieve this you will need to maintain the data separately from the bitmap. This can be a simple array or an array/list with a simple class holding information for each block such as world position.
When your block is within the visible area then you draw it. You may or may not have to iterate through the whole array, but that isn't really a problem (you can also calculate the visible array on a separate thread). You can also make the function more intelligent by creating region indexes so you don't iterate all blocks.
To add a new block to the array, calculate it's canvas position to world coordinates, add it and then render the array again (or the area where the block is drawn).
This is how controls with scrollable areas are drawn by the system too.
Enable double-buffering will keep it clear and flicker-less.
In this case I would also use a panel with separate scroll bars and calculate the scroll-bars' relative position.

Related

Redraw panel contents after ClientSizeChanged

So my application runs in fixed size window and in full screen. The problem I'm facing is how to properly scale the current contents of the panel (which depend on the application use) when the window is resized. This is my current code:
private void Form1_ClientSizeChanged(object sender, EventArgs e)
{
System.Drawing.Drawing2D.Matrix transformMatrix = new System.Drawing.Drawing2D.Matrix();
float px = panel2.Width;
float py = panel2.Height;
panel2.Width = this.Width / 2;
panel2.Height = panel2.Width;
panel2.Location = new Point(this.Width - panel2.Width - 30, 30);
transformMatrix.Scale(panel2.Width / px, panel2.Height / py);
panel2.Region.Transform(transformMatrix);
//Rest of the code
}
But the drawn content doesn't scale, and if I use Invalidate() or Refresh() the drawn content gets cleared (the panel is redrawn empty). What am I missing?
.NET doesn't remember what's drawn on the panel, as simple as that. As soon as anything invalidates the windows bitmap buffer (causing a WM_PAINT), it's going to be repainted again. So, you have to draw what you want to draw using the Paint event (or overriding OnPaint).
However, there is another way that might be easier to implement - don't paint into a Panel. Instead, paint into a PictureBox (or rather, a Bitmap assigned to the Image property of the PictureBox). The Bitmap will be reused when invalidating (and redrawing) the picture box, so nothing will be lost. By using PictureBox.ScaleMode, you can define how you want the picture box to scale the bitmap, and it will do so as well as it can.
In any case, transforming the Region property doesn't do anything useful - you're simply changing the region, not doing anything to the drawing itself. To use 2D transformation matrices, you want to apply them on a Graphics object during the drawing (in Paint handler or OnPaint override) - drawing anything on the Graphics object will then transform everything you're trying to draw, which in your case means scaling the painting.
So you have to decide: do you want to just scale a stored bitmap with the painted image, or do you want to redraw it all from scratch (which also means you can pick any level of detail you can provide)?
I think that you're mistaking what the Region property is meant for. According to the MSDN docs (empasis mine, replace 'window' with 'control' when reading):
The window region is a collection of pixels within the window where the operating system permits drawing. The operating system does not display any portion of a window that lies outside of the window region. The coordinates of a control's region are relative to the upper-left corner of the control, not the client area of the control.
All that you're doing is changing the region that the OS will allow painting, which explains why you're not seeing anything. I think that you should be resizing the control when the form is resized, either through Anchor, or through my preference of Dock with several controls, or a panel like TableLayoutPanel where it will handle scaling and relative sizing for you.
Thank you for your answers, but I wrote my own function and logic that serves the purpose for this application. Basically the function checks for the state of the application variables, and calls the appropriate function that originally drew the content, and since those functions use the panel width and height as arguments they properly scale the drawn content and retain the drawing composition.
P.S. I'll accept Luaan's answers since it offers a valid alternative and is complete.

Using Bitmap in Bouncing Balls

I have a WinForm application 'Bouncing Balls' , and I need to paint the balls
on a bitmap and present the bitmap on this form.
I have a plusButton that adds new ball, and i'm saving each new ball in a list.
Now, the Form_Paint method is telling to each ball to draw himself, it works fine
until there are a lot of balls and the all application become very slow..
Here is my Code:
The paint method of the form code:
private void Form1_Paint(object sender, PaintEventArgs e)
{
ballsArray.drawImage(bmp,e, ClientRectangle);
}
NOTE: ballsArray is from type AllBalls, this is a class that wraps the ball methods, inside his c'tor i'm creating a list that keeps each ball. the bmp, is created when the form is loading - on Form_Load() method.
The drawImage of ballsArray code:
public void drawImage(Bitmap bmp,PaintEventArgs e, Rectangle r)
{
foreach (Ball b in allBalls)
{
b.drawImage(bmp,e, r);
}
}
The drawImage of Ball code:
public void drawImage(Bitmap bmp, PaintEventArgs e, Rectangle r)
{
using (Graphics g = Graphics.FromImage(bmp))
{
e.Graphics.FillEllipse(brush, ballLocation);
g.DrawImage(bmp, 0, 0);
}
}
NOTE: ballLocation is a rectangle that represent the location of the ball in each
step of movement..
So what I'm doing wrong? What causing the application to be slowly?
I have a constraint to draw everything on the bitmap and present it on the form.
I'm also passing the bitmap that I create when the form is loading, because I need to draw each on it.
Some basic techniques to make this fast:
Don't double-buffer yourself and especially don't double-buffer twice. The double-buffering you get by setting the form's DoubleBuffer property to true is superior to most any double-buffering you'd do yourself. The buffer is highly optimized to work efficiently with your video adapter's settings. So completely drop your bmp variable and draw to the e.Graphics you got from the Paint event handler argument.
You are not using the passed r argument. Possibly intended to support clipping invisible balls. The one you want to pass is e.ClipRectangle, you can skip painting balls that are completely outside of this rectangle. While that's an optimization, it isn't one that's commonly useful when you use the Aero theme and you do get inconsistent redraw rates so you might want to skip that one.
It isn't very clear why you use both Graphics.FillEllipse and Graphics.DrawImage when you draw the ball. The image ought to overlap the circle so just remove FillEllipse.
Pay a lot of attention to the Bitmap object that stores the ball graphic. First thing you want to make sure is that it is drawn with the exact size of the image so it doesn't have to be rescaled. Rescaling is very expensive. While you don't have any rescaling in your DrawImage() call, you will still get it if the resolution of the bitmap is not the same as the resolution of your video adapter. The next step will solve that
The pixel format of the ball bitmap is very important. You want one that permits copying the bitmap straight to video memory without any format conversion. On any modern machine, that format is PixelFormat.Format32bppPArgb. The difference is enormous, it draws ten times faster than any of the other ones. You won't get this format from an image resource you added, you'll have to create that bitmap when your program starts up. Check this answer for the required code.
You ought to be able to render at least 15 times faster when you follow these guidelines. If that's still enough then you do need to turn to DirectX, it has the unbeatable advantage of being able to store the ball graphic in video memory so you don't get the expensive blt from main memory to video memory.
DrawImage on Paint (or for that matter on MouseMove) is very bad design.
Graphics.DrawImage is expensive operation, and to the screen it is extra expensive.
To improve your user experience (slowness), You should paint on MouseDown/MouseUp events.
In addition, First draw to MemoryBuffer in your drawImage method and after preparing the final image, draw it once on the UI. This technique is known as double buffering.
Don't Flicker! Double Buffer! - CodeProject
In addition you can also look at BitBlit Native API for fast color/image transfer to screen.
A minimalistic c# example is here
Enable double-buffering on your form (DoubleBuffered = true).

How to selective redraw shapes keeping other intact

I am working on Window CE application with custom control.
In custom control I am drawing three circles (One circle filled), one arrowhead one text and one line manually. It is like a angle selector in Photoshop.
Now when I move the cursor complete control redraw and cause a flickering affect.
I have tried drawing controls to bitmap first then in last draw this bitmap using e.drawimage() method but same flickering problem.
Now I have only 2 shapes(arrowhead+line) that needs redraw, others are with static behaviour.
So my question is there any way to selective draw shapes keeping other intact. Draw all shapes first time but just draw the arrowhead and line after that keeping all other shapes at there location intact.
This is what I am doing in
onPiant override method.
using (SolidBrush inLineFill = new SolidBrush(circleColor))
{
Point[] polyPoints = ...;
e.Graphics.DrawLine(....);
e.Graphics.FillPolygon(inLineFill, polyPoints);
if (firstTime)
{
e.Graphics.DrawEllipse(...);
e.Graphics.DrawEllipse(...);
e.Graphics.DrawEllipse(...);
e.Graphics.FillEllipse(...);
e.Graphics.DrawString(...);
firstTime = false ;
}
}
I see complete control first time but I only see the arrowhead with line after that.
There are probably several things you can do to improve the behavior.
Override OnPaintBackground and leave it empty
Cache that SolidBrush instead of creating a new one every time OnPaint is called
Draw all of the shapes that are static to a member-level Bitmap that you cache. In OnPaint, do a DrawImage of that image then your arrowhead and line (the changing stuff).
Do all of this drawing to a Bitmap (that you cache, not create every time), then DrawImage that to the screen graphics
If you don't have overlapping stuff and you've got reasonable rectangular regions, setting a clipping area to surround your changes will prevent unnecessary drawing

Graphics are too slow in my project

I am developing zoo simulator project. It contains three thing types to draw: a map, animal environments and the animals themselves. The map is too big to fit on screen, player needs to move screen to see other parts of it. I am using a timer to draw. On its tick, it calls Invalidate() for the form being drawing on. In ZooForm_Paint method, I first draw every thing in the map on mapBuffer Bitmap. Since mapBuffer is too big to fit on screen, I draw (on screen) the part of mapBuffer the player is where.
Unfortunately, it seems that drawing everything in the map (although it may not be viewed) on mapBuffer slows the game. Can I draw my evironments and animals without need to draw entire map first?
How?
My code:
public void DrawGame(Graphics g, ref Point locationOnMap)
{
this.drawBufferMap();
this.drawMapLocation(g, ref locationOnMap);
}
private void drawBufferMap()
{
Bitmap buffer = new Bitmap(this.map.Size.Width, this.map.Size.Height);
using (Graphics graphics = Graphics.FromImage(buffer))
{
graphics.DrawImageUnscaled(this.map.Picture, new Point()); // draw entire map
foreach (var item in this.zoo.Environments) // draw all env.
{
graphics.DrawImageUnscaled(item.Picture, item.Bounds.Location);
}
foreach (var item in this.zoo.ILocatables) // draw all ILocatables
{
graphics.DrawImageUnscaled(item.Picture, item.Location);
}
}
if (this.mapBuffer != null)
{
this.mapBuffer.Dispose();
}
this.mapBuffer = buffer;
}
private void drawMapLocation(Graphics g, ref Point location)
{
g.DrawImage(this.mapBuffer, new Rectangle(0, 0, viewSize.Width, viewSize.Height),
new Rectangle(location.X, location.Y, viewSize.Width, viewSize.Height), GraphicsUnit.Pixel);
}
I don't think you are going to get any easy solutions. I can offer a few tips and opinions:
You seem to be creating a new BitMap every time you paint the screen. This is definitely not a good idea, as large bitmaps are absolutely huge in terms of memory. What you probably want to do is create one when your game loads, and then simply clear it and repaint it at every frame. I think this is probably one of the bigger performance issues you have.
There are a number of optimisations you could make afterwards. E.g. you are "rendering" the image that you will end up painting to the screen on the user interface thread. If the rendering process takes long, this will be noticeable. Typically this work happens on a background thread, and then the UI thread just checks if it can repaint using the new image. (I am simplifying things greatly here).
For graphics intensive applications, WinForms is not a particularly good environment, as others have pointed out. You will not get any hardware acceleration at all. Moving to XNA is one option, but if your application is also quite rich in terms of standard WinForms screens and controls, this is probably not an easy option. Another suggested alternative might be WPF, where you might be able to get away with using transformations to move things around, which are hardware accelerated, and are not too dissimilar to a WinForms application (well, you don't need to implement your own buttons, etc).
Hope this helps a bit.
As Daniel pointed out: creating a new bitmap each time you need to draw your map will decrease performance. Reuse the same bitmap over and over instead.
Creating a bitmap larger that you need is also very bad for performance. If you need it to scroll around, then it's fine. But if you paint a new image each time anyway, then you should just create it exactly the same size you need. Then you can call Graphics.TranslateTransform to compensate for the new coordinates so you can leave your existion code unchanged.
This will make it possible for GDI+ to clip your graphics and simply just don't draw things outside your map bitmap - which will speed things up.

equivalent CreateGraphics in wpf

So, I've used winForms .CreateGraphics to draw a variety of different things, from lines to boxes to images. It was very snappy and responsive.
I am trying to learn WPF in C#
I found that WPF allows me to "add" rectangle objects to a canvas which will display them properly. HOWEVER, I am drawing hundreds of thousands of rectangles at times, and the draw rate can become exceedingly slow, and the UI becomes less snappy when I move even 1 of the rectangles.
Painting directly onto an element in winForms was not very fast, but it was consistent regardless of how much I painted.
Is there a similar solution to doing this in WPF?
I tried adding a linq to System.Drawing, which gave me a Graphics object, but none of the wpf elements i tried have the .CreateGraphics() method.
WPF uses a different model for graphics manipulation than WinForms.
With WinForms, you are able to directly edit the pixels on the screen. The concept of your rectangle is lost after the pixels are drawn. Drawing pixels is a very fast operation.
With WPF, you are not controlling the pixels on the screen. DirectDraw is. DirectDraw is a vector-based compositing engine. You do not draw pixels. You define vector shapes (or visuals). The concept of a shape, or a rectangle, is RETAINED, even after the image is rendered to the screen. When you add a new rectangle which overlaps the others, ALL OTHER RECTANGLES NEED TO BE REDRAWN. This is likely where your performance is slowing down. This does not happen when using WinForms.
You can improve the performance of WPF a bit by overriding OnRender. You can cut out the overhead of the Rectangle object and directly provide the visuals. However, you are still not drawing pixels to the screen. You are defining shapes that DirectDraw uses to render the image. In this regard, the OnRender name may be a bit misleading.
I am sure you can find plenty of tricks to improve performance of your application in WPF. There are ways to still paint pixels - but that is kinda defeating the point of WPF.
What are you doing that requires thousands of rectangles?
You would need to create a control that overrides OnRender and do your drawing in there. There isn't a way for you to draw onto another control, but a control can draw itself.
Also, keep in mind that WPF uses retained graphics, so if you change something you need to invalidate the visual as needed.
EDIT:
Something like:
public class MyControl : Control {
public MyControl() {
this.Rects = new ObservableCollection<Rect>();
// TODO: attach to CollectionChanged to know when to invalidate visual
}
public ObservableCollection<Rect> Rects { get; private set; }
protected override void OnRender(DrawingContext dc) {
SolidColorBrush mySolidColorBrush = new SolidColorBrush();
mySolidColorBrush.Color = Colors.LimeGreen;
Pen myPen = new Pen(Brushes.Blue, 10);
foreach (Rect rect in this.Rects)
dc.DrawRectangle(mySolidColorBrush, myPen, rect);
}
}
As was said, WPF uses a retained graphics methodology so your actually creating 100,000 Rectangle objects in memory and then drawing all of them. The slowdowns are probably due to garbage collection and general memory issues.
Aside from override the OnRender method, here's a couple of things you could look into though.
Drawing the rectangles to an image in a background thread using the GDI methods your familiar and then write the result to a WPF WriteableBitmap
Use the D3DImage and take advantage of hardware acceleration. This requires you to know the DirectX (or Direct2D) libraries. If your interested in this approach, I'd suggest looking into SlimDx.
The problem is most likeley not that WPF can't render 1000s of graphic objects, but that your creating and adding items too far up the WPF object hierachy. It does after all use the GPU for all the graphical grunt work.
You should add objects as close to the "Visual" class as possible, as soon as you start adding objects based on the latter "UIElement" you are asking WPF to track user clicks, hovers and so on for each object, not just draw it.

Categories