// 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); }
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); }