// This code was written for the OpenTK library and has been released // to the Public Domain. // It is provided "as is" without express or implied warranty of any kind. using System; using System.Collections.Generic; using System.Drawing; using System.Text; using OpenTK; using OpenTK.Graphics; using OpenTK.Graphics.OpenGL; namespace Examples.Tutorial { [Example("Text Rendering (2D)", ExampleCategory.OpenGL, "1.x", Documentation = "TextRendering")] class TextRendering : GameWindow { TextRenderer renderer; Font serif = new Font(FontFamily.GenericSerif, 24); Font sans = new Font(FontFamily.GenericSansSerif, 24); Font mono = new Font(FontFamily.GenericMonospace, 24); /// /// Uses System.Drawing for 2d text rendering. /// public class TextRenderer : IDisposable { Bitmap bmp; Graphics gfx; int texture; Rectangle dirty_region; bool disposed; #region Constructors /// /// Constructs a new instance. /// /// The width of the backing store in pixels. /// The height of the backing store in pixels. public TextRenderer(int width, int height) { if (width <= 0) throw new ArgumentOutOfRangeException("width"); if (height <= 0) throw new ArgumentOutOfRangeException("height "); if (GraphicsContext.CurrentContext == null) throw new InvalidOperationException("No GraphicsContext is current on the calling thread."); bmp = new Bitmap(width, height, System.Drawing.Imaging.PixelFormat.Format32bppArgb); gfx = Graphics.FromImage(bmp); gfx.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias; texture = GL.GenTexture(); GL.BindTexture(TextureTarget.Texture2D, texture); GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear); GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear); GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba, width, height, 0, PixelFormat.Rgba, PixelType.UnsignedByte, IntPtr.Zero); } #endregion #region Public Members /// /// Clears the backing store to the specified color. /// /// A . public void Clear(Color color) { gfx.Clear(color); dirty_region = new Rectangle(0, 0, bmp.Width, bmp.Height); } /// /// Draws the specified string to the backing store. /// /// The to draw. /// The that will be used. /// The that will be used. /// The location of the text on the backing store, in 2d pixel coordinates. /// The origin (0, 0) lies at the top-left corner of the backing store. public void DrawString(string text, Font font, Brush brush, PointF point) { gfx.DrawString(text, font, brush, point); SizeF size = gfx.MeasureString(text, font); dirty_region = Rectangle.Round(RectangleF.Union(dirty_region, new RectangleF(point, size))); dirty_region = Rectangle.Intersect(dirty_region, new Rectangle(0, 0, bmp.Width, bmp.Height)); } /// /// Gets a that represents an OpenGL 2d texture handle. /// The texture contains a copy of the backing store. Bind this texture to TextureTarget.Texture2d /// in order to render the drawn text on screen. /// public int Texture { get { UploadBitmap(); return texture; } } #endregion #region Private Members // Uploads the dirty regions of the backing store to the OpenGL texture. void UploadBitmap() { if (dirty_region != RectangleF.Empty) { System.Drawing.Imaging.BitmapData data = bmp.LockBits(dirty_region, System.Drawing.Imaging.ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb); GL.BindTexture(TextureTarget.Texture2D, texture); GL.TexSubImage2D(TextureTarget.Texture2D, 0, dirty_region.X, dirty_region.Y, dirty_region.Width, dirty_region.Height, PixelFormat.Bgra, PixelType.UnsignedByte, data.Scan0); bmp.UnlockBits(data); dirty_region = Rectangle.Empty; } } #endregion #region IDisposable Members void Dispose(bool manual) { if (!disposed) { if (manual) { bmp.Dispose(); gfx.Dispose(); if (GraphicsContext.CurrentContext != null) GL.DeleteTexture(texture); } disposed = true; } } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } ~TextRenderer() { Console.WriteLine("[Warning] Resource leaked: {0}.", typeof(TextRenderer)); } #endregion } #region Constructor public TextRendering() : base(800, 600) { } #endregion #region OnLoad protected override void OnLoad(EventArgs e) { renderer = new TextRenderer(Width, Height); PointF position = PointF.Empty; renderer.Clear(Color.MidnightBlue); renderer.DrawString("The quick brown fox jumps over the lazy dog", serif, Brushes.White, position); position.Y += serif.Height; renderer.DrawString("The quick brown fox jumps over the lazy dog", sans, Brushes.White, position); position.Y += sans.Height; renderer.DrawString("The quick brown fox jumps over the lazy dog", mono, Brushes.White, position); position.Y += mono.Height; } #endregion #region OnUnload protected override void OnUnload(EventArgs e) { renderer.Dispose(); } #endregion #region OnResize protected override void OnResize(EventArgs e) { GL.Viewport(ClientRectangle); GL.MatrixMode(MatrixMode.Projection); GL.LoadIdentity(); GL.Ortho(-1.0, 1.0, -1.0, 1.0, 0.0, 4.0); } #endregion #region OnUpdateFrame protected override void OnUpdateFrame(FrameEventArgs e) { var keyboard = OpenTK.Input.Keyboard.GetState(); if (keyboard[OpenTK.Input.Key.Escape]) { this.Exit(); } } #endregion #region OnRenderFrame protected override void OnRenderFrame(FrameEventArgs e) { GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit); GL.MatrixMode(MatrixMode.Modelview); GL.LoadIdentity(); GL.Enable(EnableCap.Texture2D); GL.BindTexture(TextureTarget.Texture2D, renderer.Texture); GL.Begin(PrimitiveType.Quads); GL.TexCoord2(0.0f, 1.0f); GL.Vertex2(-1f, -1f); GL.TexCoord2(1.0f, 1.0f); GL.Vertex2(1f, -1f); GL.TexCoord2(1.0f, 0.0f); GL.Vertex2(1f, 1f); GL.TexCoord2(0.0f, 0.0f); GL.Vertex2(-1f, 1f); GL.End(); SwapBuffers(); } #endregion #region Main public static void Main() { using (TextRendering example = new TextRendering()) { Utilities.SetWindowTitle(example); example.Run(30.0); } } #endregion } }