XNA: Text output in system fonts

    XNA assumes the output of text only in advance prepared bitmap fonts. And it is right. Fast, OS independent, predictable text sizes.
    In my case, the exact opposite was required. Arbitrary choice of headset and font size, and low performance requirements. The task was quite difficult. There was little information on the Internet and it was extremely fragmented.


    Conditions: 2D application, the user at any time should be able to change the headset, style and font size.

    The solution is quite simple. Create a bitmap with the desired text, create a texture from it and display it.

    We create a bitmap using GDI tools.
    ///  Draw text on bitmap 
    /// Bitmap with text
    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;
    }
    


    Convert Bitmap to Texture
    ///  Create texture from bitmap 
    /// Graphic device
    /// Bitmap
    /// Texture
    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;
    }
    


    And display the texture
    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();
    }
    


    As a result, we get text with torn edges, a complete lack of smoothing.


    Converting a bitmap to a texture turned out to be a non-trivial procedure.
    The method is found here.

    For the correct transfer of the alpha channel, additional actions are required in the TextureFromBitmap method.
    First, we draw the data about the colors of the bitmap into the texture, then the information about the alpha channel.
    ///  Create texture from bitmap 
    /// Graphic device
    /// Bitmap
    /// Texture
    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;
    }
    


    Now we get a satisfactory result.


    Source code

    Problems:
    1. Slowly. In no case should you use this method in games, and even more so on mobile devices.
    2. Not reliable. The necessary fonts may not be in the system.
    3. It is advisable to track how much of the text will be displayed on the screen and trim the invisible text. The maximum texture size is 2048x2048. If the bitmap size is larger, then the texture will be created with the maximum size and then stretched by the video card to the desired size. The text will be blurry.

    You can get rid of converting to PNG in TextureFromBitmap using unmanaged code. An example can be seen here .

    Also popular now: