How to print high definition images? - c#

Printer resolutions are generally 5-6 times greater than a screen's resolution. A printer's resolution can be around 6600 x 5100 as opposed to a full HD screen's resolution: 1920 x 1080.
An 1920 x 1080 image looks great on a screen but to avoid pixelation, one should ideally render a much higher resolution image to the printer, for example, a 6600 x 5100 image.
I am trying to print a high definition image (6600 x 5100) to my high definition printer (600 dpi), but I find that the available print area is only 850 x 1100 as specified by e.PageBounds; see the code below:
Bitmap bitmapToPrint;
public void printImage()
{
bitmapToPrint = new Bitmap(1700,2200);
Font font = new Font(FontFamily.GenericSansSerif, 60, FontStyle.Regular);
string alphabet = "abcdefghijklmnopqrstuvwxyz";
Graphics graphics = Graphics.FromImage(bitmapToPrint);
graphics.DrawString(alphabet, font, System.Drawing.Brushes.Black, 0, 0);
graphics.DrawString(alphabet, font, System.Drawing.Brushes.Black, 0, 1000);
PrintDocument pd = new PrintDocument();
pd.PrinterSettings.PrinterName = "Microsoft XPS Document Writer";
pd.PrinterSettings.PrintToFile = true;
pd.PrintPage += new PrintPageEventHandler(pd_PrintPage);
pd.Print();
}
void pd_PrintPage(object sender, PrintPageEventArgs e)
{
e.Graphics.DrawImage(bitmapToPrint, new PointF(0, 0));
//Have a look at e.PageBounds, the dimensions are only 850x1100
}

Related

Blurry and large image barcode being generated using a plugin in Windows Form

Going through the steps mentioned here
and using IDAutomationCode39, I am getting the barcode image, however they are very blurr and only scans bigger size images. My barcode id will be upto 30 characters long, which is causing a very wide barcode image. Where could the problem lie? Is it the IDAutomationCode39 or my setting in my button click event below?
private void button1_Click(object sender, EventArgs e)
{
string abbre = GenerateProdCodeFromProductName();
txt2.Text = abbre;
string barcode = txt1.Text;
Bitmap bitm = new Bitmap(barcode.Length * 45, 160);
bitm.SetResolution(240, 240);
using (Graphics graphic = Graphics.FromImage(bitm))
{
Font newfont = new Font("IDAutomationHC39M", 6);
PointF point = new PointF(5f, 5f);
SolidBrush black = new SolidBrush(Color.Black);
SolidBrush white = new SolidBrush(Color.White);
graphic.FillRectangle(white, 0, 0, bitm.Width, bitm.Height);
graphic.DrawString("*" + barcode + "*", newfont, black, point);
}
using (MemoryStream Mmst = new MemoryStream())
{
bitm.Save("ms", ImageFormat.Jpeg);
pictureBox1.Image = bitm;
pictureBox1.Width = bitm.Width;
pictureBox1.Height = bitm.Height;
}
}
Thank you.
I don't see any PlugIn reference in your code, you are just using a Code39 ASCII font to print a Barcode on a Bitmap.
The problem I see is that the size of the resulting Bitmap is unscaled.
What I mean is, you let the size of the Barcode determine the size of the final graphic image.
It is usually the opposite. You have some defined dimensions for a Bitmap, because of layout constraints: you have to print the barcode to a label, for example.
If the generated Barcode is wider than its container, you have to normalize it (scale it to fit).
Here is what I propose. The size of the Bitmap if fixed (300, 150). When the Barcode is generated, its size is scaled to fit the Bitmap size.
The quality of the image is preserved, using an Bicubic Interpolation in case of down scaling. The resulting graphics is also Anti-Aliased.
(Anti-Alias has a good visual render. May not be the best choice for printing. It also depends on the printer.)
The generated Bitmap is then passed to a PictureBox for visualization and saved to disk as a PNG image (this format because of its loss-less compression).
Here is the result:
string Barcode = "*8457QK3P9*";
using (Bitmap bitmap = new Bitmap(300, 150))
{
bitmap.SetResolution(240, 240);
using (Graphics graphics = Graphics.FromImage(bitmap))
{
Font font = new Font("IDAutomationSHC39M", 10, FontStyle.Regular, GraphicsUnit.Point);
graphics.Clear(Color.White);
StringFormat stringformat = new StringFormat(StringFormatFlags.NoWrap);
graphics.TextRenderingHint = TextRenderingHint.AntiAliasGridFit;
graphics.TextContrast = 10;
PointF TextPosition = new PointF(10F, 10F);
SizeF TextSize = graphics.MeasureString(Barcode, font, TextPosition, stringformat);
if (TextSize.Width > bitmap.Width)
{
float ScaleFactor = (bitmap.Width - (TextPosition.X / 2)) / TextSize.Width;
graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
graphics.ScaleTransform(ScaleFactor, ScaleFactor);
}
graphics.DrawString(Barcode, font, new SolidBrush(Color.Black), TextPosition, StringFormat.GenericTypographic);
bitmap.Save(#"[SomePath]\[SomeName].png", ImageFormat.Png);
this.pictureBox1.Image = (Bitmap)bitmap.Clone();
font.Dispose();
}
}

Confirmation, Printing Graphic location C#

I have created the code below to help me print the admin form as a "report" like document showing the date and graph needed for documentation.
try
{
Graphics g = this.CreateGraphics();
AdminPage = new Bitmap(Size.Width, Size.Height, g);
Graphics Printed = Graphics.FromImage(AdminPage);
Printed.CopyFromScreen(519, 340, 0,0,this.Size);//519,340 this.Location.Y,this.Location.X
printPreviewAdminDialogue.ShowDialog();
}
catch(Exception )
{
MessageBox.Show("Please check printer connection!");
}
I have used the coordinate data of the screen as:
Printed.CopyFromScreen(519, 340, 0,0,this.Size);
Will this still work on any size screen or will this result in some formatting problems on other devices rather than just my laptop?
So far it looks fine Print Preview of what i want with the current code
This method takes a Control (SourceControl) reference and returns a Bitmap resulting from a screen capture of the Control's Window.
Parameters:
SourceControl: The control to be printed. It can be a TopLevel Window (a Form) or a Child Control.
Dpi: The DPI resolution of the resulting Bitmap.
ScaleToDpi: if set to true, the size of the Bitmap will be scaled to match the Dpi parameter, defining a scale factor relative to current screen resolution. E.g.: If parameter Dpi = 300 and the current screen resolution is 96 Dpi, the resulting Bitmap size will be scaled with a factor of 3.125
ClientAreaOnly: If true, captures the SourceControl client area, otherwise the full window bounds.
InterpolationMode: Defines the resulting quality of a scaled bitmap. For enlargements, Bicubic or HighQualityBicubic gives better results. Bicubic may render a shaper image. The usefulness of this parameter depends on how the image is used. The perceived visual quality may not be the same when the image is printed on paper. When printing, sharper images give a better result.
The results can be tested using a PrintPreviewControl to see the difference between a scaled and a non-scaled Bitmap.
Print the a Form including its borders with a resolution of 300 DPI but not scaled to the new resolution (modifies the resulting Bitmap resolution only):
Bitmap FullSize300Dpi = PrintControlFromScreen(this, false, 300, false, InterpolationMode.Default);
Print the a Form ClientArea with a resolution of 300 DPI and scale its dimensions to the new resolution using a HighQualityBicubic Interpolation:
Bitmap ClientArea300DpiScaled = PrintControlFromScreen(this, true, 300, true, InterpolationMode.HighQualityBicubic);
The same, but it prints the client area of a button1 control with a resolution of 96 DPI with a Bilinear Interpolation:
Bitmap Child96DpiUnscaled = PrintControlFromScreen(this.button1, true, 96, false, InterpolationMode.Bilinear);
public Bitmap PrintControlFromScreen(Control SourceControl, bool ClientAreaOnly, float Dpi, bool ScaleToDpi, InterpolationMode Interpolation)
{
using (Graphics graphics = SourceControl.CreateGraphics())
{
SizeF ScaleFactor = new SizeF((Dpi / graphics.DpiX), (Dpi / graphics.DpiY));
SizeF BitmapSize;
if (ScaleToDpi)
{
BitmapSize = ClientAreaOnly ? new SizeF((SourceControl.ClientRectangle.Size.Width * ScaleFactor.Width),
(SourceControl.ClientRectangle.Size.Height * ScaleFactor.Height))
: new SizeF((SourceControl.Bounds.Size.Width * ScaleFactor.Width),
(SourceControl.Bounds.Size.Height * ScaleFactor.Height));
}
else
{
BitmapSize = ClientAreaOnly ? SourceControl.ClientRectangle.Size : SourceControl.Bounds.Size;
}
using (Bitmap bitmap = new Bitmap((int)BitmapSize.Width, (int)BitmapSize.Height))
{
bitmap.SetResolution(ScaleFactor.Width * graphics.DpiX, ScaleFactor.Height * graphics.DpiY);
using (Graphics ImageGraph = Graphics.FromImage(bitmap))
{
ImageGraph.CompositingQuality = CompositingQuality.HighQuality;
ImageGraph.CompositingMode = CompositingMode.SourceCopy;
ImageGraph.SmoothingMode = SmoothingMode.HighQuality;
ImageGraph.PixelOffsetMode = PixelOffsetMode.HighQuality;
ImageGraph.InterpolationMode = Interpolation;
if (ClientAreaOnly)
{
ImageGraph.CopyFromScreen(SourceControl.PointToScreen(SourceControl.ClientRectangle.Location),
new Point(0, 0), SourceControl.ClientRectangle.Size);
}
else
{
if (SourceControl.TopLevelControl == SourceControl)
{
ImageGraph.CopyFromScreen(SourceControl.Bounds.Location,
new Point(0, 0), SourceControl.Bounds.Size);
}
else
{
ImageGraph.CopyFromScreen(SourceControl.PointToScreen(SourceControl.ClientRectangle.Location),
new Point(0, 0), SourceControl.Size);
}
}
if (ScaleToDpi) ImageGraph.ScaleTransform(ScaleFactor.Width, ScaleFactor.Height);
ImageGraph.DrawImage(bitmap, new Point(0, 0));
return (Bitmap)bitmap.Clone();
};
};
};
}
UPDATE1 (Example of Print Preview):
This is one possible way to show a PrintPreview of the Bitmap. It should of course be adapted to an actual Printer. This is for general use.
A Bitmap is created in a Button event handler and a PrintPreview Dialog is shown to seee the result.
This creates a ScreenShot of a PictureBox control in the current Form
(this), takes the ClientArea only, sets the resuluton to 300Dpi, does
not scale the image (keeps the screen original size), using a Bicubic
Interpolation for rendering.
Bitmap screenCapture = PrintControlFromScreen(this.pictureBox1, true, 300, false, InterpolationMode.Bicubic);
This is the Button click handler from where you can call the PrintControlFromScreen() method:
private void button1_Click(object sender, EventArgs e)
{
Bitmap screenCapture = PrintControlFromScreen(this.pictureBox1, true, 300, false, InterpolationMode.Bicubic);
PrintDocument PrintDoc = new PrintDocument();
PrintDoc.DocumentName = "ScreenShot";
PrintDoc.DefaultPageSettings.PrinterResolution = new PrinterResolution() { X = 300, Y = 300 };
PrintDoc.DefaultPageSettings.Landscape = PrintDoc.DefaultPageSettings.PaperSize.Width < screenCapture.Width;
PrintDoc.OriginAtMargins = true;
PrintDoc.PrintPage += (s, ppe) =>
{
Rectangle BitmapSize = new Rectangle(new Point(0, 0),
new Size(screenCapture.Width, screenCapture.Height));
Graphics _imagegraph = Graphics.FromImage(screenCapture);
_imagegraph.CompositingMode = CompositingMode.SourceCopy;
_imagegraph.CompositingQuality = CompositingQuality.HighQuality;
_imagegraph.SmoothingMode = SmoothingMode.HighQuality;
_imagegraph.PixelOffsetMode = PixelOffsetMode.HighQuality;
_imagegraph.InterpolationMode = InterpolationMode.HighQualityBicubic;
ImageAttributes ImageAttr = new ImageAttributes();
ImageAttr.ClearThreshold(ColorAdjustType.Bitmap);
ppe.Graphics.DrawImage(screenCapture, BitmapSize, 0F, 0F,
BitmapSize.Width, BitmapSize.Height, GraphicsUnit.Pixel, ImageAttr);
};
PrintPreviewDialog pPreviewDiag = new PrintPreviewDialog();
pPreviewDiag.Document = PrintDoc;
pPreviewDiag.AutoScaleDimensions = new SizeF(Screen.PrimaryScreen.BitsPerPixel * 1.5F,
Screen.PrimaryScreen.BitsPerPixel * 1.5F);
pPreviewDiag.AutoScaleMode = AutoScaleMode.Dpi;
pPreviewDiag.StartPosition = FormStartPosition.CenterScreen;
pPreviewDiag.ShowDialog();
}

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.

How to calculate FixedPage dimensions

This snippet is part of some code used to generate an XPS document. XPS document generation is no joke. I wish to avoid pasting any of that XPS code here if at all possible. Instead, this code focuses on the WPF portion of the problem.
The problem I am asking for you to help with is here. I have hard coded the dimensions to work for a test image:
double magicNumber_X = 3.5;//trial and error...3 too small 4 too big
fixedPage.Arrange(new Rect(new Point(magicNumber_X, 0), size));
Instead, how can I fix this code to calculate the coordinates?
Full Method:
private PageContent AddContentFromImage()
{
var pageContent = new PageContent();
var fixedPage = new FixedPage();
var bitmapImage = new BitmapImage(new Uri(hardCodedImageSampleFilePath, UriKind.RelativeOrAbsolute));
var image = new Image();
image.Source = bitmapImage;
fixedPage.Children.Add(image);
((IAddChild)pageContent).AddChild(fixedPage);
double pageWidth = 96 * 8.5;//XPS documents are 96 units per inch
double pageHeight = 96 * 11;
fixedPage.Width = pageWidth;
fixedPage.Height = pageHeight;
var size = new Size(8.5 * 96, 11 * 96);
fixedPage.Measure(size);
double magicNumber_X = 3.5;//trial and error...3 too small 4 too big
double magicNumber_Y = 0;
fixedPage.Arrange(new Rect(new Point(magicNumber_X, magicNumber_Y), size));
fixedPage.UpdateLayout();
return pageContent;
}
I'm a little surprised FixedPage.Measure(size) does not correct the issue by itself. I tried passing no params, e.g. fixedPage.Arrange(new Rect(), size)) still no go.
FWIW, this calculation worked fine when I was using PrintDocument.
private void pd_PrintPage(object sender, PrintPageEventArgs e)
{
Graphics g = e.Graphics;
RectangleF marginBounds = e.MarginBounds;
RectangleF printableArea = e.PageSettings.PrintableArea;
int availableWidth = (int)Math.Floor(printDocument.OriginAtMargins ? marginBounds.Width : (e.PageSettings.Landscape ? printableArea.Height : printableArea.Width));
int availableHeight = (int)Math.Floor(printDocument.OriginAtMargins ? marginBounds.Height : (e.PageSettings.Landscape ? printableArea.Width : printableArea.Height));
Rectangle rectangle = new Rectangle(0,0, availableWidth -1, availableHeight - 1);
g.DrawImage(_image, rectangle);
I hooked into FixedPage.Loaded event because FixedPage.ActualHeight is required in order to perform the calculation and will not be set until the control has loaded. This also means that with this mechanism FixedPage has to be displayed to correctly perform an automated print.
void fixedPage_Loaded(object sender, RoutedEventArgs e)
{
var fixedDocument = sender as FixedPage;
CalculateSize(fixedDocument);
}
private void CalculateSize(FixedPage fixedPage)
{
PrintQueue printQueue = LocalPrintServer.GetDefaultPrintQueue();
PrintCapabilities capabilities = printQueue.GetPrintCapabilities();
//get scale of the print wrt to screen of WPF visual
double scale = Math.Min(capabilities.PageImageableArea.ExtentWidth / fixedPage.ActualWidth, capabilities.PageImageableArea.ExtentHeight / fixedPage.ActualHeight);
//Transform the Visual to scale
fixedPage.LayoutTransform = new ScaleTransform(scale, scale);
//get the size of the printer page
var sz = new Size(capabilities.PageImageableArea.ExtentWidth, capabilities.PageImageableArea.ExtentHeight);
//update the layout of the visual to the printer page size.
fixedPage.Measure(sz);
double x = capabilities.PageImageableArea.OriginWidth;
double y = capabilities.PageImageableArea.OriginHeight;
fixedPage.Arrange(new Rect(new Point(x, y), sz));
fixedPage.UpdateLayout();
}

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