Draw a lot of single characters to a WinForms Control - c#

I have a custom WinForms control which could be described as some kind of terminal control (like a control containing Putty). According to this, the control should display a lot of characters, each with a different color and background color (worst case).
Currently I'm using the (kind of obsolete) Graphics.MeasureString method to determine the size of a single character in my fixed-size font so I can calculate the position of a character at a specific row and column. Then I use Graphics.DrawString to draw the characters. To optimize the performance I create a BufferedGraphics and group characters by their properties to draw consecutive characters with the same color with just one DrawString call. (Because one DrawString call per character is really slow.)
Now the problem is that DrawString apparently calculates the width of a character slightly different from MeasureString. When I draw a complete line at once, the resulting text width is different from what I calculated using the width of a single character multiplied by the character count of the line. It's just one or two pixels (or maybe less), but you can see it clearly - especially because I'm using anti-alias so you can even see a difference of just half a pixel.
The following sample code draws a long string a on the form's graphics, followed by character 'B'. Then it draws just a 'B' on the position calculated by measuring a.
var f = new Form {
Width = 1200,
Height = 500,
Font = new Font("Consolas", 11, FontStyle.Regular)
};
f.Paint += delegate(object sender, PaintEventArgs e) {
var a = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
var size = e.Graphics.MeasureString(a, f.Font, new PointF(0, 0), StringFormat.GenericTypographic);
using (var black = new SolidBrush(Color.Black))
{
e.Graphics.DrawString(a + "B", f.Font, black, 0, 0, StringFormat.GenericTypographic);
e.Graphics.DrawString("B", f.Font, black, size.Width, 20, StringFormat.GenericTypographic);
}
};
f.Show();
And if you look closely, you will see that the second B is about one pixel more right - at least with my display settings (100% dpi scale, ClearType enabled). Although one pixel is not much, when you draw lines using unicode characters U+2500 through U+257F it looks pretty ugly if the characters aren't perfectly aligned.
Also I can't use the TextRenderer class because its MeasureString method returns integer values, but DrawString of course does not draw each character on a full pixel position (what would be required to align it with a position calculated using row/column and the measured integer character size).
So my question is: Is there any (efficient) method to draw strings which are perfectly aligned to their corresponding position calculated using the character size?
P.S.: MeasureString with a 200-character-string returns exactly 200 times the return value of MeasureString with a single-character-string.

I have had some similar issues with measuring the strings in Graphics. I have used SizeInPoints, a property from the class Font and multiplied it for the number of characters I have in the string... I dont't know if it helps.
If not it can be a "rounding" problem with the pixels... then I would try to scale up the font size (maybe 10 times), measure it and then divide it by 10 again when using the size to color the background.
I hope it helps.
Good luck! Regards!

I'm having same issue.
It seems that the behavior depends on the font size.
After changing font with following code, this issue didn't occur.
Font = new Font("Consolas", 20.0F, FontStyle.Regular, GraphicsUnit.Pixel)
However, with another font size as below this issue still occurs.
Font = new Font("Consolas", 20.3F, FontStyle.Regular, GraphicsUnit.Pixel)
My guess on background of this issue: MeasureString and DrawString uses different layout routine and they have different characteristic in rounding error of float number. Only with 'simple' font size they gives same results.
Also, this issue didn't occur with bitmap fonts.

Related

gdi+ - Graphics.MeasureString either too wide or too narrow

I'm using the System.Drawing library in C# to measure a string's size:
SizeF size = gfx.MeasureString("Hello", myFont);
However, this returns a size with a bit of spacing around the text. Here's the text rendered with a red bounding box that represents the size MeasureString returned. The TopLeft corner of both the box and the text is exactly the same point.
I stumbled upon this question on Stack Overflow that recommended using StringFormat.GenericTypographic to remove the spacing. So I changed my code to the following:
SizeF size = gfx.MeasureString("Hello", myFont, 0, StringFormat.GenericTypographic);
Which yields the following result (again, the TopLeft corner of the box and the text is identical):
Almost perfect, but the size is consistently too narrow and cuts into the last letter. These results are reproducible with any font of any size I tried, with any string and with any width parameter.
My current workaround is to offset the text 5 pixels to the left when drawing it. What am I missing?
Turns out I also had to draw the string using StringFormat.GenericTypographic, not just measure with it. The new DrawString call looks like this:
gfx.DrawString("Hello", myFont, myBrush, topLeft, StringFormat.GenericTypographic);

How to draw a string without padding

I am using the GraphicsPath.AddString() function, but it draws the text with a little space around the text. Any idea how to draw the string without that padding, only the paths of the text?
My code is like this:
GraphicsPath gp = new GraphicsPath();
gp.AddString(text, font.FontFamily, (int)font.Style, font.Size,
boundsRectangle, format);
g.DrawPath(pen, gp);
What is happening is the under the hood it is probably using Graphics.MeasureString(), from the documentation :
GDI+ adds a small amount (1/6 em) to each end of every string displayed. This 1/6 em allows >for glyphs with overhanging ends (such as italic 'f'), and also gives GDI+ a small amount >of leeway to help with grid fitting expansion.
The default action of DrawString will work against you in displaying adjacent runs:
Firstly the default StringFormat adds an extra 1/6 em at each end of each output;
Secondly, when grid fitted widths are less than designed, the string is allowed to contract
by up to an em.
To avoid these problems:
Always pass MeasureString and DrawString a StringFormat based on the typographic >StringFormat (GenericTypographic).
Set the Graphics TextRenderingHint to TextRenderingHintAntiAlias. This rendering method >uses anti-aliasing and sub-pixel glyph positioning to avoid the need for grid-fitting, and >is thus inherently resolution independent.
So it looks like you should be able to fix this using the correct StringFormat.

Is it possible to calculate how many characters will fit in a fixed width label if font is provided in C#?

I have a fixed width (Height is not the problem) label component in C#. Now I want to calculate that approximately how many characters will fit in to that width if font size and font family is provided. I know that every character takes different pixels while rendering so it is not possible to get the exact number of characters. But, I think if we take the letter who takes more no of pixels and calculate considering that letter, we will able to get approximate number of characters that will fit into the fixed width according to font provided. So, if we consider the character 'W' as the widest one then how will I calculate the number of 'W's that will fit in particular width.
I can not use the GDI+ Graphics.MeasureString method since I want it before rendering the character.
When I use the GDI TextRenderer class
SizeF sizeOfW = TextRenderer.MeasureText("W", new Font("DejaVu Sans", 28.0F));
It returns {59.0, 44.0}, which I find is completely wrong, because if I took width of label as 80 px, according to above calculation It will have only one 'W' but it's not the case in reality.
So can anybody tell where I am going wrong?
You can use the following:
var g=Graphics.FromHwnd(label1.Handle);
int charFitted, linesFitted;
g.MeasureString(mystring, label1.Font, label1.Size, null,
out charFitted, out linesFitted);
After the execution you will have into charFitted the amout of chars that label1 can show.

How to create a font with precise character height in Windows Forms?

In my program I need to generate a bitmap with all digit characters (0..9) laid from left to right, plus a few other characters.
The user will select from the UI:
the desired font, and
the desired character height in pixels.
So I want to create a Bitmap, then a Graphics from this bitmap, then draw the digits one by one to this bitmap, and then save it to disk.
What I couldn't figure out in hours:
How do I create a font with the correct size so that the digit '0' has the height given by the user?
I played with all the parameters in the Font constructor, with properties of StringFormat, with MeasureString/MeasureText/MeasureCharacterRanges, I tried creating the font with the native CreateFont() via P/Invoke (with positive/negative nHeight). All parameters called "size" or "height" seem to indicate the size of some hypothetical character that's much larger than '0'.
I did read the theory with line height, em height, ascent, descent etc. There seem to be no notion for the real height of a character, without padding and spacing and so on
Again my question: Given the font name and the desired pixel height for the '0' glyph, how can I create a font which draws that glyph with the required height?
I would appreciate any guidance.
LATER EDIT
Some more details about my project: It's an embedded device with a big display, and I need to provide an easy way for the designers to to generate and try out bitmap fonts of their liking. A Windows tool that generates such bitmap fonts seemed like a good solution to me.
While I wasn't far off the mark with what I posted here earlier, it didn't actually work. In banging my head against this I found it interesting to note that graphics.MeasureString("M", ...) reports a height far larger than graphics.MeasureString("MM", ...). In calculating the font sizes by hand I've found the first(which correlates with GetEMSize's response) is actually the full line size. In the end I came to realize the actual character sizes of the digits aren't required to correlate to any of the metrics, whether real or .net.
I believe you could use this Font Constructor to specify the GraphicsUnit to be pixels. Then it should create the font with the appropriate size.
Adding test code - edit accordingly for your case and don't judge for style, I just wanted something I could paste in LINQPad and would produce an image.
using (var font = new Font("Arial",10,FontStyle.Regular, GraphicsUnit.Pixel))
using (var image = new Bitmap(30, 15))
using (var graphics = Graphics.FromImage(image))
{
graphics.FillRectangle(Brushes.White, new Rectangle(0, 0, 30, 15));
graphics.DrawString("Ay", font, Brushes.Black, 0, 0);
image.Save(#"E:\test.bmp", ImageFormat.Bmp);
}
Remember when setting size by Pixels that all characters in the Font need to fit in that range, meaning letters with a descender and letters with an ascender.

Drawing a contrasted string on an image

So, I have a snapshot of a video source, which I get into an Image, grab a Graphics object for it, and then draw a timestamp in the bottom right of the image. No problem thus far. However, I cannot guarantee what colour is going to be behind the text, so no matter what brush I use, it will almost certainly clash with some of the images that it is drawn on, making the text unreadable.
I am wondering if anyone knows of a way (either a method in .net, or a nice algorithm), for determining the best colour for a string based on the image behind it.
Cheers
just draw the string 5 times.
One time 1(or2) pixels to the left in black
One time 1(or2) pixels to the right in black
One time 1(or2) pixels above it in black
One time 1(or2) pixels below it in black
and the final time in white on the place where you want it
The only reliable way is to use a contrasting outline.
Back in the days of Commodore 64 sprite graphics, if you wanted something to stand out against any background, you used XOR blitting. Some people referred to this as 'reverse video'.
You can draw lines this way using ControlPaint.DrawReversibleLine, but that won't work for text.
This CodeProject article shows how you can create an XOR brush using interop to gdi32.dll.
Or, if it is allowed, you could use a background color (your choice) for the text (for example white text on black background).
Otherwise, you would need to capture the rectangle where the text is written (for every frame), create the negative image of it, and then get the median color in the rectangle and use it to write the text.
A more complex solution would get you to use two layers (initial picture - L1 and Text (transparent background, black text) - L2),
and before combining them, take all the pixels from L2 that contain text and change the color for the each pixel of the text to the "negative" underlying pixel color value of the L1, but you won't get something that's too usable from a "viewer's" point of view.
This could be a number of variations on the answer by reinier.
Draw underlying text (the four offset ones mentioned by reinier) not in black, but actually in a contrasting color to the foreground color of the actualy text.
Draw the text twice: once in a contrasting color but in bold and/or a slightly larger size, then in the text foreground color over that. Might have to fiddle a bit with coordinates and even need to do the drawing per word or even character to get both passes to nicely align and not give an ugly end result.
Do what reinier suggested, but perhaps not four times (all four directions), but maybe three or even two times to get a kind of "shaded" look.
Let go of the whole "draw text pixel by pixel using API calls" approach and use advanced multilayer compositing techniques like the ones available in WPF design.
For some examples of the last option, check out slides 18 and 21 in Advanced OSM Cartography on SlideShare.
The following snippet shows how to invert a color (background) and then applies Dinah's suggestion to create the background using Graphics.DrawString().
private static Color InvertColor(Color c)
{
return Color.FromArgb(255 - c.R, 255 - c.G, 255 - c.B);
}
// In the following, constants and inplace vars can be parameters in your code
const byte ALPHA = 192;
var textColor = Color.Orange;
var textBrush = new SolidBrush(Color.FromArgb(ALPHA, textColor));
var textBrushBkg = new SolidBrush(Color.FromArgb(ALPHA, InvertColor(textColor)));
var font = new Font("Tahoma", 7);
var info = "whatever you wanna write";
var r = new Rectangle(10, 10, 10, 10);
// write the text
using (var g = Graphics.FromImage(yourBitmap))
{
g.Clear(Color.Transparent);
// to avoid bleeding of transparent color, must use SingleBitPerPixelGridFit
g.TextRenderingHint = TextRenderingHint.SingleBitPerPixelGridFit;
// Draw background for text
g.DrawString(info, font, textBrushBkg, r.Left - 1, r.Top - 1);
g.DrawString(info, font, textBrushBkg, r.Left + 1, r.Top + 1);
g.DrawString(info, font, textBrushBkg, r.Left + 1, r.Top - 1);
g.DrawString(info, font, textBrushBkg, r.Left - 1, r.Top + 1);
// Draw text
g.DrawString(info, font, textBrush, r.Left, r.Top);
}

Categories

Resources