I'm creating a custom control, part of which is using the Graphics class to draw text to the form. Currently I'm using the following code to display it:
private float _lineHeight { get { return this.Font.Size + 5; } }
private void Control_Paint(object sender, PaintEventArgs e)
{
Graphics g = this.CreateGraphics();
Brush b = new SolidBrush(Colors[7]);
g.DrawString("Hello World!", this.Font, b, 0, 2);
g.DrawString("This has been a test of the emergency drawing system!",
this.Font, b, 0, 2 + _lineHeight);
}
I'm currently using fixedwidth fonts, and I'd like to know how wide the font will display, but there doesn't appear to be any properties for this sort of information. Is there some way of obtaining it? I want it so I can wrap lines properly when displayed.
Yes, you can use MeasureString from the Graphics class
This method returns a SizeF structure that represents
the size, in the units specified by
the PageUnit property, of the string
specified by the text parameter as
drawn with the font parameter.
private void MeasureStringMin(PaintEventArgs e)
{
// Set up string.
string measureString = "Measure String";
Font stringFont = new Font("Arial", 16);
// Measure string.
SizeF stringSize = new SizeF();
stringSize = e.Graphics.MeasureString(measureString, stringFont);
// Draw rectangle representing size of string.
e.Graphics.DrawRectangle(new Pen(Color.Red, 1), 0.0F, 0.0F, stringSize.Width, stringSize.Height);
// Draw string to screen.
e.Graphics.DrawString(measureString, stringFont, Brushes.Black, new PointF(0, 0));
}
Related
I need to convert a text containing Unicode chars into a Bitmap that could have a transparent background as well.
I found and tried different posts like this, this, or this, but no one seems to work for me.
I found also this post that suggests using TextRenderer.DrawText() method instead of System.Drawing.Graphics.DrawString() but the final result is not good anyway.
That's a code snippet:
private static Bitmap ImageFromText(string text, Font font, Color textColor, Color fillColor, System.Drawing.Graphics graphics)
{
Bitmap bmpOut;
SizeF sz = TextRenderer.MeasureText(graphics, text, font);
//SizeF sz = g.MeasureString(text, font);
if (sz.IsEmpty)
sz = new SizeF(1, 1);
bmpOut = new Bitmap((int) Math.Ceiling(sz.Width), (int) Math.Ceiling(sz.Height), PixelFormat.Format32bppArgb);
using (System.Drawing.Graphics gBmp = System.Drawing.Graphics.FromImage(bmpOut))
{
gBmp.Clear(fillColor);
gBmp.SmoothingMode = SmoothingMode.HighQuality;
gBmp.InterpolationMode = InterpolationMode.HighQualityBilinear;
gBmp.TextRenderingHint = TextRenderingHint.AntiAliasGridFit;
//gBmp.DrawString(text, font, brFore, 0, 0); // Unicode chars are not supported
TextRenderer.DrawText(gBmp, text, font, new Point(0, 0), textColor);
}
return bmpOut;
}
Here is my test code:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
string testString = "Unicode Title \uD83E\uDC25";
var font = new Font(new FontFamily("Microsoft Sans Serif"), 12.375f);
// e.Graphics.DrawString(testString, new Font(font.FontFamily, 20), new SolidBrush(Color.Black), 0, 0);
TextRenderer.DrawText(e.Graphics, testString, new Font ( font.FontFamily, 20 ), new Point(0, 50), Color.Black);
Bitmap bitmap = ImageFromText(testString, new Font(font.FontFamily, 20), Color.Black, this.BackColor, e.Graphics);
e.Graphics.DrawImage(bitmap, new Point(0, 100));
//Clipboard.SetImage(bitmap);
}
}
And this is the poor result:
How can I improve the bitmap result?
EDIT
Short recap:
I can't use System.Drawing.Graphics.DrawString() method because it does not support Unicode chars
I can't use TextRenderer.DrawText() method because TextRenderer class uses GDI and it isn't alpha aware, so it does not support a transparent background.
Is there some other way to reach my goal?
I'm trying to write text inside circle (not centered by rectangle) alignment must be from line to line inside circle.
I have successfully drawn the circle and text. I did research on Stack Overflow and Google without success about putting text inside this circle.
private void Button1_Click(object sender, EventArgs e)
{
System.Drawing.Graphics graphicsObj;
graphicsObj = this.CreateGraphics();
// Create font and brush.
Font drawFont = new Font("Arial", 5);
SolidBrush drawBrush = new SolidBrush(Color.Black);
// Create point for upper-left corner of drawing.
float x = 150.0F;
float y = 50.0F;
// Set format of string.
StringFormat drawFormat = new StringFormat();
drawFormat.FormatFlags = StringFormatFlags.FitBlackBox;
graphicsObj.DrawEllipse(Pens.Red, 20, 20, 350, 350);
graphicsObj.DrawString(richTextBox1.Text.ToString(), drawFont, drawBrush, x, y, drawFormat);
}
Looking for advise, not for exact code..
-> How to establish connection for text alignment based on rectangle.
Expected output like in this image : https://imge.to/i/miFif
exactly same but I need in C# Win.Form -> Wrap text inside a circular div
DrawString has an overload with format options:
...
DrawString(e.Cache, text, rect,
new StringFormat() {
LineAlignment = StringAlignment.Center,
Alignment = StringAlignment.Center
});
private void timer1_Tick(object sender, EventArgs e)
{
counter--;
DrawLetter();
if (counter == 0)
{
t.Stop();
TakeScreenShot();
}
}
private void DrawLetter()
{
var letter = counter.ToString();
Graphics g = Graphics.FromHdc(GetDC(IntPtr.Zero));
float width = ((float)this.ClientRectangle.Width);
float height = ((float)this.ClientRectangle.Width);
float emSize = height;
Font font = new Font(FontFamily.GenericSansSerif, emSize, FontStyle.Regular);
font = FindBestFitFont(g, letter.ToString(), font, this.ClientRectangle.Size);
SizeF size = g.MeasureString(letter.ToString(), font);
g.DrawString(letter, font, new SolidBrush(Color.White), (width - size.Width) / 2, 0);
}
private Font FindBestFitFont(Graphics g, String text, Font font, Size proposedSize)
{
// Compute actual size, shrink if needed
while (true)
{
SizeF size = g.MeasureString(text, font);
// It fits, back out
if (size.Height <= proposedSize.Height &&
size.Width <= proposedSize.Width) { return font; }
// Try a smaller font (90% of old size)
Font oldFont = font;
font = new Font(font.Name, (float)(font.Size * .9), font.Style);
oldFont.Dispose();
}
}
void TakeScreenShot()
{
bmpScreenshot = new Bitmap(Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height, PixelFormat.Format32bppArgb);
gfxScreenshot = Graphics.FromImage(bmpScreenshot);
gfxScreenshot.CopyFromScreen(Screen.PrimaryScreen.Bounds.X, Screen.PrimaryScreen.Bounds.Y, 0, 0, Screen.PrimaryScreen.Bounds.Size, CopyPixelOperation.SourceCopy);
bmpScreenshot.Save(Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory) + #"\ScreenCaptures\newfile.png", ImageFormat.Png);
}
I am able to draw the string but it is writing on top of itself.
How can I clear it? Basically I want the countdown to appear on the screen then take a screenshot.
Right now the number is overwritten by another.
You can do the following: create an additional transparent form, and it will display timer values. This will allow you to erase the previous value. In addition, this will allow to get rid of the function call GetDC via PInvoke.
Form timerForm; // main form field
// Create and show additional transparent form before starting the timer
timerForm = new Form
{
FormBorderStyle = FormBorderStyle.None,
WindowState = FormWindowState.Maximized,
TransparencyKey = SystemColors.Control,
ShowInTaskbar = false
};
timerForm.Show();
timer.Start();
Change the method DrawLetter as follows
private void DrawLetter()
{
var letter = counter.ToString();
Graphics g = timerForm.CreateGraphics();
float width = ClientRectangle.Width;
float height = ClientRectangle.Width;
float emSize = height;
using (Font font1 = new Font(FontFamily.GenericSansSerif, emSize, FontStyle.Regular))
using (Font font2 = FindBestFitFont(g, letter, font1, ClientRectangle.Size))
using (var brush = new SolidBrush(Color.White))
{
SizeF size = g.MeasureString(letter, font2);
g.Clear(SystemColors.Control);
g.DrawString(letter, font2, brush, (width - size.Width) / 2, 0);
}
}
We must release all used resources like fonts and brushes. For this I applied using.
Change the timer tick event handler as follows
private void timer1_Tick(object sender, EventArgs e)
{
counter--;
DrawLetter();
if (counter == 0)
{
timer.Stop();
TakeScreenShot();
timerForm.Dispose(); // must release
}
}
FindBestFitFont and TakeScreenShot methods remain unchanged.
Draw your font to a different bitmap. Transparent background (or whatever doesn't invert, see below - perhaps black).
(now you could also draw it with a different colored shadow to mitigate drawing on similar colored background - but the natures of SRCINVERT/XOR, below, will mitigate this as well)
Use BitBlt to copy it to the screen
Use the SRCINVERT raster op.
(note: the colors may be different as it is XORing it with pixels underneath)
Now when is is time to erase, just make the same bitblt with the same contents as previous, the double XOR effect caused by SRCINVERT will have the effect of erasing it.
Then draw the next font.
Note: if desktop is updated between calls, all bets are off.
better...
Rather than attempting a transparent background, draw it on a white background. This will eliminate contrast issues with the font, eliminate concern with dynamic updates, and eliminate problems with erasing. Sometimes you have to admit - the method & code isn't the problem, the requirements are the problem. This all depends of course on the source of the requirements, etc.
If it needs to look professional, don't put the content on the screen, draw it after you take the screen capture.
If you end up using the transparent window approach, the screen shot may miss the transparent window. To get it, see this question:
Capture screenshot Including Semitransparent windows in .NET. (could be fixed by newer .net / newer windows versions)
You need to invalidate all the windows on the desktop by using the InvalidateRect function to erase the previously drawn letter.
See additional codes below for the DrawLetter method.
[DllImport("user32")]
private static extern bool InvalidateRect(IntPtr hwnd, IntPtr rect, bool bErase);
private void DrawLetter()
{
var letter = counter.ToString();
Graphics g = Graphics.FromHdc(GetDC(IntPtr.Zero));
float width = ((float)this.ClientRectangle.Width);
float height = ((float)this.ClientRectangle.Width);
float emSize = height;
Font font = new Font(FontFamily.GenericSansSerif, emSize, FontStyle.Regular);
font = FindBestFitFont(g, letter.ToString(), font, this.ClientRectangle.Size);
SizeF size = g.MeasureString(letter.ToString(), font);
// Invalidate all the windows.
InvalidateRect(IntPtr.Zero, IntPtr.Zero, true);
// Sometimes, the letter is drawn before the windows are invalidated.
// To fix that, add a small delay before drawing the letter.
System.Threading.Thread.Sleep(100);
// Finally, draw the letter.
g.DrawString(letter, font, new SolidBrush(Color.White), (width - size.Width) / 2, 0);
}
A solution is:
You must take a snapshot of that area you want to show counter before all things. Then call DrawImage function to draw snapshot image before call DrawString function every time.
I'm trying to draw a box around a label which has been aligned using StringAlignment.Far for example. I can find the Size of text using g.MeasureString but I can't find a method to translate the origin point such that I can find a Rectangle which bounds the text.
Say I have a Point origin at which to draw from, and a StringFormat format with what alignment I wish my string to have. I can find the Size of the string using g.MeasureString(text, font). How do I translate this Point/Size pair into a rectangle which overlaps the g.DrawString(text, font, brush, origin, format) call.
It's difficult to convert c to managed code. You should use .Net code directly if it's available.
For MeasureString, see link Graphics.MeasureString Method
Example:
using System.Diagnostics;
...
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
Graphics g = e.Graphics;
Font font = new Font("Arial", 16);
SizeF sz = g.MeasureString("Text...", font);
Rectangle rc = new Rectangle(0,0, (int)sz.Width, (int)sz.Height);
Debug.WriteLine(rc.Width.ToString());
Debug.WriteLine(rc.Height.ToString());
//change top/left origin of rectangle
rc.X = 10;
rc.Y = 20;
}
You just need the width and height of text. You can change left/top corner of rectangle.
By the way, the C method gives a rectangle with top/left coordinates at zero, so it's the same information as Size
Edit
This will fit text with word-break flag in to a rectangle whose width is 100. The height of the rectangle is not known. TextRenderer.MeasureText will tell us the height of the rectangle. Top/left corner can be changed, alignment can be changed.
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
Font font = new Font("Arial", 10);
string text = "I'm trying to draw a box around a label which has been aligned.";
Size layout = new Size(100, 0);
Size sz = TextRenderer.MeasureText(e.Graphics, text, font, layout,
TextFormatFlags.WordBreak);
Rectangle rc = new Rectangle(new Point(0,0), sz);
e.Graphics.DrawRectangle(Pens.Black, rc);
TextRenderer.DrawText(e.Graphics, text, font, rc,
SystemColors.ControlText, SystemColors.Control, TextFormatFlags.WordBreak);
}
My way is use SetMeasurableCharacterRanges to obtain the region of the whole text.
Consider into OnPaint:
protected override void OnPaint(PaintEventArgs e)
{
Graphics g = e.Graphics;
Font font = new Font("Arial", 16);
string text = "Border of this text";
StringFormat sf = new StringFormat();
sf.Alignment = StringAlignment.Center;
sf.LineAlignment = StringAlignment.Center;
RectangleF area = new RectangleF(0, 0, 246, 84);
sf.SetMeasurableCharacterRanges(new CharacterRange[] { new CharacterRange(0, text.Length) });
Region[] r = g.MeasureCharacterRanges(text, font, area, sf);
Rectangle rf = new Rectangle((int)r[0].GetBounds(g).X, (int)r[0].GetBounds(g).Y, (int)r[0].GetBounds(g).Width, (int)r[0].GetBounds(g).Height);
g.DrawString(text, font, Brushes.Black, area, sf);
g.DrawRectangle(new Pen(Color.Red, 1), rf);
}
I use the function below to draw a string on an image. It words great for the most part when I draw a string using one color.
However I want to have a word be in a different color. For example I want to draw "This is a TEST", I want TEST to be red.
What accomplish this using this method?
System.Drawing.Image newImg = new System.Drawing.Bitmap(500, 500);
pictureBox1.Image = TextOverlay(newImg, "This is a TEST", this.Font, Color.Black, ContentAlignment.MiddleCenter, 0.6F);
I'm referring to this line of code and not the OverlayColor parameter:
g.DrawString(OverlayText, f, b, rect, strFormat);
Here is the complete function:
public static Bitmap TextOverlay(Image img, string OverlayText, Font OverlayFont, Color OverlayColor, System.Drawing.ContentAlignment Position, float PercentFill)
{
// create bitmap and graphics used for drawing
// "clone" image but use 24RGB format
Bitmap bmp = new Bitmap(img.Width, img.Height, System.Drawing.Imaging.PixelFormat.Format24bppRgb);
Graphics g = Graphics.FromImage(bmp);
g.DrawImage(img, 0, 0);
int alpha = 255;
// Create the brush based on the color and alpha
SolidBrush b = new SolidBrush(Color.FromArgb(alpha, OverlayColor));
// Measure the text to render (unscaled, unwrapped)
StringFormat strFormat = StringFormat.GenericTypographic;
SizeF s = g.MeasureString(OverlayText, OverlayFont, 100000, strFormat);
// Enlarge font to specified fill (estimated by AREA)
float zoom = (float)(Math.Sqrt(((double)(img.Width * img.Height) * PercentFill) / (double)(s.Width * s.Height)));
FontStyle sty = OverlayFont.Style;
Font f = new Font(OverlayFont.FontFamily, ((float)OverlayFont.Size) * zoom, sty);
int charFit;
int linesFit;
float SQRTFill = (float)(Math.Sqrt(PercentFill));
strFormat.FormatFlags = StringFormatFlags.NoClip; //|| StringFormatFlags.LineLimit || StringFormatFlags.MeasureTrailingSpaces;
strFormat.Trimming = StringTrimming.Word;
SizeF layout = new SizeF(((float)img.Width) * SQRTFill, ((float)img.Height) * 1.5F); // fit to width, allow height to go over
s = g.MeasureString(OverlayText, f, layout, strFormat, out charFit, out linesFit);
// Determine draw area based on placement
RectangleF rect = new RectangleF((bmp.Width - s.Width) / 2F,
(bmp.Height - s.Height) / 2F,
layout.Width,
((float)img.Height) * SQRTFill);
// Add rendering hint (thx to Thomas)
g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;
// finally, draw centered text!
g.DrawString(OverlayText, f, b, rect, strFormat);
// clean-up
g.Dispose();
b.Dispose();
f.Dispose();
return bmp;
}