I have created a somewhat complete application which allows me to create a map (.txt file with information about all the points of the map), load it and draw it.
My solution for this was, inside the windows forms application, to put a panel (since I need to be able to move on map) and inside that panel pictureboxes(since I want to put a background image and image on them) that represent points of map in size 50x50 pixels. The problem I am facing now is increased load time for my application, since I am loading pictures into the pictureboxes...
Does anyone have any alternative suggestion to what I have been attempting?
Visual representation might help:
The code, as requested: (well, some of it)
private void Load_Map()
{
for (int i = Y - 12; i < Y + 12; i++)
{
if ((i >= 0) & (i < Int32.Parse(MP.Mheight)))
{
string Line = xline[i];
for (int j = X - 12; j < X + 12; j++)
{
if ((j >= 0) & (j < Int32.Parse(MP.Mwidth)))
{
int X = i * Int32.Parse(MP.Mwidth) + j;
int Z = Int32.Parse(Line[j].ToString());
Map_Location[X] = Z;
Color H = new Color();
Map_Point(j, i, Map_Height(Z, H), 50);
}
}
}
}
}
Creating points:
private void Map_Point(int X, int Y, Color H, int Point_Size)
{
PictureBox MP = new PictureBox();
MP.Name = Map_Coordinates(X, Y);
MP.Size = new Size(Point_Size, Point_Size);
MP.Location = new Point(Y * (Point_Size + 1) + 4, X * (Point_Size + 1) + 4);
MP.BackColor = H;
Control MW = this.Controls["WorldMap"];
MW.Controls.Add(MP);
}
You'll be better off creating a custom control by deriving from System.Windows.Forms.Control and overriding the OnPaint method and doing your own drawing and handling click events yourself.
Using a large number of WinForms controls the way you're doing is an exercise in pain, as WinForms will create a hWnd object for each control, and WinForms doesn't scale too well, unfortunately.
You should be using System.Drawing.Graphics
Here are the MSDN Tutorials for it.
It has a method called DrawImage , which you can use instead of a picture box. For the grid you should be drawing it as a rectangle with a color for the background and vertical/horizontal lines to make the grid.
Related
So I'm writing a program that generates a chart and saves it to PNG. From what I've read, if I were drawing to a window, it doesn't behave this way, but I'm not doing that.
The problem is that when I pass the brush I use to draw the label to another method to do the drawing, sometimes the text comes out looking bold. Also, the Y coordinate seems to have something to do with it, since it happens on every other row of the chart I'm drawing. And it's not a nice bold, either, it's like a gritty, messy looking bold. Some people have suggested changing the text rendering hint to antialiased, and it solves the "bolding" problem, but it doesn't look as nice as ClearType.
Note that none of this happens if I do everything in one method without passing the brush around, which is the most puzzling part of this. Any ideas?
Here's some of the code:
// Draw the timeline.
int y = 0;
bool shadeRow = true;
foreach (TimelineRow row in timeline.chart)
{
int rowHeight = row.height + TimelineRow.ROW_GAP;
if (shadeRow)
{
g.FillRectangle(shadeBrush, 0, y, chartWidth, rowHeight);
}
// Draw name labels, guidelines, and timeline row.
g.DrawString(row.name, labelFont, labelBrush, PADDING, (int)Math.Ceiling(y + (float)PADDING / 2));
for (int i = 0; i < row.years.Length; i++)
{
int blockX = labelsWidth + i * TimelineRow.DEFAULT_HEIGHT;
g.DrawLine(i % 5 == 0 ? yearGridDark : yearGridLight, blockX, y, blockX, y + rowHeight);
}
DrawRow(row, g, labelsWidth, y + 8);
y += rowHeight;
shadeRow = !shadeRow;
}
// Draw the year labels
int x = labelsWidth;
for (int year = timeline.startYear; year <= timeline.endYear; year += 5)
{
string yearString = Convert.ToString(year);
int width = (int)g.MeasureString(yearString, labelFont).Width;
g.DrawString(yearString, labelFont, labelBrush, x - width / 2, y);
x += 5 * TimelineRow.DEFAULT_HEIGHT;
}
I've had similar issues with drawing strings. In my cases, clearing the image FIRST with the background color has fixed the problem.
Wow, that actually did it.
Use Graphics.Clear() to set the initial color:
Bitamp bmp = new Bitmap(...);
Graphics g = Graphics.FromImage(bmp);
g.Clear(Color.White);
// ... now draw with "g" ...
I'm writing a little game using C#. In fact, it just have to draw corolful circles every time user moves a mouse (or just every n milliseconds) according to the mouse location. The problem is, i have to redraw the whole pictureBox every single period of time. I know there are .Invalidate() and .Refresh() options for that, but it seems like I need to re-create Graphics object every single time I need to redraw something, that happens every second.
private void redrawCircles(int distance)
{
prevdist = distance / 5;
g = Graphics.FromImage(pictureBox1.Image);
for (int i = 0; i < n - 1; i++)
{
brushes[i] = brushes[i + 1];
}
brushes[n - 1] = BrushFromDistance(distance);
for (int i = 0; i < n; ++i)
{
g.FillEllipse(brushes[i], startX + i * rad, startY + i * rad, 2 * diag - 2 * i * rad, 2 * diag - 2 * i * rad);
}
g.Dispose();
pictureBox1.Refresh();
}
where g is:
public static System.Drawing.Graphics g;
redrawCircles is called in the MouseMove event handler, and i'm planning to cal it in Timer.Tick event handler. So it's called very often. Re-creating Graphics object seems not effective.
Do I really need re-creating Graphics object in that situation or there is an easier way?
So what I'm trying to do is create like a random image from panels of different colors. The user can choose how many panels (i.e. pixels) he wants to have and the number of different colors and then the program automatically generates that image. I'd really like to use panels for this because I will need this picture later on and need to modify every single pixel. As I'm comfortable with panels, I'd like to keep them and not use anything else.
So here's the code I'm using to create this panels:
//Creates two lists of panels
//Add items to list so that these places in the list can be used later.
//nudSizeX.Value is the user-chosen number of panels in x-direction
for (int a = 0; a < nudSizeX.Value; a++)
{
horizontalRows.Add(null);
}
//nudSizeY.Value is the user-chosen number of panels in y-direction
for (int b = 0; b < nudSizeY.Value; b++)
{
allRows.Add(null);
}
for (int i = 0; i < nudSizeY.Value; i++)
{
for (int j = 0; j < nudSizeX.Value; j++)
{
// new panel is created, random values for background color are assigned, position and size is calculated
//pnlBack is a panel used as a canvas on whoch the other panels are shown
Panel pnl = new Panel();
pnl.Size = new System.Drawing.Size((Convert.ToInt32(pnlBack.Size.Width)) / Convert.ToInt32(nudSizeX.Value), (Convert.ToInt32(pnlBack.Size.Height) / Convert.ToInt32(nudSizeY.Value)));
pnl.Location = new Point(Convert.ToInt32((j * pnl.Size.Width)), (Convert.ToInt32((i * pnl.Size.Height))));
//There are different types of panels that vary in color. nudTypesNumber iis the user-chosen value for howmany types there should be.
int z = r.Next(0, Convert.ToInt32(nudTypesNumber.Value));
//A user given percentage of the panels shall be free, i.e. white.
int w = r.Next(0, 100);
if (w < nudPercentFree.Value)
{
pnl.BackColor = Color.White;
}
//If a panel is not free/white, another rendom color is assigned to it. The random number determinig the Color is storede in int z.
else
{
switch (z)
{
case 0:
pnl.BackColor = Color.Red;
break;
case 1:
pnl.BackColor = Color.Blue;
break;
case 2:
pnl.BackColor = Color.Lime;
break;
case 3:
pnl.BackColor = Color.Yellow;
break;
}
}
//Every panel has to be added to a list called horizontal rows. This list is later added to a List<List<Panel>> calles allRows.
horizontalRows[j] = (pnl);
//The panel has also to be added to the "canvas-panel" pnl back. The advantage of using the canvas panel is that it is easier to determine the coordinates on this panel then on the whole form.
pnlBack.Controls.Add(pnl);
}
allRows[i] = horizontalRows;
}
As you might imagine, this is very slow when creating a checkerboard of 99x99 because the program has to loop through the process nearly 10000 times.
What would you to to improve performance? I said I'd like to keep doing it with panels because I'm comfortable with them, but if using panels is even more dumb than I thought, I'm open to other options. The program gets slower and slower the more panels it has already created. I guess that's because of the adding to the list that grows larger and larger?
This is how the output looks right now:
This is what I want to do with my "picture" later: I basically want to do Schellings model. That model shows how different groups of people (i.e. different colors) segregate when they want to have a certain percentage of people around them that belong to their group. That means that later on I have to be able to check for each of the panels/pixels what the neighbours are and have to be able to be able to change color of each pixel individually.
I don't want a ready solution, I'm just hoping for tips how to improve the speed of the picture-creating process.
Thank you very much
Instead of using Panels use a matrix to store your colors and other information you need.
In OnPaint event, use this matrix to draw the rectangles using GDI+.
Here is an example on how to draw 10x10 "pixels" if you have a matrix that contains colors:
private void myPanel_Paint(object sender, PaintEventArgs e)
{
for (var y=0; y < matrix.GetUpperBound(0); y++)
for (var x=0; x < matrix.GetUpperBound(1); x++)
{
var Brush = new SolidBrush(matrix[y,x]);
e.Graphics.FillRectangle(Brush, new Rectangle(x*10, y*10, 10, 10));
}
}
Use a picturebox to do your drawing. You've already got the code to see where each panel should be, just change it to draw a rectangle at each position. This way, you'll just be drawing a few rectangles on a board instead of working with 10.000 GUI objects.
Oh, keep your model/logic and view separated. Keep one matrix with all your information and just use a "Paint method" to draw it.
Your model could look something like this:
MyPanel[,] panels;
class MyPanel
{
Color color;
}
This way it's easy to check all neighbours of a panel, just check in the panels matrix.
And your view should just do something like this:
class View
{
Paint(MyPanel[,] panels)
{
//Draw
}
}
I think your best approach here is to write a custom Control class to draw the squares, and a custom collection class to hold the squares.
Your square collection class could look like this:
public sealed class ColouredSquareCollection
{
readonly int _width;
readonly int _height;
readonly Color[,] _colours;
public ColouredSquareCollection(int width, int height)
{
_width = width;
_height = height;
_colours = new Color[_width, _height];
intialiseColours();
}
public Color this[int x, int y]
{
get { return _colours[x, y]; }
set { _colours[x, y] = value; }
}
public int Width
{
get { return _width; }
}
public int Height
{
get { return _height; }
}
void intialiseColours()
{
for (int y = 0; y < _height; ++y)
for (int x = 0; x < _width; ++x)
_colours[x, y] = Color.White;
}
}
Then you write a custom control. To do so, add a new Custom control via Add new item -> Windows Forms -> Custom Control, and call it ColouredSquareHolder.
Then change the code to look like this. Notice how it is responsible for drawing all the squares:
public sealed partial class ColouredSquareHolder: Control
{
ColouredSquareCollection _squares;
public ColouredSquareHolder()
{
ResizeRedraw = true;
DoubleBuffered = true;
InitializeComponent();
}
public ColouredSquareCollection Squares
{
get
{
return _squares;
}
set
{
_squares = value;
Invalidate(); // Redraw after squares change.
}
}
protected override void OnPaint(PaintEventArgs pe)
{
base.OnPaint(pe);
if (_squares == null)
return;
int w = Width;
int h = Height;
int nx = _squares.Width;
int ny = _squares.Height;
var canvas = pe.Graphics;
for (int yi = 0; yi < ny; ++yi)
{
for (int xi = 0; xi < nx; ++xi)
{
int x1 = (xi*w)/nx;
int dx = ((xi + 1)*w)/nx - x1;
int y1 = (yi*h)/ny;
int dy = ((yi+1)*h)/ny - y1;
using (var brush = new SolidBrush(_squares[xi, yi]))
canvas.FillRectangle(brush, x1, y1, dx, dy);
}
}
}
}
Now you'll need to set up the square collection, add it to a ColouredSquareHolder and then add that to a form.
Firstly, add the ColouredSquareHolder to your test program and compile it so that it will show up in the Toolbox for the Windows Forms Editor.
Then create a new default Form called Form1, and from the Toolbox add a ColouredSquareHolder to it, and set the ColouredSquareHolder to Dock->Fill. Leave it called the default colouredSquareHolder1 for this demonstration.
Then change your Form1 class to look like this:
public partial class Form1: Form
{
readonly ColouredSquareCollection _squares;
readonly Random _rng = new Random();
public Form1()
{
InitializeComponent();
_squares = new ColouredSquareCollection(100, 100);
for (int x = 0; x < _squares.Width; ++x)
for (int y = 0; y < _squares.Height; ++y)
_squares[x, y] = randomColour();
colouredSquareHolder1.Squares = _squares;
}
Color randomColour()
{
return Color.FromArgb(_rng.Next(256), _rng.Next(256), _rng.Next(256));
}
}
Run your program and see how much faster it is at drawing the squares.
Hopefully this will give you the basis for something that you can build on.
Note: If you change the colours in the square collection, you will need to call .Invalidate() on the control in the form to make it redraw with the new colours.
well I suggest you using GDI+ instead , you can store your colors in a 2 dimensional array so you can draw the desired image based on that and also you can loop through them for further process , take a look at this code and also the demo project :
as you mentioned that you're not familiar with gdi+ , there is a demo project included so you can check it yourself and see how It's done in gdi+ :
demo project : ColorsTableDemoProject
Color[,] colorsTable;
Bitmap b;
Graphics g;
int size = 80; // size of table
int pixelWidth = 5; // size of each pixel
Random r = new Random();
int rand;
// CMDDraw is my Form button which draws the image
private void CMDDraw_Click(object sender, EventArgs e)
{
colorsTable = new Color[size, size];
pictureBox1.Size = new Size(size * pixelWidth, size * pixelWidth);
b = new Bitmap(size * pixelWidth, size * pixelWidth);
g = Graphics.FromImage(b);
for (int y = 0; y < size; y++)
{
for (int x = 0; x < size; x++)
{
rand = r.Next(0, 4);
switch (rand)
{
case 0: colorsTable[x, y] = Color.White; break;
case 1: colorsTable[x, y] = Color.Red; break;
case 2: colorsTable[x, y] = Color.Blue; break;
case 3: colorsTable[x, y] = Color.Lime; break;
default: break;
}
g.FillRectangle(new SolidBrush(colorsTable[x, y]), x * pixelWidth, y * pixelWidth, pixelWidth, pixelWidth);
}
}
pictureBox1.Image = b;
}
I'm parsing minecraft file data using Substrate. Minecraft is made up of 'chunks', which are made up of blocks. So I've got a spritesheet with the different 16x16 tiles. I use a croppedbitmap in WPF to find the appropriate graphic tile to make up a section of the map. Then I assign an Image() control in WPF the 'source', which is my croppedbitmap. Finally, I do myCanvas.Children.Add(myImage)... The problem is that after about 30 or so images in my canvas, the application slows to a crawl. This is a problem because typical minecraft maps will have hundreds or thousands of images. Is there a better way to draw a large amount of bitmap data? I like having Image controls because then I can put tooltips on the map and make it interactive. But I accept that an Image control is probably way more expensive and having thousands of them may not work. Here's my code.
(this only parses tiles of a certain value, hence if id == 9)
var mapTiles = (BitmapImage)FindResource("mapTiles");
CroppedBitmap waterImage = new CroppedBitmap(mapTiles, new Int32Rect(352, 48, 16, 16));
CroppedBitmap grassImage = new CroppedBitmap(mapTiles, new Int32Rect(0, 0, 16, 16));
foreach (ChunkRef chunk in chunkManager)
{
countOfTiles++;
for (int x = 0; x <= 15; x++)
{
for (int z = 0; z <= 15; z++)
{
int height = chunk.Blocks.GetHeight(x, z);
//TODO: Normalize Chunk X, Z so 0,0 is the smallest chunk so everything is visible on the canvas.
if (height > 0 && chunk.X > 0 && chunk.Z > 0)
{
var block = chunk.Blocks.GetBlock(x, height - 1, z);
if (block.ID == 9)
{
//352, 48
Image image = new Image();
image.Source = waterImage;
worldMap.Children.Add(image);
Canvas.SetTop(image, (chunk.X + x) * 16);
Canvas.SetLeft(image, (chunk.Z + z) * 16);
image.ToolTip = countOfTiles.ToString();
}
Instead of using the heavier Image, you could use an ImageBrush:
// use ImageBrush's instead
var waterImage = new ImageBrush(new CroppedBitmap(...));
var grassImage = new ImageBrush(new CroppedBitmap(...));
waterImage.Freeze();
grassImage.Freeze();
Later:
if (block.ID == 9)
{
var water = new Rectangle
{
Width = 16,
Height = 16,
Fill = waterImage
};
worldMap.Children.Add(water);
Canvas.SetTop(water, (chunk.X + x) * 16);
Canvas.SetLeft(water, (chunk.Z + z) * 16);
water.ToolTip = countOfTiles.ToString();
}
If I leave out Freeze, I can't get a test app with a 100x100 tile world to display properly. When I freeze each of the brushes, I can get the map to display seamlessly up to about 300x300. At 1000x1000 it almost does not load, but at this point you need to consider virtualizing the view as the UI won't handle that many UIElement's gracefully.
I've been racking my brain trying to figure out how to animate an effect. This is related to a question I asked on math.stackexchange.com.
https://math.stackexchange.com/questions/91120/equal-division-of-rectangles-to-make-total/
As a side note, I didn't implement the drawing algorithm that was defined on the question above -- instead using my own in order to change the perspective to make it look more condensed.
I've been able to draw a stationary 3d style effect, but I am having trouble wrapping my brain around the logic to make the lines below look like they are coming towards you.
My code is as follows,
List<double> sizes = new List<double>();
private void Form1_Load(object sender, EventArgs e)
{
for (int y = 1; y < 10; y++)
{
double s = ((240 / 2) / y) / 4;
sizes.Add(s);
}
sizes.Add(0);
}
int offset = 0;
private void button1_Click(object sender, EventArgs e)
{
Bitmap b = new Bitmap(320, 480);
Graphics g = Graphics.FromImage(b);
Color firstColor = Color.DarkGray;
Color secondColor = Color.Gray;
Color c = firstColor;
int yOffset = 0;
for(int i = 0; i < sizes.Count; i++)
{
c = (i % 2 == 0) ? firstColor : secondColor;
int y = (int)Math.Round(b.Height - yOffset - sizes[i]);
int height = (int)Math.Round(sizes[i]);
g.FillRectangle(new SolidBrush(c), new Rectangle(0, y + offset, b.Width, height + offset));
yOffset += (int)sizes[i];
}
this.BackgroundImage = b;
offset+=1;
}
Each button click should cause the rectangles to resize and move closer. However, my rectangles aren't growing as they should. My logic draws fine, but simply doesn't work as far as moving goes.
So my question is:
Is there an existing algorithm for this effect that I am not aware of, or is this something pretty simple that I'm over thinking? Any help in correcting my logic or pointing me in the right direction would be very appreciated.
Interesting...
(video of the answer here: http://youtu.be/estq62yz7v0)
I would do it like that:
First, drop all RECTANGLE drawing and draw your effect line by line. Like so:
for (int y=start;y<end;y++)
{
color = DetermineColorFor(y-start);
DrawLine(left, y, right, y, color);
}
This is of course pseudo-code not to be troubled with GDI+ or something.
Everything is clear here, except on how to code DetermineColorFor() method. That method will have to return color of the line at specified PROJECTED height.
Now, on the picture, you have:
you point of view (X) - didn't know how to draw an eye
red line (that's your screen - projection plane)
your background (alternating stripes at the bottom)
and few projecting lines that should help you devise the DetermineColorFor() method
Hint - use triangle similarity to go from screen coordinates to 'bar' coordinates.
Next hint - when you are in 'bar' coordinates, use modulo operator to determine color.
I'll add more hints if needed, but it would be great if you solved this on your own.
I was somehow inspired by the question, and have created a code for the solution. Here it is:
int _offset = 0;
double period = 20.0;
private void timer1_Tick(object sender, EventArgs e)
{
for (int y = Height / 3; y < Height; y++)
{
using (Graphics g = CreateGraphics())
{
Pen p = new Pen(GetColorFor(y - Height / 3));
g.DrawLine(p, 0, y, Width, y);
p.Dispose();
}
}
_offset++;
}
private Color GetColorFor(int y)
{
double d = 10.0;
double h = 20.0;
double z = 0.0;
if (y != 0)
{
z = d * h / (double)y + _offset;
}
double l = 128 + 127 * Math.Sin(z * 2.0 * Math.PI / period);
return Color.FromArgb((int)l, (int)l, (int)l);
}
Experiment with:
d - distance from the eye to the projection screen
h - height of the eye from the 'bar'
period - stripe width on the 'bar'
I had a timer on the form and event properly hooked. Timer duration was 20ms.
Considering that you're talking here about 2D rendering, as much as I understodd, to me it seems that you're gonna to reenvent the wheel. Cause what you need, IMHO; is use Matrix Transformations already available in GDI+ for 2D rendering.
Example of aplying it in GDI+ : GDI+ and MatrixTranformations
For this they use System.Drawing.Drawing2D.Matrix class, which is inside Graphics.
The best ever 2D rendering framework I ever used is Piccolo2D framework which I used with great success in big real production project. Definitely use this for your 2D rendering projects, but first you need to study it little bit.
Hope this helps.