From 18cb4d24326e2b5d59b0d51483afaf5417d719fd Mon Sep 17 00:00:00 2001 From: the_fiddler Date: Mon, 24 Nov 2008 18:03:47 +0000 Subject: [PATCH] Added missing files from working copy. --- Source/Utilities/Graphics/AlphaTexture2D.cs | 201 +++++++++++++ .../Graphics/Text/GdiPlusGlyphRasterizer .cs | 268 ++++++++++++++++++ 2 files changed, 469 insertions(+) create mode 100644 Source/Utilities/Graphics/AlphaTexture2D.cs create mode 100644 Source/Utilities/Graphics/Text/GdiPlusGlyphRasterizer .cs diff --git a/Source/Utilities/Graphics/AlphaTexture2D.cs b/Source/Utilities/Graphics/AlphaTexture2D.cs new file mode 100644 index 00000000..d4db7676 --- /dev/null +++ b/Source/Utilities/Graphics/AlphaTexture2D.cs @@ -0,0 +1,201 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Drawing; +using System.Drawing.Imaging; + +namespace OpenTK.Graphics +{ + /// + /// Encapsulates an OpenGL texture. + /// + class AlphaTexture2D : IGraphicsResource + { + #region Fields + + GraphicsContext context; + int id; + int width, height; + bool disposed; + + #endregion + + #region Constructors + + /// + /// Constructs a new Texture. + /// + public AlphaTexture2D(int width, int height) + { + Width = width; + Height = height; + } + + #endregion + + #region IGraphicsResource Members + + GraphicsContext IGraphicsResource.Context { get { return context; } } + + int IGraphicsResource.Id + { + get + { + if (id == 0) + { + GraphicsContext.Assert(); + context = GraphicsContext.CurrentContext; + + id = GL.GenTexture(); + if (id == 0) + throw new GraphicsResourceException(String.Format("Texture creation failed, (Error: {0})", GL.GetError())); + + // Ensure the texture is allocated. + GL.BindTexture(TextureTarget.Texture2D, id); + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)All.Linear); + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)All.Linear); + if (GL.SupportsExtension("Version12")) + { + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int)All.ClampToEdge); + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int)All.ClampToEdge); + } + else + { + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int)All.Clamp); + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int)All.Clamp); + } + + GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Alpha, Width, Height, 0, + OpenTK.Graphics.PixelFormat.Rgba, PixelType.UnsignedByte, IntPtr.Zero); + } + + return id; + } + } + + #endregion + + #region Public Members + + #region public int Width + + /// Gets the width of the texture. + public int Width { get { return width; } private set { width = value; } } + + #endregion + + #region public int Height + + /// Gets the height of the texture. + public int Height { get { return height; } private set { height = value; } } + + #endregion + + #region WriteRegion + + public void WriteRegion(Rectangle source, Rectangle target, int mipLevel, Bitmap bitmap) + { + if (bitmap == null) + throw new ArgumentNullException("data"); + + GraphicsUnit unit = GraphicsUnit.Pixel; + + if (!bitmap.GetBounds(ref unit).Contains(source)) + throw new InvalidOperationException("The source Rectangle is larger than the Bitmap."); + + if (mipLevel < 0) + throw new ArgumentOutOfRangeException("mipLevel"); + + Bind(); + + BitmapData data = bitmap.LockBits(source, ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb); + + GL.PushClientAttrib(ClientAttribMask.ClientPixelStoreBit); + try + { + GL.PixelStore(PixelStoreParameter.UnpackAlignment, 1); + GL.PixelStore(PixelStoreParameter.UnpackRowLength, bitmap.Width); + GL.TexSubImage2D(TextureTarget.Texture2D, mipLevel, + target.Left, target.Top, + target.Width, target.Height, + OpenTK.Graphics.PixelFormat.Rgba, + PixelType.UnsignedByte, data.Scan0); + } + finally + { + GL.PopClientAttrib(); + } + + bitmap.UnlockBits(data); + } + + public void WriteRegion(TextureRegion2D region, int mipLevel) + { + if (mipLevel < 0) + throw new ArgumentOutOfRangeException("miplevel"); + + GL.TexSubImage2D(TextureTarget.Texture2D, mipLevel, + region.Rectangle.X, region.Rectangle.Y, + region.Rectangle.Width, region.Rectangle.Height, + PixelFormat.Bgra, PixelType.UnsignedByte, region); + } + + #endregion + + #region ReadRegion + + public TextureRegion2D ReadRegion(Rectangle rect, int mipLevel) + { + if (mipLevel < 0) + throw new ArgumentOutOfRangeException("miplevel"); + + TextureRegion2D region = new TextureRegion2D(rect); + + GL.GetTexImage(TextureTarget.Texture2D, mipLevel, PixelFormat.Bgra, PixelType.UnsignedByte, region.Data); + + return region; + } + + #endregion + + #region Bind + + public void Bind() + { + GL.BindTexture(TextureTarget.Texture2D, (this as IGraphicsResource).Id); + } + + #endregion + + #endregion + + #region IDisposable Members + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + void Dispose(bool manual) + { + if (!disposed) + { + if (manual) + { + GL.DeleteTexture(id); + } + disposed = true; + } + } + + ~AlphaTexture2D() + { + GraphicsContext context = (this as IGraphicsResource).Context; + if (context != null) + (context as IGraphicsContextInternal).RegisterForDisposal(this); + } + + #endregion + } +} diff --git a/Source/Utilities/Graphics/Text/GdiPlusGlyphRasterizer .cs b/Source/Utilities/Graphics/Text/GdiPlusGlyphRasterizer .cs new file mode 100644 index 00000000..92b13e6d --- /dev/null +++ b/Source/Utilities/Graphics/Text/GdiPlusGlyphRasterizer .cs @@ -0,0 +1,268 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Drawing; +using System.Drawing.Text; + +using OpenTK.Graphics.Text; +using OpenTK.Platform; +using System.Diagnostics; + +namespace OpenTK.Graphics.Text +{ + class GdiPlusGlyphRasterizer : IGlyphRasterizer + { + #region Fields + + Dictionary block_cache = new Dictionary(); + System.Drawing.Graphics graphics = System.Drawing.Graphics.FromImage(new Bitmap(1, 1)); + + IntPtr[] regions = new IntPtr[GdiPlus.MaxMeasurableCharacterRanges]; + CharacterRange[] characterRanges = new CharacterRange[GdiPlus.MaxMeasurableCharacterRanges]; + + TextExtents extents = new TextExtents(); + + // Check the constructor, too, for additional flags. + static readonly StringFormat default_string_format = StringFormat.GenericTypographic; + static readonly StringFormat load_glyph_string_format = StringFormat.GenericDefault; + + static readonly char[] newline_characters = new char[] { '\n', '\r' }; + + #endregion + + #region Constructors + + static GdiPlusGlyphRasterizer() + { + default_string_format.FormatFlags |= StringFormatFlags.MeasureTrailingSpaces; + } + + public GdiPlusGlyphRasterizer() + { } + + #endregion + + #region IGlyphRasterizer Members + + #region Rasterize + + public Bitmap Rasterize(Glyph glyph) + { + using (Bitmap bmp = new Bitmap((int)(2 * glyph.Font.Size), (int)(2 * glyph.Font.Size))) + using (System.Drawing.Graphics gfx = System.Drawing.Graphics.FromImage(bmp)) + { + // Small sizes look blurry without gridfitting, so turn that on. + if (glyph.Font.Size <= 18.0f) + gfx.TextRenderingHint = TextRenderingHint.AntiAliasGridFit; + else + gfx.TextRenderingHint = TextRenderingHint.AntiAlias; + + gfx.Clear(Color.Transparent); + gfx.DrawString(glyph.Character.ToString(), glyph.Font, Brushes.White, PointF.Empty); + + Rectangle tight_rect = FindEdges(bmp); + Bitmap tight_glyph = bmp.Clone(tight_rect, System.Drawing.Imaging.PixelFormat.Format32bppArgb); + //new Bitmap(tight_rect.Width, tight_rect.Height); + //return bmp.Clone(FindEdges(bmp), System.Drawing.Imaging.PixelFormat.Format32bppArgb); + return tight_glyph; + } + } + + #endregion + + #region MeasureText + + //public RectangleF MeasureText(TextBlock block) + //{ + // return MeasureText(block, ref extents); + //} + + public TextExtents MeasureText(TextBlock block) + { + // First, check if we have cached this text block. Do not use block_cache.TryGetValue, to avoid thrashing + // the user's TextBlockExtents struct. + 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 ((block.Options & TextPrinterOptions.NoCache) == 0) + block_cache.Add(block, new TextExtents(extents.BoundingBox, extents.GlyphExtents)); + + return extents; + } + + #endregion + + #region MeasureGlyph + + public RectangleF MeasureGlyph(Glyph glyph) + { + using (Bitmap bmp = Rasterize(glyph)) + { + return FindEdges(bmp); + } + } + + #endregion + + #endregion + + #region Private Members + + #region MeasureTextExtents + + void MeasureTextExtents(TextBlock block, ref TextExtents extents) + { + // Todo: Parse layout options: + StringFormat format = default_string_format; + + extents.Clear(); + + PointF origin = PointF.Empty; + SizeF size = SizeF.Empty; + + IntPtr native_graphics = GdiPlus.GetNativeGraphics(graphics); + IntPtr native_font = GdiPlus.GetNativeFont(block.Font); + IntPtr native_string_format = GdiPlus.GetNativeStringFormat(format); + + int height = 0; + + // It seems that the mere presence of \n and \r characters + // is enough for Mono to botch the layout (even if these + // characters are not processed.) We'll need to find a + // different way to perform layout on Mono, probably + // through Pango. + // Todo: This workaround allocates memory. + //if (Configuration.RunningOnMono) + { + string[] lines = block.Text.Replace("\r", String.Empty).Split('\n'); + foreach (string s in lines) + { + extents.AddRange(MeasureGlyphExtents( + s, height, 0, s.Length, block.LayoutRectangle, + native_graphics, native_font, native_string_format)); + height += block.Font.Height; + } + } + + extents.BoundingBox = new RectangleF(extents[0].X, extents[0].Y, extents[extents.Count - 1].Right, extents[extents.Count - 1].Bottom); + } + + #endregion + + #region MeasureGlyphExtents + + // Gets the bounds of each character in a line of text. + // The line is processed in blocks of 32 characters (GdiPlus.MaxMeasurableCharacterRanges). + IEnumerable MeasureGlyphExtents(string text, int height, int line_start, int line_length, + RectangleF layoutRect, IntPtr native_graphics, IntPtr native_font, IntPtr native_string_format) + { + RectangleF rect = new RectangleF(); + int line_end = line_start + line_length; + while (line_start < line_end) + { + //if (text[line_start] == '\n' || text[line_start] == '\r') + //{ + // line_start++; + // continue; + //} + + int num_characters = (line_end - line_start) > GdiPlus.MaxMeasurableCharacterRanges ? + GdiPlus.MaxMeasurableCharacterRanges : + line_end - line_start; + int status = 0; + + for (int i = 0; i < num_characters; i++) + { + characterRanges[i] = new CharacterRange(line_start + i, 1); + + IntPtr region; + status = GdiPlus.CreateRegion(out region); + regions[i] = region; + Debug.Assert(status == 0, String.Format("GDI+ error: {0}", status)); + } + + status = GdiPlus.SetStringFormatMeasurableCharacterRanges(native_string_format, num_characters, characterRanges); + Debug.Assert(status == 0, String.Format("GDI+ error: {0}", status)); + + status = GdiPlus.MeasureCharacterRanges(native_graphics, text, text.Length, + native_font, ref layoutRect, native_string_format, num_characters, regions); + Debug.Assert(status == 0, String.Format("GDI+ error: {0}", status)); + + for (int i = 0; i < num_characters; i++) + { + GdiPlus.GetRegionBounds(regions[i], native_graphics, ref rect); + Debug.Assert(status == 0, String.Format("GDI+ error: {0}", status)); + GdiPlus.DeleteRegion(regions[i]); + Debug.Assert(status == 0, String.Format("GDI+ error: {0}", status)); + + rect.Y += height; + + yield return rect; + } + + line_start += num_characters; + } + } + + #endregion + + #region FindEdges + + Rectangle FindEdges(Bitmap bmp) + { + return Rectangle.FromLTRB( + FindLeftEdge(bmp), + FindTopEdge(bmp), + FindRightEdge(bmp), + FindBottomEdge(bmp)); + } + + #endregion + + #region Find[Left|Right|Top|Bottom]Edge + + // Iterates through the bmp, and returns the first row or line that contains a non-transparent pixels. + + int FindLeftEdge(Bitmap bmp) + { + for (int x = 0; x < bmp.Width; x++) + for (int y = 0; y < bmp.Height; y++) + if (bmp.GetPixel(x, y).A != 0) + return x; + + return bmp.Width; + } + + int FindRightEdge(Bitmap bmp) + { + 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; + + return 0; + } + + int FindTopEdge(Bitmap bmp) + { + // Don't trim the top edge, because the layout engine expects it to be 0. + return 0; + } + + int FindBottomEdge(Bitmap bmp) + { + 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; + + return 0; + } + + #endregion + + #endregion + } +}