I need to create some kind of graph in .net desktop application.
I use .Net Framework 4.7.2 and System.Drawing library.
The scenario looked like this.
In the console application I create a bitmap with X and Y axes and some simple lines and some text on those lines, nothing complicated.
After creating the bitmap, I save it as .bmp file.
The problem is that the letters and numbers I create are bad and look furry, I tried with different fonts and different settings but the result was always the same.
The picture looks like this:
and under a magnifyer it looks like this:
This is my sample code:
// create new bitmap
Bitmap bitmap = new Bitmap(900, 700, System.Drawing.Imaging.PixelFormat.Format32bppPArgb);
Graphics g = Graphics.FromImage(bitmap);
g.Clear(Color.LightGray);
// set attr
g.SmoothingMode = SmoothingMode.AntiAlias;
g.InterpolationMode = InterpolationMode.Bicubic;
// draw X and Y axes
Pen pen = new Pen(Color.Black, 1);
g.DrawLine(pen, new Point(100, 100), new Point(100,600));
g.DrawLine(pen, new Point(100, 600), new Point(800, 600));
SolidBrush brush = new SolidBrush(Color.Black);
Font font = new Font("Lucida Console", 14, FontStyle.Bold);
g.SmoothingMode = SmoothingMode.AntiAlias;
g.InterpolationMode = InterpolationMode.Bicubic;
// grid numbeers on x axis
for (int i = 0; i < 8; i++)
{
g.DrawString(i.ToString(), font, brush, 100 * (i+1), 620);
}
// grid numbeers on y axis
for (int i = 0; i < 6; i++)
{
g.DrawString(i.ToString(), font, brush, 70, 600 - (100 * i));
}
// label 1
font = new Font("Lucida Console", 14, FontStyle.Bold);
g.DrawString("Line - X axis", font, brush, 100, 650);
// label 2
font = new Font("Lucida Console", 32, FontStyle.Bold);
g.DrawString("PORCO El.01", font, brush, 550, 150);
// label 3
font = new Font("Lucida Console", 24, FontStyle.Regular);
g.DrawString("PORCO El.01", font, brush, 550, 200);
// save as .bmp file
bitmap.Save(Path.Combine(picturePath, "sample.bmp"));
Is it possible to get better text quality? Is there maybe another 3rd party library that can achieve better results?
Thanks in advance!
Related
My code. Output:
Bitmap bitmap = new Bitmap(600, 300, PixelFormat.Format32bppArgb);
Graphics g = Graphics.FromImage(bitmap);
GraphicsPath path = new GraphicsPath();
StringFormat format = new StringFormat();
Rectangle rect = new Rectangle(0, 0, 600, 300);
SolidBrush Brush = new SolidBrush(Color.White);
g.FillRectangle(Brush, rect);
g.SmoothingMode = SmoothingMode.AntiAlias;
format.Alignment = StringAlignment.Center;
format.LineAlignment = StringAlignment.Center;
path.AddString(textBox1.Text, FontFamily.GenericSansSerif, (int)FontStyle.Bold, 128, rect, format);
Brush = new SolidBrush(Color.Blue);
g.FillPath(Brush, path);
float x = path.GetBounds().X;
float y = path.GetBounds().Y;
float w = path.GetBounds().Width / textBox1.Text.Length;
float h = path.GetBounds().Height;
Pen redPen = new Pen(Color.Red, 2);
for (int i = 0; i < textBox1.Text.Length+1; i++)
{
rect = new Rectangle((int)x, (int)y, (int)w*i, (int)h);
g.DrawRectangle(redPen, rect);
}
pictureBox1.Image = bitmap;
I am getting the wrong result in the output because I cannot get the corners of the letters and am using the wrong way.
But i need get correct pixels of letter corners, draw rectangle and fill it.
Like this:
This line made me find one error in your code:
for (int i = 0; i < textBox1.Text.Length+1; i++)
It should be:
for (int i = 0; i < textBox1.Text.Length; i++)
But then indeed only 3 red boxes seem to appear.
The reason for that is the first rectangle (with your code) is very small, because the width is (int)w*i. That should be (int)w*(i+1).
Now back to the place where you are drawing rectangles.
If you take the text 'WWWW', you will see that your solution seems pretty OK.
But if you test with 'IIII', ten you should note that the left-most 'I' is left aligned in the red box, and the right-most 'I' is right aligned in the red box.
You are drawing 4 equal boxes round 4 letters with different with.
A solution could be to draw the letters with a monospaced font.
or, if you do not want a monospaced font, look at How to measure width of character precisely?
How to build a string that have a fixed length of pixels, and append three variables to it at specific starting positions in pixels, variables data constantly changes but their starting positions are constant within the string.
// Below is what am trying to accomplish if I were to use Graphics DrawString
Bitmap bmp = new Bitmap(656, 5);
using (Graphics g = Graphics.FromImage(bmp))
{
Font font = new Font("Arial", 14, FontStyle.Regular, GraphicsUnit.Point);
g.Clear(Color.White);
g.DrawString(first_number, font, Brushes.Black, 10, 1;
g.DrawString(full_name, font, Brushes.Black, 150, 1);
g.DrawString(second_number, font, Brushes.Black, 550, 1);
}
Hello I am printing a text on Receipt printer
on which I have to print Product name But products name get overwrites on Qty field can i get that name string in new line after particular length I am using following Code.
column 3 have a Product name Like (Almond Kesar Kanti Body Cleanser) How to get this text in new line and manage space of Invoice.
while (i < dataGridView2.Rows.Count)
{
if (height > e.MarginBounds.Height)
{
height = 500;
width = 500;
//e.HasMorePages = true;
return;
}
//height += dataGridView1.Rows[i].Height;
// e.Graphics.DrawRectangle(Pens.Black, 20, height, dataGridView1.Columns[0].Width, dataGridView1.Rows[0].Height);
e.Graphics.DrawString(dataGridView2.Rows[i].Cells["Column3"].FormattedValue.ToString(), dataGridView2.Font, Brushes.Black, CurrentX, CurrentY + 20);
e.Graphics.DrawString(dataGridView2.Rows[i].Cells["Column4"].FormattedValue.ToString(), dataGridView2.Font, Brushes.Black, CurrentX + 150, CurrentY + 20);
// e.Graphics.DrawString(dataGridView2.Rows[i].Cells["Column5"].FormattedValue.ToString(), dataGridView2.Font, Brushes.Black, CurrentX + 150, CurrentY + 20);
e.Graphics.DrawString(dataGridView2.Rows[i].Cells["Column10"].FormattedValue.ToString(), dataGridView2.Font, Brushes.Black, CurrentX + 200, CurrentY + 20);
i++;
CurrentY = CurrentY + 20;
}
You are not actually incrementing your CurrentY variable between the calls to DrawString. Therefore each line is printed at the same Y-coordinate.
Add CurrentY = CurrentY + 20; between each call to DrawString and just use
e.Graphics.DrawString(dataGridView2.Rows[i].Cells["Column3"].FormattedValue.ToString(), dataGridView2.Font, Brushes.Black, CurrentX, CurrentY);
Better yet, don't increment it by a fixed value but use e.Graphics.MeasureString to calculate the actual height of each line and increment by this value+spacing, like in this example:
Bitmap bmp = new Bitmap(200, 200);
using (Graphics g = Graphics.FromImage(bmp)) {
//The individual lines to draw
string[] lines = {
"Foo1",
"Foo2",
"Foo3"
};
int y = 1; //The starting Y-coordinate
int spacing = 10; //The space between each line in pixels
for (i = 0; i <= lines.Count - 1; i++) {
g.DrawString(lines[i], this.Font, Brushes.Black, 1, y);
//Increment the coordinate after drawing each line
y += Convert.ToInt32(g.MeasureString(lines[i], this.Font).Height) + spacing;
}
}
PictureBox1.Image = bmp;
Results:
Edit
To fit text into a given area you need to use a different overload of DrawString. You need to draw the string with a given LayoutRectangle like this:
Bitmap bmp = new Bitmap(200, 200);
using (Graphics g = Graphics.FromImage(bmp)) {
g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAliasGridFit;
string LongText = "This is a really long text that should be automatically wrapped to a certain rectangle.";
Rectangle DestinationRectangle = new Rectangle(10, 10, 100, 150);
g.DrawRectangle(Pens.Red, DestinationRectangle);
using (StringFormat sf = new StringFormat()) {
g.DrawString(LongText, new Font("Arial", 9), Brushes.Black, DestinationRectangle, sf);
}
}
PictureBox1.Image = bmp;
The DestinationRectangle defines where the text is printed and it is automatically wrapped. The problem is, that the line spacing is defined by the Font you use, as you can see in this example with different fonts:
If you can't find a font that works for you (or define your own) you would need to split the text into words and fit them yourself into the given rectangle by drawing them word for word, measuring each with the MeasureString function and break the line when you would go over the limit.
But beware, text layout is hard, very very hard, to get all the corner cases right.
I draw lines with the same pen, but the line widths are different in result. Why?
Bitmap b = new Bitmap(400, 400);
Graphics g = Graphics.FromImage(b);
g.PageUnit = GraphicsUnit.Point;
g.Clear(Color.White);
Pen pen = new Pen(Color.Red, 1.2f);
for (int i = 20; i < 200; i = i + 20)
{
g.DrawLine(pen, 10, i, 190, i);
}
g.Dispose();
b.Save("d:/temp/test.png", ImageFormat.Png);
b.Dispose()
Here is the result:
MSDN for GraphicsUnit
It's because you're working with Points and not Pixels and the variation in the width of the lines is the result of a rounding errors in the placement of the line and the width of the line in relation to how it gets rendered in pixels in the final product.
If you don't care about printing the image, it might be best to stick with Pixels.
Edit: If you want to continue using points, space things relative to your pen width:
Bitmap b = new Bitmap(400, 400);
Graphics g = Graphics.FromImage(b);
g.PageUnit = GraphicsUnit.Point;
g.Clear(Color.White);
Pen pen = new Pen(Color.Red, 1.2f);
for (float i = 20f * pen.Width; i < 200f * pen.Width; i = i + 20f * pen.Width)
{
g.DrawLine(pen, 10f, i, 190f, i);
}
g.Dispose();
b.Save("c:/temp/test.png", ImageFormat.Png);
b.Dispose();
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.