template project, first version

This commit is contained in:
rachelchu
2017-10-11 15:01:05 +02:00
commit 8e902224cf
794 changed files with 326190 additions and 0 deletions

View File

@@ -0,0 +1,125 @@
// DirectXTK MakeSpriteFont tool
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// http://go.microsoft.com/fwlink/?LinkId=248929
using System;
using System.Linq;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
namespace MakeSpriteFont
{
// Extracts font glyphs from a specially marked 2D bitmap. Characters should be
// arranged in a grid ordered from top left to bottom right. Monochrome characters
// should use white for solid areas and black for transparent areas. To include
// multicolored characters, add an alpha channel to the bitmap and use that to
// control which parts of the character are solid. The spaces between characters
// and around the edges of the grid should be filled with bright pink (red=255,
// green=0, blue=255). It doesn't matter if your grid includes lots of wasted space,
// because the converter will rearrange characters, packing as tightly as possible.
public class BitmapImporter : IFontImporter
{
// Properties hold the imported font data.
public IEnumerable<Glyph> Glyphs { get; private set; }
public float LineSpacing { get; private set; }
public void Import(CommandLineOptions options)
{
// Load the source bitmap.
Bitmap bitmap;
try
{
bitmap = new Bitmap(options.SourceFont);
}
catch
{
throw new Exception(string.Format("Unable to load '{0}'.", options.SourceFont));
}
// Convert to our desired pixel format.
bitmap = BitmapUtils.ChangePixelFormat(bitmap, PixelFormat.Format32bppArgb);
// What characters are included in this font?
var characters = CharacterRegion.Flatten(options.CharacterRegions).ToArray();
int characterIndex = 0;
char currentCharacter = '\0';
// Split the source image into a list of individual glyphs.
var glyphList = new List<Glyph>();
Glyphs = glyphList;
LineSpacing = 0;
foreach (Rectangle rectangle in FindGlyphs(bitmap))
{
if (characterIndex < characters.Length)
currentCharacter = characters[characterIndex++];
else
currentCharacter++;
glyphList.Add(new Glyph(currentCharacter, bitmap, rectangle));
LineSpacing = Math.Max(LineSpacing, rectangle.Height);
}
// If the bitmap doesn't already have an alpha channel, create one now.
if (BitmapUtils.IsAlphaEntirely(255, bitmap))
{
BitmapUtils.ConvertGreyToAlpha(bitmap);
}
}
// Searches a 2D bitmap for characters that are surrounded by a marker pink color.
static IEnumerable<Rectangle> FindGlyphs(Bitmap bitmap)
{
using (var bitmapData = new BitmapUtils.PixelAccessor(bitmap, ImageLockMode.ReadOnly))
{
for (int y = 1; y < bitmap.Height; y++)
{
for (int x = 1; x < bitmap.Width; x++)
{
// Look for the top left corner of a character (a pixel that is not pink, but was pink immediately to the left and above it)
if (!IsMarkerColor(bitmapData[x, y]) &&
IsMarkerColor(bitmapData[x - 1, y]) &&
IsMarkerColor(bitmapData[x, y - 1]))
{
// Measure the size of this character.
int w = 1, h = 1;
while ((x + w < bitmap.Width) && !IsMarkerColor(bitmapData[x + w, y]))
{
w++;
}
while ((y + h < bitmap.Height) && !IsMarkerColor(bitmapData[x, y + h]))
{
h++;
}
yield return new Rectangle(x, y, w, h);
}
}
}
}
}
// Checks whether a color is the magic magenta marker value.
static bool IsMarkerColor(Color color)
{
return color.ToArgb() == Color.Magenta.ToArgb();
}
}
}

View File

@@ -0,0 +1,244 @@
// DirectXTK MakeSpriteFont tool
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// http://go.microsoft.com/fwlink/?LinkId=248929
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
namespace MakeSpriteFont
{
// Assorted helpers for doing useful things with bitmaps.
public static class BitmapUtils
{
// Copies a rectangular area from one bitmap to another.
public static void CopyRect(Bitmap source, Rectangle sourceRegion, Bitmap output, Rectangle outputRegion)
{
if (sourceRegion.Width != outputRegion.Width ||
sourceRegion.Height != outputRegion.Height)
{
throw new ArgumentException();
}
using (var sourceData = new PixelAccessor(source, ImageLockMode.ReadOnly, sourceRegion))
using (var outputData = new PixelAccessor(output, ImageLockMode.WriteOnly, outputRegion))
{
for (int y = 0; y < sourceRegion.Height; y++)
{
for (int x = 0; x < sourceRegion.Width; x++)
{
outputData[x, y] = sourceData[x, y];
}
}
}
}
// Checks whether an area of a bitmap contains entirely the specified alpha value.
public static bool IsAlphaEntirely(byte expectedAlpha, Bitmap bitmap, Rectangle? region = null)
{
using (var bitmapData = new PixelAccessor(bitmap, ImageLockMode.ReadOnly, region))
{
for (int y = 0; y < bitmapData.Region.Height; y++)
{
for (int x = 0; x < bitmapData.Region.Width; x++)
{
byte alpha = bitmapData[x, y].A;
if (alpha != expectedAlpha)
return false;
}
}
}
return true;
}
// Checks whether a bitmap contains entirely the specified RGB value.
public static bool IsRgbEntirely(Color expectedRgb, Bitmap bitmap)
{
using (var bitmapData = new PixelAccessor(bitmap, ImageLockMode.ReadOnly))
{
for (int y = 0; y < bitmap.Height; y++)
{
for (int x = 0; x < bitmap.Width; x++)
{
Color color = bitmapData[x, y];
if (color.A == 0)
continue;
if ((color.R != expectedRgb.R) ||
(color.G != expectedRgb.G) ||
(color.B != expectedRgb.B))
{
return false;
}
}
}
}
return true;
}
// Converts greyscale luminosity to alpha data.
public static void ConvertGreyToAlpha(Bitmap bitmap)
{
using (var bitmapData = new PixelAccessor(bitmap, ImageLockMode.ReadWrite))
{
for (int y = 0; y < bitmap.Height; y++)
{
for (int x = 0; x < bitmap.Width; x++)
{
Color color = bitmapData[x, y];
// Average the red, green and blue values to compute brightness.
int alpha = (color.R + color.G + color.B) / 3;
bitmapData[x, y] = Color.FromArgb(alpha, 255, 255, 255);
}
}
}
}
// Converts a bitmap to premultiplied alpha format.
public static void PremultiplyAlpha(Bitmap bitmap)
{
using (var bitmapData = new PixelAccessor(bitmap, ImageLockMode.ReadWrite))
{
for (int y = 0; y < bitmap.Height; y++)
{
for (int x = 0; x < bitmap.Width; x++)
{
Color color = bitmapData[x, y];
int a = color.A;
int r = color.R * a / 255;
int g = color.G * a / 255;
int b = color.B * a / 255;
bitmapData[x, y] = Color.FromArgb(a, r, g, b);
}
}
}
}
// To avoid filtering artifacts when scaling or rotating fonts that do not use premultiplied alpha,
// make sure the one pixel border around each glyph contains the same RGB values as the edge of the
// glyph itself, but with zero alpha. This processing is an elaborate no-op when using premultiplied
// alpha, because the premultiply conversion will change the RGB of all such zero alpha pixels to black.
public static void PadBorderPixels(Bitmap bitmap, Rectangle region)
{
using (var bitmapData = new PixelAccessor(bitmap, ImageLockMode.ReadWrite))
{
// Pad the top and bottom.
for (int x = region.Left; x < region.Right; x++)
{
CopyBorderPixel(bitmapData, x, region.Top, x, region.Top - 1);
CopyBorderPixel(bitmapData, x, region.Bottom - 1, x, region.Bottom);
}
// Pad the left and right.
for (int y = region.Top; y < region.Bottom; y++)
{
CopyBorderPixel(bitmapData, region.Left, y, region.Left - 1, y);
CopyBorderPixel(bitmapData, region.Right - 1, y, region.Right, y);
}
// Pad the four corners.
CopyBorderPixel(bitmapData, region.Left, region.Top, region.Left - 1, region.Top - 1);
CopyBorderPixel(bitmapData, region.Right - 1, region.Top, region.Right, region.Top - 1);
CopyBorderPixel(bitmapData, region.Left, region.Bottom - 1, region.Left - 1, region.Bottom);
CopyBorderPixel(bitmapData, region.Right - 1, region.Bottom - 1, region.Right, region.Bottom);
}
}
// Copies a single pixel within a bitmap, preserving RGB but forcing alpha to zero.
static void CopyBorderPixel(PixelAccessor bitmapData, int sourceX, int sourceY, int destX, int destY)
{
Color color = bitmapData[sourceX, sourceY];
bitmapData[destX, destY] = Color.FromArgb(0, color);
}
// Converts a bitmap to the specified pixel format.
public static Bitmap ChangePixelFormat(Bitmap bitmap, PixelFormat format)
{
Rectangle bounds = new Rectangle(0, 0, bitmap.Width, bitmap.Height);
return bitmap.Clone(bounds, format);
}
// Helper for locking a bitmap and efficiently reading or writing its pixels.
public sealed class PixelAccessor : IDisposable
{
// Constructor locks the bitmap.
public PixelAccessor(Bitmap bitmap, ImageLockMode mode, Rectangle? region = null)
{
this.bitmap = bitmap;
this.Region = region.GetValueOrDefault(new Rectangle(0, 0, bitmap.Width, bitmap.Height));
this.data = bitmap.LockBits(Region, mode, PixelFormat.Format32bppArgb);
}
// Dispose unlocks the bitmap.
public void Dispose()
{
if (data != null)
{
bitmap.UnlockBits(data);
data = null;
}
}
// Query what part of the bitmap is locked.
public Rectangle Region { get; private set; }
// Get or set a pixel value.
public Color this[int x, int y]
{
get
{
return Color.FromArgb(Marshal.ReadInt32(PixelAddress(x, y)));
}
set
{
Marshal.WriteInt32(PixelAddress(x, y), value.ToArgb());
}
}
// Helper computes the address of the specified pixel.
IntPtr PixelAddress(int x, int y)
{
return data.Scan0 + (y * data.Stride) + (x * sizeof(int));
}
// Fields.
Bitmap bitmap;
BitmapData data;
}
}
}

View File

@@ -0,0 +1,137 @@
// DirectXTK MakeSpriteFont tool
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// http://go.microsoft.com/fwlink/?LinkId=248929
using System;
using System.Linq;
using System.ComponentModel;
using System.Globalization;
using System.Collections.Generic;
namespace MakeSpriteFont
{
// Describes a range of consecutive characters that should be included in the font.
[TypeConverter(typeof(CharacterRegionTypeConverter))]
public class CharacterRegion
{
// Constructor.
public CharacterRegion(char start, char end)
{
if (start > end)
throw new ArgumentException();
this.Start = start;
this.End = end;
}
// Fields.
public char Start;
public char End;
// Enumerates all characters within the region.
public IEnumerable<Char> Characters
{
get
{
for (char c = Start; c <= End; c++)
{
yield return c;
}
}
}
// Flattens a list of character regions into a combined list of individual characters.
public static IEnumerable<Char> Flatten(IEnumerable<CharacterRegion> regions)
{
if (regions.Any())
{
// If we have any regions, flatten them and remove duplicates.
return regions.SelectMany(region => region.Characters).Distinct();
}
else
{
// If no regions were specified, use the default.
return defaultRegion.Characters;
}
}
// Default to just the base ASCII character set.
static CharacterRegion defaultRegion = new CharacterRegion(' ', '~');
}
// Custom type converter enables CommandLineParser to parse CharacterRegion command line options.
public class CharacterRegionTypeConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
return sourceType == typeof(string);
}
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
// Input must be a string.
string source = value as string;
if (string.IsNullOrEmpty(source))
{
throw new ArgumentException();
}
// Supported input formats:
// A
// A-Z
// 32-127
// 0x20-0x7F
char[] split = source.Split('-')
.Select(ConvertCharacter)
.ToArray();
switch (split.Length)
{
case 1:
// Only a single character (eg. "a").
return new CharacterRegion(split[0], split[0]);
case 2:
// Range of characters (eg. "a-z").
return new CharacterRegion(split[0], split[1]);
default:
throw new ArgumentException();
}
}
static char ConvertCharacter(string value)
{
if (value.Length == 1)
{
// Single character directly specifies a codepoint.
return value[0];
}
else
{
// Otherwise it must be an integer (eg. "32" or "0x20").
return (char)(int)intConverter.ConvertFromInvariantString(value);
}
}
static TypeConverter intConverter = TypeDescriptor.GetConverter(typeof(int));
}
}

View File

@@ -0,0 +1,99 @@
// DirectXTK MakeSpriteFont tool
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// http://go.microsoft.com/fwlink/?LinkId=248929
using System.Collections.Generic;
using System.Drawing;
namespace MakeSpriteFont
{
// Available output texture formats.
public enum TextureFormat
{
Auto,
Rgba32,
Bgra4444,
CompressedMono,
}
// Feature levels
public enum FeatureLevel
{
FL9_1,
FL9_2,
FL9_3,
FL10_0,
FL10_1,
FL11_0,
FL11_1,
FL12_0,
FL12_1,
}
// Options telling the tool what to do.
public class CommandLineOptions
{
// Input can be either a system (TrueType) font or a specially marked bitmap file.
[CommandLineParser.Required]
public string SourceFont;
// Output spritefont binary.
[CommandLineParser.Required]
public string OutputFile;
// Which characters to include in the font (eg. "/CharacterRegion:0x20-0x7F /CharacterRegion:0x123")
[CommandLineParser.Name("CharacterRegion")]
public readonly List<CharacterRegion> CharacterRegions = new List<CharacterRegion>();
// Fallback character used when asked to render a codepoint that is not
// included in the font. If zero, missing characters throw exceptions.
public readonly int DefaultCharacter = 0;
// Size and style for TrueType fonts (ignored when converting a bitmap font).
public float FontSize = 23;
public FontStyle FontStyle = FontStyle.Regular;
// Spacing overrides. Zero is default spacing, negative closer together, positive further apart.
public float LineSpacing = 0;
public float CharacterSpacing = 0;
// Use smooth or sharp antialiasing mode for TrueType rasterization?
public bool Sharp = false;
// What format should the output texture be?
public TextureFormat TextureFormat = TextureFormat.Auto;
// By default, font textures use premultiplied alpha format. Set this if you want interpolative alpha instead.
public bool NoPremultiply = false;
// Dumps the generated sprite texture to a bitmap file (useful for debugging).
public string DebugOutputSpriteSheet = null;
// Controls texture-size based warnings
public FeatureLevel FeatureLevel = FeatureLevel.FL9_1;
// For large fonts, the default tightest pack is too slow
public bool FastPack = false;
}
}

View File

@@ -0,0 +1,253 @@
// DirectXTK MakeSpriteFont tool
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// http://go.microsoft.com/fwlink/?LinkId=248929
using System;
using System.IO;
using System.Linq;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.ComponentModel;
namespace MakeSpriteFont
{
// Reusable, reflection based helper for parsing commandline options.
public class CommandLineParser
{
object optionsObject;
Queue<FieldInfo> requiredOptions = new Queue<FieldInfo>();
Dictionary<string, FieldInfo> optionalOptions = new Dictionary<string, FieldInfo>();
List<string> requiredUsageHelp = new List<string>();
List<string> optionalUsageHelp = new List<string>();
// Constructor.
public CommandLineParser(object optionsObject)
{
this.optionsObject = optionsObject;
// Reflect to find what commandline options are available.
foreach (FieldInfo field in optionsObject.GetType().GetFields())
{
string fieldName = GetOptionName(field);
if (GetAttribute<RequiredAttribute>(field) != null)
{
// Record a required option.
requiredOptions.Enqueue(field);
requiredUsageHelp.Add(string.Format("<{0}>", fieldName));
}
else
{
// Record an optional option.
optionalOptions.Add(fieldName.ToLowerInvariant(), field);
if (field.FieldType == typeof(bool))
{
optionalUsageHelp.Add(string.Format("/{0}", fieldName));
}
else
{
optionalUsageHelp.Add(string.Format("/{0}:value", fieldName));
}
}
}
}
public bool ParseCommandLine(string[] args)
{
// Parse each argument in turn.
foreach (string arg in args)
{
if (!ParseArgument(arg.Trim()))
{
return false;
}
}
// Make sure we got all the required options.
FieldInfo missingRequiredOption = requiredOptions.FirstOrDefault(field => !IsList(field) || GetList(field).Count == 0);
if (missingRequiredOption != null)
{
ShowError("Missing argument '{0}'", GetOptionName(missingRequiredOption));
return false;
}
return true;
}
bool ParseArgument(string arg)
{
if (arg.StartsWith("/"))
{
// Parse an optional argument.
char[] separators = { ':' };
string[] split = arg.Substring(1).Split(separators, 2, StringSplitOptions.None);
string name = split[0];
string value = (split.Length > 1) ? split[1] : "true";
FieldInfo field;
if (!optionalOptions.TryGetValue(name.ToLowerInvariant(), out field))
{
ShowError("Unknown option '{0}'", name);
return false;
}
return SetOption(field, value);
}
else
{
// Parse a required argument.
if (requiredOptions.Count == 0)
{
ShowError("Too many arguments");
return false;
}
FieldInfo field = requiredOptions.Peek();
if (!IsList(field))
{
requiredOptions.Dequeue();
}
return SetOption(field, arg);
}
}
bool SetOption(FieldInfo field, string value)
{
try
{
if (IsList(field))
{
// Append this value to a list of options.
GetList(field).Add(ChangeType(value, ListElementType(field)));
}
else
{
// Set the value of a single option.
field.SetValue(optionsObject, ChangeType(value, field.FieldType));
}
return true;
}
catch
{
ShowError("Invalid value '{0}' for option '{1}'", value, GetOptionName(field));
return false;
}
}
static object ChangeType(string value, Type type)
{
TypeConverter converter = TypeDescriptor.GetConverter(type);
return converter.ConvertFromInvariantString(value);
}
static bool IsList(FieldInfo field)
{
return typeof(IList).IsAssignableFrom(field.FieldType);
}
IList GetList(FieldInfo field)
{
return (IList)field.GetValue(optionsObject);
}
static Type ListElementType(FieldInfo field)
{
var interfaces = from i in field.FieldType.GetInterfaces()
where i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IEnumerable<>)
select i;
return interfaces.First().GetGenericArguments()[0];
}
static string GetOptionName(FieldInfo field)
{
var nameAttribute = GetAttribute<NameAttribute>(field);
if (nameAttribute != null)
{
return nameAttribute.Name;
}
else
{
return field.Name;
}
}
void ShowError(string message, params object[] args)
{
string name = Path.GetFileNameWithoutExtension(Process.GetCurrentProcess().ProcessName);
Console.Error.WriteLine(message, args);
Console.Error.WriteLine();
Console.Error.WriteLine("Usage: {0} {1}", name, string.Join(" ", requiredUsageHelp));
if (optionalUsageHelp.Count > 0)
{
Console.Error.WriteLine();
Console.Error.WriteLine("Options:");
foreach (string optional in optionalUsageHelp)
{
Console.Error.WriteLine(" {0}", optional);
}
}
}
static T GetAttribute<T>(ICustomAttributeProvider provider) where T : Attribute
{
return provider.GetCustomAttributes(typeof(T), false).OfType<T>().FirstOrDefault();
}
// Used on optionsObject fields to indicate which options are required.
[AttributeUsage(AttributeTargets.Field)]
public sealed class RequiredAttribute : Attribute
{
}
// Used on an optionsObject field to rename the corresponding commandline option.
[AttributeUsage(AttributeTargets.Field)]
public sealed class NameAttribute : Attribute
{
public NameAttribute(string name)
{
this.Name = name;
}
public string Name { get; private set; }
}
}
}

View File

@@ -0,0 +1,43 @@
// DirectXTK MakeSpriteFont tool
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// http://go.microsoft.com/fwlink/?LinkId=248929
using System.Drawing;
namespace MakeSpriteFont
{
// Represents a single character within a font.
public class Glyph
{
// Constructor.
public Glyph(char character, Bitmap bitmap, Rectangle? subrect = null)
{
this.Character = character;
this.Bitmap = bitmap;
this.Subrect = subrect.GetValueOrDefault(new Rectangle(0, 0, bitmap.Width, bitmap.Height));
}
// Unicode codepoint.
public char Character;
// Glyph image data (may only use a portion of a larger bitmap).
public Bitmap Bitmap;
public Rectangle Subrect;
// Layout information.
public float XOffset;
public float YOffset;
public float XAdvance;
}
}

View File

@@ -0,0 +1,54 @@
// DirectXTK MakeSpriteFont tool
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// http://go.microsoft.com/fwlink/?LinkId=248929
using System.Drawing;
namespace MakeSpriteFont
{
// Crops unused space from around the edge of a glyph bitmap.
public static class GlyphCropper
{
public static void Crop(Glyph glyph)
{
// Crop the top.
while ((glyph.Subrect.Height > 1) && BitmapUtils.IsAlphaEntirely(0, glyph.Bitmap, new Rectangle(glyph.Subrect.X, glyph.Subrect.Y, glyph.Subrect.Width, 1)))
{
glyph.Subrect.Y++;
glyph.Subrect.Height--;
glyph.YOffset++;
}
// Crop the bottom.
while ((glyph.Subrect.Height > 1) && BitmapUtils.IsAlphaEntirely(0, glyph.Bitmap, new Rectangle(glyph.Subrect.X, glyph.Subrect.Bottom - 1, glyph.Subrect.Width, 1)))
{
glyph.Subrect.Height--;
}
// Crop the left.
while ((glyph.Subrect.Width > 1) && BitmapUtils.IsAlphaEntirely(0, glyph.Bitmap, new Rectangle(glyph.Subrect.X, glyph.Subrect.Y, 1, glyph.Subrect.Height)))
{
glyph.Subrect.X++;
glyph.Subrect.Width--;
glyph.XOffset++;
}
// Crop the right.
while ((glyph.Subrect.Width > 1) && BitmapUtils.IsAlphaEntirely(0, glyph.Bitmap, new Rectangle(glyph.Subrect.Right - 1, glyph.Subrect.Y, 1, glyph.Subrect.Height)))
{
glyph.Subrect.Width--;
glyph.XAdvance++;
}
}
}
}

View File

@@ -0,0 +1,285 @@
// DirectXTK MakeSpriteFont tool
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// http://go.microsoft.com/fwlink/?LinkId=248929
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
namespace MakeSpriteFont
{
// Helper for arranging many small bitmaps onto a single larger surface.
public static class GlyphPacker
{
public static Bitmap ArrangeGlyphsFast(Glyph[] sourceGlyphs)
{
// Build up a list of all the glyphs needing to be arranged.
List<ArrangedGlyph> glyphs = new List<ArrangedGlyph>();
int largestWidth = 1;
int largestHeight = 1;
for (int i = 0; i < sourceGlyphs.Length; i++)
{
ArrangedGlyph glyph = new ArrangedGlyph();
glyph.Source = sourceGlyphs[i];
// Leave a one pixel border around every glyph in the output bitmap.
glyph.Width = sourceGlyphs[i].Subrect.Width + 2;
glyph.Height = sourceGlyphs[i].Subrect.Height + 2;
if (glyph.Width > largestWidth)
largestWidth = glyph.Width;
if (glyph.Height > largestHeight)
largestHeight = glyph.Height;
glyphs.Add(glyph);
}
// Work out how big the output bitmap should be.
int outputWidth = GuessOutputWidth(sourceGlyphs);
// Place each glyph in a grid based on the largest glyph size
int curx = 0;
int cury = 0;
for (int i = 0; i < glyphs.Count; i++)
{
glyphs[i].X = curx;
glyphs[i].Y = cury;
curx += largestWidth;
if (curx + largestWidth > outputWidth)
{
curx = 0;
cury += largestHeight;
}
}
// Create the merged output bitmap.
int outputHeight = MakeValidTextureSize(cury + largestHeight, false);
return CopyGlyphsToOutput(glyphs, outputWidth, outputHeight);
}
public static Bitmap ArrangeGlyphs(Glyph[] sourceGlyphs)
{
// Build up a list of all the glyphs needing to be arranged.
List<ArrangedGlyph> glyphs = new List<ArrangedGlyph>();
for (int i = 0; i < sourceGlyphs.Length; i++)
{
ArrangedGlyph glyph = new ArrangedGlyph();
glyph.Source = sourceGlyphs[i];
// Leave a one pixel border around every glyph in the output bitmap.
glyph.Width = sourceGlyphs[i].Subrect.Width + 2;
glyph.Height = sourceGlyphs[i].Subrect.Height + 2;
glyphs.Add(glyph);
}
// Sort so the largest glyphs get arranged first.
glyphs.Sort(CompareGlyphSizes);
// Work out how big the output bitmap should be.
int outputWidth = GuessOutputWidth(sourceGlyphs);
int outputHeight = 0;
// Choose positions for each glyph, one at a time.
for (int i = 0; i < glyphs.Count; i++)
{
if (i > 0 && (i % 500) == 0)
{
Console.Write(".");
}
PositionGlyph(glyphs, i, outputWidth);
outputHeight = Math.Max(outputHeight, glyphs[i].Y + glyphs[i].Height);
}
if (glyphs.Count >= 500)
{
Console.WriteLine();
}
// Create the merged output bitmap.
outputHeight = MakeValidTextureSize(outputHeight, false);
return CopyGlyphsToOutput(glyphs, outputWidth, outputHeight);
}
// Once arranging is complete, copies each glyph to its chosen position in the single larger output bitmap.
static Bitmap CopyGlyphsToOutput(List<ArrangedGlyph> glyphs, int width, int height)
{
Bitmap output = new Bitmap(width, height, PixelFormat.Format32bppArgb);
int usedPixels = 0;
foreach (ArrangedGlyph glyph in glyphs)
{
Glyph sourceGlyph = glyph.Source;
Rectangle sourceRegion = sourceGlyph.Subrect;
Rectangle destinationRegion = new Rectangle(glyph.X + 1, glyph.Y + 1, sourceRegion.Width, sourceRegion.Height);
BitmapUtils.CopyRect(sourceGlyph.Bitmap, sourceRegion, output, destinationRegion);
BitmapUtils.PadBorderPixels(output, destinationRegion);
sourceGlyph.Bitmap = output;
sourceGlyph.Subrect = destinationRegion;
usedPixels += (glyph.Width * glyph.Height);
}
float utilization = ( (float)usedPixels / (float)(width * height) ) * 100;
Console.WriteLine("Packing efficiency {0}%", utilization );
return output;
}
// Internal helper class keeps track of a glyph while it is being arranged.
class ArrangedGlyph
{
public Glyph Source;
public int X;
public int Y;
public int Width;
public int Height;
}
// Works out where to position a single glyph.
static void PositionGlyph(List<ArrangedGlyph> glyphs, int index, int outputWidth)
{
int x = 0;
int y = 0;
while (true)
{
// Is this position free for us to use?
int intersects = FindIntersectingGlyph(glyphs, index, x, y);
if (intersects < 0)
{
glyphs[index].X = x;
glyphs[index].Y = y;
return;
}
// Skip past the existing glyph that we collided with.
x = glyphs[intersects].X + glyphs[intersects].Width;
// If we ran out of room to move to the right, try the next line down instead.
if (x + glyphs[index].Width > outputWidth)
{
x = 0;
y++;
}
}
}
// Checks if a proposed glyph position collides with anything that we already arranged.
static int FindIntersectingGlyph(List<ArrangedGlyph> glyphs, int index, int x, int y)
{
int w = glyphs[index].Width;
int h = glyphs[index].Height;
for (int i = 0; i < index; i++)
{
if (glyphs[i].X >= x + w)
continue;
if (glyphs[i].X + glyphs[i].Width <= x)
continue;
if (glyphs[i].Y >= y + h)
continue;
if (glyphs[i].Y + glyphs[i].Height <= y)
continue;
return i;
}
return -1;
}
// Comparison function for sorting glyphs by size.
static int CompareGlyphSizes(ArrangedGlyph a, ArrangedGlyph b)
{
const int heightWeight = 1024;
int aSize = a.Height * heightWeight + a.Width;
int bSize = b.Height * heightWeight + b.Width;
if (aSize != bSize)
return bSize.CompareTo(aSize);
else
return a.Source.Character.CompareTo(b.Source.Character);
}
// Heuristic guesses what might be a good output width for a list of glyphs.
static int GuessOutputWidth(Glyph[] sourceGlyphs)
{
int maxWidth = 0;
int totalSize = 0;
foreach (Glyph glyph in sourceGlyphs)
{
maxWidth = Math.Max(maxWidth, glyph.Subrect.Width);
totalSize += glyph.Subrect.Width * glyph.Subrect.Height;
}
int width = Math.Max((int)Math.Sqrt(totalSize), maxWidth);
return MakeValidTextureSize(width, true);
}
// Rounds a value up to the next larger valid texture size.
static int MakeValidTextureSize(int value, bool requirePowerOfTwo)
{
// In case we want to DXT compress, make sure the size is a multiple of 4.
const int blockSize = 4;
if (requirePowerOfTwo)
{
// Round up to a power of two.
int powerOfTwo = blockSize;
while (powerOfTwo < value)
powerOfTwo <<= 1;
return powerOfTwo;
}
else
{
// Round up to the specified block size.
return (value + blockSize - 1) & ~(blockSize - 1);
}
}
}
}

View File

@@ -0,0 +1,25 @@
// DirectXTK MakeSpriteFont tool
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// http://go.microsoft.com/fwlink/?LinkId=248929
using System.Collections.Generic;
namespace MakeSpriteFont
{
// Importer interface allows the conversion tool to support multiple source font formats.
public interface IFontImporter
{
void Import(CommandLineOptions options);
IEnumerable<Glyph> Glyphs { get; }
float LineSpacing { get; }
}
}

View File

@@ -0,0 +1,60 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProductVersion>8.0.30703</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{7329B02D-C504-482A-A156-181D48CE493C}</ProjectGuid>
<OutputType>Exe</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>MakeSpriteFont</RootNamespace>
<AssemblyName>MakeSpriteFont</AssemblyName>
<TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
<TargetFrameworkProfile>Client</TargetFrameworkProfile>
<FileAlignment>512</FileAlignment>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|AnyCPU'">
<DebugSymbols>true</DebugSymbols>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<DebugType>full</DebugType>
<PlatformTarget>AnyCPU</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|AnyCPU'">
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<Optimize>true</Optimize>
<DebugType>pdbonly</DebugType>
<PlatformTarget>AnyCPU</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
</PropertyGroup>
<ItemGroup>
<Compile Include="BitmapUtils.cs" />
<Compile Include="CharacterRegion.cs" />
<Compile Include="CommandLineParser.cs" />
<Compile Include="GlyphCropper.cs" />
<Compile Include="IFontImporter.cs" />
<Compile Include="SpriteFontWriter.cs" />
<Compile Include="TrueTypeImporter.cs" />
<Compile Include="BitmapImporter.cs" />
<Compile Include="GlyphPacker.cs" />
<Compile Include="CommandLineOptions.cs" />
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Glyph.cs" />
</ItemGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Drawing" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

View File

@@ -0,0 +1,187 @@
// DirectXTK MakeSpriteFont tool
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// http://go.microsoft.com/fwlink/?LinkId=248929
using System;
using System.IO;
using System.Linq;
using System.Drawing;
namespace MakeSpriteFont
{
public class Program
{
public static int Main(string[] args)
{
// Parse the commandline options.
var options = new CommandLineOptions();
var parser = new CommandLineParser(options);
if (!parser.ParseCommandLine(args))
return 1;
try
{
// Convert the font.
MakeSpriteFont(options);
return 0;
}
catch (Exception e)
{
// Print an error message if conversion failed.
Console.WriteLine();
Console.Error.WriteLine("Error: {0}", e.Message);
return 1;
}
}
static void MakeSpriteFont(CommandLineOptions options)
{
// Import.
Console.WriteLine("Importing {0}", options.SourceFont);
float lineSpacing;
Glyph[] glyphs = ImportFont(options, out lineSpacing);
Console.WriteLine("Captured {0} glyphs", glyphs.Length);
// Optimize.
Console.WriteLine("Cropping glyph borders");
foreach (Glyph glyph in glyphs)
{
GlyphCropper.Crop(glyph);
}
Console.WriteLine("Packing glyphs into sprite sheet");
Bitmap bitmap;
if (options.FastPack)
{
bitmap = GlyphPacker.ArrangeGlyphsFast(glyphs);
}
else
{
bitmap = GlyphPacker.ArrangeGlyphs(glyphs);
}
// Emit texture size warning based on known Feature Level limits.
if (bitmap.Width > 16384 || bitmap.Height > 16384)
{
Console.WriteLine("WARNING: Resulting texture is too large for all known Feature Levels (9.1 - 12.1)");
}
else if (bitmap.Width > 8192 || bitmap.Height > 8192)
{
if (options.FeatureLevel < FeatureLevel.FL11_0)
{
Console.WriteLine("WARNING: Resulting texture requires a Feature Level 11.0 or later device.");
}
}
else if (bitmap.Width > 4096 || bitmap.Height > 4096)
{
if (options.FeatureLevel < FeatureLevel.FL10_0)
{
Console.WriteLine("WARNING: Resulting texture requires a Feature Level 10.0 or later device.");
}
}
else if (bitmap.Width > 2048 || bitmap.Height > 2048)
{
if (options.FeatureLevel < FeatureLevel.FL9_3)
{
Console.WriteLine("WARNING: Resulting texture requires a Feature Level 9.3 or later device.");
}
}
// Adjust line and character spacing.
lineSpacing += options.LineSpacing;
foreach (Glyph glyph in glyphs)
{
glyph.XAdvance += options.CharacterSpacing;
}
// Automatically detect whether this is a monochromatic or color font?
if (options.TextureFormat == TextureFormat.Auto)
{
bool isMono = BitmapUtils.IsRgbEntirely(Color.White, bitmap);
options.TextureFormat = isMono ? TextureFormat.CompressedMono :
TextureFormat.Rgba32;
}
// Convert to premultiplied alpha format.
if (!options.NoPremultiply)
{
Console.WriteLine("Premultiplying alpha");
BitmapUtils.PremultiplyAlpha(bitmap);
}
// Save output files.
if (!string.IsNullOrEmpty(options.DebugOutputSpriteSheet))
{
Console.WriteLine("Saving debug output spritesheet {0}", options.DebugOutputSpriteSheet);
bitmap.Save(options.DebugOutputSpriteSheet);
}
Console.WriteLine("Writing {0} ({1} format)", options.OutputFile, options.TextureFormat);
SpriteFontWriter.WriteSpriteFont(options, glyphs, lineSpacing, bitmap);
}
static Glyph[] ImportFont(CommandLineOptions options, out float lineSpacing)
{
// Which importer knows how to read this source font?
IFontImporter importer;
string fileExtension = Path.GetExtension(options.SourceFont).ToLowerInvariant();
string[] BitmapFileExtensions = { ".bmp", ".png", ".gif" };
if (BitmapFileExtensions.Contains(fileExtension))
{
importer = new BitmapImporter();
}
else
{
importer = new TrueTypeImporter();
}
// Import the source font data.
importer.Import(options);
lineSpacing = importer.LineSpacing;
var glyphs = importer.Glyphs
.OrderBy(glyph => glyph.Character)
.ToArray();
// Validate.
if (glyphs.Length == 0)
{
throw new Exception("Font does not contain any glyphs.");
}
if ((options.DefaultCharacter != 0) && !glyphs.Any(glyph => glyph.Character == options.DefaultCharacter))
{
throw new Exception("The specified DefaultCharacter is not part of this font.");
}
return glyphs;
}
}
}

View File

@@ -0,0 +1,45 @@
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// http://go.microsoft.com/fwlink/?LinkId=248929
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("MakeSpriteFont")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Microsoft Corporation")]
[assembly: AssemblyProduct("MakeSpriteFont")]
[assembly: AssemblyCopyright("Copyright (c) Microsoft Corporation. All rights reserved")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("12c0da00-f622-41f2-ab8f-1b4e19aa2a6f")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

View File

@@ -0,0 +1,272 @@
// DirectXTK MakeSpriteFont tool
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// http://go.microsoft.com/fwlink/?LinkId=248929
using System;
using System.IO;
using System.Drawing;
using System.Drawing.Imaging;
namespace MakeSpriteFont
{
// Writes the output spritefont binary file.
public static class SpriteFontWriter
{
const string spriteFontMagic = "DXTKfont";
const int DXGI_FORMAT_R8G8B8A8_UNORM = 28;
const int DXGI_FORMAT_B4G4R4A4_UNORM = 115;
const int DXGI_FORMAT_BC2_UNORM = 74;
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times")]
public static void WriteSpriteFont(CommandLineOptions options, Glyph[] glyphs, float lineSpacing, Bitmap bitmap)
{
using (FileStream file = File.OpenWrite(options.OutputFile))
using (BinaryWriter writer = new BinaryWriter(file))
{
WriteMagic(writer);
WriteGlyphs(writer, glyphs);
writer.Write(lineSpacing);
writer.Write(options.DefaultCharacter);
WriteBitmap(writer, options, bitmap);
}
}
static void WriteMagic(BinaryWriter writer)
{
foreach (char magic in spriteFontMagic)
{
writer.Write((byte)magic);
}
}
static void WriteGlyphs(BinaryWriter writer, Glyph[] glyphs)
{
writer.Write(glyphs.Length);
foreach (Glyph glyph in glyphs)
{
writer.Write((int)glyph.Character);
writer.Write(glyph.Subrect.Left);
writer.Write(glyph.Subrect.Top);
writer.Write(glyph.Subrect.Right);
writer.Write(glyph.Subrect.Bottom);
writer.Write(glyph.XOffset);
writer.Write(glyph.YOffset);
writer.Write(glyph.XAdvance);
}
}
static void WriteBitmap(BinaryWriter writer, CommandLineOptions options, Bitmap bitmap)
{
writer.Write(bitmap.Width);
writer.Write(bitmap.Height);
switch (options.TextureFormat)
{
case TextureFormat.Rgba32:
WriteRgba32(writer, bitmap);
break;
case TextureFormat.Bgra4444:
WriteBgra4444(writer, bitmap);
break;
case TextureFormat.CompressedMono:
WriteCompressedMono(writer, bitmap, options);
break;
default:
throw new NotSupportedException();
}
}
// Writes an uncompressed 32 bit font texture.
static void WriteRgba32(BinaryWriter writer, Bitmap bitmap)
{
writer.Write(DXGI_FORMAT_R8G8B8A8_UNORM);
writer.Write(bitmap.Width * 4);
writer.Write(bitmap.Height);
using (var bitmapData = new BitmapUtils.PixelAccessor(bitmap, ImageLockMode.ReadOnly))
{
for (int y = 0; y < bitmap.Height; y++)
{
for (int x = 0; x < bitmap.Width; x++)
{
Color color = bitmapData[x, y];
writer.Write(color.R);
writer.Write(color.G);
writer.Write(color.B);
writer.Write(color.A);
}
}
}
}
// Writes a 16 bit font texture.
static void WriteBgra4444(BinaryWriter writer, Bitmap bitmap)
{
writer.Write(DXGI_FORMAT_B4G4R4A4_UNORM);
writer.Write(bitmap.Width * sizeof(ushort));
writer.Write(bitmap.Height);
using (var bitmapData = new BitmapUtils.PixelAccessor(bitmap, ImageLockMode.ReadOnly))
{
for (int y = 0; y < bitmap.Height; y++)
{
for (int x = 0; x < bitmap.Width; x++)
{
Color color = bitmapData[x, y];
int r = color.R >> 4;
int g = color.G >> 4;
int b = color.B >> 4;
int a = color.A >> 4;
int packed = b | (g << 4) | (r << 8) | (a << 12);
writer.Write((ushort)packed);
}
}
}
}
// Writes a block compressed monochromatic font texture.
static void WriteCompressedMono(BinaryWriter writer, Bitmap bitmap, CommandLineOptions options)
{
if ((bitmap.Width & 3) != 0 ||
(bitmap.Height & 3) != 0)
{
throw new ArgumentException("Block compression requires texture size to be a multiple of 4.");
}
writer.Write(DXGI_FORMAT_BC2_UNORM);
writer.Write(bitmap.Width * 4);
writer.Write(bitmap.Height / 4);
using (var bitmapData = new BitmapUtils.PixelAccessor(bitmap, ImageLockMode.ReadOnly))
{
for (int y = 0; y < bitmap.Height; y += 4)
{
for (int x = 0; x < bitmap.Width; x += 4)
{
CompressBlock(writer, bitmapData, x, y, options);
}
}
}
}
// We want to compress our font textures, because, like, smaller is better,
// right? But a standard DXT compressor doesn't do a great job with fonts that
// are in premultiplied alpha format. Our font data is greyscale, so all of the
// RGBA channels have the same value. If one channel is compressed differently
// to another, this causes an ugly variation in brightness of the rendered text.
// Also, fonts are mostly either black or white, with grey values only used for
// antialiasing along their edges. It is very important that the black and white
// areas be accurately represented, while the precise value of grey is less
// important.
//
// Trouble is, your average DXT compressor knows nothing about these
// requirements. It will optimize to minimize a generic error metric such as
// RMS, but this will often sacrifice crisp black and white in exchange for
// needless accuracy of the antialiasing pixels, or encode RGB differently to
// alpha. UGLY!
//
// Fortunately, encoding monochrome fonts turns out to be trivial. Using DXT3,
// we can fix the end colors as black and white, which gives guaranteed exact
// encoding of the font inside and outside, plus two fractional values for edge
// antialiasing. Also, these RGB values (0, 1/3, 2/3, 1) map exactly to four of
// the possible 16 alpha values available in DXT3, so we can ensure the RGB and
// alpha channels always exactly match.
static void CompressBlock(BinaryWriter writer, BitmapUtils.PixelAccessor bitmapData, int blockX, int blockY, CommandLineOptions options)
{
long alphaBits = 0;
int rgbBits = 0;
int pixelCount = 0;
for (int y = 0; y < 4; y++)
{
for (int x = 0; x < 4; x++)
{
long alpha;
int rgb;
int value = bitmapData[blockX + x, blockY + y].A;
if (options.NoPremultiply)
{
// If we are not premultiplied, RGB is always white and we have 4 bit alpha.
alpha = value >> 4;
rgb = 0;
}
else
{
// For premultiplied encoding, quantize the source value to 2 bit precision.
if (value < 256 / 6)
{
alpha = 0;
rgb = 1;
}
else if (value < 256 / 2)
{
alpha = 5;
rgb = 3;
}
else if (value < 256 * 5 / 6)
{
alpha = 10;
rgb = 2;
}
else
{
alpha = 15;
rgb = 0;
}
}
// Add this pixel to the alpha and RGB bit masks.
alphaBits |= alpha << (pixelCount * 4);
rgbBits |= rgb << (pixelCount * 2);
pixelCount++;
}
}
// Output the alpha bit mask.
writer.Write(alphaBits);
// Output the two endpoint colors (black and white in 5.6.5 format).
writer.Write((ushort)0xFFFF);
writer.Write((ushort)0);
// Output the RGB bit mask.
writer.Write(rgbBits);
}
}
}

View File

@@ -0,0 +1,252 @@
// DirectXTK MakeSpriteFont tool
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// http://go.microsoft.com/fwlink/?LinkId=248929
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Drawing.Text;
using System.Runtime.InteropServices;
namespace MakeSpriteFont
{
// Uses System.Drawing (aka GDI+) to rasterize TrueType fonts into a series of glyph bitmaps.
public class TrueTypeImporter : IFontImporter
{
// Properties hold the imported font data.
public IEnumerable<Glyph> Glyphs { get; private set; }
public float LineSpacing { get; private set; }
// Size of the temp surface used for GDI+ rasterization.
const int MaxGlyphSize = 1024;
public void Import(CommandLineOptions options)
{
// Create a bunch of GDI+ objects.
using (Font font = CreateFont(options))
using (Brush brush = new SolidBrush(Color.White))
using (StringFormat stringFormat = new StringFormat(StringFormatFlags.NoFontFallback))
using (Bitmap bitmap = new Bitmap(MaxGlyphSize, MaxGlyphSize, PixelFormat.Format32bppArgb))
using (Graphics graphics = Graphics.FromImage(bitmap))
{
graphics.PixelOffsetMode = options.Sharp ? PixelOffsetMode.None : PixelOffsetMode.HighQuality;
graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
graphics.TextRenderingHint = TextRenderingHint.AntiAliasGridFit;
// Which characters do we want to include?
var characters = CharacterRegion.Flatten(options.CharacterRegions);
var glyphList = new List<Glyph>();
// Rasterize each character in turn.
int count = 0;
foreach (char character in characters)
{
++count;
if (count == 500)
{
if (!options.FastPack)
{
Console.WriteLine("WARNING: capturing a large font. This may take a long time to complete and could result in too large a texture. Consider using /FastPack.");
}
Console.Write(".");
}
else if ((count % 500) == 0)
{
Console.Write(".");
}
Glyph glyph = ImportGlyph(character, font, brush, stringFormat, bitmap, graphics);
glyphList.Add(glyph);
}
if (count > 500)
{
Console.WriteLine();
}
Glyphs = glyphList;
// Store the font height.
LineSpacing = font.GetHeight();
}
}
// Attempts to instantiate the requested GDI+ font object.
static Font CreateFont(CommandLineOptions options)
{
Font font = new Font(options.SourceFont, PointsToPixels(options.FontSize), options.FontStyle, GraphicsUnit.Pixel);
try
{
// The font constructor automatically substitutes fonts if it can't find the one requested.
// But we prefer the caller to know if anything is wrong with their data. A simple string compare
// isn't sufficient because some fonts (eg. MS Mincho) change names depending on the locale.
// Early out: in most cases the name will match the current or invariant culture.
if (options.SourceFont.Equals(font.FontFamily.GetName(CultureInfo.CurrentCulture.LCID), StringComparison.OrdinalIgnoreCase) ||
options.SourceFont.Equals(font.FontFamily.GetName(CultureInfo.InvariantCulture.LCID), StringComparison.OrdinalIgnoreCase))
{
return font;
}
// Check the font name in every culture.
foreach (CultureInfo culture in CultureInfo.GetCultures(CultureTypes.SpecificCultures))
{
if (options.SourceFont.Equals(font.FontFamily.GetName(culture.LCID), StringComparison.OrdinalIgnoreCase))
{
return font;
}
}
// A font substitution must have occurred.
throw new Exception(string.Format("Can't find font '{0}'.", options.SourceFont));
}
catch
{
font.Dispose();
throw;
}
}
// Converts a font size from points to pixels. Can't just let GDI+ do this for us,
// because we want identical results on every machine regardless of system DPI settings.
static float PointsToPixels(float points)
{
return points * 96 / 72;
}
// Rasterizes a single character glyph.
static Glyph ImportGlyph(char character, Font font, Brush brush, StringFormat stringFormat, Bitmap bitmap, Graphics graphics)
{
string characterString = character.ToString();
// Measure the size of this character.
SizeF size = graphics.MeasureString(characterString, font, Point.Empty, stringFormat);
int characterWidth = (int)Math.Ceiling(size.Width);
int characterHeight = (int)Math.Ceiling(size.Height);
// Pad to make sure we capture any overhangs (negative ABC spacing, etc.)
int padWidth = characterWidth;
int padHeight = characterHeight / 2;
int bitmapWidth = characterWidth + padWidth * 2;
int bitmapHeight = characterHeight + padHeight * 2;
if (bitmapWidth > MaxGlyphSize || bitmapHeight > MaxGlyphSize)
throw new Exception("Excessively large glyph won't fit in my lazily implemented fixed size temp surface.");
// Render the character.
graphics.Clear(Color.Black);
graphics.DrawString(characterString, font, brush, padWidth, padHeight, stringFormat);
graphics.Flush();
// Clone the newly rendered image.
Bitmap glyphBitmap = bitmap.Clone(new Rectangle(0, 0, bitmapWidth, bitmapHeight), PixelFormat.Format32bppArgb);
BitmapUtils.ConvertGreyToAlpha(glyphBitmap);
// Query its ABC spacing.
float? abc = GetCharacterWidth(character, font, graphics);
// Construct the output Glyph object.
return new Glyph(character, glyphBitmap)
{
XOffset = -padWidth,
XAdvance = abc.HasValue ? padWidth - bitmapWidth + abc.Value : -padWidth,
YOffset = -padHeight,
};
}
// Queries APC spacing for the specified character.
static float? GetCharacterWidth(char character, Font font, Graphics graphics)
{
// Look up the native device context and font handles.
IntPtr hdc = graphics.GetHdc();
try
{
IntPtr hFont = font.ToHfont();
try
{
// Select our font into the DC.
IntPtr oldFont = NativeMethods.SelectObject(hdc, hFont);
try
{
// Query the character spacing.
var result = new NativeMethods.ABCFloat[1];
if (NativeMethods.GetCharABCWidthsFloat(hdc, character, character, result))
{
return result[0].A +
result[0].B +
result[0].C;
}
else
{
return null;
}
}
finally
{
NativeMethods.SelectObject(hdc, oldFont);
}
}
finally
{
NativeMethods.DeleteObject(hFont);
}
}
finally
{
graphics.ReleaseHdc(hdc);
}
}
// Interop to the native GDI GetCharABCWidthsFloat method.
static class NativeMethods
{
[DllImport("gdi32.dll")]
public static extern IntPtr SelectObject(IntPtr hdc, IntPtr hObject);
[DllImport("gdi32.dll")]
public static extern bool DeleteObject(IntPtr hObject);
[DllImport("gdi32.dll", CharSet = CharSet.Unicode)]
public static extern bool GetCharABCWidthsFloat(IntPtr hdc, uint iFirstChar, uint iLastChar, [Out] ABCFloat[] lpABCF);
[StructLayout(LayoutKind.Sequential)]
public struct ABCFloat
{
public float A;
public float B;
public float C;
}
}
}
}