How do i get a REAL straight line with c#? the code below draws a line, great, but this line is not perfect, it's not pixel by pixel straight, is there better code out there that produces a better, accurate straight line?
namespace Dimension3D
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void canvas_Paint(object sender, PaintEventArgs e)
{
Graphics gObject = canvas.CreateGraphics();
Brush red = new SolidBrush(Color.Red);
Pen redPen = new Pen(red, 8);
gObject.DrawLine(redPen, 10, 10, 35, 500);
}
}
}
What you see as a straight line is actually a trick. It is "smoothed" out so the jagged parts are surrounded by shaded pixels (See Below). This explains how to do it better than I can.
https://msdn.microsoft.com/en-us/library/system.drawing.graphics.smoothingmode.aspx
The images below are the same. The top is just zoomed in.
You have to turn off AntiAlias for drawing pixel perfect lines.
gObject.SmothingMode = SmoothingMode.None;
Don't forget to put it back to Default after so you don't change image rendering or other stuff that needs anitialias to look nice
Related
im currently working on a school project which is basically a game that involves GDI-Winforms animations.
I chose my game to be something like prison break where you are trying to escape the prison while guards with light-torches are walking around the room.
So, i have the Guard class which represents the guard and has A Path to walk on. Furthermore, the guard can rotate in 90deg. angles. (animated to a certain degree)
When i rotate the guard, i actually rotate the Graphics object that was passed through the Form_Paint event:
void Game_Paint(object sender, PaintEventArgs e)
{
e.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
ply.Draw(e.Graphics); // Draws the player
grd.Draw(e.Graphics); // Draws the guard
e.Graphics.DrawPolygon(Pens.Red, grd.GetPath()); // Draws the guard's path
}
When i was dealing with only 1 guard, it was working great. Smooth and did the exactly what he was supposed to do.
When i tried to add 1 more guard, they started to freak out. Soon enough i figured out that its because im sending both guards to draw the same instance of Graphics. Which also means, both of them are rotating it.
Lets say one is rotating by -90, the other will too, but his angle class member variable will not be -90, then all hell breaks loose.
Rotation of graphics method (sits inside the guard class):
public Graphics RotateGuard(Graphics g, Point pivot, float angle) // returns the rotated Graphics object
{
if (!float.IsNaN(angle))
{
using (Matrix m = new Matrix())
{
m.RotateAt(angle, pivot);
g.Transform = m;
}
}
return g;
}
The next thing i did was to give each of them this.CreateGraphics().
void Game_Paint(object sender, PaintEventArgs e)
{
e.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
ply.Draw(e.Graphics); // Draws the player
foreach (Guard grd in this.guards )
{
grd.Draw(this.CreateGraphics()); // Draws the guard
e.Graphics.DrawPolygon(Pens.Red, grd.GetPath()); // Draws the guard's path
}
}
Then everything worked just fine. The only thing is, it seemed like it was really heavy for the GPU to process or something. It drew the guard once about every 5 frames less than he supposed to be drawn.
I googled but couldn't find anything but people saying that "There is no need to clone a Graphics object", but still, i can't think of any better WORKING solution.
How can i solve this problem nicely?
Thanks in advanced.
CreateGraphics() is a really bad idea. You should do all the drawing to the Graphics passed by PaintEventArgs. So your initial code is just fine. However, what you need to do is to ensure that every object that receives a Graphics in its Draw method leaves it unchanged after doing its job. This can be achieved by using Graphics.Save and Graphics.Restore like this
class Guard
{
public void Draw(Graphics g)
{
var state = g.Save();
try
{
// The actual drawing code
}
finally
{
g.Restore(state);
}
}
}
An efficient way is to maintain one overall transform and have a matrix for each thing you wish to draw. Prior to drawing you multiply the current transform with the object's transform. Afterwards you reset the transform before drawing next.
void Game_Paint(object sender, PaintEventArgs e)
{
e.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
var g = e.Graphics;
g.ResetTransform();
g.MultiplyTransform (playerMatrix);
ply.Draw(e.Graphics); // Draws the player
g.ResetTransform();
foreach (Guard grd in this.guards )
{
g.MultiplyTransform (grd.Matrix);
grd.Draw(this.CreateGraphics()); // Draws the guard
e.Graphics.DrawPolygon(Pens.Red, grd.GetPath()); // Draws the guard's path
g.ResetTransform();
}
}
Such concepts are similar to how things are done in 3D graphics such as Direct3D; XNA; OpenGL and Unity3D.
After drawing with the rotated Graphics tool object simply call e.Graphics.ResetTransform().
Also you may want to look into Graphics.Save() and Graphics.Restore() if you have made a few settings you want to return to.. It can save few states and when done with them bring them back up. Very nice, at least if you keep count of what you are doing.
And, of course you could undo the Translation/Rotation by doing the reverse calls in the reverse order, but the other methods are simpler and made for just your case.
Note that Graphics doesn't contain any graphics, it is a tool used to draw into an associated Bitmap or onto a control's surface..
Finally: Never, never use CreateGraphics !!! Its results are non-persistent, which you want only very rarely..
I figured out what it was, then i used what #Ivan did.
void Game_Paint(object sender, PaintEventArgs e)
{
SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
e.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
var saved = e.Graphics.Save();
ply.Draw(e.Graphics); // Draws the player
foreach (Guard grd in this.guards )
{
grd.Draw(e.Graphics); // Draws the guard
e.Graphics.Restore(saved);
e.Graphics.DrawPolygon(Pens.Red, grd.GetPath()); // Draws the guard's path
}
}
All i had to do is instead of using this.DoubleBuffered i used SetStyle(ControlStyles.OptimizedDoubleBuffer, true); which at first i thought both does the same, but evidently it does not.
Then, i saved the current graphics state and re-drew it.
Thanks a lot for everyone!
I'm attempting to use a custom built .PNG file of letters from the alphabet to give my game some nice looking textual graphics. However, since the core functionality of my WinForms game works using minimal graphics (I have a timer set to fire a little animation routine that fades out the word "Guessed" when a user enters a word that they've already tried - this is done using DrawString()). Right now I'm trying to get DrawImage(Image, dstRect, srcRect, Unit) to work, and I have it set to run on form load.
private void DrawLetters()
{
// Create image.
Graphics letters = this.CreateGraphics();
Image newImage = Image.FromFile(strPath + "Letter_Map.png");
// Create rectangle for displaying image.
Rectangle destRect = new Rectangle(25, 25, 80, 80);
// Create rectangle for source image.
Rectangle srcRect = new Rectangle(0, 0, 833, 833);
GraphicsUnit units = GraphicsUnit.Pixel;
// Draw image to screen.
letters.DrawImage(newImage, destRect, srcRect, units);
}
This is practically verbatim from the MSDN site. My form populates with an array of Labels right now to show the user the grid of letters needed for the game. Is it them being drawn that overwrites the custom rectangle? I want to replace them all with images eventually. I understand srcRect to be (x, y, width, height) so I gave it the full size of the 'spritesheet'. For dstRect I only want it to place a block 80x80px on the form starting around 25x, 25y.
For the heck of it I created a blank form and called my DrawLetters() function during that form load event (I copied the function into that Form's code). I saw nothing, though, so I am starting to get a bit confused. I may need some education about just HOW the drawing works in conjunction with forms and controls being drawn on screen.
EDIT This https://stackoverflow.com/questions/837423/render-a-section-of-an-image-to-a-bitmap-c-sharp-winforms was what initially got me going, but this user has a working knowledge of XNA and seems to be trying to combine that with WinForms. XNA would be overkill, I believe, for the simple text game I am trying to 'pretty up'.
You need to override the forms OnPaintMethod to get access to the forms Graphics object which you can then use to display the image on the form.
If you want to display a portion of the image, you will need to use a different overload of DrawImage, as follows:
public partial class DrawImageDemo : Form
{
public DrawImageDemo()
{
InitializeComponent();
}
private Image _sprites;
public Image Sprites
{
get
{
if (_sprites == null)
{
_sprites = Image.FromFile("test.jpg");
}
return _sprites;
}
}
protected override void OnPaint(PaintEventArgs e)
{
// The forms graphics object
Graphics g = e.Graphics;
// Portion of original image to display
Rectangle region = new Rectangle(0, 0, 80, 80);
g.DrawImage(Sprites, 25, 25, region, GraphicsUnit.Pixel);
base.OnPaint(e);
}
}
I have written this code, however, it doesn't work. Not only will this not work, but none of the methods I have tried for drawing have worked. I've spent more than an hour or two trying to solve this, but to no success. Ive tried simple programs where all it does is display a small line, but it wont work no matter what i do :c
What am I doing wrong, or what could cause this?
private void pictureBox1_MouseDown(object sender,
MouseEventArgs m,
EventArgs e,
PaintEventArgs q)
{
if (m.Button == System.Windows.Forms.MouseButtons.Left)
{
Point currpoint = System.Windows.Forms.Cursor.Position;
Point origin = new Point(0, 0);
decimal sizee = nud.Value;
int size = Convert.ToInt32(sizee);
Random randonGen = new Random();
Color randomColor = Color.FromArgb(randonGen.Next(255),
randonGen.Next(255),
randonGen.Next(255));
Pen selPen = new Pen(randomColor, size);
Graphics g = Graphics.FromImage(pictureBox1.Image);
g.DrawLine(selPen, 3, 3, 133, 133);
}
}
Try adding a
pictureBox1.Invalidate();
call.
This is not the right way to draw to a picture box:
private void pictureBox1_MouseDown(object sender,
MouseEventArgs m,
EventArgs e,
PaintEventArgs q)
{
if (m.Button == System.Windows.Forms.MouseButtons.Left)
{
Point currpoint = System.Windows.Forms.Cursor.Position;
Point origin = new Point(0, 0);
decimal sizee = nud.Value;
int size = Convert.ToInt32(sizee);
Random randonGen = new Random();
Color randomColor = Color.FromArgb(randonGen.Next(255),
randonGen.Next(255),
randonGen.Next(255));
Pen selPen = new Pen(randomColor, size);
using(Graphics g = pictureBox1.CreateGraphics()) // Use the CreateGraphics method to create a graphic and draw on the picture box. Use using in order to free the graphics resources.
{
g.DrawLine(selPen, 3, 3, 133, 133);
}
}
}
Btw, this method will create a temporary image which is reseted when the control is invalidated. For a more persistent drawing, you need to listen to the Paint event of the picture box and draw your graphics there.
You must draw it from image first. then attach it to pictureBox1
Bitmap canvas = new Bitmap(pictureBox1.Width, pictureBox1.Height);
Graphics g = Graphics.FromImage(canvas);
Point currpoint = System.Windows.Forms.Cursor.Position;
Point origin = new Point(0, 0);
decimal sizee = nud.Value;
int size = Convert.ToInt32(sizee);
Random randonGen = new Random();
Color randomColor = Color.FromArgb(randonGen.Next(255),
randonGen.Next(255),
randonGen.Next(255));
Pen selPen = new Pen(randomColor, size);
g.DrawLine(selPen, 3, 3, 133, 133);
pictureBox1.image = canvas;
This is an old question and if anyone else has a similar problem. See below. First let's examine the Ops code.
(1) See code: The first recommended change is to keep the Pen's format simple until we have a better understanding about how the Pen actually works when drawing to graphics. Look at the Op's line where we create graphics from image which is a perfectly good example of how to directly draw ("which means to write") to the supplied bitmap by use of the bitmap's graphics context. Next, the Op provides an excellent example of the Graphics DrawLine method which can draw the defined line to the supplied bitmap.
(2) Due to missing details we have to make the following assumptions about the Op's supplied bitmap and about their method for drawing a line to the bitmap. Assuming there already exists an image inside this pictureBox1; if an image is not set the graphics we get from image will be from a null image or that each pixel will be black just as a footnote:
(a) Is the Pen's color unique to the existing bitmap and is the alpha component of the color high enough to actually see the resultant color when it's drawn (when in doubt use a unique solid color or at least set the alpha channel to 255)?
(b) This line the Op wants to draw is starting Left 3, Top 3 to Left 133 and that is 3-pixels to the right of bitmap's left side where this line has a height of 133 and as such the Pen's line size was changed to a width = 3 for demonstration purposes.
(c) The final consideration, is the pictureBox1.Size sufficient for us to see this drawn line? The line's geometry forms a rectangle similar to this RectangleF(3, 3, 3, 133) structure, so if the pictureBox1 Bounds rectangle intersects with the derived line's rectangle then the area of that intersection is where the line could be drawn and considered visible.
Before we can draw to the pictureBox1 image from graphics we must first convert the pictureBox1 image data back to a usable image type like a bitmap for example. The reason is the picture box stores only pixel data in array format and is not directly usable by GDI/GDI+ without conversion to an image type ie. bitamp, jpeg, png etc..
One can avoid this messy conversion if you handle you own painting by the way of a custom user control and by properly handling the PaintEventArgs OnPaint implementation and/or by using related graphics screen buffer context scenarios.
For those who just want the answer about what's missing:
private void button1_Click(Object sender, EventArgs e)
{
Pen selPen = new Pen(Color.Red, 2); // The Op uses random color which is not good idea for testing so we'll choose a solid color not on the existing bitmap and we'll confine our Pen's line size to 2 until we know what we're doing.
// Unfortionately the picture box "image" once loaded is not directly usable afterwords.
// We need tp recreate the pictureBox1 image to a usable form, being the "newBmp", and for efficiency the bitmap type is chosen
Bitmap newBmp = new Bitmap(pictureBox1.Width, pictureBox1.Height, PixelFormat.Format32bppArgb); // Tip: Using System.Drawing.Imaging for pixel format which uses same pixel format as screen for speed
// We create the graphics from our new and empty bitmap container
Graphics g = Graphics.FromImage(newBmp);
// Next we draw the pictureBox1 data array to our equivelent sized bitmap container
g.DrawImage(pictureBox1.Image, 0, 0);
g.DrawLine(selPen, 3, 3, 3, 133); // Format: (pen, x1, y1, x2, y2)
pictureBox1.Image = newBmp;
// Don't forget to dispose of no longer needed resources
g.Dispose();
selPen.Dispose();
newBmp.Dispose(); // or save newBmp to file before dispose ie. newBmp.Save("yourfilepath", ImageFormat.Jpeg) or in whatever image type you disire;
}
The Op's code so far only draws a line to the bitmap's surface next if we are to "see" this change we must either save bitmap to file to be viewed later in an image viewer or we must draw the updated bitmap to our display monitor, the screen.
There are several methods with which to draw to your monitor's screen. The most common graphics contexts one could use are Control.CreateGraghics, graphics to screen method from (PaintEventArgs) and/or by using a graphics screen buffer sometimes called and used as a manual double buffered graphics context in which all is implemented by the way of DrawImage method from graphics.
The simplest solution, in this case based upon the Op's own code, is to display this newly updated bitmap using the pictureBox1 control. We'll simply update the control's image with the newly updated bitmap of course once first converted to a usage graphics image as seen in the above code descriptions.
Happy coding!
I want to print text in C# using System.Drawing, but setting the StringFormat.DirectionVirtical flag only seems to print text downward. I want it to print the other way, like you see in graphs.
This will be for more than just forms, so I want to see if there is a way to do this without using a transformation matrix while drawing.
Is there any way to accomplish this?
Use Graphics.RotateTransform to get the text rotate the way you want. For example:
public partial class Form1 : Form {
public Form1() {
InitializeComponent();
}
protected override void OnPaint(PaintEventArgs e) {
string text = "Vertical text";
SizeF textSize = e.Graphics.MeasureString(text, this.Font);
e.Graphics.RotateTransform(-90);
e.Graphics.DrawString(text, this.Font, Brushes.Black,
new PointF(-textSize.Width, 0));
}
}
I writing my own textbox control in C# for Winforms.
One thing i can't figure out: how to draw the text position sign in various sizes?
It is called the 'caret'. The winapi functions are not wrapped by winforms, you'll have to pinvoke them. Start reading here. You'll find code in my answer here.
Try this.
I created a method which is meant to be called from the paint handler of whichever control you're drawing in. For simplicity I just used the form itself in mine. You probably have a panel or some other control.
The method accepts the graphics object, the scale of the cursor and the upper/left position of where to start drawing. The scale is just the height but all the math is performed relative to the height. You can tweak those numbers any way you want.
private void Form1_Paint(object sender, PaintEventArgs e)
{
DrawCaret(e.Graphics, 30, new Point(20, 20));
DrawCaret(e.Graphics, 50, new Point(100, 100));
}
private static void DrawCaret(Graphics g, int scale, Point loc)
{
g.SmoothingMode = SmoothingMode.HighQuality;
int height = scale;
int width = scale/10;
int rectBase = scale/5;
g.FillRectangle(Brushes.Black, loc.X, loc.Y, width, height);
var path = new GraphicsPath();
path.AddPolygon(new[]
{
new Point(loc.X+width, loc.Y),
new Point(loc.X+width+rectBase/2, loc.Y+rectBase/2),
new Point(loc.X+width, loc.Y+rectBase),
});
g.FillPath(Brushes.Black, path);
}
This sample produces the following output: