Issues with DrawString on a Bitmap - c#

I have this constructor that takes in an input of an array of Color objects and some misc strings (like file location and size of list), and stores this heatmap into a bitmap instance called _image_. Everything works out fine, but my main issue is I have no way to render text onto this heatmap. I would like to overlay title text and x and y-axis labels onto this bitmap.
public HeatMap(IEnumerable<Color> colors, int width, int height, string file, int U, int V) {
if (colors == null)
throw new ArgumentNullException("colors");
if (width <= 0)
throw new ArgumentException("width must be at least 1");
if (height <= 0)
throw new ArgumentException("height must be at least 1");
_width = width;
_height = height;
_file = file;
_image = new Bitmap(U, V, PixelFormat.Format32bppArgb);
Graphics graphics = Graphics.FromImage(_image);
graphics.Clear(Color.White);
graphics.Dispose();
int x = 0;
int y = 0;
foreach (Color color in colors) {
_image.SetPixel(x, y, color); // This can be speeded up by using GH_MemoryBitmap if you want.
y++;
if (y >= V) {
y = 0;
x++;
}
if (x >= U)
break;
}
}
Below, I also have a method that resizes the image (without too much distortion), and I figured I might as well make use of this since I have a graphics object I can use, like so:
private Bitmap ResizeBitmap(Bitmap sourceBMP, int width, int height) {
Bitmap result = new Bitmap(width, height);
using (Graphics g = Graphics.FromImage(result)) {
g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.NearestNeighbor;
g.DrawImage(sourceBMP, 0, 0, width, height);
g.DrawString("A heatmap.", SystemFonts.DefaultFont, Brushes.Black, 10, 10);
}
return result;
}
Using the above, I was able to overlay text over it, as a starting point. My question is, how do I add a white boarder over the above image (so my text doesn't overlap my graph), and then overlay x and y-axis text onto it?
I guess my specific question really is - how do I render text onto, say, every four columns of my heatmap? In most instances, there are 100 x-axis objects, and like 24 y-axis objects. The y-axis would have the time of the day, while the x-axis has the day of the year. My familiarity with using graphics in C# is very low, so any pointers is very appreciated.

You've to use the DrawString before the graphics.Dispose() and after the Clear().
Graphics graphics = Graphics.FromImage(_image);
graphics.Clear(Color.WhiteSmoke);
graphics.DrawString("your text", SystemFonts.DefaultFont, Brushes.Black, new PointF(0, 0));
graphics.Dispose();
Where the PointF(0, 0) represents the up-left corner of your text placeholder, you've to calculate the text position based on the positions of your heatmap.
UPDATE (alignment)
You can align your text in relation with a virtual rectangle, doing something like:
Graphics graphics = Graphics.FromImage(_image);
graphics.Clear(Color.WhiteSmoke);
StringFormat stringFormat = new StringFormat() { Alignment = StringAlignment.Center, LineAlignment = StringAlignment.Center };
graphics.DrawString("your text", SystemFonts.DefaultFont, Brushes.Black, new RectangleF(0, 0, 100, 100), stringFormat);
graphics.Dispose();
In this case I've created a virtual box of 100x100 positioned on the top-left corner, and the text is h/v centered relatively to this box.

Related

Display an object on specific position of an image

I have a bitmap and I'm trying to show a user control object on a specific position(based on X and Y) of that bitmap image, Do you have any solution?
actually I have used map file with the specific extension(*.ECW).
also I could add ellipse or draw an image in the specific position but I would add a specific component (for a example a button that created as a user Control).
sorry for this unexecutable code . assume we have a graphic object that a picture or map placed on as a background .
now we have to add a component to specific point .
I have drawn an ellipse like this
ImageLayout BackgroundImageLayout;
protected Bitmap Background;
int ppiRadius =100;
Background = new Bitmap(Width, Height);
BackgroundImageLayout = ImageLayout.Center;
// Set up the map bitmap
// Get a graphics obejct for the bitmap
var graphics = Graphics.FromImage(Background);
graphics.FillRectangle(new SolidBrush(System.Drawing.Color.Black), 0, 0,
Width, Height);
// Draw the layers to the bitmap
var hdc = graphics.GetHdc();
if (_allLayersHandle != null)
{
foreach (var v in _allLayersHandle)
{
LastError = GlobalMapperDLL.GM_DrawLayer(hdc, v.Value,
ref _currentViewRect, 0, 0,
Background.Width, Background.Height);
}
}
graphics.ReleaseHdc(hdc);
if (LastError != GlobalMapperDLL.GM_Error_t32.GM_Error_None) return;
_centerPoint = GISConvert.GetCenterPixel();
graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
var point = GISConvert.LatLonToPixel(sitePosition);
graphics.DrawEllipse(new System.Drawing.Pen(System.Drawing.Brushes.Red, 2), new RectangleF((point.X - ppiRadius / 2), (point.Y - ppiRadius / 2), ppiRadius, ppiRadius));
//or graphics.DrawImage

How can I clear the Graphics before drawing another letter?

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.

Write a word from a string in a different color using g.DrawString()

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;
}

Dynamically resizing font to fit space while using Graphics.DrawString

Does anyone have a tip whereas you could dynamically resize a font to fit a specific area? For example, I have an 800x110 rectangle and I want to fill it with the max size font that would support the entire string I'm trying to display.
Bitmap bitmap = new Bitmap(800, 110);
using (Graphics graphics = Graphics.FromImage(bitmap))
using (Font font1 = new Font("Arial", 120, FontStyle.Regular, GraphicsUnit.Pixel))
{
Rectangle rect1 = new Rectangle(0, 0, 800, 110);
StringFormat stringFormat = new StringFormat();
stringFormat.Alignment = StringAlignment.Center;
stringFormat.LineAlignment = StringAlignment.Center;
graphics.TextRenderingHint = TextRenderingHint.ClearTypeGridFit;
graphics.DrawString("Billy Reallylonglastnameinstein", font1, Brushes.Red, rect1, stringFormat);
}
bitmap.Save(Server.MapPath("~/Fonts/" + System.Guid.NewGuid() + ".png"));
Obviously that whole name won't render in the space provided at the large font size. There has to be a simple way to do this?
You should do a scale transform on Font.Size the following function is an example of doing that but you can improve it to apply better results.
Here is FindFont function which get a room and a text with prefered size and gives you a font in which you can set that whole text fits the room!
// This function checks the room size and your text and appropriate font
// for your text to fit in room
// PreferedFont is the Font that you wish to apply
// Room is your space in which your text should be in.
// LongString is the string which it's bounds is more than room bounds.
private Font FindFont(
System.Drawing.Graphics g,
string longString,
Size Room,
Font PreferedFont
) {
// you should perform some scale functions!!!
SizeF RealSize = g.MeasureString(longString, PreferedFont);
float HeightScaleRatio = Room.Height / RealSize.Height;
float WidthScaleRatio = Room.Width / RealSize.Width;
float ScaleRatio = (HeightScaleRatio < WidthScaleRatio)
? ScaleRatio = HeightScaleRatio
: ScaleRatio = WidthScaleRatio;
float ScaleFontSize = PreferedFont.Size * ScaleRatio;
return new Font(PreferedFont.FontFamily, ScaleFontSize);
}
For your question you can call it like the following code:
Bitmap bitmap = new Bitmap(800, 110);
using (System.Drawing.Graphics graphics = System.Drawing.Graphics.FromImage(bitmap))
using (Font font1 = new Font("Arial", 120, FontStyle.Regular, GraphicsUnit.Pixel))
{
Rectangle rect1 = new Rectangle(0, 0, 800, 110);
StringFormat stringFormat = new StringFormat();
stringFormat.Alignment = StringAlignment.Center;
stringFormat.LineAlignment = StringAlignment.Center;
graphics.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit;
Font goodFont = FindFont(graphics, "Billy Reallylonglastnameinstein", rect1.Size, font1);
graphics.DrawString(
"Billy Reallylonglastnameinstein",
goodFont,
Brushes.Red,
rect1,
stringFormat
);
}
I've adapted Saeed's great function to more suit my requirements. Comments explain all:
// You hand this the text that you need to fit inside some
// available room, and the font you'd like to use.
// If the text fits nothing changes
// If the text does not fit then it is reduced in size to
// make it fit.
// PreferedFont is the Font that you wish to apply
// FontUnit is there because the default font unit is not
// always the one you use, and it is info required in the
// constructor for the new Font.
public static void FindGoodFont(Graphics Graf, string sStringToFit,
Size TextRoomAvail,
ref Font FontToUse,
GraphicsUnit FontUnit)
{
// Find out what the current size of the string in this font is
SizeF RealSize = Graf.MeasureString(sStringToFit, FontToUse);
Debug.WriteLine("big string is {0}, orig size = {1},{2}",
sStringToFit, RealSize.Width, RealSize.Height);
if ((RealSize.Width <= TextRoomAvail.Width) && (RealSize.Height <= TextRoomAvail.Height))
{
Debug.WriteLine("The space is big enough already");
// The current font is fine...
return;
}
// Either width or height is too big...
// Usually either the height ratio or the width ratio
// will be less than 1. Work them out...
float HeightScaleRatio = TextRoomAvail.Height / RealSize.Height;
float WidthScaleRatio = TextRoomAvail.Width / RealSize.Width;
// We'll scale the font by the one which is furthest out of range...
float ScaleRatio = (HeightScaleRatio < WidthScaleRatio) ? ScaleRatio = HeightScaleRatio : ScaleRatio = WidthScaleRatio;
float ScaleFontSize = FontToUse.Size * ScaleRatio;
Debug.WriteLine("Resizing with scales {0},{1} chose {2}",
HeightScaleRatio, WidthScaleRatio, ScaleRatio);
Debug.WriteLine("Old font size was {0}, new={1} ",FontToUse.Size,ScaleFontSize);
// Retain whatever the style was in the old font...
FontStyle OldFontStyle = FontToUse.Style;
// Get rid of the old non working font...
FontToUse.Dispose();
// Tell the caller to use this newer smaller font.
FontToUse = new Font(FontToUse.FontFamily,
ScaleFontSize,
OldFontStyle,
FontUnit);
}
This is just an update for #Saeed's FindFont function.
GraphicsUnit.Pixel needs to be added to FindFont function return line. Without GraphicsUnit.Pixel, system dpi will effect the drawn string. Problem will arise when the dpi of system and bitmap mismatches. You can see more detail in this Windows DPI setting affects Graphics.DrawString. Since GraphicsUnit of PreferedFont is already set to GraphicsUnit.Pixel and return font is not set with GraphicsUnit.Pixel. In this case text will go out of the Room dimension, if bitmap dpi is larger than system dpi and font size will go smaller than the expected size if bitmap dpi is smaller than system dpi. Here is the updated function.
private Font FindFont( System.Drawing.Graphics g , string longString , Size Room , Font PreferedFont)
{
SizeF RealSize = g.MeasureString(longString, PreferedFont);
float HeightScaleRatio = Room.Height / RealSize.Height;
float WidthScaleRatio = Room.Width / RealSize.Width;
float ScaleRatio = (HeightScaleRatio < WidthScaleRatio) ? ScaleRatio = HeightScaleRatio : ScaleRatio = WidthScaleRatio;
float ScaleFontSize = PreferedFont.Size * ScaleRatio;
return new Font(PreferedFont.FontFamily, ScaleFontSize,PreferedFont.Style,GraphicsUnit.Pixel);
}
I don't want to bash against saaeds solution which is probably pretty awesome, too. But I found another one on msdn: Dynamic Graphic Text Resizing which worked for me.
public Font GetAdjustedFont(Graphics GraphicRef, string GraphicString, Font OriginalFont, int ContainerWidth, int MaxFontSize, int MinFontSize, bool SmallestOnFail)
{
// We utilize MeasureString which we get via a control instance
for (int AdjustedSize = MaxFontSize; AdjustedSize >= MinFontSize; AdjustedSize--)
{
Font TestFont = new Font(OriginalFont.Name, AdjustedSize, OriginalFont.Style);
// Test the string with the new size
SizeF AdjustedSizeNew = GraphicRef.MeasureString(GraphicString, TestFont);
if (ContainerWidth > Convert.ToInt32(AdjustedSizeNew.Width))
{
// Good font, return it
return TestFont;
}
}
// If you get here there was no fontsize that worked
// return MinimumSize or Original?
if (SmallestOnFail)
{
return new Font(OriginalFont.Name,MinFontSize,OriginalFont.Style);
}
else
{
return OriginalFont;
}
}
Here is my solution that support wrapping.
public static Font GetAdjustedFont(Graphics graphic, string str, Font originalFont, Size containerSize)
{
// We utilize MeasureString which we get via a control instance
for (int adjustedSize = (int)originalFont.Size; adjustedSize >= 1; adjustedSize--)
{
var testFont = new Font(originalFont.Name, adjustedSize, originalFont.Style, GraphicsUnit.Pixel);
// Test the string with the new size
var adjustedSizeNew = graphic.MeasureString(str, testFont, containerSize.Width);
if (containerSize.Height > Convert.ToInt32(adjustedSizeNew.Height))
{
// Good font, return it
return testFont;
}
}
return new Font(originalFont.Name, 1, originalFont.Style, GraphicsUnit.Pixel);
}
How to use:
var font = GetAdjustedFont(drawing, text, originalfont, wrapSize);
drawing.DrawString(text, font, textBrush, new Rectangle(0, 0, wrapSize.Width, wrapSize.Height));

Center text output from Graphics.DrawString()

I'm using the .NETCF (Windows Mobile) Graphics class and the DrawString() method to render a single character to the screen.
The problem is that I can't seem to get it centred properly. No matter what I set for the Y coordinate of the location of the string render, it always comes out lower than that and the larger the text size the greater the Y offset.
For example, at text size 12, the offset is about 4, but at 32 the offset is about 10.
I want the character to vertically take up most of the rectangle it's being drawn in and be centred horizontally. Here's my basic code. this is referencing the user control it's being drawn in.
Graphics g = this.CreateGraphics();
float padx = ((float)this.Size.Width) * (0.05F);
float pady = ((float)this.Size.Height) * (0.05F);
float width = ((float)this.Size.Width) - 2 * padx;
float height = ((float)this.Size.Height) - 2 * pady;
float emSize = height;
g.DrawString(letter, new Font(FontFamily.GenericSansSerif, emSize, FontStyle.Regular),
new SolidBrush(Color.Black), padx, pady);
Yes, I know there is the label control that I could use instead and set the centring with that, but I actually do need to do this manually with the Graphics class.
I'd like to add another vote for the StringFormat object.
You can use this simply to specify "center, center" and the text will be drawn centrally in the rectangle or points provided:
StringFormat format = new StringFormat();
format.LineAlignment = StringAlignment.Center;
format.Alignment = StringAlignment.Center;
However there is one issue with this in CF. If you use Center for both values then it turns TextWrapping off. No idea why this happens, it appears to be a bug with the CF.
To align a text use the following:
StringFormat sf = new StringFormat();
sf.LineAlignment = StringAlignment.Center;
sf.Alignment = StringAlignment.Center;
e.Graphics.DrawString("My String", this.Font, Brushes.Black, ClientRectangle, sf);
Please note that the text here is aligned in the given bounds. In this sample this is the ClientRectangle.
Through a combination of the suggestions I got, I came up with this:
private void DrawLetter()
{
Graphics g = this.CreateGraphics();
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.Black), (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();
}
}
So far, this works flawlessly.
The only thing I would change is to move the FindBestFitFont() call to the OnResize() event so that I'm not calling it every time I draw a letter. It only needs to be called when the control size changes. I just included it in the function for completeness.
To draw a centered text:
TextRenderer.DrawText(g, "my text", Font, Bounds, ForeColor, BackColor,
TextFormatFlags.HorizontalCenter |
TextFormatFlags.VerticalCenter |
TextFormatFlags.GlyphOverhangPadding);
Determining optimal font size to fill an area is a bit more difficult. One working soultion I found is trial-and-error: start with a big font, then repeatedly measure the string and shrink the font until it fits.
Font FindBestFitFont(Graphics g, String text, Font font,
Size proposedSize, TextFormatFlags flags)
{
// Compute actual size, shrink if needed
while (true)
{
Size size = TextRenderer.MeasureText(g, text, font, proposedSize, flags);
// 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.FontFamily, (float)(font.Size * .9));
oldFont.Dispose();
}
}
You'd use this as:
Font bestFitFont = FindBestFitFont(g, text, someBigFont, sizeToFitIn, flags);
// Then do your drawing using the bestFitFont
// Don't forget to dispose the font (if/when needed)
Here's some code. This assumes you are doing this on a form, or a UserControl.
Graphics g = this.CreateGraphics();
SizeF size = g.MeasureString("string to measure");
int nLeft = Convert.ToInt32((this.ClientRectangle.Width / 2) - (size.Width / 2));
int nTop = Convert.ToInt32((this.ClientRectangle.Height / 2) - (size.Height / 2));
From your post, it sounds like the ClientRectangle part (as in, you're not using it) is what's giving you difficulty.
You can use an instance of the StringFormat object passed into the DrawString method to center the text.
See Graphics.DrawString Method and StringFormat Class.

Categories