Oled, improving doble font size

Rewriting OzOLED and adding a few extra functions, I tried to double an 8x8 font to a 16x16 font.
On a 0.96" display, the double font is a nice to have feature.
Adding a double font bitmap for just the numbers requires 32 x 10 = 320 bytes plus at least a punctuation,
in total 352 bytes.
Just doubling the bitmap has the disadvantage of a ragged loook.
The advantage is that the doubling can be applied to all ascii characters plus
upper range up to the value 255 using just a few more memory bytes.

I have looked for an existing way to improve the displayed font, but have not found any solution.
So I decided to give the following a try:

One way to soften the look could be to add a bit, converting sharp corners to staircases.

An example:

Original       Result
##             ## 
##             ###
  ##            ###
  ##             ##

Using a simple forward looking function comparing three positions at a time does
an imperfect job, soften some of the edges and distorting the image slightly.

Dumping the bitmap using a cmd line program, it is easy to show the results
just doubling bits, a teoretical result, and the actual result.
Extra bits added are shown as 0.

Origianal           Times Two               Optimal x2            Resulting x2

 ...#....         ......##........          ......##........      ......##........
 ..####..         ......##........          ......##........      .....0##........     <
 .#.#....         ....########....          ....########....      ....########....
 ..###...         ....########....          ...0########....      ...0########....     <
 ...#.#..         ..##..##........          ..##..##........      ..##..##........
 .####...         ..##..##........          ..##..##0.......      ..##.0##........     <
 ...#....         ....######......          ...0######......      ....######......
 ........         ....######......          ....######0.....      ....######......     <
                  ......##..##....          .....0##..##....      ......##..##....
                  ......##..##....          ......##..##....      .....0##.0##....     <
                  ..########......          ..########0.....      ..########......
                  ..########......          ..########......      ..########......     <
                  ......##........          ......##.......      .....0##........
                  ......##........          ......##........      ......##........     <
                  ................          ................      ................
                  ................          ................      ................     <
                                                                   ^ ^ ^ ^ ^ ^ ^ ^

The < and ^ shows where bits can be added using the forward looking function.

Overall the result is a little bit easier on the eyes, but is it worth the trouble and
extra memory usage?

Or to put it in another way:
Is there a way to do a better job not using a lot of memory?

The code is an extract from my MiniOled library which can be found here:
https://github.com/cbpdk/MiniOled

uint8_t MiniOled::doubleHalfByte(uint8_t c) {
  
  uint8_t t = 4;
  uint8_t b = 8;
  c = c& 0x0f;
  
  //Shift 4 lower bits and double them
  for(char i=3; i >=0; i--) {
    if((c & b) == b ) {
      c |=  b << t; 
      c |=  b << (t-1);
      if(i > 0)
        c &= ~b;                  //clear bit
    }
    b >>=1;
    t--;
  }
  return c;
}


uint8_t MiniOled::adjustPattern(uint8_t item1, uint8_t item2) {
  uint8_t currBit = 3;      //bit 1,2
  uint8_t nextBit = 0x0c;   //bit 3,4
  uint8_t prevBit = 0;      //Empty
  
  //Ignore first and last bits as either previous or next bits are out of range.
  for(char i = 0; i < 4; i++) {
    //Second bits compare to first bits next pair
    if(i<3 && (item1 & currBit) != currBit && 
      (item2 & currBit) == currBit &&
      (item1 & nextBit) == nextBit) {
      item1 |= currBit << 1; // add one bit, second already set
    } else  if(i >0 && (item1 & currBit) != currBit && 
      (item1 & prevBit) == prevBit &&
      (item2 & currBit) == currBit  ) {
      item1 |= currBit >> 1; // add one bit, second already set
    } 
    currBit <<= 2;
    nextBit <<= 2;
    prevBit = currBit >> 2;
  }
  return item1;
}

void MiniOled::printStringx2adv(const char *string, uint8_t X, uint8_t Y, uint8_t numChar) {
  
  if( X > 13 || Y > 7)   
    return;
  setCursor(X, Y);
  
  uint8_t tmpY = Y;
  uint8_t count = 0;
  uint8_t dc;
  uint8_t halfx2[FONT_WIDTH]; //Holds one half of the height
 
  do {
    count = 0;
   
    //Send one full length row at a time.
    beginData();
    
    while (string[count] && count < numChar) {
      
      //Get all columns for half one character.
      for (char i = 0; i < FONT_WIDTH; i++) {
        uint8_t c = (uint8_t)string[count];
#ifdef CUSTOM_RANGE
        //Above ascii, try remap
        if( c > 127 )
          c = mapChar(c);
#endif        
        if (c < FONT_START || c >= FONT_END )  { //ASCII plus above
          dc = 0;    //Unrecognized, print a space
        } else {
          c -= 32; 
          dc = pgm_read_byte(&BasicFont[c][i]);
          if( Y - tmpY == 1)
            dc >>= 4;   //Second run, use upper half byte
          halfx2[i]  = doubleHalfByte(dc);
        }
      }
    
      //Assumes FONT_WIDTH is the width of a normal character on the display.
      for (char j = 0; j < FONT_WIDTH; j++) {
        dc= halfx2[j];
        sendDataByte(dc);
        dc = adjustPattern(dc, (j < FONT_WIDTH-1) ? halfx2[j+1]:0);
        sendDataByte(dc);
      }
      count++;
     }
    Wire.endTransmission();
    Y += 1;
    setCursor(X, Y);  //Next row
     
  } while( Y - tmpY < 2); 
  
  if(tmpY +2 < MO_MAX_ROWS-1)
    tmpY +=2;
  setCursor(X, tmpY); 
}

The code is not optimised, making it easier to follow.
Attached is a photo showing the difference on a 0.96" display.

dblfont.jpg

Any library that inherits from Adafrùit_GFX will be able to print the standard font in x2, x3, ...

Most libraries that support multiple fonts will allow a cut-price font e.g. digits only.
So you only have to provide 10 attractive big digits.
Adafruit x2 looks ok. x3, x4 ... look pretty ugly.

David.

Nice, but still does not look as good as a true 16x16 font. But yes, to save flash ROM it might be a good approach...

16x16 font:

Adafruit x2 looks ok. x3, x4 ... look pretty ugly.

David.

I think the idea of the OP was to do an upscaling with included anti-aliasing.

Oliver

Yes, my idea was to do some kind of anti-aliasing/softning edges.
It can never be as good as the right size bitmapped font, but better looking than just doubling
the number of bits. And the functions saves a bit of flash memory, especially when using e.g
attiny 85.
I have used u8g and Adafrùit_GFX before. And yes, the fonts looks good.

I suspect a simpler symetrical algorithm will give better results where any diagonal pixel pair has extra pixels added:

You could scan the bit patterns using a shifting 2 bit mask + XOR + AND and the code required would be small.

Running the algorithm on a few characters:

@bodmer
Amazing!
Thank you for the help, it saved my day :slight_smile:
It really improved the look of the x2 font.
I have attached photos to show the difference between a times 2 font and the optimised times 2 font on a 0.96" Oled.