Bold Font is Rendered Wrong - c#

When I try to render a Chinese string like 试标记好不好 Graphics.DrawString draws it
even if I change the Font Linking to SimSun. On the other hand TextRenderer works but it fails to render readable strings when bold fonts are used. It seems there is no correct way to render bold strings.
The issue is described in greater detail here. Am I doing something wrong or does Windows not support professional looking localizable applications with some bold strings in the UI?
The code to repro the issue is for a Windows Forms application with two PictureBoxes:
const string combined = "测试标记好不好This is a aber long";
private void cFontSize_ValueChanged(object sender, EventArgs e)
{
Bitmap bmp = new Bitmap(750, 140);
Font f = new Font("Arial", (float)cFontSize.Value, IsBold ? FontStyle.Bold : FontStyle.Regular);
using (var g = Graphics.FromImage(bmp))
{
g.CompositingMode = System.Drawing.Drawing2D.CompositingMode.SourceOver;
// Rendering with a Background Color solves the issue but
// this would produce boxes of white or black color in the displayed image which looks even worse
TextRenderer.DrawText(g, combined, f, new Point(0, 0), FontColor);
}
cPictureBox.Image = bmp;
Bitmap bmp2 = new Bitmap(750, 140);
using (var g = Graphics.FromImage(bmp2))
{
g.CompositingMode = System.Drawing.Drawing2D.CompositingMode.SourceOver;
g.DrawString(combined, f, FontBrush, 0, 0);
}
cPicture2.Image = bmp2;
}
Update 1:
When I add as Font Link Setting to HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\FontLink\SystemLink
Arial Bold
SIMSUNB.TTC,SimSun Bold
MSGOTHIC.TTC,MS UI Gothic
then Graphics.DrawString looks ok now althogh TextRender now gets problems. After restarting the application now both outputs look font wise ok although TextRenderer still has the problem that bold fonts become unreadable due to anti aliasing with black. I will restart the machine to check out any caching effects.

Related

Graphics.DrawString renders text with black outline on a transparent background

I'm drawing a string on a Bitmap with transparent background using Graphics.DrawString() and I get text with a black contour, when the Font size is smaller than 23 millimeters (the Font is created with GraphicsUnit.Millimeter).
Code:
Bitmap bmp = new Bitmap(2000, 2000);
Color alpha = Color.FromArgb(0, 0, 0, 0);
for (int x = 0; x < bmp.Width; x++)
for (int y = 0; y < bmp.Height; y++)
bmp.SetPixel(x, y, alpha);
Graphics g = Graphics.FromImage(bmp);
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
g.SmoothingMode = SmoothingMode.HighQuality;
Font labelFont = new Font("Cascadia Mono SemiBold", 23/*22*/, FontStyle.Regular, GraphicsUnit.Millimeter);
Brush brush = new SolidBrush(Color.White);
g.DrawString("Some text", labelFont, brush, 200, 200);
23 Millimeters-Units font:
22 Millimeters-Units font:
I tried to use TextRenderer, but this draws text without transparent background.
The code presented here has multiple problems:
The initial loop is counter-productive for multiple reasons:
Tries to fill a Bitmap with a transparent color, but this is already the non-color associated with a newly created Bitmap (Color.FromArb(0, 0, 0, 0))
Uses the SetPixel() method, the slowest possible tool for the task
If needed, a Bitmap can be filled with a Color using the Graphics.Clear() method, which calls a native GDI+ function to perform the task
Setting an InterpolationMode in this context is not useful, this property selects the algorithm used to scale or rotate images
The SmoothingMode property selects the algorithm used to anti-aliasing lines, curves and the edges of filled areas. It doesn't apply to the rendering of Fonts, so has no effect on the drawn text. It applies to text rendered with a GraphicsPath, since the text is converted to curves
None of the disposable objects (Graphics, Font, Brush) is either disposed explicitly or declared with using statements (which is pretty bad). It's not clear when the Bitmap is disposed, but could be the duty of the code that uses it
To specify the rendering mode of Fonts, the TextRenderingHint property is used instead. Since it's not specified, the System default smoothing of Font is used, usually ClearType.
About this rendering form, see the notes in:
Drawing a Long String on to a Bitmap results in Drawing Issues
ClearType uses intra-pixel smoothing, designed initially for LCD screens, to blend text with a background; it's especially effective with small Fonts sizes. It doesn't support alpha colors (not in this context, at least).
The device context in which the text is rendered, a GDI+ MemoryBitmap, doesn't use or understand this type of hinting (smoothing), so the pixels that fail to render are filled with an empty color, which notoriously appears as black
A black-ish contour might manifests with different Font sizes (not just the measures reported in the question), when the ClearType hinting fill is less than one pixel
To fix the rendering, remove the clutter, specify a suitable TextRenderingHint mode and declare correctly all disposable objects.
I'm not including the Bitmap, because I don't know how it's used. It must be disposed at some point, of course (very important, it allocates unmanaged resources, the Garbage Collector cannot help you)
var bmp = new Bitmap(2000, 2000);
using (var g = Graphics.FromImage(bmp))
using (var font = new Font("Cascadia Mono SemiBold", 22, FontStyle.Regular, GraphicsUnit.Millimeter))
using (var brush = new SolidBrush(Color.White)) {
g.CompositingQuality = CompositingQuality.HighQuality;
g.TextRenderingHint = TextRenderingHint.AntiAliasGridFit;
g.DrawString("Some text", font, brush, 200, 200);
}
TextRenderingHint.AntiAliasGridFit is appropriate here (see also the linked notes), the characters are drawn using their anti-aliased glyph bitmap and hinting (smoothing)
TextRenderingHint.AntiAlias can also be used in this context (and costs slightly less)
CompositingQuality.HighQuality is not actually used here, there isn't really any composition, since no image is rendered against a background (which may require gamma correction), but also has no cost. You can keep it, in case you decide to draw a bitmap onto the current, at some point; or simply remove it.
About TextRenderer draws text without transparent background
This is not correct. TextRenderer (GDI) can of course render text with a transparent background, it just doesn't support an alpha color (in this context)
As mentioned, we're working with an in-memory GDI+ Device Context
But, if you draw the same text in a different Device Context, e.g., the surface of a Control, then things change.
Also note that TextRenderer cannot be used to render text when printing (for the same reasons previously described).
Test this code, subscribing to the Paint event of a PictureBox, also adding a background Image (without a background image the result doesn't change, it's just more visible)
Graphics.DrawString() is used to render text with a semi-transparent (ARGB) Color
TextFormatFlags flags = TextFormatFlags.Left | TextFormatFlags.Top;
private void someControl_Paint(object sender, PaintEventArgs e)
{
using (var font = new Font("Segoe UI", 22, FontStyle.Regular, GraphicsUnit.Millimeter))
using (var brush = new SolidBrush(Color.White)) {
TextRenderer.DrawText(e.Graphics, "Some text", font,
new Rectangle(new Point(0, 10), pictureBox1.ClientSize), Color.White, Color.Transparent, flags);
e.Graphics.CompositingQuality = CompositingQuality.HighQuality;
e.Graphics.TextRenderingHint = TextRenderingHint.AntiAliasGridFit;
e.Graphics.DrawString("Some text", font, brush, 0, 130);
}
using (Font font1 = new Font("Segoe UI", 8.0f, FontStyle.Regular, GraphicsUnit.Millimeter))
using (Font font2 = new Font("Segoe UI", 5.5f, FontStyle.Regular, GraphicsUnit.Millimeter))
using (var brush = new SolidBrush(Color.FromArgb(100, Color.Black))) {
e.Graphics.DrawString("← TextRenderer", font1, brush, 610, 70);
e.Graphics.DrawString("ForeColor: White, BackColor: Transparent", font2, brush, 610, 110);
e.Graphics.DrawString("← GDI+ Graphics", font1, brush, 610, 190);
e.Graphics.DrawString("ForeColor: White, Hinting: AntiAliasGridFit", font2, brush, 610, 230);
}
}
Resulting in (enlarge it):

Graphics drawstring UTF8 Chinese Characters

Preface: We sell machines that tell you the quality of the air. We have a touch screen display that we recently upgraded to support Chinese. I wrote a C# application that connects to the device serially and attempts to draw the screen so users can connect to their device remotely and control it. This works great until you put the device into Chinese, then my C# app draws all the symbols as their ASCII equivalent (not Chinese symbols).
I use the Graphics library to do all my drawing, in which I call the DrawString method to draw text on the screen. I have a picturebox in which I am doing the graphics drawing on. How do I draw the Chinese correctly?
Code:
private Bitmap image;
private Color gForeColor;
private Color gBackColor;
private Font gFont;
private int gTextsize;
private int gLineSize;
private StringFormat gTextAlign;
private void Initialize()
{
gForeColor = Color.Black;
gTextsize = 14;
gLineSize = 1;
gFont = new Font("Microsoft Sans Serif", gTextsize, FontStyle.Regular);
gTextAlign = new StringFormat();
gTextAlign.LineAlignment = StringAlignment.Center;
gTextAlign.Alignment = StringAlignment.Near;
gTextAlign.FormatFlags = StringFormatFlags.MeasureTrailingSpaces;
image = new Bitmap(pictureBox1.ClientSize.Width, pictureBox1.ClientSize.Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
}
private void DrawText(string Text, int X1, int Y1)
{
using (Graphics g = Graphics.FromImage(image))
{
g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;
SolidBrush sb = new SolidBrush(gForeColor); //Foreground
SolidBrush bb = new SolidBrush(gBackColor); //Background
g.DrawString(Text, gFont, sb, X1, Y1, gTextAlign);
pictureBox1.Invalidate(); //refresh
gTextAlign.Alignment = StringAlignment.Near; //horizontal alignment reverts to Left after a text display command is issued
}
}
I tried adding this line
Text = Encoding.UTF8.GetString(Encoding.Default.GetBytes(Text)); To try and decode the text properly but now it prints '??????' instead of the ASCII letters or Chinese symbols.
Example:
English
Chinese
Edit: It seems the ReadExisting() from the SerialPort was converting all the bytes into '??????'. I stopped using ReadExisting and switched to Read which keeps the raw bytes. So now I'm getting the correct ASCII, but still not Chinese. I re-added my line Text = Encoding.UTF8.GetString(Encoding.Default.GetBytes(Text)); and now I'm getting Chinese with some extra symbols that aren't correct. Am I using the wrong encoding?
Are you sure, that you can use Microsoft Sans Serif with Chinese? I highly doubt.

Graphis.DrawString always uses the default font

I have a project in which I create an image with rotated text around an invisible circle.
The drawing in itself is working just fine. However, it seems that no matter the font I use, I always get the same result, which is I assume some low quality default font.
Here is the code :
Bitmap objBmpImage = new Bitmap(1000, 1000);
System.Drawing.Text.InstalledFontCollection installedFontCollection = new System.Drawing.Text.InstalledFontCollection();
FontFamily[] fontFamilies = installedFontCollection.Families;
System.Drawing.Font objFont = new System.Drawing.Font(fontFamilies.Where(x => x.Name == "Arial").FirstOrDefault(),10);
Graphics objGraphics = Graphics.FromImage(objBmpImage);
objGraphics.Clear(Color.Transparent);
float angle = (float)360.0 / (float)competences.Count();
objGraphics.TranslateTransform(500, 450);
objGraphics.RotateTransform(-90 - (angle / 3));
int nbComptetence = competences.Count();
int indexCompetence = 0;
foreach (T_Ref_Competence competence in competences)
{
byte r, g, b;
HexToInt(competence.T_Ref_CompetenceNiveau2.T_Ref_CompetenceNiveau1.Couleur, out r, out g, out b);
Brush brush = new System.Drawing.SolidBrush(Color.FromArgb(255,r,g,b));
if (indexCompetence * 2 < nbComptetence)
{
objGraphics.DrawString(competence.Nom, objFont, brush, 255, 0);
objGraphics.RotateTransform(angle);
}
else
{
objGraphics.RotateTransform(180);
objGraphics.RotateTransform(angle/2);
float textSize = objGraphics.MeasureString(competence.Nom, objFont).Width;
objGraphics.DrawString(competence.Nom, objFont, brush, -253 - textSize, 0);
objGraphics.RotateTransform(angle);
objGraphics.RotateTransform(-180);
objGraphics.RotateTransform(-angle / 2);
}
indexCompetence++;
}
I get the font using the installed families like this
System.Drawing.Text.InstalledFontCollection installedFontCollection = new System.Drawing.Text.InstalledFontCollection();
FontFamily[] fontFamilies = installedFontCollection.Families;
System.Drawing.Font objFont = new System.Drawing.Font(fontFamilies.Where(x => x.Name == "Arial").FirstOrDefault(),10);
I tried using other font but the result is always the same. Is there anything I am missing ? If not, what could be the reason ?
Thanks,
EDIT : To answer the question, what is it that I want exactly, consider this :
This image is a screenshot of a web site I am making. The chart in the middle was generated using charts.js, but its limitation force me to draw the text as a background image. It actually takes most of my screen so it can't really get much bigger than this. As you can see, the text font is pretty blurry and I would simply want it to be easier to read. I though the font was the problem, but I don't really know.
I am not really familiar with the whole image drawing part of C#, so if there are is better way to draw my text (which can change depending of many variables), I will gladly try other things.
Option 1: change text rendering
objGraphics.TextRenderingHint = TextRenderingHint.SingleBitPerPixel
Option 2: change the mode of anti aliasing
objGraphics.InterpolationMode=InterpolationMode.NearestNeighbor;
Option 3: change the DPI of the image
You'll get the best result if you scale the input image and then draw the text in higher DPI.
The default DPI for a Bitmap are 96. Probably the JS library exported with that setting.
If you want a smoother rendering of the font, you need to increase the DPI, e.g.
objBmpImage.SetResolution(1200,1200);
If you do so, you probably need to increase the number of pixels your Bitmap has.
If the "ugly" text just fitted the 1000x1000 picture, you now need 1000*1200/96=12500 pixels.
Before the change (using Arial 10 pt):
After the change (still using Arial 10 pt):
Note that the size in centimeters doesn't change. So it will still print well.

Overloading control drawing in c# has text rendering problems

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.

ASP.NET TextRenderer.DrawText Awful Text Images

Since I've tried to draw string with every combination of smoothing and rendering with Graphics.DrawString() I was thinking that text renderer would do a better job drawing my strings but I think was wrong.
This is how it is supposed to look like:
And this is how it looks like:
Here is my code:
Graphics objGraphics2 = Graphics.FromImage(objBitmap);
objGraphics2.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
objGraphics2.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;
objGraphics2.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality;
objGraphics2.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBilinear;
Font textFont = new Font(textFontFamily, PxtoEm(textSize));
SolidBrush b = new SolidBrush(textColor);
TextRenderer.DrawText(objGraphics2, textValue, textFont, new Rectangle(0, 0, Width, Height), textColor);
Is my PxtoEm method wrong?
public float PxtoEm(int px)
{
float em = (float)(Convert.ToDouble(Convert.ToDouble(px) * Convert.ToDouble(72) / Convert.ToDouble(objBitmap.HorizontalResolution)));
return em;
}
I need some suggestions because this is really awful, it gets worse with larger fonts and images aren't shrunk.
UPDATE: Got it working with bigger fonts(ie. 20px) but with smaller fonts it gets kind of erased on some letters:
This is how it's suposed to be with font Arial 10px:
This is result with Graphics.DrawString()
As you can see it really isn't very good but closest I got. I made some changes to code and got better results with larger font:
This is how it's suposed to be with font Arial 20px:
This is drawing result:
And here is the changed code(I droped em method and used pixels directly, switched to Graphics.DrawString() instead of TextRenderer.DrawText()
Graphics objGraphics = Graphics.FromImage(objBitmap);
objGraphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
objGraphics.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAliasGridFit;
objGraphics.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality;
objGraphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
objGraphics.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality;
Font textFont = new Font(textFontFamily, textSize,GraphicsUnit.Pixel);
SolidBrush b = new SolidBrush(textColor);
PointF origin = new PointF((float)TextLeft,(float)TextTop);
StringFormat format = StringFormat.GenericTypographic;
objGraphics.DrawString(textValue, textFont, b , origin, format);
If someone has some suggestion to maybe write different method for smaller text sizes and use above code for larger as it works nicely, post it and I'll try it!
UPDATE 3: Finally found solution for everything, and solution was rather simple:
DON'T USE TRANSPARENT BACKGROUND!
And settings are:
objGraphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
objGraphics.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit; // <-- important!
objGraphics.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality;
objGraphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
objGraphics.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality;
objGraphics.TextContrast = 0;
Here is final image with these settings on white background:
Exactly the same, thanks for suggestions and replies.
I'm not sure it will help but why not create your font without the function call, like this:
Font textFont = new Font(textFontFamily, textSize, GraphicsUnit.Pixel);
I built something to generate image buttons using similar functionality and I had issues with kerneling and the font not stretching to the desired with. The following settings got me really close to what I wanted but still not 100%.
objGraphics2.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality;
objGraphics2.TextRenderingHint = System.Drawing.Drawing2D.TextRenderingHint.AntiAliasGridFit;
Set Graphics.TextRenderingHint to SingleBitPerPixelGridFit.
I'm not sure if this will solve the issue, but I had a similar problem with drawing text in Direct3D, check out PixelOffsetMode, set it to Half.

Categories