#region ================== Copyright (c) 2007 Pascal vd Heiden /* * Copyright (c) 2007 Pascal vd Heiden, www.codeimp.com * This program is released under GNU General Public License * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * */ #endregion #region ================== Namespaces using System; using System.Drawing.Drawing2D; using System.Drawing.Imaging; using System.Drawing.Text; using System.IO; using System.Drawing; using SlimDX.Direct3D9; using SlimDX; using CodeImp.DoomBuilder.Geometry; using Font = System.Drawing.Font; #endregion namespace CodeImp.DoomBuilder.Rendering { public interface ITextLabel //mxd. Methods and properties required to render a textlabel { // Required to render text label bool SkipRendering { get; } Texture Texture { get; } VertexBuffer VertexBuffer { get; } // Access/setup Font Font { get; } string Text { get; set; } PixelColor Color { get; set; } PixelColor BackColor { get; set; } void Update(float translatex, float translatey, float scalex, float scaley); } public class TextLabel : IDisposable, ID3DResource, ITextLabel { #region ================== Constants #endregion #region ================== Variables // The text is stored as a polygon in a vertex buffer private VertexBuffer textbuffer; private Texture texture; private Font font; //mxd // Text settings private string text; private Vector2D location; //mxd private bool transformcoords; private PixelColor color; private PixelColor backcolor; private TextAlignmentX alignx; private TextAlignmentY aligny; private bool drawbg; //mxd //mxd. Label image settings... private SizeF textsize; private Size texturesize; private RectangleF textrect; private RectangleF bgrect; private PointF textorigin; // This keeps track if changes were made private bool updateneeded; private bool textureupdateneeded; //mxd private float lasttranslatex = float.MinValue; private float lasttranslatey; private float lastscalex; private float lastscaley; //mxd. Rendering private bool skiprendering; //mxd. Compatibility private float scale; // Disposing private bool isdisposed; // ano - static stuff to prevent often alloc/dealloc performance hits private static StringFormat strFormat; private static SolidBrush brush; private static Pen pen; #endregion #region ================== Properties // Properties public Vector2D Location { get { return location; } set { location = value; updateneeded = true; } } //mxd public string Text { get { return text; } set { if(text != value) { text = value; textsize = Size.Empty; textureupdateneeded = true; } } } public Font Font { get { return font; } set { font.Dispose(); font = value; textsize = Size.Empty; textureupdateneeded = true; } } //mxd public bool TransformCoords { get { return transformcoords; } set { transformcoords = value; updateneeded = true; } } public SizeF TextSize { get { if(textureupdateneeded) Update(General.Map.Renderer2D.TranslateX, General.Map.Renderer2D.TranslateY, General.Map.Renderer2D.Scale, -General.Map.Renderer2D.Scale); return textsize; } } public TextAlignmentX AlignX { get { return alignx; } set { alignx = value; updateneeded = true; } } public TextAlignmentY AlignY { get { return aligny; } set { aligny = value; updateneeded = true; } } public PixelColor Color { get { return color; } set { if(!color.Equals(value)) { color = value; textureupdateneeded = true; } } } public PixelColor BackColor { get { return backcolor; } set { if(!backcolor.Equals(value)) { backcolor = value; textureupdateneeded = true; } } } public bool DrawBackground { get { return drawbg; } set { if(drawbg != value) { drawbg = value; textureupdateneeded = true; } } } //mxd public Texture Texture { get { return texture; } } //mxd public VertexBuffer VertexBuffer { get { return textbuffer; } } public bool SkipRendering { get { return skiprendering; } } //mxd //mxd. Compatibility settings [Obsolete("Backcolor property is deprecated, please use BackColor property instead.")] public PixelColor Backcolor { get { return BackColor; } set { BackColor = value.WithAlpha(128); } } [Obsolete("Scale property is deprecated, please assign the font directly using Font property instead.")] public float Scale { get { return scale; } set { scale = value; font.Dispose(); font = new Font(new FontFamily(General.Settings.TextLabelFontName), (float)Math.Round(scale * 0.75f), (General.Settings.TextLabelFontBold ? FontStyle.Bold : FontStyle.Regular)); textsize = Size.Empty; textureupdateneeded = true; } } [Obsolete("Rectangle property is deprecated, please use Location property instead.")] public RectangleF Rectangle { get { return new RectangleF(location.x, location.y, 0f, 0f); } set { location = new Vector2D(value.X, value.Y); updateneeded = true; } } // Disposing public bool IsDisposed { get { return isdisposed; } } #endregion #region ================== Constructor / Disposer // Constructor public TextLabel() { // Initialize this.text = ""; this.font = new Font(new FontFamily(General.Settings.TextLabelFontName), General.Settings.TextLabelFontSize, (General.Settings.TextLabelFontBold ? FontStyle.Bold : FontStyle.Regular)); //General.Settings.TextLabelFont; //mxd this.location = new Vector2D(); //mxd this.color = new PixelColor(255, 255, 255, 255); this.backcolor = new PixelColor(128, 0, 0, 0); this.alignx = TextAlignmentX.Center; this.aligny = TextAlignmentY.Top; this.textsize = SizeF.Empty; //mxd this.texturesize = Size.Empty; //mxd this.updateneeded = true; this.textureupdateneeded = true; //mxd InitializeStatics(); // Register as resource General.Map.Graphics.RegisterResource(this); // We have no destructor GC.SuppressFinalize(this); } //mxd. Compatibility constructor... [Obsolete("TextLabel(int capacity) is deprecated, please use TextLabel() instead.")] public TextLabel(int unused) { // Initialize this.text = ""; this.font = new Font(new FontFamily(General.Settings.TextLabelFontName), General.Settings.TextLabelFontSize, (General.Settings.TextLabelFontBold ? FontStyle.Bold : FontStyle.Regular)); // General.Settings.TextLabelFont; this.location = new Vector2D(); this.color = new PixelColor(255, 255, 255, 255); this.backcolor = new PixelColor(128, 0, 0, 0); this.alignx = TextAlignmentX.Center; this.aligny = TextAlignmentY.Top; this.textsize = SizeF.Empty; this.texturesize = Size.Empty; this.updateneeded = true; this.textureupdateneeded = true; InitializeStatics(); // Register as resource General.Map.Graphics.RegisterResource(this); // We have no destructor GC.SuppressFinalize(this); } // Diposer public void Dispose() { // Not already disposed? if(!isdisposed) { // Clean up UnloadResource(); font.Dispose(); // Unregister resource General.Map.Graphics.UnregisterResource(this); // Done isdisposed = true; } } #endregion #region ================== Methods // ano - share resources instead of constantly alloc/dealloc public void InitializeStatics() { if (strFormat == null) { strFormat = new StringFormat(); strFormat.FormatFlags = StringFormatFlags.FitBlackBox | StringFormatFlags.NoWrap; strFormat.Alignment = StringAlignment.Center; strFormat.LineAlignment = StringAlignment.Center; } if (brush == null) { // if we actually see magenta, know we made a mistake somewhere brush = new SolidBrush(System.Drawing.Color.Magenta); } if (pen == null) { pen = new Pen(System.Drawing.Color.Magenta); } } // This updates the text if needed public void Update(float translatex, float translatey, float scalex, float scaley) { // Check if transformation changed and needs to be updated if(transformcoords && (translatex != lasttranslatex || translatey != lasttranslatey || scalex != lastscalex || scaley != lastscaley)) { lasttranslatex = translatex; //mxd lasttranslatey = translatey; //mxd lastscalex = scalex; //mxd lastscaley = scaley; //mxd updateneeded = true; } // Update if needed if(updateneeded || textureupdateneeded) { // Only build when there are any vertices if(text.Length > 0) { // Transform? Vector2D abspos = (transformcoords ? location.GetTransformed(translatex, translatey, scalex, scaley) : location); // Update text and texture sizes if(textsize.IsEmpty || texturesize.IsEmpty) { textorigin = new PointF(4, 3); textrect = new RectangleF(textorigin, General.Interface.MeasureString(text, font)); textrect.Width = (float)Math.Round(textrect.Width); textrect.Height = (float)Math.Round(textrect.Height); bgrect = new RectangleF(0, 0, textrect.Width + textorigin.X * 2, textrect.Height + textorigin.Y * 2); // Store calculated text size... textsize = new SizeF(textrect.Width + textorigin.X * 2, textrect.Height + textorigin.Y * 2); // Make PO2 image, for speed and giggles... texturesize = new Size(General.NextPowerOf2((int)textsize.Width), General.NextPowerOf2((int)textsize.Height)); switch(alignx) { case TextAlignmentX.Center: bgrect.X = (texturesize.Width - bgrect.Width) / 2; break; case TextAlignmentX.Right: bgrect.X = texturesize.Width - bgrect.Width; break; } switch(aligny) { case TextAlignmentY.Middle: bgrect.Y = (texturesize.Height - bgrect.Height) / 2; break; case TextAlignmentY.Bottom: bgrect.Y = texturesize.Height - bgrect.Height; break; } textrect.X += bgrect.X; textrect.Y += bgrect.Y; } // Align the text horizontally float beginx = 0; switch(alignx) { case TextAlignmentX.Left: beginx = abspos.x; break; case TextAlignmentX.Center: beginx = abspos.x - texturesize.Width * 0.5f; break; case TextAlignmentX.Right: beginx = abspos.x - texturesize.Width; break; } // Align the text vertically float beginy = 0; switch(aligny) { case TextAlignmentY.Top: beginy = abspos.y; break; case TextAlignmentY.Middle: beginy = abspos.y - texturesize.Height * 0.5f; break; case TextAlignmentY.Bottom: beginy = abspos.y - texturesize.Height; break; } //mxd. Skip when not on screen... RectangleF abssize = new RectangleF(beginx, beginy, texturesize.Width, texturesize.Height); Size windowsize = General.Map.Graphics.RenderTarget.ClientSize; skiprendering = (abssize.Right < 0.1f) || (abssize.Left > windowsize.Width) || (abssize.Bottom < 0.1f) || (abssize.Top > windowsize.Height); if(skiprendering) return; //mxd. Update texture if needed if(textureupdateneeded) { // Get rid of old texture if(texture != null) { texture.Dispose(); texture = null; } // Create label image Bitmap img = CreateLabelImage(text, font, color, backcolor, drawbg, textrect, bgrect, texturesize, textorigin); //texturesize = img.Size; // Create texture MemoryStream memstream = new MemoryStream((img.Size.Width * img.Size.Height * 4) + 4096); img.Save(memstream, ImageFormat.Bmp); memstream.Seek(0, SeekOrigin.Begin); texture = Texture.FromStream(General.Map.Graphics.Device, memstream, (int)memstream.Length, img.Size.Width, img.Size.Height, 1, Usage.None, Format.Unknown, Pool.Managed, General.Map.Graphics.PostFilter, General.Map.Graphics.MipGenerateFilter, 0); } //mxd. Create the buffer if(textbuffer == null || textbuffer.Disposed) { textbuffer = new VertexBuffer(General.Map.Graphics.Device, 4 * FlatVertex.Stride, Usage.Dynamic | Usage.WriteOnly, VertexFormat.None, Pool.Default); } //mxd. Lock the buffer using(DataStream stream = textbuffer.Lock(0, 4 * FlatVertex.Stride, LockFlags.Discard | LockFlags.NoSystemLock)) { FlatQuad quad = new FlatQuad(PrimitiveType.TriangleStrip, beginx, beginy, beginx + texturesize.Width, beginy + texturesize.Height); stream.WriteRange(quad.Vertices); } // Done filling the vertex buffer textbuffer.Unlock(); } else { // No faces in polygon textsize = SizeF.Empty; //mxd texturesize = Size.Empty; //mxd skiprendering = true; //mxd } // Text updated updateneeded = false; textureupdateneeded = false; //mxd } } //mxd private static Bitmap CreateLabelImage(string text, Font font, PixelColor color, PixelColor backcolor, bool drawbg, RectangleF textrect, RectangleF bgrect, Size texturesize, PointF textorigin) { Bitmap result = new Bitmap(texturesize.Width, texturesize.Height); using (Graphics g = Graphics.FromImage(result)) { g.SmoothingMode = SmoothingMode.HighQuality; g.TextRenderingHint = TextRenderingHint.AntiAliasGridFit; g.CompositingQuality = CompositingQuality.HighQuality; // Draw text with BG if (drawbg) { GraphicsPath p = new GraphicsPath(); float radius = textorigin.X; const float outlinewidth = 1; RectangleF pathrect = bgrect; pathrect.Width -= 1; pathrect.Height -= 1; // Left line p.AddLine(pathrect.Left, pathrect.Bottom - radius + outlinewidth, pathrect.Left, pathrect.Top + radius); p.AddArc(pathrect.Left, pathrect.Top, radius, radius, 180, 90); // Top line p.AddLine(pathrect.Left + radius, pathrect.Top, pathrect.Right - radius, pathrect.Top); p.AddArc(pathrect.Right - radius, pathrect.Top, radius, radius, 270, 90); // Right line p.AddLine(pathrect.Right, pathrect.Top + radius, pathrect.Right, pathrect.Bottom - radius); p.AddArc(pathrect.Right - radius, pathrect.Bottom - radius, radius, radius, 0, 90); // Bottom line p.AddLine(pathrect.Left + radius, pathrect.Bottom, pathrect.Left + radius, pathrect.Bottom); p.AddArc(pathrect.Left, pathrect.Bottom - radius, radius, radius, 90, 90); // Fill'n'draw bg brush.Color = color.ToColor(); g.FillPath(brush, p); pen.Color = backcolor.ToColor(); g.DrawPath(pen, p); // Draw text textrect.Inflate(4, 2); brush.Color = backcolor.ToColor(); g.DrawString(text, font, brush, textrect, strFormat); } // Draw plain text else { RectangleF plainbgrect = textrect; if (text.Length > 1) plainbgrect.Inflate(6, 2); RectangleF plaintextrect = textrect; plaintextrect.Inflate(6, 4); brush.Color = backcolor.ToColor(); g.FillRectangle(brush, plainbgrect); brush.Color = color.ToColor(); g.DrawString(text, font, brush, plaintextrect, strFormat); } } return result; } // This unloads the resources public void UnloadResource() { // Clean up if(textbuffer != null) { textbuffer.Dispose(); textbuffer = null; } if(texture != null) //mxd { texture.Dispose(); texture = null; } // Need to update before we can render updateneeded = true; textureupdateneeded = true; //mxd } // This (re)loads the resources public void ReloadResource() { } #endregion } }