Fixed TextExtents behavior when returning either cached or uncached instances.
Reduced memory pressure by adding object pooling to TextExtents.
This commit is contained in:
parent
a868c4b4e8
commit
c0549b11fa
7 changed files with 246 additions and 95 deletions
|
@ -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<Vector2>());
|
||||
{
|
||||
// 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<Vector2>());
|
||||
{
|
||||
// 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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<PoolableTextExtents> text_extents_pool = new ObjectPool<PoolableTextExtents>();
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
42
Source/Utilities/Graphics/Text/PoolableTextExtents.cs
Normal file
42
Source/Utilities/Graphics/Text/PoolableTextExtents.cs
Normal file
|
@ -0,0 +1,42 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace OpenTK.Graphics.Text
|
||||
{
|
||||
class PoolableTextExtents : TextExtents, IPoolable<PoolableTextExtents>
|
||||
{
|
||||
ObjectPool<PoolableTextExtents> owner;
|
||||
|
||||
#region Constructors
|
||||
|
||||
public PoolableTextExtents()
|
||||
{
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IPoolable<PoolableTextExtents> Members
|
||||
|
||||
ObjectPool<PoolableTextExtents> IPoolable<PoolableTextExtents>.Owner
|
||||
{
|
||||
get { return owner; }
|
||||
set { owner = value; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IPoolable Members
|
||||
|
||||
void IPoolable.OnAcquire()
|
||||
{
|
||||
Clear();
|
||||
}
|
||||
|
||||
void IPoolable.OnRelease()
|
||||
{
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
|
@ -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<RectangleF> glyph_extents;
|
||||
protected RectangleF text_extents;
|
||||
protected List<RectangleF> glyph_extents = new List<RectangleF>();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
public TextExtents(RectangleF bbox)
|
||||
: this(bbox, null)
|
||||
internal TextExtents()
|
||||
{
|
||||
}
|
||||
|
||||
public TextExtents(RectangleF bbox, IEnumerable<RectangleF> 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<RectangleF>)[i]; }
|
||||
internal set { (GlyphExtents as List<RectangleF>)[i] = value; }
|
||||
get { return glyph_extents[i]; }
|
||||
internal set { glyph_extents[i] = value; }
|
||||
}
|
||||
|
||||
public IEnumerable<RectangleF> GlyphExtents
|
||||
{
|
||||
get
|
||||
{
|
||||
if (glyph_extents == null)
|
||||
glyph_extents = new List<RectangleF>();
|
||||
return (IEnumerable<RectangleF>)glyph_extents;
|
||||
}
|
||||
}
|
||||
|
||||
public int Count
|
||||
{
|
||||
get { return (GlyphExtents as List<RectangleF>).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<RectangleF>).Add(glyphExtent);
|
||||
glyph_extents.Add(glyphExtent);
|
||||
}
|
||||
|
||||
internal void AddRange(IEnumerable<RectangleF> glyphExtents)
|
||||
{
|
||||
(GlyphExtents as List<RectangleF>).AddRange(glyphExtents);
|
||||
glyph_extents.AddRange(glyphExtents);
|
||||
}
|
||||
|
||||
internal void Clear()
|
||||
{
|
||||
BoundingBox = RectangleF.Empty;
|
||||
(GlyphExtents as List<RectangleF>).Clear();
|
||||
glyph_extents.Clear();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IDisposable Members
|
||||
|
||||
public virtual void Dispose()
|
||||
{
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
|
17
Source/Utilities/IPoolable.cs
Normal file
17
Source/Utilities/IPoolable.cs
Normal file
|
@ -0,0 +1,17 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace OpenTK
|
||||
{
|
||||
interface IPoolable : IDisposable
|
||||
{
|
||||
void OnAcquire();
|
||||
void OnRelease();
|
||||
}
|
||||
|
||||
interface IPoolable<T> : IPoolable where T : IPoolable<T>, new()
|
||||
{
|
||||
ObjectPool<T> Owner { get; set; }
|
||||
}
|
||||
}
|
42
Source/Utilities/ObjectPool.cs
Normal file
42
Source/Utilities/ObjectPool.cs
Normal file
|
@ -0,0 +1,42 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace OpenTK
|
||||
{
|
||||
class ObjectPool<T> where T : IPoolable<T>, new()
|
||||
{
|
||||
Queue<T> pool = new Queue<T>();
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue