I want to put some text on an image. Here is my method:
private Bitmap ConvertTextToImage(string text, FontFamily fontFamily, float fontSize, FontStyle fontStyle = FontStyle.Regular,
StringFormat stringFormat = default, float MaxWidth = float.MaxValue, float MaxHeight = float.MaxValue, Color backgroundColor = default, Color foregroundColor = default)
{
Bitmap bitmap = new Bitmap(1, 1);
Graphics graphics = Graphics.FromImage(bitmap);
if (stringFormat == default) stringFormat = new StringFormat();
if (backgroundColor == default) backgroundColor = Color.Transparent;
if (foregroundColor == default) foregroundColor = Color.Black;
Font font = new Font(fontFamily, fontSize, fontStyle);
SizeF stringSize = graphics.MeasureString(text, font, int.MaxValue, stringFormat);
while (stringSize.Width > MaxWidth || stringSize.Height > MaxHeight)
{
fontSize -= (float)0.1;
font = new Font(fontFamily, fontSize, fontStyle);
stringSize = graphics.MeasureString(text, font, int.MaxValue, stringFormat);
}
bitmap = new Bitmap((int)stringSize.Width, (int)stringSize.Height);
graphics = Graphics.FromImage(bitmap);
graphics.CompositingQuality = CompositingQuality.HighQuality;
graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
graphics.SmoothingMode = SmoothingMode.HighQuality;
graphics.TextRenderingHint = TextRenderingHint.AntiAliasGridFit;
graphics.Clear(backgroundColor);
int x = 0;
if (stringFormat.FormatFlags == StringFormatFlags.DirectionRightToLeft && stringFormat.Alignment == StringAlignment.Center)
x = (int)stringSize.Width / 2;
else if (stringFormat.FormatFlags == StringFormatFlags.DirectionRightToLeft) x = (int)stringSize.Width;
else if (stringFormat.Alignment == StringAlignment.Center) x += (int)stringSize.Width / 2;
graphics.DrawString(text, font, new SolidBrush(foregroundColor), x, 0, stringFormat);
return bitmap;
}
//...
Good for big font sizes or simple fonts (eg. Samim). But for a complex font (IranNastaliq) in small size, it gets like this (brown is on main image and black is generated by C#):
So I decided to use GraphicsPath.AddString instead of Graphics.DrawString:
GraphicsPath graphicsPath = new GraphicsPath();
//...
graphicsPath.AddString(text, fontFamily, (int)fontStyle, fontSize * graphics.DpiY / 72, new Point(x, 0), stringFormat);
graphics.FillPath(new SolidBrush(foregroundColor), graphicsPath);
But result is still bad:
How can I render higher quality texts? Image resolution is 2480x3508x300dpi and font size is about 13.
Thanks to TaW's comment, problem solved. I should adjust bitmap resolution (DPI) to match resolution of original picture. Here is correct code:
private Bitmap ConvertTextToImage(string text, FontFamily fontFamily, float fontSize,
FontStyle fontStyle = FontStyle.Regular, StringFormat stringFormat = default,
float MaxWidth = float.MaxValue, float MaxHeight = float.MaxValue, float xDpi = 72, float yDpi = 72,
Color backgroundColor = default, Color foregroundColor = default)
{
//...
graphics.TextRenderingHint = TextRenderingHint.AntiAliasGridFit;
bitmap.SetResolution(xDpi,yDpi);
//...
graphics.DrawString(text, font, new SolidBrush(foregroundColor), x, 0, stringFormat);
}
private void Write()
{
//...
Bitmap name = ConvertTextToImage(Name.Text, IranNastaliq, 58, MaxWidth: 525, xDpi: 300, yDpi: 300);
graphics.DrawImage(name, new Point(1104, 1700));
//...
bitmap.Save("img.jpg", ImageFormat.Jpeg);
}
Result (black text is drawn by GDI):
Related
I'm drawing some texts on a graphic object. With some fonts drawn text has some unwanted vertical offset.
Here is the code:
Bitmap img = new Bitmap(Width, Height);
Graphics GraphicObject = Graphics.FromImage(img);
GraphicObject.TextRenderingHint = TextRenderingHint.AntiAlias;
GraphicObject.DrawRectangle(Pens.Red, X, Y, Width, Height);
StringFormat format = new StringFormat();
format.FormatFlags = StringFormatFlags.NoClip;
GraphicObject.DrawString(
"SampleText",
new FontFamily("Font_Name"),
Color.White,
new RectangleF(X, Y, Width, Height),
format
);
And here is the result:
As you see, position of two fonts are wrong
How I can fix this issue?
After some hard works, I fixed the problem with a little dirty solution.
First I draw text on an empty Graphics object. then I'll scan for first pixel of text in y axis. This point is the y offset of font. Then I'll draw the text in my Graphics object at y - yOffset
Bitmap img = new Bitmap(Width, Height);
Graphics GraphicObject = Graphics.FromImage(img);
GraphicObject.TextRenderingHint = TextRenderingHint.AntiAlias;
GraphicObject.DrawRectangle(Pens.Red, X, Y, layer.Width, layer.Height);
StringFormat format = new StringFormat();
format.FormatFlags = StringFormatFlags.NoClip;
int yOffset = GetYOffset(layer.Width,layer.Height,TextFont,layer.Text, format);
GraphicObject.DrawString(
"SampleText",
new FontFamily("Font_Name"),
Color.White,
new RectangleF(X, Y - yOffset, layer.Width, layer.Height),
format
);
...
private int GetYOffset(int Width, int Height, System.Drawing.Font TextFont, string Text, StringFormat format)
{
Bitmap img = new Bitmap(1000, 1000);
Graphics testGaraphic = null;
testGaraphic = Graphics.FromImage(img);
testGaraphic.FillRectangle(Brushes.White, 0, 0, Width, Height);
testGaraphic.DrawString(Text, TextFont, Brushes.Black, 0, 0, format);
for (int y = 0; y < Height; y++)
for (int x = 0; x < Width; x++)
if (img.GetPixel(x, y).Name != "ffffffff")
return y;
return 0;
}
The Result:
The solution above by Ali Gonabadi saved my day. I made some modification in order to be more precise in case of anti-aliasing text. Below is the function I adapted to get X offset coordinate with a couple of modifications related to the size of the Bitmap and the SmoothingMode:
private int GetXOffset(int Width, int Height, System.Drawing.Font TextFont, string Text, StringFormat format)
{
Bitmap image = new Bitmap(Width, Height);
Graphics g = Graphics.FromImage(image);
g.SmoothingMode = SmoothingMode.AntiAlias;
g.FillRectangle(Brushes.Black, 0, 0, Width, Height);
g.DrawString(Text, TextFont, Brushes.White, 0, 0, format);
for (int x = 0; x < Width; x++)
for (int y = 0; y < Height; y++)
if (image.GetPixel(x, y).Name != "ff000000")
return x;
return 0;
}
I am using netDXF (https://netdxf.codeplex.com/) to generate a DXF file for use with AutoCAD. However, I have an issue with getting the width of MText correct. I want to be able to define a width that the text should fit into, and change the width factor of the text (squash it horizontally) so that it fits in the defined area. So if I have a 40mm width to fit the text into and the text is 80mm long, it needs to have a width factor of 0.5. The only problem is that I don't know how to accurately determine the width of the text. I have tried the following methods and was unsuccessful in getting the correct result:
Why is Graphics.MeasureString() returning a higher than expected number?
Measure a String without using a Graphics object?
http://www.codeproject.com/Articles/2118/Bypass-Graphics-MeasureString-limitations
I have attached my code. I am basically printing a horizontal line using each of the 3 methods to calculate text width and comparing it to the actual text width. If I change the font, I get varying results. I have attached two images. One using the code with Calibri and one with Arial. I need the line to be on the edges of the text no matter what font I use.
Here is my code:
public void TestMethod1()
{
Application.SetCompatibleTextRenderingDefault(false);
//text width in mm
float textWidth = 40;
float textHeight = 200;
string labelText = "HELLO WORLD!";
TextStyle textStyle = new TextStyle("Calibri");
DxfDocument dxf = new DxfDocument();
Layer layer1 = new Layer("layer1");
layer1.Color = new AciColor(0, 0, 255);
layer1.Name = "Text";
MText text1 = new MText(new Vector2(0, 0), textHeight, 0, textStyle);
text1.Layer = layer1;
text1.AttachmentPoint = MTextAttachmentPoint.MiddleCenter;
//Will the text fit in the bounds of the rectangle? If not change width factor so it does.
Font f = new Font(textStyle.FontName, textHeight);
Size size = TextRenderer.MeasureText(labelText, f);
SizeF sizeF = graphicsMeasureString(labelText, f);
int width = MeasureDisplayStringWidth(labelText, f);
float widthFactor = Math.Min(1, textWidth / sizeF.Width);
MTextFormattingOptions mtextOptions = new MTextFormattingOptions(text1.Style);
//mtextOptions.WidthFactor = widthFactor;
text1.Write(labelText, mtextOptions);
//Red, g.MeasureString
Line line1 = new Line(new Vector2(0 - sizeF.Width / 2, 0), new Vector2(0 + sizeF.Width / 2, 0));
line1.Color = new AciColor(255, 0, 0);
//Green, TextRenderer
Line line2 = new Line(new Vector2(0 - size.Width / 2, 5), new Vector2(0 + size.Width / 2, 5));
line2.Color = new AciColor(0, 255, 0);
//Yellow, MeasureDisplayStringWidth
Line line3 = new Line(new Vector2(0 - width / 2, -5), new Vector2(0 + width / 2, -5));
line3.Color = new AciColor(255, 255, 0);
dxf.AddEntity(text1);
dxf.AddEntity(line1);
dxf.AddEntity(line2);
dxf.AddEntity(line3);
dxf.Save("Text Width Test.dxf");
}
public SizeF graphicsMeasureString(string text, Font f)
{
Bitmap fakeImage = new Bitmap(1, 1);
Graphics g = Graphics.FromImage(fakeImage);
SizeF sizeF = g.MeasureString(text, f, new PointF(100, 0), StringFormat.GenericTypographic);
return sizeF;
}
public int MeasureDisplayStringWidth(string text, Font f)
{
Size size = TextRenderer.MeasureText(text, f);
Bitmap fakeImage = new Bitmap(1, 1);
Graphics g = Graphics.FromImage(fakeImage);
System.Drawing.StringFormat format = new System.Drawing.StringFormat();
System.Drawing.RectangleF rect = new System.Drawing.RectangleF(0, 0, 1000, 1000);
System.Drawing.CharacterRange[] ranges = { new System.Drawing.CharacterRange(0, text.Length) };
System.Drawing.Region[] regions = new System.Drawing.Region[1];
format.SetMeasurableCharacterRanges(ranges);
regions = g.MeasureCharacterRanges(text, f, rect, format);
rect = regions[0].GetBounds(g);
return (int)(rect.Right + 1.0f);
}
I'm using the GraphicsPath object to draw text in a rectangle. The rectangle is larger than the text, and I want to draw the text in any of the rectangle's corners, and also at the center of its edges.
The problem I have is that when I draw the path, a border is being left around the source rectangle. I want to be able to eliminate that border and make the text touch its bounding rectangle.
Here is the code I have:
private void Form1_Paint(object sender, PaintEventArgs e)
{
var g = e.Graphics;
g.SmoothingMode = SmoothingMode.HighQuality;
Rectangle textRect = new Rectangle(100, 100, 150, 150);
Font f = new Font("Arial", 16);
float emSize = f.Height * f.FontFamily.GetCellAscent(f.Style) /
f.FontFamily.GetEmHeight(f.Style);
foreach (StringAlignment lineAlignment in Enum.GetValues(typeof(StringAlignment)))
{
foreach (StringAlignment alignment in Enum.GetValues(typeof(StringAlignment)))
{
StringFormat sf = new StringFormat() { LineAlignment = lineAlignment, Alignment = alignment };
using (GraphicsPath gp = new GraphicsPath())
{
gp.AddString("txt", f.FontFamily, (int)f.Style, emSize, textRect, sf);
RectangleF bounds = gp.GetBounds();
g.FillPath(Brushes.Black, gp);
g.DrawRectangle(Pens.Red, Rectangle.Round(bounds));
}
}
}
g.DrawRectangle(Pens.Blue, textRect);
}
And here is the result:
Basically, I want the red rectangles (and the text they contain) to touch the blue rectangle, and to eliminate the border between them. Also, I need to use the GraphicsPath and not DrawString.
I ended up writing a helper method to calculate the offset of the rectangles and translate the text before drawing it. Here is the method I wrote:
private PointF FixAlignment(RectangleF parentRect, RectangleF childRect,
StringAlignment lineAlignment, StringAlignment alignment)
{
float xOffset = 0;
float yOffset = 0;
switch (lineAlignment)
{
case StringAlignment.Near:
yOffset = parentRect.Top - childRect.Top;
break;
case StringAlignment.Far:
yOffset = parentRect.Bottom - childRect.Bottom;
break;
}
switch (alignment)
{
case StringAlignment.Near:
xOffset = parentRect.Left - childRect.Left;
break;
case StringAlignment.Far:
xOffset = parentRect.Right - childRect.Right;
break;
}
return new PointF(xOffset, yOffset);
}
I used it in the Form1_Paint method like this:
private void Form1_Paint(object sender, PaintEventArgs e)
{
var g = e.Graphics;
g.SmoothingMode = SmoothingMode.HighQuality;
Rectangle textRect = new Rectangle(100, 100, 150, 150);
Font f = new Font("Arial", 16);
float emSize = f.Height * f.FontFamily.GetCellAscent(f.Style) /
f.FontFamily.GetEmHeight(f.Style);
foreach (StringAlignment lineAlignment in Enum.GetValues(typeof(StringAlignment)))
{
foreach (StringAlignment alignment in Enum.GetValues(typeof(StringAlignment)))
{
StringFormat sf = new StringFormat() { LineAlignment = lineAlignment, Alignment = alignment };
using (GraphicsPath gp = new GraphicsPath())
{
gp.AddString("txt", f.FontFamily, (int)f.Style, emSize, textRect, sf);
RectangleF bounds = gp.GetBounds();
// Calculate the rectangle offset
PointF offset = FixAlignment(textRect, bounds, lineAlignment, alignment);
// Translate using the offset
g.TranslateTransform(offset.X, offset.Y);
g.FillPath(Brushes.Black, gp);
g.DrawRectangle(Pens.Red, Rectangle.Round(bounds));
// Translate back to the original location
g.TranslateTransform(-offset.X, -offset.Y);
}
}
}
g.DrawRectangle(Pens.Blue, textRect);
}
Here is the result:
public static System.Drawing.Image GenerateGiftCard(String text, Font font, Color textColor)
{
System.Drawing.Image img = Bitmap.FromFile(#"G:\xxx\images\gift-card.jpg");
Graphics drawing = Graphics.FromImage(img);
//measure the string to see how big the image needs to be
SizeF textSize = drawing.MeasureString(text, font);
//create a brush for the text
Brush textBrush = new SolidBrush(textColor);
float x, y;
x = img.Width / 2 - textSize.Width / 2;
y = img.Height / 2 - textSize.Height / 2;
drawing.DrawString(text, font, textBrush, x, y);
drawing.Save();
textBrush.Dispose();
drawing.Dispose();
return img;
}
But the text generated by this code is "plain" not dimensional and no shadow beneath it.
This is the font style I want:
Is there anything I can do to generate the same style via my code?
Does anyone know how to use SiteMapPath or ResolveURL objects to transfer a relative path to a physical one? cheers,
First render the shadow by drawing the text with a darker, optionally translucent brush at an offset. After the shadow is rendered, overlay the regular text.
Example:
public static System.Drawing.Image GenerateGiftCard(String text, Font font, Color textColor, Color shadowColor, SizeF shadowOffset)
{
System.Drawing.Image img = Bitmap.FromFile(#"G:\xxxx\images\gift-card.jpg");
Graphics drawing = Graphics.FromImage(img);
//measure the string to see how big the image needs to be
SizeF textSize = drawing.MeasureString(text, font);
//create a brush for the text
Brush shadowBrush = new SolidBrush(shadowColor); // <-- Here
Brush textBrush = new SolidBrush(textColor);
float x, y;
x = img.Width / 2 - textSize.Width / 2;
y = img.Height / 2 - textSize.Height / 2;
drawing.DrawString(text, font, shadowBrush, x + shadowOffset.Width, y + shadowOffset.Height); // <-- Here
drawing.DrawString(text, font, textBrush, x, y);
drawing.Save();
textBrush.Dispose();
drawing.Dispose();
return img;
}
I've been trying to do this, but for some reason this is just giving me weird results:
int bpp = Screen.PrimaryScreen.BitsPerPixel;
string fontName = "Tahoma";
Font font = new Font(fontName, 10 * bpp, GraphicsUnit.Point);
Bitmap bm = new Bitmap(20 * bpp, 20 * bpp);
Graphics g = Graphics.FromImage(bm);
TextRenderer.DrawText(g, "a", font, new Rectangle(0, 0, 5 * bpp, 6 * bpp), Color.Black);
g.Flush();
pictureBox1.Image = bm;
What am I doing wrong here? I don't see anything printed on the picture. If I remove all bpp references, I can see it, but it's pretty small.
You are aware that BitsPerPixel describes the color depth (the number of memory bits that are used to describe the color of a pixel), and has nothing to do with resolution?
I assume that what you want to do is to draw the text in a size that is related to the resolution, which you can do by referring to the DpiX and DpiY properties of the Graphics object.
Update
I am not sure if yo need to bring Dpi into the calculation for this. All you need to do is to create a Rectangle that defines the desired size of your text, and then calculate the correct font size to make the text fit inside the rectangle. The following does that (but maximizes the text size both vertical and horizontal direction). It might give you some pointers to solve your problem:
Bitmap bm = new Bitmap(50, 50);
using (Font font = new Font(fontName, 10, GraphicsUnit.Point))
using (Graphics g = Graphics.FromImage(bm))
{
g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;
StringFormat stringFormat = new StringFormat()
{
Alignment = StringAlignment.Center,
LineAlignment = StringAlignment.Near
};
Rectangle rect = new Rectangle(0, 0, bm.Width, bm.Height);
// measure how large the text is on the Graphics object with the current font size
SizeF s = g.MeasureString(text, font);
// calculate how to scale the font to make the text fit
float fontScale = Math.Max(s.Width / rect.Width, s.Height / rect.Height);
using (Font fontForDrawing = new Font(font.FontFamily, font.SizeInPoints / fontScale, GraphicsUnit.Point))
{
g.DrawString(text, fontForDrawing, Brushes.Black, rect, stringFormat);
}
}
And if you want to print the text with a given point size, you don't need to go about measuring; just set the font size:
Bitmap bm = new Bitmap(20, 20);
using (Font font = new Font(fontName, 6, GraphicsUnit.Point))
using (Graphics g = Graphics.FromImage(bm))
{
g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;
StringFormat stringFormat = new StringFormat()
{
Alignment = StringAlignment.Center,
LineAlignment = StringAlignment.Near
};
Rectangle rect = new Rectangle(0, 0, bm.Width, bm.Height);
g.DrawString(text, font, Brushes.Black, rect, stringFormat);
}
I would try:
int ImgQual = 600;
int Width = 50;
int Height = 50;
Font TextFont = New Font("Tahoma", 14, FontStyle.Bold)
Bitmap bmp = New Bitmap(Width, Height);
bmp.SetResolution(ImgQual, ImgQual);
System.Drawing.Graphics g = Graphics.FromImage(bmp);
System.Drawing.StringFormat sf = New System.Drawing.StringFormat();
sf.Alignment = StringAlignment.Center;
g.DrawString("a", NumberTextFont, Brushes.Black, New RectangleF(0, 0, Width, Height), sf);
return bmp;
There are two primary reasons the "a" is small:
The height of a font is measured using a point size (which is 1/72nd of a inch, quite different from a number of pixels because different computer screens have different numbers of pixels per inch).
Font height is measured from the top of a line of text to the bottom. As some letters have ascenders (the tall bar in "h") and others have descenders (the dangly bit of a "g"), an "a" will occupy only about a third of the height of the font.
If you wish your "a" to fill the image, then you will need to draw it into a larger rectangle and "clip off" the empty regions above and below the "a". If you're not too worried about screen DPI on different computers, then you can just experiment until you find a font size (and position to draw at) that works.