diff --git a/Source/Utilities/Graphics/Text/GL1TextOutputProvider.cs b/Source/Utilities/Graphics/Text/GL1TextOutputProvider.cs index 501a13c7..c75dbe9e 100644 --- a/Source/Utilities/Graphics/Text/GL1TextOutputProvider.cs +++ b/Source/Utilities/Graphics/Text/GL1TextOutputProvider.cs @@ -57,63 +57,64 @@ namespace OpenTK.Graphics.Text public void Print(TextBlock block, IGlyphRasterizer rasterizer, GlyphCache cache) { - TextExtents extents = rasterizer.MeasureText(block); - - //GL.BindTexture(TextureTarget.Texture2D, 2); - - //GL.Begin(BeginMode.Quads); - - //GL.TexCoord2(0, 0); - //GL.Vertex2(0, 0); - //GL.TexCoord2(1, 0); - //GL.Vertex2(256, 0); - //GL.TexCoord2(1, 1); - //GL.Vertex2(256, 256); - //GL.TexCoord2(0, 1); - //GL.Vertex2(0, 256); - - //GL.End(); - - //GL.Translate(0, 256, 0); - - // Build layout - int current = 0; - foreach (Glyph glyph in block) + using (TextExtents extents = rasterizer.MeasureText(block)) { - if (glyph.IsWhiteSpace) + //GL.BindTexture(TextureTarget.Texture2D, 2); + + //GL.Begin(BeginMode.Quads); + + //GL.TexCoord2(0, 0); + //GL.Vertex2(0, 0); + //GL.TexCoord2(1, 0); + //GL.Vertex2(256, 0); + //GL.TexCoord2(1, 1); + //GL.Vertex2(256, 256); + //GL.TexCoord2(0, 1); + //GL.Vertex2(0, 256); + + //GL.End(); + + //GL.Translate(0, 256, 0); + + // Build layout + int current = 0; + foreach (Glyph glyph in block) { - current++; - continue; - } - else if (!cache.Contains(glyph)) - cache.Add(glyph); + if (glyph.IsWhiteSpace) + { + current++; + continue; + } + else if (!cache.Contains(glyph)) + cache.Add(glyph); - CachedGlyphInfo info = cache[glyph]; - RectangleF position = extents[current++]; - - // Use the real glyph width instead of the measured one (we want to achieve pixel perfect output). - position.Size = info.Rectangle.Size; + CachedGlyphInfo info = cache[glyph]; + RectangleF position = extents[current++]; - if (!active_lists.ContainsKey(info.Texture)) - if (inactive_lists.Count > 0) - active_lists.Add(info.Texture, inactive_lists.Dequeue()); - else - active_lists.Add(info.Texture, new List()); - { - // Interleaved array: Vertex, TexCoord, Vertex, ... - active_lists[info.Texture].Add(new Vector2(info.RectangleNormalized.Left, info.RectangleNormalized.Top)); - active_lists[info.Texture].Add(new Vector2(position.Left, position.Top)); - active_lists[info.Texture].Add(new Vector2(info.RectangleNormalized.Left, info.RectangleNormalized.Bottom)); - active_lists[info.Texture].Add(new Vector2(position.Left, position.Bottom)); - active_lists[info.Texture].Add(new Vector2(info.RectangleNormalized.Right, info.RectangleNormalized.Bottom)); - active_lists[info.Texture].Add(new Vector2(position.Right, position.Bottom)); + // Use the real glyph width instead of the measured one (we want to achieve pixel perfect output). + position.Size = info.Rectangle.Size; - active_lists[info.Texture].Add(new Vector2(info.RectangleNormalized.Right, info.RectangleNormalized.Bottom)); - active_lists[info.Texture].Add(new Vector2(position.Right, position.Bottom)); - active_lists[info.Texture].Add(new Vector2(info.RectangleNormalized.Right, info.RectangleNormalized.Top)); - active_lists[info.Texture].Add(new Vector2(position.Right, position.Top)); - active_lists[info.Texture].Add(new Vector2(info.RectangleNormalized.Left, info.RectangleNormalized.Top)); - active_lists[info.Texture].Add(new Vector2(position.Left, position.Top)); + if (!active_lists.ContainsKey(info.Texture)) + if (inactive_lists.Count > 0) + active_lists.Add(info.Texture, inactive_lists.Dequeue()); + else + active_lists.Add(info.Texture, new List()); + { + // Interleaved array: Vertex, TexCoord, Vertex, ... + active_lists[info.Texture].Add(new Vector2(info.RectangleNormalized.Left, info.RectangleNormalized.Top)); + active_lists[info.Texture].Add(new Vector2(position.Left, position.Top)); + active_lists[info.Texture].Add(new Vector2(info.RectangleNormalized.Left, info.RectangleNormalized.Bottom)); + active_lists[info.Texture].Add(new Vector2(position.Left, position.Bottom)); + active_lists[info.Texture].Add(new Vector2(info.RectangleNormalized.Right, info.RectangleNormalized.Bottom)); + active_lists[info.Texture].Add(new Vector2(position.Right, position.Bottom)); + + active_lists[info.Texture].Add(new Vector2(info.RectangleNormalized.Right, info.RectangleNormalized.Bottom)); + active_lists[info.Texture].Add(new Vector2(position.Right, position.Bottom)); + active_lists[info.Texture].Add(new Vector2(info.RectangleNormalized.Right, info.RectangleNormalized.Top)); + active_lists[info.Texture].Add(new Vector2(position.Right, position.Top)); + active_lists[info.Texture].Add(new Vector2(info.RectangleNormalized.Left, info.RectangleNormalized.Top)); + active_lists[info.Texture].Add(new Vector2(position.Left, position.Top)); + } } } diff --git a/Source/Utilities/Graphics/Text/GdiPlusGlyphRasterizer .cs b/Source/Utilities/Graphics/Text/GdiPlusGlyphRasterizer .cs index 8709bfc7..e1f722c9 100644 --- a/Source/Utilities/Graphics/Text/GdiPlusGlyphRasterizer .cs +++ b/Source/Utilities/Graphics/Text/GdiPlusGlyphRasterizer .cs @@ -34,6 +34,7 @@ using System.Drawing.Text; using OpenTK.Graphics.Text; using OpenTK.Platform; using System.Diagnostics; +using System.Drawing.Imaging; namespace OpenTK.Graphics.Text { @@ -47,11 +48,11 @@ namespace OpenTK.Graphics.Text IntPtr[] regions = new IntPtr[GdiPlus.MaxMeasurableCharacterRanges]; CharacterRange[] characterRanges = new CharacterRange[GdiPlus.MaxMeasurableCharacterRanges]; - TextExtents extents = new TextExtents(); - Bitmap glyph_surface; System.Drawing.Graphics glyph_renderer; + readonly ObjectPool text_extents_pool = new ObjectPool(); + // Check the constructor, too, for additional flags. static readonly StringFormat default_string_format = StringFormat.GenericTypographic; static readonly StringFormat load_glyph_string_format = StringFormat.GenericDefault; @@ -78,6 +79,8 @@ namespace OpenTK.Graphics.Text public Bitmap Rasterize(Glyph glyph) { + RectangleF r = MeasureText(new TextBlock(glyph.Character.ToString(), glyph.Font, TextPrinterOptions.NoCache, RectangleF.Empty)).BoundingBox; + EnsureSurfaceSize(ref glyph_surface, ref glyph_renderer, glyph.Font); SetTextRenderingOptions(glyph_renderer, glyph.Font); @@ -85,7 +88,25 @@ namespace OpenTK.Graphics.Text glyph_renderer.Clear(Color.Transparent); glyph_renderer.DrawString(glyph.Character.ToString(), glyph.Font, Brushes.White, PointF.Empty, glyph.Font.Style == FontStyle.Italic ? load_glyph_string_format : default_string_format); - return glyph_surface.Clone(FindEdges(glyph_surface), System.Drawing.Imaging.PixelFormat.Format32bppArgb); + + RectangleF r2 = FindEdges(glyph_surface); + + return glyph_surface.Clone(r2, System.Drawing.Imaging.PixelFormat.Format32bppArgb); + } + + public void Rasterize(Glyph glyph, ref Bitmap bmp, out Rectangle rect) + { + EnsureSurfaceSize(ref bmp, ref glyph_renderer, glyph.Font); + + using (System.Drawing.Graphics gfx = System.Drawing.Graphics.FromImage(bmp)) + { + SetTextRenderingOptions(gfx, glyph.Font); + + gfx.Clear(Color.Transparent); + gfx.DrawString(glyph.Character.ToString(), glyph.Font, Brushes.White, PointF.Empty, + glyph.Font.Style == FontStyle.Italic ? load_glyph_string_format : default_string_format); + rect = FindEdges(bmp); + } } #endregion @@ -99,10 +120,10 @@ namespace OpenTK.Graphics.Text if (block_cache.ContainsKey(block)) return block_cache[block]; - // If this block is not cached, we have to measure it and place it in the cache. - MeasureTextExtents(block, ref extents); + // If this block is not cached, we have to measure it and (potentially) place it in the cache. + TextExtents extents = MeasureTextExtents(block); if ((block.Options & TextPrinterOptions.NoCache) == 0) - block_cache.Add(block, new TextExtents(extents.BoundingBox, extents.GlyphExtents)); + block_cache.Add(block, extents); return extents; } @@ -147,7 +168,7 @@ namespace OpenTK.Graphics.Text #region MeasureTextExtents - void MeasureTextExtents(TextBlock block, ref TextExtents extents) + TextExtents MeasureTextExtents(TextBlock block) { // Todo: Parse layout options: StringFormat format = default_string_format; @@ -156,7 +177,7 @@ namespace OpenTK.Graphics.Text //else // format = default_string_format; - extents.Clear(); + TextExtents extents = text_extents_pool.Acquire(); PointF origin = PointF.Empty; SizeF size = SizeF.Empty; @@ -189,6 +210,8 @@ namespace OpenTK.Graphics.Text } extents.BoundingBox = new RectangleF(extents[0].X, extents[0].Y, extents[extents.Count - 1].Right, extents[extents.Count - 1].Bottom); + + return extents; } #endregion @@ -252,13 +275,28 @@ namespace OpenTK.Graphics.Text #region FindEdges + #pragma warning disable 0649 + + struct Pixel { public byte B, G, R, A; } + + #pragma warning restore 0649 + Rectangle FindEdges(Bitmap bmp) { - return Rectangle.FromLTRB( - FindLeftEdge(bmp), - FindTopEdge(bmp), - FindRightEdge(bmp), - FindBottomEdge(bmp)); + BitmapData data = bmp.LockBits( + new Rectangle(0, 0, bmp.Width, bmp.Height), + ImageLockMode.ReadOnly, + System.Drawing.Imaging.PixelFormat.Format32bppArgb); + + Rectangle rect = Rectangle.FromLTRB( + FindLeftEdge(bmp, data.Scan0), + FindTopEdge(bmp, data.Scan0), + FindRightEdge(bmp, data.Scan0), + FindBottomEdge(bmp, data.Scan0)); + + bmp.UnlockBits(data); + + return rect; } #endregion @@ -267,34 +305,40 @@ namespace OpenTK.Graphics.Text // Iterates through the bmp, and returns the first row or line that contains a non-transparent pixels. - int FindLeftEdge(Bitmap bmp) + int FindLeftEdge(Bitmap bmp, IntPtr ptr) { // Don't trim the left edge, because the layout engine expects it to be 0. return 0; } - int FindRightEdge(Bitmap bmp) + int FindRightEdge(Bitmap bmp, IntPtr ptr) { for (int x = bmp.Width - 1; x >= 0; x--) for (int y = 0; y < bmp.Height; y++) - if (bmp.GetPixel(x, y).A != 0) - return x + 1; + unsafe + { + if (((Pixel*)(ptr) + y * bmp.Width + x)->A != 0) + return x + 1; + } return 0; } - int FindTopEdge(Bitmap bmp) + int FindTopEdge(Bitmap bmp, IntPtr ptr) { // Don't trim the top edge, because the layout engine expects it to be 0. return 0; } - int FindBottomEdge(Bitmap bmp) + int FindBottomEdge(Bitmap bmp, IntPtr ptr) { for (int y = bmp.Height - 1; y >= 0; y--) for (int x = 0; x < bmp.Width; x++) - if (bmp.GetPixel(x, y).A != 0) - return y + 1; + unsafe + { + if (((Pixel*)(ptr) + y * bmp.Width + x)->A != 0) + return y + 1; + } return 0; } diff --git a/Source/Utilities/Graphics/Text/IGlyphRasterizer.cs b/Source/Utilities/Graphics/Text/IGlyphRasterizer.cs index dc568191..d07f7429 100644 --- a/Source/Utilities/Graphics/Text/IGlyphRasterizer.cs +++ b/Source/Utilities/Graphics/Text/IGlyphRasterizer.cs @@ -38,5 +38,6 @@ namespace OpenTK.Graphics.Text { Bitmap Rasterize(Glyph glyph); TextExtents MeasureText(TextBlock block); + void Rasterize(Glyph glyph, ref Bitmap bmp, out Rectangle rect); } } diff --git a/Source/Utilities/Graphics/Text/PoolableTextExtents.cs b/Source/Utilities/Graphics/Text/PoolableTextExtents.cs new file mode 100644 index 00000000..7b7c74a0 --- /dev/null +++ b/Source/Utilities/Graphics/Text/PoolableTextExtents.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace OpenTK.Graphics.Text +{ + class PoolableTextExtents : TextExtents, IPoolable + { + ObjectPool owner; + + #region Constructors + + public PoolableTextExtents() + { + } + + #endregion + + #region IPoolable Members + + ObjectPool IPoolable.Owner + { + get { return owner; } + set { owner = value; } + } + + #endregion + + #region IPoolable Members + + void IPoolable.OnAcquire() + { + Clear(); + } + + void IPoolable.OnRelease() + { + } + + #endregion + } +} diff --git a/Source/Utilities/Graphics/Text/TextExtents.cs b/Source/Utilities/Graphics/Text/TextExtents.cs index 3f646b4b..c8a241a7 100644 --- a/Source/Utilities/Graphics/Text/TextExtents.cs +++ b/Source/Utilities/Graphics/Text/TextExtents.cs @@ -33,31 +33,21 @@ using System.Drawing; namespace OpenTK.Graphics.Text { // Holds layout information about a TextBlock. - public struct TextExtents + public class TextExtents : IDisposable { #region Fields - RectangleF text_extents; - List glyph_extents; + protected RectangleF text_extents; + protected List glyph_extents = new List(); #endregion #region Constructors - public TextExtents(RectangleF bbox) - : this(bbox, null) + internal TextExtents() { } - public TextExtents(RectangleF bbox, IEnumerable glyphExtents) - : this() - { - BoundingBox = bbox; - - if (glyphExtents != null) - AddRange(glyphExtents); - } - #endregion #region Public Members @@ -70,23 +60,29 @@ namespace OpenTK.Graphics.Text public RectangleF this[int i] { - get { return (GlyphExtents as List)[i]; } - internal set { (GlyphExtents as List)[i] = value; } + get { return glyph_extents[i]; } + internal set { glyph_extents[i] = value; } } public IEnumerable GlyphExtents { get { - if (glyph_extents == null) - glyph_extents = new List(); return (IEnumerable)glyph_extents; } } public int Count { - get { return (GlyphExtents as List).Count; } + get { return glyph_extents.Count; } + } + + public TextExtents Clone() + { + TextExtents extents = new TextExtents(); + extents.glyph_extents.AddRange(GlyphExtents); + extents.BoundingBox = BoundingBox; + return extents; } #endregion @@ -95,18 +91,26 @@ namespace OpenTK.Graphics.Text internal void Add(RectangleF glyphExtent) { - (GlyphExtents as List).Add(glyphExtent); + glyph_extents.Add(glyphExtent); } internal void AddRange(IEnumerable glyphExtents) { - (GlyphExtents as List).AddRange(glyphExtents); + glyph_extents.AddRange(glyphExtents); } internal void Clear() { BoundingBox = RectangleF.Empty; - (GlyphExtents as List).Clear(); + glyph_extents.Clear(); + } + + #endregion + + #region IDisposable Members + + public virtual void Dispose() + { } #endregion diff --git a/Source/Utilities/IPoolable.cs b/Source/Utilities/IPoolable.cs new file mode 100644 index 00000000..68d83f36 --- /dev/null +++ b/Source/Utilities/IPoolable.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace OpenTK +{ + interface IPoolable : IDisposable + { + void OnAcquire(); + void OnRelease(); + } + + interface IPoolable : IPoolable where T : IPoolable, new() + { + ObjectPool Owner { get; set; } + } +} diff --git a/Source/Utilities/ObjectPool.cs b/Source/Utilities/ObjectPool.cs new file mode 100644 index 00000000..3ad38112 --- /dev/null +++ b/Source/Utilities/ObjectPool.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace OpenTK +{ + class ObjectPool where T : IPoolable, new() + { + Queue pool = new Queue(); + + public ObjectPool() + { } + + public T Acquire() + { + T item; + + if (pool.Count > 0) + { + item = pool.Dequeue(); + item.OnAcquire(); + } + else + { + item = new T(); + item.Owner = this; + item.OnAcquire(); + } + + return item; + } + + public void Release(T item) + { + if (item == null) + throw new ArgumentNullException("item"); + + item.OnRelease(); + pool.Enqueue(item); + } + } +}