Drawing string by GDI+ in exactly in specified point - c#

I have problem with drawing string in c#
Here is my code:
Graphic.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAliasGridFit;
Pen pen = new Pen(brush, 2f);
Font font = new Font("Segoue UI", 15);
graphic.DrawString("2", font, brush, new PointF(0f, 0f));
Previously i created picturebox and i did graphic obiect from it.
The problem is that "2" is not drawing in (0;0) but in (4;5). Event if i turn off AntiAlias is still in (4;5).
I check it by drawing two lines from (0;0) to (200;0) and from (0;0) to (0;200) and according to them i calculated that error in drawing string.
Any ideas why? I need draw string in exactly point.
//Edit
i was also trying set font in this way:
Font font = new Font("Microsoft Sans Serif", 10);
and draw "2" in (0,0), the real position of "2" is (3,3). With drawing "1" error is the same (3;3).

Graphics.DrawString is automatically padding the box it uses to draw strings. The reason is that it's purpose is to draw label texts etc. for controls and therefor is "ready-made" so there is padding around the text.
Microsoft eventually realized that this approach wasn't the best for situations when one needed more accurate text drawing (ie. text editors etc.), and for that reason developed the TextRendered class which wraps GDI instead of GDI+ and give back the "old" more accurate string drawing.
Try:
TextRenderer.DrawText(e.Graphics, "2", font, new Point(0, 0), brush);
You might experience padding even with this, but here you can more reliable compensate for that. Check the link below to see what flags you can use with the method.
Note: if you need to measure text it's important to use the TextRenderer.MeasureText() for this.
For more details, go to:
http://msdn.microsoft.com/en-us/library/system.windows.forms.textrenderer.aspx
Flags:
http://msdn.microsoft.com/en-us/library/w3cdh3zw.aspx

what happens if your "string" has a descending letter, like a g or a j? each font allows for descending parts of letters, so you might need to account for that by looking at the font's metrics
http://msdn.microsoft.com/en-us/library/xwf9s90b.aspx

Related

Draw a lot of single characters to a WinForms Control

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.

How do I use DrawString without trimming?

I find the way the DrawString function cuts off entire characters or words to be counterintuitive. Showing parts of the text clearly conveys that something is missing.
Here are some examples:
StringTrimming.None:
StringTrimming.Character:
What I want:
(GIMP mockup)
Is StringTrimming.None the same as StringTrimming.Character? They seem to behave exactly alike in my case. Is there something I could have overlooked or is this a known "feature"?
According to the docs StringTrimming.None "Specifies no trimming."
This site with examples created with Java even show "None" to trim more characters than "Character".
Are there other tricks to get this effect?
Note: I do not want to display "…" or similar. I find this to be a waste of space but that is probably a discussion for UX.
It's possible that the text appears to be trimmed because it's actually wrapping invisibly onto the next line. In the call to Graphics.DrawString, disable wrapping by passing a StringFormat object whose FormatFlags property is set to NoWrap:
StringFormat format =
new StringFormat
{
FormatFlags = StringFormatFlags.NoWrap,
Trimming = StringTrimming.None
};
g.DrawString(s, font, brush, rect, format);
As a workaround, you can give the DrawString method a bigger RectangleF than you really want, and set the clipping region of your Graphics object to the actual RectangleF you want to draw the string in.
RectangleF rect = new RectangleF(10, 10, 30, 15);
e.Graphics.DrawRectangle(Pens.Red, Rectangle.Truncate(rect));
RectangleF fakeRect = new RectangleF(rect.Location, new SizeF(short.MaxValue, short.MaxValue));
Region r = e.Graphics.Clip;
e.Graphics.SetClip(rect);
e.Graphics.DrawString("1 Default", this.Font, SystemBrushes.ControlText, fakeRect);
e.Graphics.Clip = r;
Note that this code assumes you wish to anchor your text to the top left corner of the rectangle. Otherwise you should generate the fake rectangle in a method that maintains the same anchor point as your intended layout rectangle.

Any better ways to anti-alias text on .NET 2.0?

I want to anti-alias like on this screenshot:
I've tried all the TextRenderingHints, but it still looks bad. Notice that when I did the screenshot the PNG smoother it, but you can still see that the second text isn't as good as the first.
Here's my code:
private void Form1_Paint(object sender, PaintEventArgs e) {
try {
SolidBrush solidBrush = new SolidBrush(Color.Black);
PrivateFontCollection fonts;
FontFamily family = LoadFontFamily(Properties.Resources.SegoeWP_Light, out fonts);
Font font = new Font(family, 18);
e.Graphics.SmoothingMode = SmoothingMode.HighQuality;
e.Graphics.TextRenderingHint = TextRenderingHint.SystemDefault;
e.Graphics.DrawString("Downloads", font, solidBrush, new PointF(135, 80));
} catch (Exception ex) {
MessageBox.Show(ex.ToString());
}
}
Is there a different way to smooth fonts on WinForms (.NET 2.0) ?
I believe Photoshop does oversampling. You might not be able to get results quite as good as Photoshop -- it's got better algorithms for pixel snapping, and it probably does things like grow the outlines slightly to get a darker result -- but if SmoothingMode.AntiAlias (as George suggested) isn't enough, you can try these steps to see if they get the results you want.
Create a new Font with a size twice as big as what you want to end up with.
Measure the text using the larger font, and create a Bitmap of that size. Make sure to create it using a format that supports alpha transparency.
Draw the text to your Bitmap (at (0, 0)). (You probably want to use SmoothingMode.AntiAlias here.)
Draw the Bitmap to your final target, resizing it down by 50%. Make sure to use a high-quality resizing mode.
This will give more of the "smooth" effect from your sample image, because you're basically drawing to a sub-pixel grid. Your results may vary, though -- you might need to draw at (0, 1) instead of (0, 0) to get it to look sharp enough, and you may need to tweak the font size. You could even try drawing at 3x instead of 2x, but you won't get much benefit from going farther than that.
The big concern would be that your text might not come out dark enough -- if lines draw at 1 pixel wide in the 2x size, then they'll only be 50% black in the final result. The best way to deal with this is to pick a font with thicker lines. You could also try overdrawing the text in your Bitmap -- draw it once at (0, 0), then again at (0, 1) or (1,0) -- to see if that thickens the lines enough to look good once you size it back down. You'll have to experiment.
I wouldn't recommend this for general-purpose text drawing (where the user selects the layout and the font size), but if you can be in full control of the font size, you can probably tweak this to get pretty good results.
I would have thought the below would have been enough.
e.Graphics.TextRenderingHint = TextRenderingHint.AntiAlias;
If not maybe try the sample on the page below with no changes, then gradually add in your changes until it breaks.
How to: Use Antialiasing with Text

TextRenderer with Graphics transform

I've been working on a custom control and I've run into an issue with TextRenderer acting a bit surprisingly. In my OnPaint event I apply transform to the Graphics object to compensate for the scroll position like this:
e.Graphics.Transform = new System.Drawing.Drawing2D.Matrix(1, 0, 0, 1, this.AutoScrollPosition.X, this.AutoScrollPosition.Y);
Then I pass the graphic object to all sub elements of the control so that they paint themselves onto it. One of this elements should draw text string onto the graphics surface. And this is where I've got an issue. This line seems to work correctly when scrolling:
e.Graphics.DrawString(this.Text, this.Font, brush, new PointF(this.Rectangle.X, this.Rectangle.Y));
But when I use TextRenderer I get a completely different result. Heres the text line that supposed to draw the text:
TextRenderer.DrawText(e.Graphics, this.Text, this.Font, this.Rectangle, this.TextColor, TextFormatFlags.PreserveGraphicsClipping | TextFormatFlags.PreserveGraphicsTranslateTransform);
I thought that these two lines should produce the same result. But for some reason the second one applies the graphics transform differently and as a result, when I scroll the control all the text lines move around with different speed than the rest of the elements on the drawing surface. Could someone explain me why this is happening?
Here's my best guess at this: TextRenderer.DrawText is GDI-based and therefore resolution-dependant. Graphics.DrawString is GDI+ and therefore resolution-independant. See also this article.
Since you say that the texts "move around with different speed", probably what happens is that the GDI call uses a different "default" resolution than the one your Graphics object has. That'd mean that you'd have to adjust your AutoScrollCoordinates to respect the difference between your Graphics object resolution and the "default" GDI resolution.

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