template project, first version
This commit is contained in:
125
DirectXTK/MakeSpriteFont/BitmapImporter.cs
Normal file
125
DirectXTK/MakeSpriteFont/BitmapImporter.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
244
DirectXTK/MakeSpriteFont/BitmapUtils.cs
Normal file
244
DirectXTK/MakeSpriteFont/BitmapUtils.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
137
DirectXTK/MakeSpriteFont/CharacterRegion.cs
Normal file
137
DirectXTK/MakeSpriteFont/CharacterRegion.cs
Normal 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));
|
||||
}
|
||||
}
|
||||
99
DirectXTK/MakeSpriteFont/CommandLineOptions.cs
Normal file
99
DirectXTK/MakeSpriteFont/CommandLineOptions.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
253
DirectXTK/MakeSpriteFont/CommandLineParser.cs
Normal file
253
DirectXTK/MakeSpriteFont/CommandLineParser.cs
Normal 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; }
|
||||
}
|
||||
}
|
||||
}
|
||||
43
DirectXTK/MakeSpriteFont/Glyph.cs
Normal file
43
DirectXTK/MakeSpriteFont/Glyph.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
54
DirectXTK/MakeSpriteFont/GlyphCropper.cs
Normal file
54
DirectXTK/MakeSpriteFont/GlyphCropper.cs
Normal 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++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
285
DirectXTK/MakeSpriteFont/GlyphPacker.cs
Normal file
285
DirectXTK/MakeSpriteFont/GlyphPacker.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
25
DirectXTK/MakeSpriteFont/IFontImporter.cs
Normal file
25
DirectXTK/MakeSpriteFont/IFontImporter.cs
Normal 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; }
|
||||
}
|
||||
}
|
||||
60
DirectXTK/MakeSpriteFont/MakeSpriteFont.csproj
Normal file
60
DirectXTK/MakeSpriteFont/MakeSpriteFont.csproj
Normal 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>
|
||||
187
DirectXTK/MakeSpriteFont/Program.cs
Normal file
187
DirectXTK/MakeSpriteFont/Program.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
45
DirectXTK/MakeSpriteFont/Properties/AssemblyInfo.cs
Normal file
45
DirectXTK/MakeSpriteFont/Properties/AssemblyInfo.cs
Normal 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")]
|
||||
272
DirectXTK/MakeSpriteFont/SpriteFontWriter.cs
Normal file
272
DirectXTK/MakeSpriteFont/SpriteFontWriter.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
252
DirectXTK/MakeSpriteFont/TrueTypeImporter.cs
Normal file
252
DirectXTK/MakeSpriteFont/TrueTypeImporter.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user