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!
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);
}
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);
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With