Not logged inGosu Forums
Forum back to libgosu.org Help Search Register Login
Up Topic Gosu / Gosu Exchange / Disabling antialiasing for Gosu::Font
- - By thePapacy Date 2011-05-25 05:05
Is there a way to disable antialiasing when using Gosu::Font and Gosu::Image.from_text()? I've hand-drawn a pixel font which is intended to be completely sharp, but as it is, I can't use the font without relying on pre-rendered images.

vs.
Parent - - By erisdiscord Date 2011-05-25 13:29
No way that I know of. I'm pretty sure Gosu uses different text renderers depending on the platform, anyway, so some renderers may not even support it.

If you aren't dealing with dynamic text, you might be better off using static images anyway. It's faster and guaranteed to be consistent across platforms (well, as consistent as any other image drawing) at the cost of increased distribution size.

On the other hand, if you're using a lot of dynamic text, implementing a simple bitmap font class isn't too difficult and you can add features that Gosu::Font doesn't support, like word wrapping. I've got such a class lying around somewhere, if I can find it.
Parent - - By lol_o2 Date 2011-05-25 14:48
I made a bitmap font class for one of my games. I can give it here if you want.
Parent - By erisdiscord Date 2011-05-25 19:15
I actually found mine a while ago, so I added support for rendering to an image for the hell of it. I'm about to post it. :)
Parent - By erisdiscord Date 2011-05-25 19:26
As for custom bitmap font classes, here's the one I wrote. It should be more-or-less usable as a drop-in replacement for Gosu::Font, although it doesn't hook into Gosu::Image.from_text. Instead, I've added a render method that accepts the same arguments. The render method requires TexPlay, but everything else should work with just Gosu.

Beware that rendering to an image is slow, so you'll probably want to do it at some kind of loading screen rather than on the fly. I'm not sure how to solve that since TexPlay doesn't natively support splice-and-scale as far as I know. The way I've done it is a dirty hack. :)

As for licensing, consider it under the same license as Gosu.

require 'gosu'
require 'json'
require 'weakref'

class BitmapFont
 
  WORD_BREAK     = /\s|\b-|$/
  CHARSET_RANGE  = 0x20..0x7f
 
  def initialize window, filename, height = nil
    properties = File.open(filename) { |io| JSON.load io }
   
    # probably shouldn't hold onto this, but...
    @window = WeakRef.new window
   
    @name = properties['name'] || File.basename(filename).sub(/\.[^\.]+$/, '')
   
    # font metrics
    @width  = Integer properties['width']
    @height = Integer properties['height'] || @width
    @scale  = height ? height.to_f / @height : 1
   
    @exceptions = properties['exceptions'] || { }
   
    # images
    image_filename = if properties.include? 'image'
      File.expand_path properties['image'], File.dirname(filename)
    else
      filename.sub /\.[^\.]+$/, '.png'
    end
   
    tile_width  = properties['tile_width']  || @width
    tile_height = properties['tile_height'] || @height
   
    @images = *Gosu::Image.load_tiles(window,
      image_filename, tile_width, tile_height, false)
  end # initialize
 
  def height
    @height * @scale
  end
 
  def text_width text, factor_x = 1
    text.each_char.inject(0) { |width, ch| width + char_width(ch) } * factor_x
  end
 
  def draw string, x, y, z, factor_x = 1, factor_y = 1, color = 0xffffffff, mode = :default
    x_off = 0
    @window.scale factor_x, factor_y, x, y do
      string.each_char do |ch|
        image = char_image(ch)
        image.draw x + x_off, y, z, @scale, @scale, color, mode if image
       
        x_off += char_width(ch)
      end
    end
  end # draw
 
  def draw_rel text, x, y, z, rel_x, rel_y, factor_x = 1, factor_y = 1, color = 0xffffffff, mode = :default
    draw_width  = text_width(text, factor_x)
    draw_height = height * factor_y
    @window.translate(-draw_width * rel_x, -draw_height * rel_y) do
      draw text, x, y, z, factor_x, factor_y, color, mode
    end
  end
 
  def draw_rot text, x, y, z, angle, factor_x = 1, factor_y = 1, color = 0xffffffff, mode = :default
    @window.translate(x, y) do
      @window.rotate(angle) do
        draw_rel text, 0, 0, z, 0.5, 0.5, factor_x, factor_y, color, mode
      end
    end
  end
 
  def render text, line_spacing, max_width, align
    raise NotImplementedError,
      "Rendering to an image requires TexPlay to be installed", caller \
      unless defined? ::TexPlay
   
    lines = word_wrap(text, max_width)
   
    draw_width  = max_width
    draw_height = (height + line_spacing).floor * lines.count
   
    canvas = TexPlay.create_image @window.__getobj__, draw_width, draw_height,
      :caching => false
   
    y_off = 0
    lines.each do |line|
      render_line canvas, line, y_off, align
      y_off += (height + line_spacing).floor
    end
   
    canvas
  end # render
 
  private
 
  def parse_characters spec
    case spec
    when Enumerable
      spec.inject([]) { |charset, subspec|
        charset += parse_characters(subspec) }
    when /^(.)-(.)$/
      ($1..$2).to_a
    else
      spec.characters
    end
  end
 
  def char_image ch
    if CHARSET_RANGE.include? ch.ord
      index = ch.ord - CHARSET_RANGE.begin
      @images[index]
    end
  end
 
  def char_width ch
    if @exceptions.include? ch
      @exceptions[ch] * @scale
    else
      @width * @scale
    end
  end # char_width
 
  def word_wrap text, max_width
    lines = [ ]
   
    first_index = 0
    last_index  = test_index = text.index(WORD_BREAK)
   
    while test_index
      if text_width(text[first_index ... test_index]) >= max_width
        lines << text[first_index ... last_index]
        first_index = last_index + 1
      end
     
      last_index = test_index
      test_index = text.index WORD_BREAK, last_index + 1
    end
   
    lines << text[first_index .. last_index]
    lines
  end # word_wrap
 
  def render_line canvas, line, y_off, align
    x_off = case align
            when :left   then  0
            when :center then (canvas.width - text_width(line)) / 2
            when :right  then  canvas.width - text_width(line)
            end
   
    line.each_char do |ch|
      image       = char_image(ch)
      draw_width  = char_width(ch)
      draw_height = height.floor
     
      canvas.rect x_off, y_off, x_off + draw_width, y_off + draw_height,
        :color => :white,
        :fill  => true,
        :color_control => proc { |src, dst, x, y|
          image.get_pixel(((x - x_off) / @scale).floor,
                          ((y - y_off) / @scale).floor) || src || :none }
     
      x_off += draw_width
    end
  end # render_line
 
end # BitmapFont

begin
  require 'texplay'
rescue LoadError
  $stderr.puts('TexPlay is not available; BitmapFont rendering will be ' +
               'unavailable') if $DEBUG
end
Parent - - By jlnr (dev) Date 2011-05-26 15:28
It seems that the height at which you have drawn your font and the height at which it is rendered differ (slightly). If Gosu would disable AA, the letters may still be distorted.

Gosu::Font is nothing but a simple bitmap font too BTW. It simply uses Image.from_text to render every letter and then pieces them together. (It got a bit more complicated with the recent <b> <u> <c=...> support, but does anyone use that? :()
Nothing wrong with rolling your own (or using the one above) :)
Parent - - By lol_o2 Date 2011-05-26 17:03
I'm using <c=...> if you need to know :)
Parent - - By jlnr (dev) Date 2011-05-26 20:06
That is indeed good to know :)
Parent - By Spooner Date 2011-05-27 20:05
I would have used it, if I'd realised it was there :$
Parent - - By erisdiscord Date 2011-05-27 00:31
Well, there might be something wrong with using mine (needs more error checks, could be faster) but... :)

Hey, since Gosu's apparently generating bitmap fonts from truetype fonts anyway, would it be possible to add support for loading characters from a sprite sheet like this? :)
Parent - - By jlnr (dev) Date 2011-05-27 08:43
Hmmm, I guess I could add a Font#[]= method that would replace the internal Image for any letter by a given image (Gosu, RMagick, blob, ...). That would be possible without messing with the rest of Font and would imply that there will always be a fallback for characters that are left undefined by the Bitmap font. Thoughts?
Parent - - By erisdiscord Date 2011-05-27 15:10
That actually sounds pretty good! I think leaving the specifics of loading non-standard fonts up to us is probably best since, for instance, my BitmapFont wouldn't be suitable for people who need unicode support.

It would be most useful if paired with a constructor that creates an empty font, of course. Gosu::Font.new(window, height)?
Parent - - By RunnerPack Date 2011-05-31 11:13
I like the sound of this entire sub-thread!

Here's a semi-related idea I had:

Add a new method in the "Font#draw" family (Font#draw_each, perhaps) that yields each character's Gosu::Image, x, and y to a block, rather than drawing them. It would allow modifying the position, rotation, color, etc. of each character before drawing it. It could be used for cool typography effects like those used in the dialog boxes in the Paper Mario series of games, among others.
Parent - By jlnr (dev) Date 2011-05-31 11:20
Great idea. I can just check for block_given? in Font#draw. The C++ interface will probably not look as pretty though.
Parent - By erisdiscord Date 2011-05-31 14:05 Edited 2011-05-31 14:11
That does it. I am adding this to my bitmap font class.

Extremely simple git patch for the file I posted above:

diff --git a/bitmap_font.rb b/bitmap_font.rb
index 9a11368..08b9370 100644
--- a/bitmap_font.rb
+++ b/bitmap_font.rb
@@ -49,7 +49,12 @@ class BitmapFont
     @window.scale factor_x, factor_y, x, y do
       string.each_char do |ch|
         image = char_image(ch)
-        image.draw x + x_off, y, z, @scale, @scale, color, mode if image
+       
+        if block_given?
+          yield image, x + x_off, y
+        else
+          image.draw x + x_off, y, z, @scale, @scale, color, mode if image
+        end
        
         x_off += char_width(ch)
       end
Parent - - By Spooner Date 2011-05-31 17:35
An alternative method, suggested by jlnr, is to create the font larger than you need, then scale down, which will minimise anti-aliasing. In this monkey-patch, I extend Gosu::Font to store the font at twice the size, then render it scaled to half the size. The net result is text that is the same size as before, but less blurred.

If you want even less blurring, you could use a factor of 4:
Gosu::Font.factor_stored = 4 # How much larger is it stored as a font.
Gosu::Font.factor_rendered = 0.25 # How much smaller it is rendered.

https://gist.github.com/1000398

Sadly, this is pretty horrid. I find that the amount of aliasing for pixelly fonts is very dependent on the rendered font size in relation to the actual size in the ttf file. So, it might render cleanly at 15px, but blur a lot at 12px.
Parent - - By jlnr (dev) Date 2011-06-01 09:41

> rendered font size in relation to the actual size in the ttf file


I want to hijack this sentence to point out that TTF/OTF/… is a vector file format that is usually printed to paper or rendered given a point size, not a pixel height. If something has a true and only proper pixel height, it should not be in a vector file :) Your approach makes a whole lot of sense for "normal" fonts in a stretched environment though.
Parent - By Spooner Date 2011-06-01 11:54
Ah, yes, sorry. My system does seem to work, though, and I definitely did see more anti-aliasing artefacts at some pixel sizes and not at others when I was initially experimenting. Whatever the reason for that!

Since you make them into bitmaps before you render them, my comments do make sense regarding scaling at run-time (that is, scaling in Font#draw).
Up Topic Gosu / Gosu Exchange / Disabling antialiasing for Gosu::Font

Powered by mwForum 2.29.7 © 1999-2015 Markus Wichitill