如何使用System.Drawing绘制旋转的字符串图像?

7
我正在将字符串绘制到图像上。图像的大小是动态的,换句话说,图像的大小根据需要显示的字符串而定。为了实现这一点,我在使用Graphics.DrawString()渲染文本之前使用Graphics.MeasureString()测量大小。
这一切都很好,直到旋转出现问题。到目前为止,我将字符串绘制到位图上,并旋转整个位图。
问题在于,我只有非常有限的调色板和没有混合颜色。因此,我必须避免任何类型的抗锯齿,这只有通过使用InterpolationMode.NearestNeighbor来旋转文本位图才能实现。虽然这确保不会渲染出不需要的颜色,但结果确实非常丑陋(从用户的角度来看)。
我的想法是:应该可以通过使用Graphics.RotateTransform()旋转并避免剪切来将文本绘制到位图上,不是吗?
由于我必须首先定义要绘制的图像的大小,并且由于旋转会增加图像的大小,所以我不知道如何完成这项工作。
非常感谢您的帮助!

你想要旋转到任意角度(例如37.5度,118度等),还是只旋转到直角(90度,180度,270度)? - MusiGenesis
我不明白为什么你希望通过旋转文本来改善它的外观。这样做并不能解决缺乏抗锯齿支持的问题,因此你只能接受模糊的像素。 - Hans Passant
@Hans Passant:它是一个位,不像我希望的那样多,但它确实存在。通过使用RotateTransform,与使用InterpolationMode.NearestNeighbor旋转位图相比,它不会那么破烂。 - asp_net
抓住了,是的,那是个问题。 - Hans Passant
2个回答

4
这段代码可以给你一个想法:
    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);
    }

这里有一种使用GraphicsPath测量旋转文本边界的方法。逻辑很简单,GraphicsPath将文本转换为点列表,然后计算边界矩形。

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

测试代码:

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

嗨Habjan,感谢你的努力,但在绘制之前我不知道最终图像的大小(图像的大小始终与文本一样大)。字体、字号等参数是动态的。 - asp_net
@asp_net:我已经修改了两个方法(DrawText、GetRotatedTextBounds)。现在请看一下。 - HABJAN
谢谢Habjan!我今天早上已经解决了,见下面的答案。 - asp_net

2

作为可执行的MVC操作的解决方案:

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

    }

}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接