static void MakeSpriteFont(FontDescription options, Stream stream) { float lineSpacing; Glyph[] glyphs = ImportFont(options, out lineSpacing); // Optimize. foreach (Glyph glyph in glyphs) { GlyphCropper.Crop(glyph); } Bitmap bitmap = GlyphPacker.ArrangeGlyphs(glyphs); // Adjust line and character spacing. lineSpacing += options.LineSpacing; foreach (Glyph glyph in glyphs) { glyph.XAdvance += options.Spacing; } // Automatically detect whether this is a monochromatic or color font? if (options.Format == FontTextureFormat.Auto) { bool isMono = BitmapUtils.IsRgbEntirely(Color.White, bitmap); options.Format = isMono ? FontTextureFormat.CompressedMono : FontTextureFormat.Rgba32; } // Convert to premultiplied alpha format. if (!options.NoPremultiply) { BitmapUtils.PremultiplyAlpha(bitmap); } SpriteFontWriter.WriteSpriteFont(options, stream, glyphs, lineSpacing, bitmap); }
static void WriteBitmap(BinaryWriter writer, FontDescription options, Bitmap bitmap) { writer.Write(bitmap.Width); writer.Write(bitmap.Height); switch (options.Format) { case FontTextureFormat.Rgba32: WriteRgba32(writer, bitmap); break; case FontTextureFormat.Bgra4444: WriteBgra4444(writer, bitmap); break; case FontTextureFormat.CompressedMono: WriteCompressedMono(writer, bitmap, options); break; default: throw new NotSupportedException(); } }
// Writes a block compressed monochromatic font texture. static void WriteCompressedMono(BinaryWriter writer, Bitmap bitmap, FontDescription 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); } } } }
public void Import(FontDescription options) { // Load the source bitmap. Bitmap bitmap; try { bitmap = new Bitmap(options.FontName); } catch { throw new FontException(string.Format("Unable to load '{0}'.", options.FontName)); } // Convert to our desired pixel format. bitmap = BitmapUtils.ChangePixelFormat(bitmap, PixelFormat.Format32bppArgb); // What characters are included in this font? var characters = Utilities.ToArray(CharacterRegion.Flatten(options.CharacterRegions)); 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); } }
public static void WriteSpriteFont(FontDescription options, string outputFilename, Glyph[] glyphs, float lineSpacing, Bitmap bitmap) { using (var stream = new NativeFileStream(outputFilename, NativeFileMode.Create, NativeFileAccess.Write)) { WriteSpriteFont(options, stream, glyphs, lineSpacing, bitmap); } }
// 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, FontDescription 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); }
public void Import(FontDescription options) { //ImportBitmapFonts(options); //return; var factory = new DirectWrite.Factory(); DirectWrite.Font font = null; using (var fontCollection = factory.GetSystemFontCollection(false)) { int index; if(!fontCollection.FindFamilyName(options.FontName, out index)) { // Lets try to import System.Drawing for old system bitmap fonts (like MS Sans Serif) throw new FontException(string.Format("Can't find font '{0}'.", options.FontName)); } using(var fontFamily = fontCollection.GetFontFamily(index)) { var weight = FontWeight.Regular; var style = DirectWrite.FontStyle.Normal; switch(options.Style) { case FontStyle.Bold: weight = FontWeight.Bold; break; case FontStyle.Italic: weight = FontWeight.Regular; style = DirectWrite.FontStyle.Italic; break; case FontStyle.Regular: weight = FontWeight.Regular; break; } font = fontFamily.GetFirstMatchingFont(weight, DirectWrite.FontStretch.Normal, style); } } var fontFace = new FontFace(font); var fontMetrics = fontFace.Metrics; // Create a bunch of GDI+ objects. var fontSize = PointsToPixels(options.Size); // Which characters do we want to include? var characters = CharacterRegion.Flatten(options.CharacterRegions); var glyphList = new List<Glyph>(); // Store the font height. LineSpacing = (float)Math.Round((float)(fontMetrics.LineGap + fontMetrics.Ascent + fontMetrics.Descent) / fontMetrics.DesignUnitsPerEm * fontSize); var baseLine = (float)Math.Round((float)(fontMetrics.LineGap + fontMetrics.Ascent) / fontMetrics.DesignUnitsPerEm * fontSize); // If font size <= 13, use aliased fonts instead bool activateAntiAliasDetection = options.Size > 13; // Rasterize each character in turn. foreach (char character in characters) { var glyph = ImportGlyph(factory, fontFace, character, fontMetrics, fontSize, activateAntiAliasDetection); glyph.YOffset += baseLine; glyphList.Add(glyph); } Glyphs = glyphList; }
static void MakeSpriteFont(FontDescription options, Stream stream) { float lineSpacing; Glyph[] glyphs = ImportFont(options, out lineSpacing); // Optimize. foreach (Glyph glyph in glyphs) { // Output cleartype texture if (options.AntiAlias != FontAntiAliasMode.ClearType) { BitmapUtils.ConvertGreyToAlpha(glyph.Bitmap); } GlyphCropper.Crop(glyph); } Bitmap bitmap = GlyphPacker.ArrangeGlyphs(glyphs); // Adjust line and character spacing. lineSpacing += options.LineSpacing; foreach (Glyph glyph in glyphs) { glyph.XAdvance += options.Spacing; } // Automatically detect whether this is a monochromatic or color font? if (options.Format == FontTextureFormat.Auto) { bool isMono = BitmapUtils.IsRgbEntirely(Color.White, bitmap); options.Format = isMono ? FontTextureFormat.CompressedMono : FontTextureFormat.Rgba32; } // Convert to premultiplied alpha format. if (!options.NoPremultiply) { if (options.AntiAlias == FontAntiAliasMode.ClearType) { BitmapUtils.PremultiplyAlphaClearType(bitmap); } else { BitmapUtils.PremultiplyAlpha(bitmap); } } SpriteFontWriter.WriteSpriteFont(options, stream, glyphs, lineSpacing, bitmap); }
/// <summary> /// Compiles the specified font description into a <see cref="SpriteFontData" /> object. /// </summary> /// <param name="fontDescription">The font description.</param> /// <param name="stream">The stream to output the compiled SpriteFontData.</param> /// <returns>A SpriteFontData object.</returns> public static void Compile(FontDescription fontDescription, Stream stream) { MakeSpriteFont(fontDescription, stream); }
public void Import(FontDescription 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 (System.Drawing.Graphics graphics = System.Drawing.Graphics.FromImage(bitmap)) { graphics.PixelOffsetMode = 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. foreach (char character in characters) { Glyph glyph = ImportGlyph(character, font, brush, stringFormat, bitmap, graphics); glyphList.Add(glyph); } Glyphs = glyphList; // Store the font height. LineSpacing = font.GetHeight(); } }
// Attempts to instantiate the requested GDI+ font object. static Font CreateFont(FontDescription options) { Font font = new Font(options.FontName, PointsToPixels(options.Size), (System.Drawing.FontStyle)options.Style, 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.FontName.Equals(font.FontFamily.GetName(CultureInfo.CurrentCulture.LCID), StringComparison.OrdinalIgnoreCase) || options.FontName.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.FontName.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.FontName)); } catch { font.Dispose(); throw; } }
public void Import(FontDescription options) { var factory = new DirectWrite.Factory(); DirectWrite.Font font = null; using (var fontCollection = factory.GetSystemFontCollection(false)) { int index; if (!fontCollection.FindFamilyName(options.FontName, out index)) { // Lets try to import System.Drawing for old system bitmap fonts (like MS Sans Serif) throw new FontException(string.Format("Can't find font '{0}'.", options.FontName)); } using (var fontFamily = fontCollection.GetFontFamily(index)) { var weight = FontWeight.Regular; var style = DirectWrite.FontStyle.Normal; switch (options.Style) { case FontStyle.Bold: weight = FontWeight.Bold; break; case FontStyle.Italic: weight = FontWeight.Regular; style = DirectWrite.FontStyle.Italic; break; case FontStyle.Regular: weight = FontWeight.Regular; break; } font = fontFamily.GetFirstMatchingFont(weight, DirectWrite.FontStretch.Normal, style); } } var fontFace = new FontFace(font); var fontMetrics = fontFace.Metrics; // Create a bunch of GDI+ objects. var fontSize = PointsToPixels(options.Size); // Which characters do we want to include? var characters = CharacterRegion.Flatten(options.CharacterRegions); var glyphList = new List <Glyph>(); // Store the font height. LineSpacing = (float)(fontMetrics.LineGap + fontMetrics.Ascent + fontMetrics.Descent) / fontMetrics.DesignUnitsPerEm * fontSize; var baseLine = (float)(fontMetrics.LineGap + fontMetrics.Ascent) / fontMetrics.DesignUnitsPerEm * fontSize; // Rasterize each character in turn. foreach (char character in characters) { var glyph = ImportGlyph(factory, fontFace, character, fontMetrics, fontSize, options.AntiAlias); glyph.YOffset += baseLine; glyphList.Add(glyph); } Glyphs = glyphList; factory.Dispose(); }
/// <summary> /// Compiles the specified font description into a <see cref="SpriteFontData"/> object. /// </summary> /// <param name="fontDescription">The font description.</param> /// <returns>A SpriteFontData object.</returns> public static SpriteFontData Compile(FontDescription fontDescription) { // We are using a MemoryStream, this is not efficient // but this was a quickest way to use existing from MakeSpriteFont from DirectXTk var stream = new MemoryStream(); MakeSpriteFont(fontDescription, stream); stream.Position = 0; return SpriteFontData.Load(stream); }
public static void WriteSpriteFont(FontDescription options, Stream outputStream, Glyph[] glyphs, float lineSpacing, Bitmap bitmap) { var writer = new BinaryWriter(outputStream); WriteMagic(writer); writer.Write(SpriteFontData.Version); WriteGlyphs(writer, glyphs); writer.Write(lineSpacing); writer.Write(options.DefaultCharacter); WriteBitmap(writer, options, bitmap); writer.Flush(); }
static Glyph[] ImportFont(FontDescription options, out float lineSpacing) { // Which importer knows how to read this source font? IFontImporter importer; string fileExtension = Path.GetExtension(options.FontName).ToLowerInvariant(); var BitmapFileExtensions = new List<string> { ".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; // Get all glyphs var glyphs = new List<Glyph>(importer.Glyphs); // Validate. if (glyphs.Count == 0) { throw new FontException("Font does not contain any glyphs."); } // Sort the glyphs glyphs.Sort((left, right) => left.Character.CompareTo(right.Character)); // Check that the default character is part of the glyphs if (options.DefaultCharacter != 0) { bool defaultCharacterFound = false; foreach (var glyph in glyphs) { if (glyph.Character == options.DefaultCharacter) { defaultCharacterFound = true; break; } } if (!defaultCharacterFound) { throw new FontException("The specified DefaultCharacter is not part of this font."); } } return glyphs.ToArray(); }
public void Run(string[] args) { // Print the exe header PrintHeader(); // Parse the command line if (!ParseCommandLine(args)) { Environment.Exit(1); } // Loads the description var filePath = Path.Combine(Environment.CurrentDirectory, XmlFontFile); var fontDescription = FontDescription.Load(XmlFontFile); var defaultOutputFile = Path.Combine(Path.GetDirectoryName(filePath), Path.GetFileNameWithoutExtension(filePath)); // Compiles to SpriteData OutputFile = OutputFile ?? defaultOutputFile; string dependencyFile = null; if (CompileOnlyIfNewer) { dependencyFile = Path.Combine(OutputDependencyDirectory, FileDependencyList.GetDependencyFileNameFromSourcePath(Path.GetFileName(filePath))); } bool forceCompilation = (DebugOutputSpriteSheet != null && !File.Exists(DebugOutputSpriteSheet)); var result = FontCompiler.CompileAndSave(filePath, OutputFile, dependencyFile); if (result.Logger.HasErrors) { ErrorColor(); foreach (var logMessage in result.Logger.Messages) { Console.WriteLine(logMessage); } ResetColor(); Environment.Exit(1); } else if (result.IsContentGenerated || forceCompilation) { Console.WriteLine("Writing [{0}] ({1} format)", OutputFile, fontDescription.Format); // Save output files. if (!string.IsNullOrEmpty(DebugOutputSpriteSheet)) { Console.WriteLine("Saving debug output spritesheet {0}", DebugOutputSpriteSheet); var spriteFontData = SpriteFontData.Load(OutputFile); if (spriteFontData.Bitmaps.Length > 0 && spriteFontData.Bitmaps[0].Data is SpriteFontData.BitmapData) { var bitmapData = (SpriteFontData.BitmapData)spriteFontData.Bitmaps[0].Data; using (var image = Image.New2D(bitmapData.Width, bitmapData.Height, 1, bitmapData.PixelFormat)) { Utilities.Write(image.DataPointer, bitmapData.Data, 0, bitmapData.Data.Length); image.Save(DebugOutputSpriteSheet, ImageFileType.Dds); } } } } else { Console.WriteLine("No need to write [{0}]. File is up-to-date from XML description", OutputFile); } }