4

I am generating images from specified text but there is a problem I'm facing: I cannot remove the top and bottom padding of the drawn Text inside the image I generate.

I tried to change string format while using Graphics.DrawString(), but I only managed to remove the left and right padding.

private void button1_Click(object sender, EventArgs e)
{
    Font font = new Font("Arial", 52, FontStyle.Regular);
    Image i = GetTextAsImage(textBox1.Text,400, font, Color.Black, Color.LightGray);
    i.Save("myImage.jpeg", ImageFormat.Jpeg);
}

private Image GetTextAsImage(String text, int widthInPixel, Font textFont, Color textColor, Color backColor)
{
    //first, create a dummy bitmap just to get a graphics object
    Image img = new Bitmap(1, 1);
    Graphics drawing = Graphics.FromImage(img);

    //measure the string to see how big the image needs to be
    SizeF textSize = drawing.MeasureString(text, textFont);

    //free up the dummy image and old graphics object
    img.Dispose();
    drawing.Dispose();

    //create a new image of the right size
    img = new Bitmap((int)textSize.Width, textFont.Height);

    drawing = Graphics.FromImage(img);
    drawing.SmoothingMode = SmoothingMode.AntiAlias;
    drawing.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;
    //paint the background
    drawing.Clear(backColor);
    //create a brush for the text
    Brush textBrush = new SolidBrush(textColor);
    drawing.DrawString(text, textFont, textBrush, 0, 0,StringFormat.GenericTypographic);

    drawing.Save();
    textBrush.Dispose();
    drawing.Dispose();
    return img;
}

This is the Output I get:

Output I get

This is the expected output:

This is the expected output

Jimi
  • 29,621
  • 8
  • 43
  • 61
Affan Sheikh
  • 113
  • 11
  • Try `TextRenderer.DrawText`.With this you can specify `TextFormatFlags.NoPadding` – preciousbetine Jan 26 '19 at 16:34
  • Do you want to remove padding or _all_ of the unused rows above and below the text, i.e. if the text consists only of "short" characters without descenders ("acemnorsuvwxz") do you want a really short image? – HABO Jan 26 '19 at 16:40
  • `TextRenderer.DrawText / MeasureText` seems to be preferred over `Graphics.DrawString / MeasureString`. According to the article [The wonders of text rendering and GDI](https://theartofdev.com/2013/08/12/the-wonders-of-text-rendering-and-gdi/), it gives better and faster results. It also explains how to remove padding. – Olivier Jacot-Descombes Jan 26 '19 at 16:49

1 Answers1

5

I propose you a slightly different method, using the GraphicsPath class to both measure and draw the text on a Bitmap object.

The advantage is that the GraphicsPath class reports the actual coordinates where the object(s) that it contains will be drawn and also the size of the text, in relation to a specific Font.
These measures are returned in a RectagleF structure, calling the GraphicsPath.GetBounds() method.
The base constructor assumes a Pen size of 1 pixel.

There's only one (small) detail to take care of: the GDI+ Bitmap object accepts dimensions expressed in integer values only, while all the other measures are expressed in floating point values.
We need to compensate for the rounding, but it's usually just ± 1 pixel.

Sample of the results:

GraphicsPath GetBounds text measure

A description of the procedure:

  • Define a Font Family and Size
  • Add the Text string to the GraphicsPath object
  • Get the GraphicsPath bounding rectangle of the text object
  • Build a Bitmap object using the bounding rectangle Size
  • Move the World coordinates, with Graphics.TranslateTransform, to the coordinates defined by the bounding rectangle Y position and the Pen Size, using their negative value: we need to move backwards that measure.
  • Draw the Text

See also these notes about GraphicsPath and Fonts:
Properly draw text using Graphics Path

Sample code:

using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Drawing.Text;


string text = "This is my Text";
Font font = new Font("Arial", 52, FontStyle.Regular, GraphicsUnit.Point);
float penSize = 1f;

using (var path = new GraphicsPath()) {
    path.AddString(text, font.FontFamily, (int)font.Style, font.Size, Point.Empty, StringFormat.GenericTypographic);

    RectangleF textBounds = path.GetBounds();

    using (var bitmap = new Bitmap((int)textBounds.Width, (int)textBounds.Height, PixelFormat.Format32bppArgb))
    using (var g = Graphics.FromImage(bitmap)) 
    using (var brush = new SolidBrush(Color.LightGreen)) {
        g.SmoothingMode = SmoothingMode.AntiAlias;
        // When rendering without a GraphicsPath object
        //g.TextRenderingHint = TextRenderingHint.ClearTypeGridFit;
        g.Clear(Color.Black);
        g.TranslateTransform(-(textBounds.X + penSize), -(textBounds.Y + penSize));
        g.FillPath(brush, path);
        
        bitmap.Save("[Image Path]", ImageFormat.Png);
        // Or: return (Bitmap)bitmap.Clone();
        // Or: return bitmap; declaring the bitmap without the using statement
    }
}
Jimi
  • 29,621
  • 8
  • 43
  • 61
  • This was so close to perfect for me. But try it with a single character, such as A. I see the bottom left is maybe cropped by about 4 pixels. But then try with the number 1. It fails very badly on that. As well as I. Can anyone see why this may be? – Craig Oct 12 '20 at 08:09
  • @Craig A single char is more difficult to measure (you usually need at least two): `path.GetBounds()` may return a different measure (without the right padding). I'll give it a look – Jimi Oct 12 '20 at 08:14
  • 1
    @Craig I slightly changed the transformation method, so it should ignore the drawing box's padded bounds in any case. Let me know if you find some other glitch (note that this is a *simplified* calculation method, the Font object should be treated more carefully). See [Properly draw text using Graphics Path](https://stackoverflow.com/a/53074638/7444103) for more info about this matter. – Jimi Oct 12 '20 at 09:50
  • Brilliant! I'll play with it, but it seems like you're a master! – Craig Oct 12 '20 at 23:20