XNA предполагает вывод текста только заранее подготовленными растровыми шрифтами. И это правильно. Быстро, не зависимо от ОС, предсказуемые размеры текста.
В моём случае требовалось совершенно противоположное. Произвольный выбор гарнитуры и размера шрифта, и низкие требования к производительности. Задача оказалась довольно трудной. Информации в интернете оказалось мало и она была крайне разрознена.
Условия: 2D приложение, пользователь в любое время должен иметь возможность изменить гарнитуру, стиль и размер шрифта.
Способ решения довольно прост. Создаём битмап с нужным текстом, создаём из него текстуру и отображаем её.
Битмап создаём средствами GDI.
/// <summary> Draw text on bitmap </summary>
/// <returns>Bitmap with text</returns>
private System.Drawing.Bitmap Layout() {
// Get font
var font = new System.Drawing.Font( fontName, fontSize, fontStyle);
// Get text size
var bitmap = new System.Drawing.Bitmap( 1, 1 );
var graphics = System.Drawing.Graphics.FromImage( bitmap );
var textSize = graphics.MeasureString( text, font );
// Draw text on bitmap
bitmap = new System.Drawing.Bitmap( (int) textSize.Width, (int) textSize.Height );
graphics = System.Drawing.Graphics.FromImage( bitmap );
graphics.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAliasGridFit;
graphics.DrawString( text, font, new System.Drawing.SolidBrush( this.color ), 0f, 0f );
return bitmap;
}
* This source code was highlighted with Source Code Highlighter.
Преобразуем битмап в текстуру
/// <summary> Create texture from bitmap </summary>
/// <param name="gdev">Graphic device</param>
/// <param name="bmp">Bitmap</param>
/// <returns>Texture</returns>
private static Texture2D TextureFromBitmap(GraphicsDevice gdev, System.Drawing.Bitmap bmp) {
Stream fs = new MemoryStream();
bmp.Save(fs, System.Drawing.Imaging.ImageFormat.Png);
var tex = Texture2D.FromStream(gdev, fs);
fs.Close();
fs.Dispose();
return tex;
}
* This source code was highlighted with Source Code Highlighter.
И отображаем текстуру
public void Draw() {
var spriteBatch = new SpriteBatch( graphicsDevice );
spriteBatch.Begin();
spriteBatch.Draw(
texture,
coordinate,
new Rectangle(0, 0, texture.Width, texture.Height),
Color.White,
0f, new Vector2(0, 0),
1.0f / textureDownsizeRatio,
SpriteEffects.None, 0);
spriteBatch.End();
}
* This source code was highlighted with Source Code Highlighter.
В результате получаем текст с рваными краями, полное отсутствие сглаживания.
Конвертация битмапа в текстуру оказалась нетривиальной процедурой.
Метод найден здесь
Для корректной передачи альфа-канала требуется произвести дополнительные действия в методе TextureFromBitmap.
Сначала отрисовываем в текстуру данные о цветах битмапа, потом информацию об альфа-канале.
/// <summary> Create texture from bitmap </summary>
/// <param name="gdev">Graphic device</param>
/// <param name="bmp">Bitmap</param>
/// <returns>Texture</returns>
private static Texture2D TextureFromBitmap(GraphicsDevice gdev, System.Drawing.Bitmap bmp) {
Stream fs = new MemoryStream();
bmp.Save(fs, System.Drawing.Imaging.ImageFormat.Png);
var tex = Texture2D.FromStream(gdev, fs);
fs.Close();
fs.Dispose();
// Setup a render target to hold our final texture which will have premulitplied alpha values
var res = new RenderTarget2D(gdev, tex.Width, tex.Height);
gdev.SetRenderTarget(res);
gdev.Clear(Color.Black);
// Multiply each color by the source alpha, and write in just the color values into the final texture
var blendColor = new BlendState {
ColorWriteChannels = ColorWriteChannels.Red | ColorWriteChannels.Green | ColorWriteChannels.Blue,
AlphaDestinationBlend = Blend.Zero,
ColorDestinationBlend = Blend.Zero,
AlphaSourceBlend = Blend.SourceAlpha,
ColorSourceBlend = Blend.SourceAlpha
};
var spriteBatch = new SpriteBatch(gdev);
spriteBatch.Begin(SpriteSortMode.Immediate, blendColor);
spriteBatch.Draw(tex, tex.Bounds, Color.White);
spriteBatch.End();
// Now copy over the alpha values from the PNG source texture to the final one, without multiplying them
var blendAlpha = new BlendState {
ColorWriteChannels = ColorWriteChannels.Alpha,
AlphaDestinationBlend = Blend.Zero,
ColorDestinationBlend = Blend.Zero,
AlphaSourceBlend = Blend.One,
ColorSourceBlend = Blend.One
};
spriteBatch.Begin(SpriteSortMode.Immediate, blendAlpha);
spriteBatch.Draw(tex, tex.Bounds, Color.White);
spriteBatch.End();
// Release the GPU back to drawing to the screen
gdev.SetRenderTarget(null);
return res;
}
* This source code was highlighted with Source Code Highlighter.
Теперь получаем удовлетворительный результат.
Исходный код.
Проблемы:
1. Медленно. Ни в коем случае не надо использовать этот метод в играх и тем более на мобильных устройствах.
2. Не надежно. Нужных шрифтов может не оказаться в системе.
3. Желательно отслеживать какая часть текста будет отображаться на экране и обрезать невидимый текст. Максимальный размер текстуры 2048x2048. Если размер битмапа будет больше, то текстура будет создана максимального размера и потом растянута средствами видеокарты до нужного размера. Текст будет размытым.
Можно избавиться от переконвертирования в PNG в TextureFromBitmap используя неуправляемый код. Пример можно посмотреть здесь.
Комментариев нет:
Отправить комментарий