I'm looking for a way to speed up the drawing of my game engine, which is currently the significant bottleneck, and is causing slowdowns. I'm on the verge of converting it over to XNA, but I just noticed something.
Say I have a small image that I've loaded.
Image img = Image.FromFile("mypict.png");
We have a picturebox on the screen we want to draw on. So we have a handler.
pictureBox1.Paint += new PaintEventHandler(pictureBox1_Paint);
I want our loaded image to be tiled on the picturebox (this is for a game, after all). Why on earth is this code:
void pictureBox1_Paint(object sender, PaintEventArgs e)
{
for (int y = 0; y < 16; y++)
for (int x = 0; x < 16; x++)
e.Graphics.DrawImage(image, x * 16, y * 16, 16, 16);
}
over 25 TIMES FASTER than this code:
Image buff = new Bitmap(256, 256, PixelFormat.Format32bppPArgb); // actually a form member
void pictureBox1_Paint(object sender, PaintEventArgs e)
{
using (Graphics g = Graphics.FromImage(buff))
{
for (int y = 0; y < 16; y++)
for (int x = 0; x < 16; x++)
g.DrawImage(image, x * 16, y * 16, 16, 16);
}
e.Graphics.DrawImage(buff, 0, 0, 256, 256);
}
To eliminate the obvious, I've tried commenting out the last e.Graphics.DrawImage (which means I don't see anything, but it gets rid a call that isn't in the first example). I've also left in the using block (needlessly) in the first example, but it's still just as blazingly fast. I've set properties of g to match e.Graphics - things like InterpolationMode, CompositingQuality, etc, but nothing I do bridges this incredible gap in performance. I can't find any difference between the two Graphics objects. What gives?
My test with a System.Diagnostics.Stopwatch says that the first code snippet runs at about 7100 fps, while the second runs at a measly 280 fps. My reference image is VS2010ImageLibrary\Objects\png_format\WinVista\SecurityLock.png, which is 48x48 px, and which I modified to be 72 dpi instead of 96, but those made no difference either.
When you're drawing to the screen, the OS is able to take advantage of special hardware in the graphics adapter to do simple operations such as copying an image around.
I'm getting ~5 msec for both. 7100 fps is way too fast for the software rendering done by GDI+. Video drivers notoriously cheat to win benchmarks, they can detect that a BitBlt doesn't have to be performed because the image didn't change. Try passing random values to e.Graphics.TranslateTransform to eliminate the cheat.
Are you sure the difference isn't from the using-block, i.e. setting up the try-finally block and creating the Graphics instance from the image buffer.
I could easily see the latter as being an expensive operation, unlike the paint event where you simply get a reference to an already created graphics instance.
Related
First time I ever ask a question here so correct me if I´m doing it wrong.
Picture of my chess set:
Every time I move a piece it lags for about 1 second. Every piece and tile has an Image and there is exactly 96 Images. Every time I move a piece it clears everything with black and then update the graphics.
In the early stages of the chess I didn't have any Images and used different colors instead and only a few pieces there was no noticeable lag and the piece moved in an instant.
public void updateGraphics(PaintEventArgs e, Graphics g, Bitmap frame)
{
g = Graphics.FromImage(frame);
g.Clear(Color.Black);
colorMap(g);
g.Dispose();
e.Graphics.DrawImageUnscaled(frame, 0, 0);
}
The function colorMap(g) looks like this:
private void colorMap(Graphics g)
{
for (int y = 0; y < SomeInts.amount; y++)
{
for (int x = 0; x < SomeInts.amount; x++)
{
//Tiles
Bundle.tile[x, y].colorBody(g, x, y);
//Pieces
player1.colorAll(g);
player2.colorAll(g);
}
}
}
The colorAll function executes every pieces colorBody(g) function which look like this:
public void colorBody(Graphics g)
{
//base.colorBody() does the following: body = new Rectangle(x * SomeInts.size + SomeInts.size / 4, y * SomeInts.size + SomeInts.size / 4, size, size);
base.colorBody();
if (team == 1)
{
//If its a white queen
image = Image.FromFile("textures/piece/white/queen.png");
}
if (team == 2)
{
//If its a black queen
image = Image.FromFile("textures/piece/black/queen.png");
}
g.DrawImage(image, body);
}
and finaly the function that moves the piece:
public void movePiece(MouseEventArgs e)
{
for (int y = 0; y < SomeInts.amount; y++)
{
for (int x = 0; x < SomeInts.amount; x++)
{
if (Bundle.tile[x, y].body.Contains(e.Location))
{
//Ignore this
for (int i = 0; i < queens.Count; i++)
{
Queen temp = queens.ElementAt<Queen>(i);
temp.move(x, y);
}
//Relevant
player1.move(x, y);
player2.move(x, y);
}
}
}
}
Thank you for reading all this! I could make a link to the whole program if my coding examples is not enough.
You're calling Image.FromFile in every refresh, for every image - effectively reloading every image file every time from disk.
Have you considered loading the images once, and storing the resulting Images somewhere useful? (say, an array, Image[2,6] would be adequate)
Why do you redraw the board each time? Can't you just leave the board where it is and display an image with transparent background over it? That way you have one image as a background (the board), plus 64 smaller images placed over the board in a grid and just change the image being displayed on each move.
That way, you can let windows handle the drawing...
Also, load the images of the pieces at the start of the application.
In addition to not calling Image.FromFile() inside updateGraphics() (which is definitely your biggest issue), you shouldn't be attempting to redraw the entire board every on every call to updateGraphics() - most of the time, only a small portion of the board will be invalidated.
The PaintEventArgs contains an parameter, ClipRectangle, which specifies which portion of the board needs redrawing. See if you can't figure out which tiles intersect with that rectangle, and only redraw those tiles :)
Hint: Write a function Point ScreenToTileCoords(Point) which takes a screen coordinate and returns which board-tile is at that coordinate. Then the only tiles you need to redraw are
Point upperLeftTileToBeDrawn = ScreenToTileCoords(e.ClipRectangle.Left, e.ClipRectangle.Top);
Point lowerRightTileToBeDrawn = ScreenToTileCoords(e.ClipRectangle.Right - 1, e.ClipRectangle.Bottom- 1);
Also, make sure your control is double-buffered, to avoid tearing. This is much simpler than #Steve B's link in the comments above states; assuming this is a UserControl, simply set
this.DoubleBuffered = true;
Well, what about this:
Do not clear the whole board but only those parts that need to be cleared.
Alternative:
Update to WPF - it moves drawing to the graphics card - and just move pieces around, in a smart way (i.e. have a control / object for every piece).
Today I am trying to solve problem with a blinking panel, when I draw onto it.
Lots of threads I read, like these:
how to stop flickering C# winforms,
Double buffering with Panel,
How can I draw on Panel so it does not blink?
So I tried to draw onto PictureBox, MyPanel with doubleBuffered, but the best solution I found, when I read, that I can't use g.Clear() every time, after that, even on non-doubleBuffered panel, blinking disappeared.
I even read, that I should free Graphics after draw is done. So I use everywhere using(Graphics g = panel.CreateGraphics()).
So my question, is it a great idea to create graphics for bitmap only when I draw something to it? Because before I created Bitmap, and Graphics (only for this bitmap, not for all components), so I had Graphics available for this bitmap every time
Here is my code:
public void newSizeDrawing()
{
Size size = collector.getLetterSize(selectedName);
Size drawingSize = new Size(size.Width * (pixelSizeArray[pixelSize] + 1),size.Height * (pixelSizeArray[pixelSize] + 1));
bitmapDraw = new Bitmap(drawingSize.Width, drawingSize.Height);
int width = (this.MinimumSize.Width - panelDraw.MinimumSize.Width) + drawingSize.Width + 10;
int height = (this.MinimumSize.Height - panelDraw.MinimumSize.Height) + drawingSize.Height + 10;
this.Size = new Size(
(width > this.MinimumSize.Width) ? width : this.MinimumSize.Width,
(height > this.MinimumSize.Height) ? height : this.MinimumSize.Height);
zeroDrawPosition = new Point((panelDraw.Size.Width - bitmapDraw.Width) / 2 - 1, (panelDraw.Size.Height - bitmapDraw.Height) / 2 - 1);
using (Graphics g = panelDraw.CreateGraphics())
{
g.Clear(panelDraw.BackColor);
}
redrawDrawingLetter();
}
public void redrawDrawingLetter()
{
bool[][] grid = collector.getArray(selectedName);
using (Graphics graphicDraw = Graphics.FromImage(bitmapDraw))
{
graphicDraw.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighSpeed;
graphicDraw.Clear(panelDraw.BackColor);
int pxSize = pixelSizeArray[pixelSize];
for (int y = 0; y < grid.Length; y++)
{
for (int x = 0; x < grid[y].Length; x++)
{
graphicDraw.FillRectangle((grid[y][x] ? Brushes.Black : Brushes.White), x * (pxSize + 1), y * (pxSize + 1), pxSize, pxSize);
}
}
}
redrawDrawingPanel();
}
private void redrawDrawingPanel()
{
using (Graphics g = panelDraw.CreateGraphics())
{
if (bitmapDraw != null)
g.DrawImage(bitmapDraw, zeroDrawPosition);
}
}
private void panelDraw_Paint(object sender, PaintEventArgs e)
{
redrawDrawingPanel();
}
Nobody can explain to me how to draw in C# the best way. So maybe my code isn't good, but that is reason why I asking how to do it correctly.
newSizeDrawing is called by myself only, when user click on + or - button. I have bool double-dimension array if pixel is on or off. This is program for drawing letters for microchips and LED display (often 8px height of letter).
I wrote a method that checks if the mouse moved from one "pixel" to another, so I don't redraw it after every call mouseMove event, because "pixel" can be from 10x10 px to 30x30 px.
private void panelDraw_Paint(object sender, PaintEventArgs e)
{
redrawDrawingPanel();
}
This is fundamentally wrong. The Paint event passes e.Graphics to let you draw whatever you want to paint. When you turn on double-buffering, e.Graphics refers to a bitmap, it is initialized with the BackColor. You then proceed to drawing using another Graphics object you got from CreateGraphics(). That one draws directly to the screen.
The flicker effect you see if very pronounced. For a split second you see what the other Graphics context draws. Then your panelDraw_Paint() method returns and Winforms draws the double-buffered bitmap. There's nothing on it so it immediately erases what you drew.
Modify the redrawDrawingPanel() method and give it an argument of type Graphics. Pass e.Graphics in the call. And only use that Graphics object, remove all calls to CreateGraphics().
I know the whole
Bitmap bmp = new Bitmap(100,100) ;
Graphics graphics = Graphics.FromImage(bmp) ;
But that is not what I need to do. Essentially what I am trying to do is slightly modify how something is draw on the screen (it is a rectangle with a line in the middle). I need to change the background of the rectangle (which I could do with a pixel comparison or other similar method).
If I could somehow turn it into a bitmap. There is no way (that I can tell to get/set/modify) the graphics object on the pixel level.
Bob Powell has written an excellent article about accessing image bits directly.
https://web.archive.org/web/20141229164101/http://bobpowell.net/lockingbits.aspx
Once you've used the Graphics, just save the Bitmap - the changes made with the graphics are made to the bitmap.
You could do this using a for loop like so:
for(int i = 0; i < bmp.Width; i++)
{
for(int j = 0; j < bmp.Height; j++)
{
var pixel = bmp.GetPixel(i, j);
//do stuff with pixel here :)
}
}
EDIT: After seeing Tilak's comment, if performance is an issue for you then he is right! (Thanks, I did not know about LockBits! +1) Some examples can be found on the MS BitmapData Class page. However, if this seems complicated the above method should do the trick :)
I have a small paint program that I am working on. I am using SetPixel on a bitmap to do that drawing of lines. When the brush size gets large, like 25 pixels across there is a noticeable performance drop. I am wondering if there is a faster way to draw to a bitmap. Here is a bit of the background of the project:
I am using bitmaps so that I can utilise layers, like in Photoshop or The GIMP.
Lines are being drawn manually because this will eventually use graphics tablet pressure to alter the size of the line over its length.
The lines should eventually be anti-aliaced/smoothed along the edges.
I'll include my drawing code just in case it is this that is slow and not the Set-Pixel bit.
This is in the windows where the painting happens:
private void canvas_MouseMove(object sender, MouseEventArgs e)
{
m_lastPosition = m_currentPosition;
m_currentPosition = e.Location;
if(m_penDown && m_pointInWindow)
m_currentTool.MouseMove(m_lastPosition, m_currentPosition, m_layer);
canvas.Invalidate();
}
Implementation of MouseMove:
public override void MouseMove(Point lastPos, Point currentPos, Layer currentLayer)
{
DrawLine(lastPos, currentPos, currentLayer);
}
Implementation of DrawLine:
// The primary drawing code for most tools. A line is drawn from the last position to the current position
public override void DrawLine(Point lastPos, Point currentPos, Layer currentLayer)
{
// Creat a line vector
Vector2D vector = new Vector2D(currentPos.X - lastPos.X, currentPos.Y - lastPos.Y);
// Create the point to draw at
PointF drawPoint = new Point(lastPos.X, lastPos.Y);
// Get the amount to step each time
PointF step = vector.GetNormalisedVector();
// Find the length of the line
double length = vector.GetMagnitude();
// For each step along the line...
for (int i = 0; i < length; i++)
{
// Draw a pixel
PaintPoint(currentLayer, new Point((int)drawPoint.X, (int)drawPoint.Y));
drawPoint.X += step.X;
drawPoint.Y += step.Y;
}
}
Implementation of PaintPoint:
public override void PaintPoint(Layer layer, Point position)
{
// Rasterise the pencil tool
// Assume it is square
// Check the pixel to be set is witin the bounds of the layer
// Set the tool size rect to the locate on of the point to be painted
m_toolArea.Location = position;
// Get the area to be painted
Rectangle areaToPaint = new Rectangle();
areaToPaint = Rectangle.Intersect(layer.GetRectangle(), m_toolArea);
// Check this is not a null area
if (!areaToPaint.IsEmpty)
{
// Go through the draw area and set the pixels as they should be
for (int y = areaToPaint.Top; y < areaToPaint.Bottom; y++)
{
for (int x = areaToPaint.Left; x < areaToPaint.Right; x++)
{
layer.GetBitmap().SetPixel(x, y, m_colour);
}
}
}
}
Thanks a lot for any help you can provide.
You can lock the bitmap data and use pointers to manually set the values. It's much faster. Though you'll have to use unsafe code.
public override void PaintPoint(Layer layer, Point position)
{
// Rasterise the pencil tool
// Assume it is square
// Check the pixel to be set is witin the bounds of the layer
// Set the tool size rect to the locate on of the point to be painted
m_toolArea.Location = position;
// Get the area to be painted
Rectangle areaToPaint = new Rectangle();
areaToPaint = Rectangle.Intersect(layer.GetRectangle(), m_toolArea);
Bitmap bmp;
BitmapData data = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
int stride = data.Stride;
unsafe
{
byte* ptr = (byte*)data.Scan0;
// Check this is not a null area
if (!areaToPaint.IsEmpty)
{
// Go through the draw area and set the pixels as they should be
for (int y = areaToPaint.Top; y < areaToPaint.Bottom; y++)
{
for (int x = areaToPaint.Left; x < areaToPaint.Right; x++)
{
// layer.GetBitmap().SetPixel(x, y, m_colour);
ptr[(x * 3) + y * stride] = m_colour.B;
ptr[(x * 3) + y * stride + 1] = m_colour.G;
ptr[(x * 3) + y * stride + 2] = m_colour.R;
}
}
}
}
bmp.UnlockBits(data);
}
SetPixel does this:
is locks the whole image, sets the pixel and unlocks it
try to do that: you acquire a lock for the whole memory image with lockbits, process you update and release the lock after.
lockbits
I usually use an array to represent the raw pixel data. And then copy between that array and the bitmap with unsafe code.
Making the array of Color is a bad idea, since the Color struct is relatively large(12 bytes+). So you can either define your own 4 byte struct(that's the one I chose) or simply use an array of int or byte.
You should also reuse your array, since GC on the LOH tends to be expensive.
My code can be found at:
https://github.com/CodesInChaos/ChaosUtil/blob/master/Chaos.Image/
An alternative is writing all your code using pointers into the bitmap directly. That's a bit faster still, but can make the code uglier and more error prone.
Just an idea: Fill an offscreen bitmap with your Brush pixels. You only need to regenerate this bitmap when the brush, size or color is changed. And then just draw this bitmap onto your existing bitmap, where the mouse is located.
If you can modulate a bitmap with a color, you could set the pixels in grayscale and modulate it with the current brush color.
You're calling GetBitmap within your nested for loop. It looks like that's not necessary, you should GetBitmap outside the for loops as the reference isn't going to change.
Also look at #fantasticfix answer, Lockbits nearly always sorts slow performance issues with getting/setting pixels
In my project, I'm using (uncompressed 16-bit grayscale) gigapixel images which come from a high resolution scanner for measurement purposes. Since these bitmaps can not be loaded in memory (mainly due to memory fragmentation) I'm using tiles (and tiled TIFF on disc). (see StackOverflow topic on this)
I need to implement panning/zooming in a way like Google Maps or DeepZoom. I have to apply image processing on the fly before presenting it on screen, so I can not use a precooked library which directly accesses an image file. For zooming I intend to keep a multi-resolution image in my file (pyramid storage). The most useful steps seem to be +200%, 50% and show all.
My code base is currently C# and .NET 3.5. Currently I assume Forms type, unless WPF gives me great advantage in this area. I have got a method which can return any (processed) part of the underlying image.
Specific issues:
hints or references on how to implement this pan/zoom with on-demand generation of image parts
any code which could be used as a basis (preferably commercial or LGPL/BSD like licenses)
can DeepZoom be used for this (i.e. is there a way that I can provide a function to provide a tile at the right resulution for the current zoom level?) ( I need to have pixel accurate addressing still)
This CodeProject article: Generate...DeepZoom Image Collection might be a useful read since it talks about generating a DeepZoom image source.
This MSDN article has a section Dynamic Deep Zoom: Supplying Image Pixels at Run Time and links to this Mandelbrot Explorer which 'kinda' sounds similar to what you're trying to do (ie. he is generating specific parts of the mandelbrot set on-demand; you want to retrieve specific parts of your gigapixel image on-demand).
I think the answer to "can DeepZoom be used for this?" is probably "Yes", however as it is only available in Silverlight you will have to do some tricks with an embedded web browser control if you need a WinForms/WPF client app.
Sorry I can't provide more specific answers - hope those links help.
p.s. I'm not sure if Silverlight supports TIFF images - that might be an issue unless you convert to another format.
I decided to try something myself. I came up with a straightforward GDI+ code, which uses the tiles I've already got. I just filter out the parts which are relevant for current clipping region. It works like magic! Please find my code below.
(Form settings double buffering for the best results)
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
Graphics dc = e.Graphics;
dc.ScaleTransform(1.0F, 1.0F);
Size scrollOffset = new Size(AutoScrollPosition);
int start_x = Math.Min(matrix_x_size,
(e.ClipRectangle.Left - scrollOffset.Width) / 256);
int start_y = Math.Min(matrix_y_size,
(e.ClipRectangle.Top - scrollOffset.Height) / 256);
int end_x = Math.Min(matrix_x_size,
(e.ClipRectangle.Right - scrollOffset.Width + 255) / 256);
int end_y = Math.Min(matrix_y_size,
(e.ClipRectangle.Bottom - scrollOffset.Height + 255) / 256);
// start * contain the first and last tile x/y which are on screen
// and which need to be redrawn.
// now iterate trough all tiles which need an update
for (int y = start_y; y < end_y; y++)
for (int x = start_x; x < end_x; x++)
{ // draw bitmap with gdi+ at calculated position.
dc.DrawImage(BmpMatrix[y, x],
new Point(x * 256 + scrollOffset.Width,
y * 256 + scrollOffset.Height));
}
}
To test it, I've created a matrix of 80x80 of 256 tiles (420 MPixel). Of course I'll have to add some deferred loading in real life. I can leave tiles out (empty) if they are not yet loaded. In fact, I've asked my client to stick 8 GByte in his machine so I don't have to bother about performance too much. Once loaded tiles can stay in memory.
public partial class Form1 : Form
{
bool dragging = false;
float Zoom = 1.0F;
Point lastMouse;
PointF viewPortCenter;
private readonly Brush solidYellowBrush = new SolidBrush(Color.Yellow);
private readonly Brush solidBlueBrush = new SolidBrush(Color.LightBlue);
const int matrix_x_size = 80;
const int matrix_y_size = 80;
private Bitmap[,] BmpMatrix = new Bitmap[matrix_x_size, matrix_y_size];
public Form1()
{
InitializeComponent();
Font font = new Font("Times New Roman", 10, FontStyle.Regular);
StringFormat strFormat = new StringFormat();
strFormat.Alignment = StringAlignment.Center;
strFormat.LineAlignment = StringAlignment.Center;
for (int y = 0; y < matrix_y_size; y++)
for (int x = 0; x < matrix_x_size; x++)
{
BmpMatrix[y, x] = new Bitmap(256, 256, PixelFormat.Format24bppRgb);
// BmpMatrix[y, x].Palette.Entries[0] = (x+y)%1==0?Color.Blue:Color.White;
using (Graphics g = Graphics.FromImage(BmpMatrix[y, x]))
{
g.FillRectangle(((x + y) % 2 == 0) ? solidBlueBrush : solidYellowBrush, new Rectangle(new Point(0, 0), new Size(256, 256)));
g.DrawString("hello world\n[" + x.ToString() + "," + y.ToString() + "]", new Font("Tahoma", 8), Brushes.Black,
new RectangleF(0, 0, 256, 256), strFormat);
g.DrawImage(BmpMatrix[y, x], Point.Empty);
}
}
BackColor = Color.White;
Size = new Size(300, 300);
Text = "Scroll Shapes Correct";
AutoScrollMinSize = new Size(256 * matrix_x_size, 256 * matrix_y_size);
}
Turned out this was the easy part. Getting async multithreaded i/o done in the background was a lot harder to acchieve. Still, I've got it working in the way described here. The issues to resolve were more .NET/Form multithreading related than to this topic.
In pseudo code it works like this:
after onPaint (and on Tick)
check if tiles on display need to be retrieved from disc
if so: post them to an async io queue
if not: check if tiles close to display area are already loaded
if not: post them to an async io/queue
check if bitmaps have arrived from io thread
if so: updat them on screen, and force repaint if visible
Result: I now have my own Custom control which uses roughly 50 MByte for very fast access to arbitrary size (tiled) TIFF files.
I guess you can address this issue following the steps below:
Image generation:
segment your image in multiple subimages (tiles) of a small resolution, for instace, 500x500. These images are depth 0
combine a series of tiles with depth 0 (4x4 or 6x6), resize the combination generating a new tile with 500x500 pixels in depth 1.
continue with this approach until get the entire image using only a few tiles.
Image visualization
Start from the highest depth
When user drags the image, load the tiles dynamically
When the user zoom a region of the image, decrease the depth, loading the tiles for that region in a higher resolution.
The final result is similar to Google Maps.