Converting a monochrome FreeType bitmap to an 8-bit pixel array

Viewed 980

I've spent a few hours trying to convert a monochrome bitmap generated by FreeType to a pixel array that uses 8 bits per pixel (so that I can use them as alpha values for OpenGL). I know I could just get a normal 8bpp bitmap and go with it, but I really need the monochrome version. And I've already tried the method of thresholding pixels, but the result is not at all satisfactory. It seems that once you have the anti-aliased version, you just can't go back.

The snippet below is what I have now, and it's really close to working just right, except for a few letters.

if (FT_Load_Glyph(face, glyphIndex, FT_LOAD_MONOCHROME | FT_LOAD_TARGET_MONO))
  throw exception();

if (FT_Render_Glyph(face->glyph, FT_RENDER_MODE_MONO))
  throw exception();

const Size bitmapSize = Size(
    face->glyph->metrics.width / 64, face->glyph->metrics.height / 64
);

u8 *bitmapBits = face->glyph->bitmap.buffer;    
u8 *bitmapBytes = new u8[bitmapSize.width * bitmapSize.height];

memset(bitmapBytes, 0, bitmapSize.width * bitmapSize.height);

int width = face->glyph->bitmap.width;
int height = face->glyph->bitmap.rows;

const u8 *src = bitmapBits;

for (int row = 0; row < height; ++row)
{
  u8 *dst = &bitmapBytes[row * width];

  for (int col = 0; col < width / 8; ++col)
  {
      u8 byte = src[col];

      *dst++ = ((byte & (1 << 7)) != 0) ? 255: 0;
      *dst++ = ((byte & (1 << 6)) != 0) ? 255: 0;
      *dst++ = ((byte & (1 << 5)) != 0) ? 255: 0;
      *dst++ = ((byte & (1 << 4)) != 0) ? 255: 0;
      *dst++ = ((byte & (1 << 3)) != 0) ? 255: 0;
      *dst++ = ((byte & (1 << 2)) != 0) ? 255: 0;
      *dst++ = ((byte & (1 << 1)) != 0) ? 255: 0;
      *dst++ = ((byte & (1 << 0)) != 0) ? 255: 0;
  }

  u8 trailingByte = src[width / 8];

  int i = 7;

  switch (width % 8)
  {
  case 7: *dst++ = ((trailingByte & (1 << (i--))) != 0) ? 255 : 0;
  case 6: *dst++ = ((trailingByte & (1 << (i--))) != 0) ? 255 : 0;
  case 5: *dst++ = ((trailingByte & (1 << (i--))) != 0) ? 255 : 0;
  case 4: *dst++ = ((trailingByte & (1 << (i--))) != 0) ? 255 : 0;
  case 3: *dst++ = ((trailingByte & (1 << (i--))) != 0) ? 255 : 0;
  case 2: *dst++ = ((trailingByte & (1 << (i--))) != 0) ? 255 : 0;
  case 1: *dst++ = ((trailingByte & (1 << (i--))) != 0) ? 255 : 0;
  case 0: break;
  }

  src += face->glyph->bitmap.pitch;
}

Here's the output:

enter image description here

I tried to figure out what the distorted letters have in common (maybe that could help me), but I don't know.

Really, I don't know what's wrong with the way I'm doing this. But there must be something I'm missing. Changing the font doesn't fix the problem (except for distorting different letters than before).

Edit: I noticed that face->glyph->bitmap.width and face->glyph->bitmap.rows sometimes return slightly different values than face->glyph->metrics.width / 64 and face->glyph->metrics.height / 64. I've tried switching them around, and the rendering is definitely better, but there are still some characters (like 't' and 'g' with Arial, and 'X' and '0' with Microsoft Sans Serif) that are a bit shifted or distorted.

Arial: enter image description here

Microsoft Sans Serif: enter image description here

This is really weird and I must be missing something, because I don't believe it's a bug. FreeType seems to be too solid and reliable of a library. Besides, loading fonts as non-monochromatic works perfectly, so this is not a problem with the code that renders the string.

1 Answers

The number of bytes in a row is not necessarily the minimum number that fit the bits in the row, but is reported in the pitch field of the bitmap.

Related