Scaled font smoothing

Posted on
  • While developing my current Bangle app, I needed to use a larger font. In my case, the built-in 6x15 font, scaled x2. As expected, it looked a bit rough. I wondered if there was some way to automatically smooth things out without generating a 12x30 font and I came up with the proof-of-concept program below.

    In short, it adds extra pixels in black squares that have two adjacent white squares forming a 'corner'.

    It draws some sample text three times: the top is the base x1 scale text. Next is the xN native scaled-up rendering. The last is my smoothed scaled-up xN rendering, using the x1 render as the source.

    Tapping on the screen will cycle through the 7-bit ASCII characters. When all the characters are cycled, the scale increases by 1 and the characters start again.

    It's a bit slow, so would be better off implemented in C.

    Do what you like with it. I think it could reduce the need for large fonts.

    //    B   -> is drawn as
    //
    //                    BBBB
    //   ???         BBB  WBBB
    //   WB?  -> BB  WBB  WWBB
    //   ?W?     WB  WWB  WWWB
    //
    // source -> x2  x3   x4
    //
    // W=white
    // B=black
    // ?=any
    
    function process(x, y, dx, dy, scale, ox, oy) {
      var s, sx, sy;
      s = scale - 1;
      ox += x * scale;
      oy += y * scale;
      if (g.getPixel(x, y) != g.getColor()) {
        ox += (dx > 0) ? s : 0;
        oy += (dy > 0) ? s : 0;
        if (g.getPixel(x, y + dy) == g.getColor() && g.getPixel(x + dx, y) == g.getColor()) {
          for (sx = 0; sx < s; sx++) {
            for (sy = 0; (sx + sy) < s; sy++) {
              g.setPixel(ox - sx * dx, oy - sy * dy, g.getColor());
            }
          }
        }
      } else {
        g.fillRect(ox, oy, ox + s, oy + s);
      }
    }
    
    var ch = 33;
    var scale = 2;
    
    function draw() {
      var i, s = "";
      for (i = 0; i < 5; i++) {
        s += String.fromCharCode(ch);
        ch++;
        if (ch == 127) {
          break;
        }
      }
      g.clear();
      g.setFontAlign(-1, -1, 0);
      g.setFont6x15(1);
      g.drawString(s, 2, 2, true);
      g.setFont6x15(scale);
      g.drawString(s, 2 + scale, 20, true);
      for (var y = 2; y < 15; y++) {
        for (var x = 2; x < 35; x++) {
          process(x, y,  1,  1, scale, 0, 90);
          process(x, y, -1,  1, scale, 0, 90);
          process(x, y,  1, -1, scale, 0, 90);
          process(x, y, -1, -1, scale, 0, 90);
        }
      }
      if (ch == 127) {
        ch = 33;
        scale++;
      }
    }
    
    Bangle.on('touch', (zone, e) => {
      draw();
    });
    draw();
    
  • It's a bit slow, so would be better off implemented in C.

    such code would be doable in InlineC https://www.espruino.com/InlineC well except calling getPixel/setPixel but g.buffer is flat array that could be used directly.

    Or in this case try just to put "compiled" there to process, see https://www.espruino.com/Compilation
    Also check hints there, you may put g.getPixel,g.setPixel to local variables as it is called in loop.

  • Do you have some before/after examples?

    It sounds pretty neat though - It could be applied to the font, but on bangle.js 2 at least it could be a function a bit like fillRect that you just apply to a rectangular area to smooth it out. It could be used for scaling images too then

    edit: also just a quick thought - is what we're doing actually going through every 2nd row and column and saying:

       A
     D x B
       C
    

    Set x if A+B+C+D > 2?

  • There's always the Vector Font.
    setFont('Vector', size) or there is the custom font tutorial.

  • What my code is doing is iterating over every pixel of the character and looking for 'empty' pixels that have two 'full' pixels that are at 90deg to each other relative to the 'empty' pixel. It then draws sub-pixels in the scaled-up 'empty' pixel to smooth out the stair-step effect created by the naïve scaling.

    It's really only good for smoothing out monochrome things, like bitmapped fonts, not images in general.

    Quick set of sample images: https://imgur.com/a/KUDrfPQ

    The vector font is a good for a lot of cases, and it might be a better option for my immediate requirements.

  • Sounds conceptually similar to EPX/Scale2x: https://en.wikipedia.org/wiki/Pixel-art_­scaling_algorithms#EPX/Scale2%C3%97/AdvM­AME2%C3%97

    There are a number of pixel art scaling algorithms, if you want to generalize this they're worth looking into.

  • Post a reply
    • Bold
    • Italics
    • Link
    • Image
    • List
    • Quote
    • code
    • Preview
About

Scaled font smoothing

Posted by Avatar for andrewg_oz @andrewg_oz

Actions