Added GL1.1 and GL1.2 rendering codepaths.

Added selectable TextQuality level.
Updated all interfaces to support the above.
This commit is contained in:
the_fiddler 2009-02-12 16:27:24 +00:00
parent 1502fc27b7
commit e7e5e1453f
13 changed files with 324 additions and 120 deletions

View file

@ -20,11 +20,11 @@ namespace OpenTK.Graphics
void Begin();
void End();
void Print(string text, Font font, Color color);
void Print(string text, Font font, Color color, RectangleF layoutRectangle);
void Print(string text, Font font, Color color, RectangleF layoutRectangle, TextPrinterOptions options);
void Print(string text, Font font, Color color, SizeF size);
void Print(string text, Font font, Color color, SizeF size, TextPrinterOptions options);
TextExtents Measure(string text, Font font);
TextExtents Measure(string text, Font font, RectangleF layoutRectangle);
TextExtents Measure(string text, Font font, RectangleF layoutRectangle, TextPrinterOptions options);
TextExtents Measure(string text, Font font, SizeF size);
TextExtents Measure(string text, Font font, SizeF size, TextPrinterOptions options);
[Obsolete("Use TextPrinter.Print instead")]
void Draw(TextHandle handle);

View file

@ -0,0 +1,55 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
namespace OpenTK.Graphics.Text
{
sealed class GL11TextOutputProvider : GL1TextOutputProvider
{
#region Fields
TextQuality quality;
GlyphCache cache;
#endregion
#region Constuctors
public GL11TextOutputProvider(TextQuality quality)
{
if (quality == TextQuality.High || quality == TextQuality.Default)
this.quality = TextQuality.Medium;
else
this.quality = quality;
cache = new GlyphCache<AlphaTexture2D>();
}
#endregion
#region Protected Members
protected override void SetBlendFunction()
{
GL.BlendFunc(BlendingFactorSrc.SrcAlpha, BlendingFactorDest.OneMinusSrcAlpha); // For grayscale
}
protected override void SetColor(Color color)
{
GL.Color4(color);
}
protected override TextQuality TextQuality
{
get { return quality; }
}
protected override GlyphCache Cache
{
get { return cache; }
}
#endregion
}
}

View file

@ -0,0 +1,48 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
namespace OpenTK.Graphics.Text
{
sealed class GL12TextOutputProvider : GL1TextOutputProvider
{
#region Fields
TextQuality quality;
GlyphCache cache;
#endregion
#region Constuctors
public GL12TextOutputProvider(TextQuality quality)
{
this.quality = quality;
cache = new GlyphCache<RgbaTexture2D>();
}
#endregion
protected override void SetBlendFunction()
{
GL.BlendFunc(BlendingFactorSrc.ConstantColorExt, BlendingFactorDest.OneMinusSrcColor); // For subpixel with color
}
protected override void SetColor(Color color)
{
GL.BlendColor(color);
}
protected override TextQuality TextQuality
{
get { return quality; }
}
protected override GlyphCache Cache
{
get { return cache; }
}
}
}

View file

@ -32,7 +32,7 @@ using System;
namespace OpenTK.Graphics.Text
{
class GL1TextOutputProvider : ITextOutputProvider
abstract class GL1TextOutputProvider : ITextOutputProvider
{
#region Fields
@ -57,22 +57,17 @@ namespace OpenTK.Graphics.Text
#region Print
public void Print(TextBlock block, PointF location, Color color, IGlyphRasterizer rasterizer, GlyphCache cache)
public void Print(TextBlock block, Color color, IGlyphRasterizer rasterizer)
{
GL.PushAttrib(AttribMask.TextureBit | AttribMask.EnableBit | AttribMask.ColorBufferBit | AttribMask.DepthBufferBit);
GL.Enable(EnableCap.Texture2D);
GL.Enable(EnableCap.Blend);
GL.BlendFunc(BlendingFactorSrc.ConstantColorExt, BlendingFactorDest.OneMinusSrcColor); // For subpixel with color
SetBlendFunction();
GL.Disable(EnableCap.DepthTest);
//GL.TexEnv(TextureEnvTarget.TextureEnv, TextureEnvParameter.TextureEnvMode, (int)TextureEnvMode.Modulate);
//GL.Enable(EnableCap.ColorMaterial);
//GL.BlendFunc(BlendingFactorSrc.SrcAlpha, BlendingFactorDest.OneMinusSrcAlpha); // For grayscale
//GL.BlendFunc(BlendingFactorSrc.One, BlendingFactorDest.OneMinusSrcColor); // For subpixel
using (TextExtents extents = rasterizer.MeasureText(block, location))
using (TextExtents extents = rasterizer.MeasureText(block))
{
// Build layout
int current = 0;
@ -83,10 +78,10 @@ namespace OpenTK.Graphics.Text
current++;
continue;
}
else if (!cache.Contains(glyph))
cache.Add(glyph);
else if (!Cache.Contains(glyph))
Cache.Add(glyph, rasterizer, TextQuality);
CachedGlyphInfo info = cache[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).
@ -123,12 +118,12 @@ namespace OpenTK.Graphics.Text
key.Bind();
GL.Translate(location.X, location.Y, 0);
if (!legacy_mode)
{
GL.Scale(2.0 / (viewport[2] - viewport[0]), -2.0 / (viewport[3] - viewport[1]), 1);
}
GL.BlendColor(color);
SetColor(color);
GL.Begin(BeginMode.Triangles);
@ -201,5 +196,29 @@ namespace OpenTK.Graphics.Text
#endregion
#endregion
#region Protected Members
protected abstract void SetBlendFunction();
protected abstract void SetColor(Color color);
protected abstract TextQuality TextQuality { get; }
protected abstract GlyphCache Cache { get; }
#endregion
#region Static Members
public static GL1TextOutputProvider Create(TextQuality quality)
{
if (!GL.SupportsFunction("BlendFunc") || quality == TextQuality.Low || quality == TextQuality.Medium)
return new GL11TextOutputProvider(quality);
else
return new GL12TextOutputProvider(quality);
}
#endregion
}
}

View file

@ -48,6 +48,8 @@ namespace OpenTK.Graphics.Text
Bitmap glyph_surface;
System.Drawing.Graphics glyph_renderer;
readonly List<RectangleF> measured_glyphs = new List<RectangleF>(256);
readonly ObjectPool<PoolableTextExtents> text_extents_pool = new ObjectPool<PoolableTextExtents>();
// Check the constructor, too, for additional flags.
@ -73,8 +75,7 @@ namespace OpenTK.Graphics.Text
}
}
public GdiPlusGlyphRasterizer()
{ }
public GdiPlusGlyphRasterizer() { }
#endregion
@ -84,15 +85,13 @@ namespace OpenTK.Graphics.Text
public Bitmap Rasterize(Glyph glyph)
{
//RectangleF r = MeasureText(
// new TextBlock(
// glyph.Character.ToString(), glyph.Font,
// TextPrinterOptions.NoCache, SizeF.Empty),
// PointF.Empty).BoundingBox;
return Rasterize(glyph, TextQuality.Default);
}
public Bitmap Rasterize(Glyph glyph, TextQuality quality)
{
EnsureSurfaceSize(ref glyph_surface, ref glyph_renderer, glyph.Font);
SetTextRenderingOptions(glyph_renderer, glyph.Font);
SetTextRenderingOptions(glyph_renderer, glyph.Font, quality);
glyph_renderer.Clear(Color.Transparent);
glyph_renderer.DrawString(glyph.Character.ToString(), glyph.Font, Brushes.White, PointF.Empty,
@ -103,32 +102,16 @@ namespace OpenTK.Graphics.Text
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.Clear(Color.FromArgb(255, 0, 0, 0));
//gfx.DrawString(glyph.Character.ToString(), glyph.Font, Brushes.White, PointF.Empty,
// glyph.Font.Style & FontStyle.Italic != 0 ? load_glyph_string_format : default_string_format);
System.Windows.Forms.TextRenderer.DrawText(gfx, glyph.Character.ToString(), glyph.Font, Point.Empty, Color.White);
//,
// (glyph.Font.Style & FontStyle.Italic) != 0 ?
// System.Windows.Forms.TextFormatFlags.GlyphOverhangPadding :
// System.Windows.Forms.TextFormatFlags.Default);
rect = FindEdges(bmp);
}
}
#endregion
#region MeasureText
public TextExtents MeasureText(TextBlock block, PointF location)
public TextExtents MeasureText(TextBlock block)
{
return MeasureText(block, TextQuality.Default);
}
public TextExtents MeasureText(TextBlock block, TextQuality quality)
{
// First, check if we have cached this text block. Do not use block_cache.TryGetValue, to avoid thrashing
// the user's TextBlockExtents struct.
@ -136,7 +119,7 @@ namespace OpenTK.Graphics.Text
return block_cache[block];
// If this block is not cached, we have to measure it and (potentially) place it in the cache.
TextExtents extents = MeasureTextExtents(block);
TextExtents extents = MeasureTextExtents(block, quality);
if ((block.Options & TextPrinterOptions.NoCache) == 0)
block_cache.Add(block, extents);
@ -171,21 +154,39 @@ namespace OpenTK.Graphics.Text
#region SetRenderingOptions
// Modify rendering settings (antialiasing, grid fitting) to improve appearance.
void SetTextRenderingOptions(System.Drawing.Graphics gfx, Font font)
void SetTextRenderingOptions(System.Drawing.Graphics gfx, Font font, TextQuality quality)
{
// Small sizes look blurry without gridfitting, so turn that on.
//if (font.Size <= 18.0f)
// gfx.TextRenderingHint = TextRenderingHint.ClearTypeGridFit;
//else
gfx.TextRenderingHint = TextRenderingHint.ClearTypeGridFit;
//gfx.TextRenderingHint = TextRenderingHint.AntiAliasGridFit;
switch (quality)
{
case TextQuality.Default:
gfx.TextRenderingHint = TextRenderingHint.SystemDefault;
break;
case TextQuality.High:
gfx.TextRenderingHint = TextRenderingHint.ClearTypeGridFit;
break;
case TextQuality.Medium:
if (font.Size <= 18.0f)
gfx.TextRenderingHint = TextRenderingHint.AntiAliasGridFit;
else
gfx.TextRenderingHint = TextRenderingHint.AntiAlias;
break;
case TextQuality.Low:
if (font.Size <= 18.0f)
gfx.TextRenderingHint = TextRenderingHint.SingleBitPerPixelGridFit;
else
gfx.TextRenderingHint = TextRenderingHint.SingleBitPerPixel;
break;
}
}
#endregion
#region MeasureTextExtents
TextExtents MeasureTextExtents(TextBlock block)
TextExtents MeasureTextExtents(TextBlock block, TextQuality quality)
{
// Todo: Parse layout options:
//StringFormat format = default_string_format;
@ -199,7 +200,7 @@ namespace OpenTK.Graphics.Text
if (block.Bounds == SizeF.Empty)
rect.Size = MaximumGraphicsClipSize;
SetTextRenderingOptions(graphics, block.Font);
SetTextRenderingOptions(graphics, block.Font, quality);
IntPtr native_graphics = GdiPlus.GetNativeGraphics(graphics);
IntPtr native_font = GdiPlus.GetNativeFont(block.Font);
@ -239,6 +240,8 @@ namespace OpenTK.Graphics.Text
IEnumerable<RectangleF> MeasureGlyphExtents(string text, int height,
RectangleF layoutRect, IntPtr native_graphics, IntPtr native_font, IntPtr native_string_format)
{
measured_glyphs.Clear();
RectangleF rect = new RectangleF();
int current = 0;
while (current < text.Length)
@ -277,11 +280,14 @@ namespace OpenTK.Graphics.Text
rect.Y += height;
yield return rect;
//yield return rect;
measured_glyphs.Add(rect);
}
current += num_characters;
}
return measured_glyphs;
}
#endregion

View file

@ -31,41 +31,54 @@ using System.Drawing;
namespace OpenTK.Graphics.Text
{
class GlyphCache : IGlyphCache
abstract class GlyphCache : IGlyphCache
{
#region IGlyphCache Members
public abstract void Add(Glyph glyph, IGlyphRasterizer rasterizer, TextQuality quality);
public abstract bool Contains(Glyph glyph);
public abstract CachedGlyphInfo this[Glyph glyph] { get; }
#endregion
}
class GlyphCache<T> : GlyphCache where T : Texture2D
{
#region Fields
IGlyphRasterizer rasterizer;
List<GlyphSheet> sheets = new List<GlyphSheet>();
List<GlyphSheet<T>> sheets = new List<GlyphSheet<T>>();
Bitmap bmp = new Bitmap(32, 32);
Dictionary<Glyph, CachedGlyphInfo> cached_glyphs = new Dictionary<Glyph, CachedGlyphInfo>();
const int SheetWidth = 512, SheetHeight = 512;
#endregion
#region Constructors
public GlyphCache(IGlyphRasterizer rasterizer)
public GlyphCache()
{
if (rasterizer == null)
throw new ArgumentNullException("rasterizer");
this.rasterizer = rasterizer;
sheets.Add(new GlyphSheet());
sheets.Add(new GlyphSheet<T>(SheetWidth, SheetHeight));
}
#endregion
#region IGlyphCache Members
public void Add(Glyph glyph)
public override void Add(Glyph glyph, IGlyphRasterizer rasterizer, TextQuality quality)
{
if (rasterizer == null)
throw new ArgumentNullException("rasterizer");
bool inserted = false;
using (Bitmap bmp = rasterizer.Rasterize(glyph))
using (Bitmap bmp = rasterizer.Rasterize(glyph, quality))
{
Rectangle rect = new Rectangle(0, 0, bmp.Width, bmp.Height);
foreach (GlyphSheet sheet in sheets)
foreach (GlyphSheet<T> sheet in sheets)
{
inserted = InsertGlyph(glyph, bmp, rect, sheet);
if (inserted)
@ -74,19 +87,19 @@ namespace OpenTK.Graphics.Text
if (!inserted)
{
GlyphSheet sheet = new GlyphSheet();
GlyphSheet<T> sheet = new GlyphSheet<T>(SheetWidth, SheetHeight);
sheets.Add(sheet);
InsertGlyph(glyph, bmp, rect, sheet);
}
}
}
public bool Contains(Glyph glyph)
public override bool Contains(Glyph glyph)
{
return cached_glyphs.ContainsKey(glyph);
}
public CachedGlyphInfo this[Glyph glyph]
public override CachedGlyphInfo this[Glyph glyph]
{
get
{
@ -99,7 +112,7 @@ namespace OpenTK.Graphics.Text
#region Private Members
// Asks the packer for an empty space and writes the glyph there.
bool InsertGlyph(Glyph glyph, Bitmap bmp, Rectangle source, GlyphSheet sheet)
bool InsertGlyph(Glyph glyph, Bitmap bmp, Rectangle source, GlyphSheet<T> sheet)
{
Rectangle target = new Rectangle();
if (!sheet.Packer.TryAdd(source, out target))

View file

@ -32,35 +32,35 @@ using System.Drawing;
namespace OpenTK.Graphics.Text
{
class GlyphSheet
class GlyphSheet<T> where T : Texture2D
{
#region Fields
//AlphaTexture2D texture;
RgbaTexture2D texture = new RgbaTexture2D(512, 512);
GlyphPacker packer = new GlyphPacker(512, 512);
readonly T texture;
readonly GlyphPacker packer;
#endregion
#region Constructors
public GlyphSheet()
{ }
public GlyphSheet(int width, int height)
{
texture = (T)typeof(T).GetConstructor(new Type[] { typeof(int), typeof(int) }).Invoke(new object[] { width, height });
packer = new GlyphPacker(width, height);
}
#endregion
#region Public Members
public Texture2D Texture
public T Texture
{
get { return texture; }
private set { texture = (RgbaTexture2D)value; }
}
public GlyphPacker Packer
{
get { return packer; }
private set { packer = value; }
}
#endregion

View file

@ -29,7 +29,7 @@ namespace OpenTK.Graphics.Text
{
interface IGlyphCache
{
void Add(Glyph glyph);
void Add(Glyph glyph, IGlyphRasterizer rasterizer, TextQuality quality);
bool Contains(Glyph glyph);
CachedGlyphInfo this[Glyph glyph] { get; }
}

View file

@ -37,7 +37,8 @@ namespace OpenTK.Graphics.Text
interface IGlyphRasterizer
{
Bitmap Rasterize(Glyph glyph);
TextExtents MeasureText(TextBlock block, PointF location);
void Rasterize(Glyph glyph, ref Bitmap bmp, out Rectangle rect);
Bitmap Rasterize(Glyph glyph, TextQuality quality);
TextExtents MeasureText(TextBlock block);
TextExtents MeasureText(TextBlock block, TextQuality quality);
}
}

View file

@ -34,7 +34,7 @@ namespace OpenTK.Graphics.Text
{
interface ITextOutputProvider
{
void Print(TextBlock block, PointF location, Color color, IGlyphRasterizer rasterizer, GlyphCache cache);
void Print(TextBlock block, Color color, IGlyphRasterizer rasterizer);
void Begin();
void End();
}

View file

@ -40,6 +40,8 @@ namespace OpenTK.Graphics.Text
protected RectangleF text_extents;
protected List<RectangleF> glyph_extents = new List<RectangleF>();
public static readonly TextExtents Empty = new TextExtents();
#endregion
#region Constructors

View file

@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace OpenTK.Graphics.Text
{
public enum TextQuality
{
Default = 0,
Low,
Medium,
High
}
}

View file

@ -30,9 +30,9 @@ namespace OpenTK.Graphics
{
#region Fields
GlyphCache glyph_cache;
IGlyphRasterizer glyph_rasterizer;
ITextOutputProvider text_output;
TextQuality text_quality;
#endregion
@ -42,21 +42,16 @@ namespace OpenTK.Graphics
/// Constructs a new TextPrinter object.
/// </summary>
public TextPrinter()
: this(null, null)
: this(null, null, TextQuality.Default) { }
public TextPrinter(TextQuality quality)
: this(null, null, quality) { }
TextPrinter(IGlyphRasterizer rasterizer, ITextOutputProvider output, TextQuality quality)
{
}
TextPrinter(IGlyphRasterizer rasterizer, ITextOutputProvider output/*, IGlyphCacheProvider, ITextOutputProvider */)
{
if (rasterizer == null)
rasterizer = new GdiPlusGlyphRasterizer();
if (output == null)
output = new GL1TextOutputProvider();
glyph_rasterizer = rasterizer;
glyph_cache = new GlyphCache(rasterizer);
text_output = output;
text_quality = quality;
}
#endregion
@ -71,7 +66,7 @@ namespace OpenTK.Graphics
[Obsolete]
public void Begin()
{
text_output.Begin();
TextOutput.Begin();
}
#endregion
@ -84,7 +79,7 @@ namespace OpenTK.Graphics
[Obsolete]
public void End()
{
text_output.End();
TextOutput.End();
}
#endregion
@ -93,25 +88,20 @@ namespace OpenTK.Graphics
public void Print(string text, Font font, Color color)
{
Print(text, font, color, RectangleF.Empty, TextPrinterOptions.Default);
Print(text, font, color, SizeF.Empty, TextPrinterOptions.Default);
}
public void Print(string text, Font font, Color color, RectangleF layoutRectangle)
public void Print(string text, Font font, Color color, SizeF size)
{
Print(text, font, color, layoutRectangle, TextPrinterOptions.Default);
Print(text, font, color, size, TextPrinterOptions.Default);
}
public void Print(string text, Font font, Color color, RectangleF layoutRectangle, TextPrinterOptions options)
public void Print(string text, Font font, Color color, SizeF size, TextPrinterOptions options)
{
if (String.IsNullOrEmpty(text))
if (!ValidateParameters(text, font, size))
return;
if (font == null)
throw new ArgumentNullException("font");
//text_output.Begin();
text_output.Print(new TextBlock(text, font, options, layoutRectangle.Size), layoutRectangle.Location, color, glyph_rasterizer, glyph_cache);
//text_output.End();
text_output.Print(new TextBlock(text, font, options, size), color, Rasterizer);
}
#endregion
@ -120,17 +110,30 @@ namespace OpenTK.Graphics
public TextExtents Measure(string text, Font font)
{
return Measure(text, font, RectangleF.Empty, TextPrinterOptions.Default);
return Measure(text, font, SizeF.Empty, TextPrinterOptions.Default);
}
public TextExtents Measure(string text, Font font, RectangleF layoutRectangle)
public TextExtents Measure(string text, Font font, SizeF size)
{
return Measure(text, font, layoutRectangle, TextPrinterOptions.Default);
return Measure(text, font, size, TextPrinterOptions.Default);
}
public TextExtents Measure(string text, Font font, RectangleF layoutRectangle, TextPrinterOptions options)
public TextExtents Measure(string text, Font font, SizeF size, TextPrinterOptions options)
{
return glyph_rasterizer.MeasureText(new TextBlock(text, font, options, layoutRectangle.Size), layoutRectangle.Location);
if (!ValidateParameters(text, font, size))
return TextExtents.Empty;
return Rasterizer.MeasureText(new TextBlock(text, font, options, size));
}
#endregion
#region Clear()
public void Clear()
{
//glyph_cache.Clear();
throw new NotImplementedException();
}
#endregion
@ -158,5 +161,48 @@ namespace OpenTK.Graphics
#endregion
#endregion
#region Private Members
IGlyphRasterizer Rasterizer
{
get
{
if (glyph_rasterizer == null)
glyph_rasterizer = new GdiPlusGlyphRasterizer();
return glyph_rasterizer;
}
}
ITextOutputProvider TextOutput
{
get
{
if (text_output == null)
text_output = GL1TextOutputProvider.Create(text_quality);
return text_output;
}
}
#endregion
#region Static Members
static bool ValidateParameters(string text, Font font, SizeF size)
{
if (String.IsNullOrEmpty(text))
return false;
if (font == null)
throw new ArgumentNullException("font");
if (size.Width < 0 || size.Height < 0)
throw new ArgumentOutOfRangeException("size");
return true;
}
#endregion
}
}