Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to draw a rotated string as an image with System.Drawing?

I am drawing strings to images. The size of the images is dynamic, or in other words, the images are as large as necessary to display the strings. To achieve that I am measuring the size with Graphics.MeasureString() before rendering the text with Graphics.DrawString().

That's all fine till the point where rotation comes into play. Until now I am drawing the string to the bitmap and rotate the whole bitmap I get as an result.

The problem is that I have a very limited color palette and no mixed colors. So I have to avoid any kind of antialiasing, which is only possible by using InterpolationMode.NearestNeighbor by rotating the text bitmap. While that makes sure no unwanted color is rendered the result is indeed very ugly (from the users point of view).

My thought: it should be possible to draw the text to the bitmap by rotating it with Graphics.RotateTransform() and avoiding clipping, isn't it?

Because I have to define the size of the image to draw on first, and because this size increases by rotating, I have no clue how to get this done.

Any help is very appreciated!

like image 1000
asp_net Avatar asked Apr 04 '11 20:04

asp_net


2 Answers

This code will give you an idea:

    public void DrawText(bool debug, Graphics g, string text, Font font, Brush brush, StringFormat format, float x, float y, float width, float height, float rotation)
    {
        float centerX = width / 2;
        float centerY = height / 2;

        if (debug)
        {
            g.FillEllipse(Brushes.Green, centerX - 5f, centerY - 5f, 10f, 10f);
        }

        GraphicsState gs = g.Save();

        Matrix mat = new Matrix();
        mat.RotateAt(rotation, new PointF(centerX, centerY), MatrixOrder.Append);

        g.Transform = mat;

        SizeF szf = g.MeasureString(text, font);

        g.DrawString(text, font, brush, (width / 2) - (szf.Width / 2), (height / 2) - (szf.Height / 2), format);

        g.Restore(gs);
    }

Here is a method to measure rotated text bounds by using GraphicsPath. Logic is simple, GraphicsPath converts text to point list and then it calculates bounds rectangle.

    public RectangleF GetRotatedTextBounds(string text, Font font, StringFormat format, float rotation, float dpiY)
    {
        GraphicsPath gp = new GraphicsPath();

        float emSize = dpiY * font.Size / 72;

        gp.AddString(text, font.FontFamily, (int)font.Style, emSize, new PointF(0, 0), format);

        Matrix mat = new Matrix();
        mat.Rotate(rotation, MatrixOrder.Append);

        gp.Transform(mat);

        return gp.GetBounds();
    }

Test code:

        float fontSize = 25f;
        float rotation = 30f;

        RectangleF txBounds = GetRotatedTextBounds("TEST TEXT", new Font("Verdana", fontSize, System.Drawing.FontStyle.Bold), StringFormat.GenericDefault, rotation, 96f);

        float inflateValue = 10 * (fontSize / 100f);

        txBounds.Inflate(inflateValue, inflateValue);

        Bitmap bmp = new System.Drawing.Bitmap((int)txBounds.Width, (int)txBounds.Height);
        using (Graphics gr = Graphics.FromImage(bmp))
        {
            gr.Clear(Color.White);
            DrawText(true, gr, "TEST TEXT", new Font("Verdana", fontSize, System.Drawing.FontStyle.Bold), Brushes.Red, new StringFormat(System.Drawing.StringFormatFlags.DisplayFormatControl), 0, 0, txBounds.Width, txBounds.Height, rotation);
        }
like image 189
HABJAN Avatar answered Dec 16 '22 09:12

HABJAN


My solution as an executable MVC action:

public class ImageController : Controller
{

    public ActionResult Test()
    {

        var text = DateTime.Now.ToString();
        var font = new Font("Arial", 20, FontStyle.Regular);
        var angle = 233;

        SizeF textSize = GetEvenTextImageSize(text, font);

        SizeF imageSize;

        if (angle == 0)
            imageSize = textSize;
        else
            imageSize = GetRotatedTextImageSize(textSize, angle);

        using (var canvas = new Bitmap((int)imageSize.Width, (int)imageSize.Height))
        {

            using(var graphics = Graphics.FromImage(canvas))
            {

                graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
                graphics.SmoothingMode = SmoothingMode.HighQuality;
                graphics.TextRenderingHint = TextRenderingHint.SingleBitPerPixelGridFit;

                SizeF textContainerSize = graphics.VisibleClipBounds.Size;
                graphics.TranslateTransform(textContainerSize.Width / 2, textContainerSize.Height / 2);
                graphics.RotateTransform(angle);

                graphics.DrawString(text, font, Brushes.Black, -(textSize.Width / 2), -(textSize.Height / 2));

            }

            var stream = new MemoryStream();
            canvas.Save(stream, ImageFormat.Png);
            stream.Seek(0, SeekOrigin.Begin);
            return new FileStreamResult(stream, "image/png");

        }

    }

    private static SizeF GetEvenTextImageSize(string text, Font font)
    {
        using (var image = new Bitmap(1, 1, PixelFormat.Format32bppArgb))
        {
            using (Graphics graphics = Graphics.FromImage(image))
            {
                return graphics.MeasureString(text, font);
            }
        }
    }

    private static SizeF GetRotatedTextImageSize(SizeF fontSize, int angle)
    {

        // Source: http://www.codeproject.com/KB/graphics/rotateimage.aspx

        double theta = angle * Math.PI / 180.0;

        while (theta < 0.0)
            theta += 2 * Math.PI;

        double adjacentTop, oppositeTop;
        double adjacentBottom, oppositeBottom;

        if ((theta >= 0.0 && theta < Math.PI / 2.0) || (theta >= Math.PI && theta < (Math.PI + (Math.PI / 2.0))))
        {
            adjacentTop = Math.Abs(Math.Cos(theta)) * fontSize.Width;
            oppositeTop = Math.Abs(Math.Sin(theta)) * fontSize.Width;
            adjacentBottom = Math.Abs(Math.Cos(theta)) * fontSize.Height;
            oppositeBottom = Math.Abs(Math.Sin(theta)) * fontSize.Height;
        }
        else
        {
            adjacentTop = Math.Abs(Math.Sin(theta)) * fontSize.Height;
            oppositeTop = Math.Abs(Math.Cos(theta)) * fontSize.Height;
            adjacentBottom = Math.Abs(Math.Sin(theta)) * fontSize.Width;
            oppositeBottom = Math.Abs(Math.Cos(theta)) * fontSize.Width;
        }

        int nWidth = (int)Math.Ceiling(adjacentTop + oppositeBottom);
        int nHeight = (int)Math.Ceiling(adjacentBottom + oppositeTop);

        return new SizeF(nWidth, nHeight);

    }

}
like image 34
asp_net Avatar answered Dec 16 '22 09:12

asp_net