Content Pipeline:

- implemented FontDescriptionProcessor
- began implementing FontDescriptionImporter
This commit is contained in:
SND\eagleeyestudios_cp 2012-10-19 21:59:35 +00:00 committed by Konstantin Koch
parent 01cac2c358
commit 72ee0fd837
5 changed files with 322 additions and 7 deletions

View File

@ -106,6 +106,7 @@
<Compile Include="Graphics\VertexContent.cs" />
<Compile Include="IContentImporter.cs" />
<Compile Include="IContentProcessor.cs" />
<Compile Include="Importer\FontDescriptionImporter.cs" />
<Compile Include="Importer\TextureImporter.cs" />
<Compile Include="Importer\WavImporter.cs" />
<Compile Include="Importer\XmlImporter.cs" />

View File

@ -12,6 +12,7 @@ using System.Text;
namespace ANX.Framework.Content.Pipeline.Graphics
{
[Flags]
public enum FontDescriptionStyle
{
Bold,

View File

@ -0,0 +1,35 @@
using System;
using System.IO;
using System.Xml;
using ANX.Framework.Content.Pipeline.Graphics;
using ANX.Framework.NonXNA.Development;
namespace ANX.Framework.Content.Pipeline.Importer
{
/// <summary>Provides methods for reading .spritefont files for use in the Content Pipeline.</summary>
[PercentageComplete(20)]
[Developer("SilentWarrior/Eagle Eye Studios")]
[TestState(TestStateAttribute.TestState.Untested)]
[ContentImporter("Spritefont", ".spritefont", DefaultProcessor = "FontDescriptionProcessor")]
public class FontDescriptionImporter : ContentImporter<FontDescription>
{
/// <summary>Called by the Framework when importing a .spritefont file to be used as a game asset. This is the method called by the Framework when an asset is to be imported into an object that can be recognized by the Content Pipeline.</summary>
/// <param name="filename">Name of a game asset file.</param>
/// <param name="context">Contains information for importing a game asset, such as a logger interface.</param>
public override FontDescription Import(string filename, ContentImporterContext context)
{
FontDescription fontDescription = null;
using (XmlReader xmlReader = XmlReader.Create(filename))
{
fontDescription = Deserialize(xmlReader, filename);
}
fontDescription.Identity = new ContentIdentity(new FileInfo(filename).FullName, "FontDescriptionImporter");
return fontDescription;
}
private FontDescription Deserialize(XmlReader reader, string filename)
{
throw new NotImplementedException();
}
}
}

View File

@ -1,9 +1,15 @@
#region Using Statements
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Drawing.Text;
using System.Globalization;
using System.Linq;
using System.Text;
using ANX.Framework.Content.Pipeline.Graphics;
using ANX.Framework.NonXNA.Development;
#endregion
@ -13,13 +19,285 @@ using ANX.Framework.Content.Pipeline.Graphics;
namespace ANX.Framework.Content.Pipeline.Processors
{
[PercentageComplete(90)]
[Developer("SilentWarrior/Eagle Eye Studios")]
[TestState(TestStateAttribute.TestState.Untested)] //due to missing importer
[ContentProcessor]
public class FontDescriptionProcessor : ContentProcessor<FontDescription, SpriteFontContent>
{
public override SpriteFontContent Process(FontDescription input, ContentProcessorContext context)
{
if (input == null)
{
throw new ArgumentNullException("input");
}
var list = new List<char>(input.Characters);
if (input.DefaultCharacter.HasValue && !input.Characters.Contains(input.DefaultCharacter.Value))
{
list.Add(input.DefaultCharacter.Value);
}
list.Sort();
var spriteFontContent = new SpriteFontContent();
// Build up a list of all the glyphs to be output.
var bitmaps = new List<Bitmap>();
var xPositions = new List<int>();
var yPositions = new List<int>();
var globalBitmap = new Bitmap(1, 1, PixelFormat.Format32bppArgb);
System.Drawing.Graphics globalGraphics = System.Drawing.Graphics.FromImage(globalBitmap);
var font = new Font(input.FontName, input.Size, XnaFontStyleToGdiFontStyle(input.Style));
try
{
const int padding = 8;
int width = padding;
int height = padding;
int lineWidth = padding;
int lineHeight = padding;
int count = 0;
// Rasterize each character in turn,
// and add it to the output list.
foreach (char ch in input.Characters)
{
Bitmap bitmap = RasterizeCharacter(globalGraphics, ch, true, font);
bitmaps.Add(bitmap);
xPositions.Add(lineWidth);
yPositions.Add(height);
lineWidth += bitmap.Width + padding;
lineHeight = Math.Max(lineHeight, bitmap.Height + padding);
// Output 16 glyphs per line, then wrap to the next line.
if ((++count == 16) || (ch == input.Characters.Count - 1))
{
width = Math.Max(width, lineWidth);
height += lineHeight;
lineWidth = padding;
lineHeight = padding;
count = 0;
}
}
using (var bitmap = new Bitmap(width, height,
PixelFormat.Format32bppArgb))
{
// Arrage all the glyphs onto a single larger bitmap.
using (System.Drawing.Graphics graphics = System.Drawing.Graphics.FromImage(bitmap))
{
graphics.Clear(System.Drawing.Color.Magenta);
graphics.CompositingMode = CompositingMode.SourceCopy;
for (int i = 0; i < bitmaps.Count; i++)
{
spriteFontContent.Cropping.Add(new Rectangle(xPositions[i], yPositions[i], bitmaps[i].Width,
bitmaps[i].Height));
graphics.DrawImage(bitmaps[i], xPositions[i],
yPositions[i]);
}
graphics.Flush();
}
spriteFontContent.Texture = ConvertBitmap(bitmap);
spriteFontContent.CharacterMap = input.Characters.ToList();
spriteFontContent.DefaultCharacter = input.DefaultCharacter;
spriteFontContent.Glyphs = CreateOutputGlyphs();
for (int i = 0; i < input.Characters.Count; i++)
{
Vector3 value = spriteFontContent.Kerning[i];
if (!input.UseKerning)
{
value.Y = spriteFontContent.Cropping[i].Width;
}
if (!input.UseKerning)
{
value.X = 0f;
value.Z = 0f;
}
spriteFontContent.Kerning[i] = value;
}
spriteFontContent.LineSpacing = (int) Math.Ceiling(font.GetHeight());
spriteFontContent.Spacing = input.Spacing;
}
}
finally
{
// Clean up temporary objects.
foreach (Bitmap bitmap in bitmaps)
bitmap.Dispose();
}
return spriteFontContent;
}
#region RasterizeCharacter
/// <summary>
/// Helper for rendering out a single font character
/// into a System.Drawing bitmap.
/// </summary>
private static Bitmap RasterizeCharacter(System.Drawing.Graphics globalGraphics, char ch, bool antiAliasing,
Font font)
{
string text = ch.ToString(CultureInfo.InvariantCulture);
SizeF size = globalGraphics.MeasureString(text, font);
var width = (int) Math.Ceiling(size.Width);
var height = (int) Math.Ceiling(size.Height);
var bitmap = new Bitmap(width, height, PixelFormat.Format32bppArgb);
using (System.Drawing.Graphics graphics = System.Drawing.Graphics.FromImage(bitmap))
{
graphics.TextRenderingHint = antiAliasing
? TextRenderingHint.AntiAliasGridFit
: TextRenderingHint.SingleBitPerPixelGridFit;
graphics.Clear(System.Drawing.Color.Transparent);
using (Brush brush = new SolidBrush(System.Drawing.Color.White))
using (var format = new StringFormat())
{
format.Alignment = StringAlignment.Near;
format.LineAlignment = StringAlignment.Near;
graphics.DrawString(text, font, brush, 0, 0, format);
}
graphics.Flush();
}
return CropCharacter(bitmap);
}
#endregion
#region CropCharacter
/// <summary>
/// Helper for cropping ununsed space from the sides of a bitmap.
/// </summary>
private static Bitmap CropCharacter(Bitmap bitmap)
{
int cropLeft = 0;
int cropRight = bitmap.Width - 1;
// Remove unused space from the left.
while ((cropLeft < cropRight) && (BitmapEmpty(bitmap, cropLeft)))
cropLeft++;
// Remove unused space from the right.
while ((cropRight > cropLeft) && (BitmapEmpty(bitmap, cropRight)))
cropRight--;
// Don't crop if that would reduce the glyph down to nothing at all!
if (cropLeft == cropRight)
return bitmap;
// Add some padding back in.
cropLeft = Math.Max(cropLeft - 1, 0);
cropRight = Math.Min(cropRight + 1, bitmap.Width - 1);
int width = cropRight - cropLeft + 1;
// Crop the glyph.
var croppedBitmap = new Bitmap(width, bitmap.Height, bitmap.PixelFormat);
using (System.Drawing.Graphics graphics = System.Drawing.Graphics.FromImage(croppedBitmap))
{
graphics.CompositingMode = CompositingMode.SourceCopy;
graphics.DrawImage(bitmap, 0, 0,
new System.Drawing.Rectangle(cropLeft, 0, width, bitmap.Height),
GraphicsUnit.Pixel);
graphics.Flush();
}
bitmap.Dispose();
return croppedBitmap;
}
#endregion
#region CreateOutputGlyphs
private static List<Rectangle> CreateOutputGlyphs()
{
throw new NotImplementedException();
}
#endregion
#region ConvertBitmap
private Texture2DContent ConvertBitmap(Bitmap bitmap)
{
var bitmapContent = new PixelBitmapContent<Color>(bitmap.Width, bitmap.Height);
var destColor = new Color();
for (int x = bitmap.Width; x > 0; x--)
{
for (int y = bitmap.Height; y > 0; y--)
{
System.Drawing.Color sourceColor = bitmap.GetPixel(x, y);
destColor.R = sourceColor.R;
destColor.G = sourceColor.G;
destColor.B = sourceColor.B;
destColor.A = sourceColor.A;
bitmapContent.SetPixel(x, y, destColor);
}
}
var textureContent = new Texture2DContent();
textureContent.Faces[0] = new MipmapChain(bitmapContent);
return textureContent;
}
#endregion
#region FontDescriptionStyleConversion
private static FontStyle XnaFontStyleToGdiFontStyle(FontDescriptionStyle fontStyle)
{
var fontStyle2 = FontStyle.Regular;
if ((fontStyle & FontDescriptionStyle.Bold) == FontDescriptionStyle.Bold)
{
fontStyle2 |= FontStyle.Bold;
}
if ((fontStyle & FontDescriptionStyle.Italic) == FontDescriptionStyle.Italic)
{
fontStyle2 |= FontStyle.Italic;
}
return fontStyle2;
}
#endregion
#region BitmapEmpty
/// <summary>
/// Helper for testing whether a column of a bitmap is entirely empty.
/// </summary>
private static bool BitmapEmpty(Bitmap bitmap, int x)
{
for (int y = 0; y < bitmap.Height; y++)
{
if (bitmap.GetPixel(x, y).A != 0)
return false;
}
return true;
}
#endregion
}
}
}

View File

@ -11,16 +11,16 @@ namespace ANX.Framework.Content.Pipeline.Processors
public sealed class SpriteFontContent
{
[ContentSerializer(ElementName = "Texture", AllowNull = false)]
internal Texture2DContent Texture { get; private set; }
internal Texture2DContent Texture { get; set; }
[ContentSerializer(ElementName = "Glyphs", AllowNull = false)]
internal List<Rectangle> Glyphs { get; private set; }
internal List<Rectangle> Glyphs { get; set; }
[ContentSerializer(ElementName = "Cropping", AllowNull = false)]
internal List<Rectangle> Cropping { get; private set; }
internal List<Rectangle> Cropping { get; set; }
[ContentSerializer(ElementName = "CharacterMap", AllowNull = false)]
internal List<char> CharacterMap { get; private set; }
internal List<char> CharacterMap { get; set; }
[ContentSerializer(ElementName = "LineSpacing", AllowNull = false)]
internal int LineSpacing { get; set; }