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);
}
}
Related
I've sub-classed a control (ToolStripStatusLabel) to try and override the way it paints. At the moment I would expect this code to effectively do nothing, but it leads to a strange output:
protected override void OnPaint(PaintEventArgs e)
{
// Create a temp image to draw to and then put that onto the control transparently
using (Bitmap bmp = new Bitmap(this.Width, this.Height))
{
using (Graphics newGraphics = Graphics.FromImage(bmp))
{
// Paint the control to the temp graphics
PaintEventArgs newEvent = new PaintEventArgs(newGraphics, e.ClipRectangle);
base.OnPaint(newEvent);
// Copy the temp image to the control
e.Graphics.Clear(this.BackColor);
e.Graphics.DrawImage(bmp, new Rectangle(0, 0, this.Width, this.Height), 0, 0, bmp.Width, bmp.Height, GraphicsUnit.Pixel);//, imgAttr);
}
}
}
When I run this code the text on the control comes out very strangely, the expected image is at the top, the actual output is at the bottom:
It looks like when the control is drawing the text the alpha blending with the antialiased text is going wrong.
Things that I've tried:
Setting the CompositingMode of e.graphics and newGraphics
Setting the TextRenderingHint.
setting the pixel format of newGraphics to 32Bpp ARGB and Premultiplied ARGB
Clearing the newGraphics with the controls background colour before asking the base class to render.
TL;DR: You'll need to render the text yourself with a custom renderer that re-implements OnRenderItemText, probably using graphics.DrawString() to do the drawing ultimately.
Another option would be to use a label in the status bar (as mentioned by #Reza Aghaei). Then you can set UseCompatibleTextRendering to true to make it use GDI+ rather than GDI
This seems to be an inherent problem in the way that the text is rendered at the lowest level. If you add a normal ToolStripStatusLabel and set its TextDirection to be Vertical90 then you get the same result, where the anti-aliasing of the text appears to have no alpha with the background.
Looking at the source you'll see that a very similar bit of code is called where the text is rendered to a bitmap and then in this case rotated:
using (Bitmap textBmp = new Bitmap(textSize.Width, textSize.Height,PixelFormat.Format32bppPArgb)) {
using (Graphics textGraphics = Graphics.FromImage(textBmp)) {
// now draw the text..
textGraphics.TextRenderingHint = TextRenderingHint.AntiAlias;
TextRenderer.DrawText(textGraphics, text, textFont, new Rectangle(Point.Empty, textSize), textColor, textFormat);
textBmp.RotateFlip((e.TextDirection == ToolStripTextDirection.Vertical90) ? RotateFlipType.Rotate90FlipNone : RotateFlipType.Rotate270FlipNone);
g.DrawImage(textBmp, textRect);
}
}
So this seems like a fundamental problem when text is rendered to a bitmaps graphics context (as opposed to the control's graphics context). Ultimately the code that is called is:
using( WindowsGraphicsWrapper wgr = new WindowsGraphicsWrapper( dc, flags ))
{
using (WindowsFont wf = WindowsGraphicsCacheManager.GetWindowsFont( font, fontQuality )) {
wgr.WindowsGraphics.DrawText( text, wf, bounds, foreColor, GetIntTextFormatFlags( flags ) );
}
}
Which I think is wading off into GDI (as opposed to GDI+) which has trouble with alpha on text.
Your best bet is to write a custom renderer that re-implements OnRenderItemText, probably with some 'inspiration' from the source from the default implementation.
I do have following issue with my graphics object.
EDIT:
I do have a picturebox_image (imageRxTx) which is a live stream from a camera. What I do in the paint event is to draw some lines on top of the image imageRxTx (not shown in the code below). This works so far without problem.
Now I need to check for circles in imageRxTx and therefore I have to use the method ProcessImage() which needs a Bitmap as parameter. Unfortunately I do not have the Bitmap image but rather the handle (hDC) to my imageRxTx.
Question: How can I get the imageRxTx from my graphics-object and "convert" it to a bitmap-image which I need to use in the method ProcessImage(Bitmap bitmap)? This method needs to be called continously in the paint-event in order to check the live-stream of my camera (imageRxTx).
Here my code:
private void imageRxTx_paint(object sender, PaintEventArgs e)
{
var settings = new Settings();
// Create a local version of the graphics object for the PictureBox.
Graphics Draw = e.Graphics;
IntPtr hDC = Draw.GetHdc(); // Get a handle to image_RxTx.
Draw.ReleaseHdc(hDC); // Release image_RxTx handle.
//Here I need to send the picturebox_image 'image_RxTx' to ProcessImage as Bitmap
AForge.Point center = ProcessImage( ?....? );
}
// Check for circles in the bitmap-image
private AForge.Point ProcessImage(Bitmap bitmap)
{
//at this point I should read the picturebox_image 'image_RxTx'
...
The video image is updated here:
private void timer1_Elapsed(object sender, EventArgs e)
{
// If Live and captured image has changed then update the window
if (PIXCI_LIVE && LastCapturedField != pxd_capturedFieldCount(1))
{
LastCapturedField = pxd_capturedFieldCount(1);
image_RxTx.Invalidate();
}
}
As the title suggests, your main problem is a (common) misconception about what a Graphics object is.
So far I can draw to my graphics object without problem
No! A 'Graphics' object does not contain any graphics. It is only the tool used to draw graphics onto a related Bitmap. So you don't draw onto the Graphics object at all; you use it to draw onto imageRxTx, whatever that is, probably the surface of some Control or Form..
This line is using an often confusing rather useless format of the Bitmap constructor:
Bitmap bmp = new Bitmap(image_RxTx.Width, image_RxTx.Height, Draw);
The last parameter is doing next to nothing; its only function is to copy the Dpi setting. In particular it does not clone or copy any content from 'Draw', which, as you know now, a Graphics object doesn't have anyway, nor any of its other settings. So yes, the bmp Bitmap is still empty after that.
If you want to draw into bmp you need to use a Graphics object that is actually bound to it:
using (Graphics G = Graphics.FromImage(bmp)
{
// draw now..
// to draw an Image img onto the Bitmap use
G.DrawImage(img, ...);
// with the right params for source and destination!
}
None of this should probably happen in the Paint event! But all the preparation code is unclear as to what you really want to do. You should explain just what is the source of the drawing and what is the target!
If instead you want to get the stuff you draw onto image_RxTx into a Bitmap you can use this method somwhere outside (!) the Paint event:
Bitmap bmp = new Bitmap(image_RxTx.Width, image_RxTx.Height);
image_RxTx.DrawToBitmap(bmp, image_RxTx.ClientRectangle);
This will use the Paint event to draw the control into a Bitmap. Not that the result includes the whole PictureBox: The BackgroundImage, the Image and the surface drawing!
Update: To get the combined content of the PictureBox, that is both its Image and what you have drawn onto the surface, you should use the code above (the last 2 lines) in the Tick event of a Timer or right after the line that triggers the Paint event. (You didn't show us how that happens.) You can't acutally put it in the Paint event itself, as it will use the Paint event and therefore would cause an infinite loop!
The method Graphics.CopyFromScreen is probably what you're looking for.
var rect = myControl.DisplayRectangle;
var destBitmap = new Bitmap(rect.Width, rect.Height, PixelFormat.Format24bppRgb);
using (var gr = Graphics.FromImage(destBitmap))
{
gr.CopyFromScreen(myControl.PointToScreen(new Point(0, 0)), new Point(0, 0), rect.Size);
}
I have a List<> object, "imagelist", that contains the paths of many images such as .png's. Now, with the following code:
private void paint_picture(PictureBox picture, string pathofpic)
{
Graphics g = picture.CreateGraphics();
Bitmap drawnpic = null;
if (imagelist.ContainsKey(picture.Name))
{
drawnpic = new Bitmap(pathofpic);
g.DrawImage(drawnpic, 0, 0, picture.Size.Width, picture.Size.Height);
imagelist[picture.Name] = pathofpic;
}
drawnpic.Dispose();
g.Dispose();
}
I call this every time the card's image is changed, but I can't seem to make the image persist on the picturebox, when I drag the picturebox across the form (for example, over other pictureboxes). The click and drag code is just moving the picturebox with the mouse, not really relevant.
I've tried invalidating the form when I de-select the image, but it doesn't do anything.
Is there something I'm missing? Screenshot below, I dragged one image around the form and it overwrote the other images it moved across:
That’s how painting works – you have to handle its Paint event and keep painting the same thing each time it needs repainting.
What you can do is draw on top of your original image:
Bitmap b = new Bitmap(picture.Image);
using (Graphics g = Graphics.FromImage(b)) {
using (Bitmap drawnpic = new Bitmap(pathofpic)) {
g.DrawImage(drawnpic, 0, 0, b.Width, b.Height);
}
}
picture.Image = b;
Then you’d save the original image somewhere and probably use it instead of picture.Image in the new Bitmap line.
And PascalCase for method names, please. ;)
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!
Hey people I have a problem I am writing a custom control. My control inherits from Windows.Forms.Control and I am trying to override the OnPaint method. The problem is kind of weird because it works only if I include one control in my form if I add another control then the second one does not get draw, however the OnPaint method gets called for all the controls. So what I want is that all my custom controls get draw not only one here is my code:
If you run the code you will see that only one red rectangle appears in the screen.
public partial class Form1 : Form
{
myControl one = new myControl(0, 0);
myControl two = new myControl(100, 0);
public Form1()
{
InitializeComponent();
Controls.Add(one);
Controls.Add(two);
}
private void Form1_Load(object sender, EventArgs e)
{
}
}
public class myControl:Control
{
public myControl(int x, int y)
{
Location = new Point(x, y);
Size = new Size(100, 20);
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
Pen myPen = new Pen(Color.Red);
e.Graphics.DrawRectangle(myPen, new Rectangle(Location, new Size(Size.Width - 1, Size.Height - 1)));
}
}
I'm guessing you are looking for something like this:
e.Graphics.DrawRectangle(Pens.Red, new Rectangle(0, 0,
this.ClientSize.Width - 1,
this.ClientSize.Height - 1));
Your Graphic object is for the interior of your control, so using Location isn't really effective here. The coordinate system starts at 0,0 from the upper-left corner of the client area of the control.
Also, you can just use the built-in Pens for colors, otherwise, if you are creating your own "new" pen, be sure to dispose of them.
LarsTech beat me to it, but you should understand why:
All drawing inside of a control is made to a "canvas" (properly called a Device Context in Windows) who coordinates are self-relative. The upper-left corner is always 0, 0.
The Width and Height are found in ClientSize or ClientRectangle. This is because a window (a control is a window in Windows), has two areas: Client area and non-client area. For your borderless/titlebar-less control those areas are one and the same, but for future-proofing you always want to paint in the client area (unless the rare occasion occurs where you want to paint non-client bits that the OS normally paints for you).