// 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(BitmapUtils.PixelAccessor bitmapData, int blockX, int blockY, SpriteFontAsset options, out BC2Pixel bc2Pixel) { 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.IsNotPremultiply) { // If we are not pre-multiplied, RGB is always white and we have 4 bit alpha. alpha = value >> 4; rgb = 0; } else { // For pre-multiplied 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. bc2Pixel.AlphaBits = alphaBits; // Output the two endpoint colors (black and white in 5.6.5 format). bc2Pixel.EndPoint = 0x0000FFFF; // Output the RGB bit mask. bc2Pixel.RgbBits = rgbBits; }
static Glyph[] ImportFont(SpriteFontAsset options, out float lineSpacing, out float baseLine) { // Which importer knows how to read this source font? IFontImporter importer; var sourceExtension = (Path.GetExtension(options.Source) ?? "").ToLowerInvariant(); var bitmapFileExtensions = new List <string> { ".bmp", ".png", ".gif" }; var importFromBitmap = bitmapFileExtensions.Contains(sourceExtension); importer = importFromBitmap ? (IFontImporter) new BitmapImporter() : new TrueTypeImporter(); // create the list of character to import var characters = GetCharactersToImport(options); // Import the source font data. importer.Import(options, characters); lineSpacing = importer.LineSpacing; baseLine = importer.BaseLine; // Get all glyphs var glyphs = new List <Glyph>(importer.Glyphs); // Validate. if (glyphs.Count == 0) { throw new Exception("Font does not contain any glyphs."); } if (!importFromBitmap && options.AntiAlias != FontAntiAliasMode.ClearType) { foreach (var glyph in importer.Glyphs) { BitmapUtils.ConvertGreyToAlpha(glyph.Bitmap, glyph.Subrect); } } // 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 InvalidOperationException("The specified DefaultCharacter is not part of this font."); } } return(glyphs.ToArray()); }