DrawString and DrawRectangle using same X&Y start at different places - c#

I have the following code:
public void DrawLetter(int Cell, string Letter, int X, int Y)
{
System.Drawing.Font fBody = new System.Drawing.Font("Courier New", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((System.Byte)(0)));
Pencil.DrawString(Letter, fBody, System.Drawing.Brushes.Black, X, Y);
Pencil.DrawRectangle(new Pen(System.Drawing.Brushes.Red), X, Y, 10, 10);
}
When I go through this twice and use 140,249 and 296,249 as co-ordinates the rectangle appears as I would expect in the correct position but the string appears a few pixels out and I can't work out why.
I put in the DrawRectangle to check that it was starting in the correct position and it is. Am I doing something wrong in the DrawString? Also if I draw a D and a Z the width of the D is 10px and Z is 8px, I thought using Courier it would give me fixed width?
UPDATE: I looked at the sample on MSDN and here is a screenshot. Even though the DrawString is positioned at 0,0 you can see that the letter does not appear at 0,0. The rectangle does though. There must be padding or something:
UPDATE 2: Using the following code seems to improve things although its not perfect:
StringFormat strFormat = new StringFormat(StringFormat.GenericTypographic);
Pencil.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;
Pencil.DrawString(measureString, stringFont, Brushes.Black, new PointF(0, 0), strFormat);

It wouldn't be the default padding / line-height etc being taken into account for the "Courier New" font would it?

Related

StringFormat.Trimming changes vertical placement of text

This is for a header/footer on a printed page. The code below is painting the left & center parts of the header/footer (I've omitted the code that paints the right) and simplified logic to illustrate the problem better. The idea is the center part of header takes precedence and the left and right will be trimmed via StringTrimming.EllipsisPath if the center is too big for them to fit. This all works except for whenever trimming occurs, GDI+ is shifting the trimmed text vertically a bit.
EDIT: I've written a dedicated sample to illustrate the problem because my original post was not clear enough for folks. This is the entire code of the sample. It's a .NET Framework Winforms app created with VS19.
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Form1_Paint(object sender, PaintEventArgs e)
{
e.Graphics.TextRenderingHint = TextRenderingHint.ClearTypeGridFit;
Font font = new Font("Century Gothic", 24F, FontStyle.Regular, GraphicsUnit.Point);
string demo = "[Emergency 123]";
StringFormat fmtTrimmed = new StringFormat(StringFormat.GenericTypographic)
{
Alignment = StringAlignment.Near,
LineAlignment = StringAlignment.Near,
Trimming = StringTrimming.EllipsisCharacter,
};
StringFormat fmtNotTrimmed = new StringFormat(StringFormat.GenericTypographic)
{
Alignment = StringAlignment.Center,
LineAlignment = StringAlignment.Near,
};
try
{
Rectangle boundsHeader = new Rectangle(10, 10, this.ClientSize.Width - 20, (int)font.GetHeight() + 8);
SizeF textSize = e.Graphics.MeasureString(demo, font, boundsHeader.Size, fmtNotTrimmed);
boundsHeader.Inflate(1, 1);
e.Graphics.DrawRectangle(Pens.Blue, boundsHeader);
float textCenterBounds = (boundsHeader.Width - textSize.Width) / 2;
e.Graphics.DrawRectangle(Pens.Green, boundsHeader.X + textCenterBounds, boundsHeader.Y, textSize.Width, textSize.Height);
e.Graphics.DrawString(demo, font, Brushes.Black, boundsHeader, fmtNotTrimmed);
RectangleF boundsLeft = new RectangleF(boundsHeader.X, boundsHeader.Y, textCenterBounds, boundsHeader.Height);
SizeF sizeLeft = e.Graphics.MeasureString(demo, font, boundsLeft.Size, fmtTrimmed);
e.Graphics.DrawRectangle(Pens.Red, boundsLeft.X, boundsLeft.Y, boundsLeft.Width, textSize.Height);
e.Graphics.DrawString(demo, font, Brushes.Black, boundsLeft, fmtTrimmed);
}
finally
{
font.Dispose();
fmtTrimmed.Dispose();
fmtNotTrimmed.Dispose();
}
}
Here the app shows that if left is of size that trimming does not occur, it is aligned vertically correctly:
If left is has a length where trimming occurs (note elipsis), vertical alignment shifts up slightly (note code uses Form1_Resize to resize rects).
You can see this most clearly by looking at the y, g, and brackets.
I've tried every combination of StringTrimming, LineAlignment, and FormatFlags and cannot resolve this. Note that in the current implementation I've also tried making the Rectangle have a height of 0. Same issue occurs if the bounds.Height is the font height, sizeCenter.Height, or bigger.
I can't believe this is a bug in GDI+, so I've got to be doing something wrong. Many searches and re-reads of the docs have not helped. I'm at my wits end.
EDIT: based on #HansPassant's comments below, I've found different fonts behave differently. In the images above I was using "Microsoft Sans Serif".
"Arial" has same problem (bold or regular)
"Tahoma" does reproduce the problem.
"Cascadia Code" DOES reproduce (it's new and fixed pitch)
"Lucida Console" DOES NOT!?!?! reproduce
"Courier New" DOES.
I've determined that some fonts are worse than others. For example, if I change the code above to use 24F as the font size I can't reproduce it with Microsoft Sans Serif).
I've also determined that StringTrimming.Character and StringTrimming.ElipsisCharacter and StringTrimming.ElipsisPath all behave the same way (so the ellipsis char does not seem to be the cause).
EDIT: Showing another example here using Century Gothic regular:
EDIT: This sample illustrates that e.Graphics.TextRenderingHint = TextRenderingHint.AntiAlias does NOT fix the problem when LineAlignment = StringAlignment.Far.

Get Rectangle's color after FillRectangles

How can I get the color of a Rectangle after creating it?
I'm using this code to create them :
SolidBrush sb = new SolidBrush(Color.Red);
Graphics g = panel1.CreateGraphics();
Rectangle[] rects = { new Rectangle(0, 0, 15, 15), new Rectangle(16, 16, 15, 15), new Rectangle(16, 0, 15, 15), new Rectangle(0, 16, 15, 15) };
g.FillRectangles(sb,rects);
And now I want to get the color of the 3rd rectangle
rects[2] = ....
Is it possible to get this? And it should return Color.Red.
You can find the position of center pixel of your rectangle, then you can use GetPixel method to get information about such as color.
Color pixelColor = myBitmap.GetPixel(50, 50);
The FillRectangles draws the rectangles with the brush (color) you passed. There is no reference from any rectangle to any color. It just executes a drawing command to a Graphics object.
A Rectangle doesn't have a color. So, nope, you cannot. If you explain why you would need it, there might be other solutions to get the desired results.
If you store the image, you only store the result (i.e. the grid of pixels). There is no information on how you created this image.
E.g. based on the bitmap image alone, you cannot differentiate between two adjacent squares (new Rectangle(0, 0, 15, 15), new Rectangle(15, 0, 15, 15) and a single rectangle that has twice the width (new Rectangle(0, 0, 30, 15)).
So the short answer to your question is no, you cannot do that.
Unless you store your information (the rectangles you drew) separately and then use that to find the appropriate pixel on the image (and this only works in simple cases - if you overlapped an earlier rectangle, it's going to be impossible)
But if you're going to store the rectangle information anyway, you might as well store its color and then you don't need to reverse engineer the image anymore. So the answer remains that you cannot do this based on an image alone.
You could write a new Class, for example:
class ColorRectangles
{
public Color color;
public Rectangle[] rects;
public ColorRectangle(Rectangle[] rects, Color color) : base(x, y, width, height)
{
this.color = color
this.rects = rects;
}
}
and then set the Rectangles Color with g.FillRectangles(new SolidBrush(Rectangle.color), Rectangle.rects);

Fit text on a custom component

I am trying to create a 'jeopardy' type game and i have gotten to the point where you just have to click on a box and a question box will appear.
Im using a customPictureBox, essentially overriding text and such:
protected override void OnPaint(PaintEventArgs pe)
{
base.OnPaint(pe);
Size size = TextRenderer.MeasureText(text, font);
int x = Width / 2 - size.Width / 2;
int y = Height / 2 - size.Height / 2;
pe.Graphics.DrawString(text, font, new SolidBrush(color), x, y);
}
on the game i add its properties like this:
CustomPictureBox box = new CustomPictureBox()
{
Text = ((QuestionBox)sender).Question.Text.ToUpper(),
SizeMode = PictureBoxSizeMode.StretchImage,
Font = new Font("Comic Sans MS", 40),
Image = Properties.Resources.MainTile,
ForeColor = Color.White,
BorderStyle = BorderStyle.FixedSingle,
Dock = DockStyle.Fill
};
The problem comes when the text length exceeds certain amount of (depending on the monitor) words, essentially what happens is that the text is drawn but a majority of it goes outside of the form, adding Autosize and Maximum size
Reference, this adds the MaxSize to the whole CustomPictureBox.
This solution seems to put an offset for the words, making longer sentences fly completely off the screen.
Ideally I need to add a newline til the words reach a preset boundary so they wouldn't go off the form.
Do not use Graphics.DrawString, use TextRenderer.DrawText instead.
Specifically, use this overload since it accepts
IDeviceContext - that's your Graphics instance, 
String - that's the string to draw,
Font - that's the font to use, 
Rectangle - that's the rectangle to use for boundaries, 
Color - that's the fore color to use, 
And TextFormatFlags - that's a flags enum that allows you to specify how to wrap text - using either default or WordBreak.
So, replace this row:
pe.Graphics.DrawString(text, font, new SolidBrush(color), x, y);
With this:
var flags = TextFormatFlags.HorizontalCenter |
TextFormatFlags.VerticalCenter |
TextFormatFlags.WordBreak;
TextRenderer.DrawText(pe.Graphics, text, font, pe.ClipRectangle, ForeColor, flags);

Adding antialiasing

I am trying to using antialiasing but I don't why it isn't working:
{
Pen pen = new Pen(Color.Black, 3);
Pen r = new Pen(Color.YellowGreen, 3);
Graphics b = panel2.CreateGraphics();
b.DrawEllipse(pen, 6, 0, 90, 90);
b.SmoothingMode = SmoothingMode.AntiAlias;
b.DrawLine(r, new Point(50, 90), new Point(50, 0));
}
First it should be noted that the Graphics object does not contain any graphics; it is a tool that lets you draw onto a related bitmap, including a control's surface. Therefore changing any of its properties, like the SmoothingMode only influences graphics you draw from then on, not anything you have drawn before..
The circle certainly would have antialised pixels if you would draw it after setting the SmoothingMode from its default None to AntiAlias.
The Line is vertical, so it doesn't need antialiasing except at its ends, where there is some. But if you tilt it or move it to a non-integer position anti-aliasing will show!
Let's modify your code a little and look closely at the result:
Pen pen = new Pen(Color.Black, 3);
Pen r = new Pen(Color.YellowGreen, 3);
Graphics b = panel2.CreateGraphics();
b.DrawEllipse(pen, 6, 6, 90, 90);
b.SmoothingMode = SmoothingMode.AntiAlias;
b.DrawLine(r, new Point(50, 90), new Point(50, 0));
b.DrawLine(r, new Point(60, 90), new Point(70, 0));
b.DrawLine(r, new PointF(40.5f, 90), new PointF(40.5f, 0));
b.DrawEllipse(pen, 6, 6, 30, 30);
The smaller circle has many gray pixels and even the original green line has a lighter top end. The two new lines are fully anti-aliased now, one because it is tilted, the other because it sits 'between' pixels.
Btw: If it is turned on you will also see anti-alising when your Pen.Width is even or when it is a non-integer number. The reason for the latter should be obvious; the former comes from the PenAlignment property. Its default Center tries to center the pen, but not at the pixel boundary but at the center of the coordinate pixels. Therefore only an uneven width will completely fill the pixels and not cause anti-aliasing. For closed shapes you can change this behaviour by changing the Pen.Alignment to Inset:
This property determines how the Pen draws closed curves and
polygons. The PenAlignment enumeration specifies five values;
however, only two values—Center and Inset—will change the appearance
of a drawn line. Center is the default value for this property and
specifies that the width of the pen is centered on the outline of the
curve or polygon. A value of Inset for this property specifies that the
width of the pen is inside the outline of the curve or polygon. The
other three values, Right, Left, and Outset, will result in a pen that
is centered.
A Pen that has its alignment set to Inset will yield unreliable
results, sometimes drawing in the inset position and sometimes in the
centered position.Also, an inset pen cannot be used to draw compound
lines and cannot draw dashed lines with Triangle dash caps.
PS: The question was not about how to draw properly, so let me just note that you never ought to do it using control.CreateGraphics as this will always only result in non-persistent graphics. Instead you need to use the Paint event and its e.Graphics object..

How to have text with bold and normal words for same label?

I need to have text for my label control which as both bold and normal character.
How can I do that? any idea or pointers??
Thanks.
EDIT:
It is Winform, and I am sorry but I have no idea, I am using TWO label for this but I want to use only one.
Is it possible.!!
I have done something like,
using (Graphics g = Graphics.FromImage(pictureBox1.Image))
{
Font drawFont = new Font("Arial", 12);
Font drawFontBold = new Font("Arial", 12, FontStyle.Bold);
SolidBrush drawBrush = new SolidBrush(Color.Black);
// find the width of single char using selected fonts
float CharWidth = g.MeasureString("Y", drawFont).Width;
// draw first part of string
g.DrawString("this is normal", drawFont, drawBrush, new RectangleF(350f, 250f, 647, 200));
// now get the total width of words drawn using above settings
float widthFirst = ("this is normal").Length() * CharWidth;
// the width of first part string to start of second part to avoid overlay
g.DrawString(" and this is bold text", drawFontBold, drawBrush, new RectangleF(350f + widthFirst, 250f, 647, 200));
}
Hope it helps..!!!
NOTE
You can use Label Paint event and Draw the way I have done for lable
I think answer can be relevant for you:
https://stackoverflow.com/a/2527744/3835956
Use a styled RichTextBox instead of a label, select the text and set it to bold.

Categories

Resources