Search code examples
visual-c++c++-clifreetypefreetype2

unsigned char* buffer to System::Drawing::Bitmap


I'm trying to create a tool/asset converter that rasterises a font to a texture page for an XNA game using the FreeType2 engine.

Below, the first image is the direct output from the FreeType2]1 engine. The second image is the result after attempting to convert it to a System::Drawing::Bitmap.

target http://www.freeimagehosting.net/uploads/fb102ee6da.jpg currentresult http://www.freeimagehosting.net/uploads/9ea77fa307.jpg

Any hints/tips/ideas on what is going on here would be greatly appreciated. Links to articles explaining byte layout and pixel formats would also be helpful.

  FT_Bitmap *bitmap = &face->glyph->bitmap;

  int width = (face->bitmap->metrics.width / 64);
  int height = (face->bitmap->metrics.height / 64);

  // must be aligned on a 32 bit boundary or 4 bytes
  int depth = 8;
  int stride = ((width * depth + 31) & ~31) >> 3;
  int bytes = (int)(stride * height);

  // as *.bmp
  array<Byte>^ values = gcnew array<Byte>(bytes);  
  Marshal::Copy((IntPtr)glyph->buffer, values, 0, bytes);

  Bitmap^ systemBitmap = gcnew Bitmap(width, height, PixelFormat::Format24bppRgb);

  // create bitmap data, lock pixels to be written.
  BitmapData^ bitmapData = systemBitmap->LockBits(Rectangle(0, 0, width, height), ImageLockMode::WriteOnly, bitmap->PixelFormat);
  Marshal::Copy(values, 0, bitmapData->Scan0, bytes);
  systemBitmap->UnlockBits(bitmapData);

  systemBitmap->Save("Test.bmp");

Update. Changed PixelFormat to 8bppIndexed.

  FT_Bitmap *bitmap = &face->glyph->bitmap; 

  // stride must be aligned on a 32 bit boundary or 4 bytes
  int depth = 8;
  int stride = ((width * depth + 31) & ~31) >> 3;
  int bytes = (int)(stride * height);

  target = gcnew Bitmap(width, height, PixelFormat::Format8bppIndexed);

  // create bitmap data, lock pixels to be written.
  BitmapData^ bitmapData = target->LockBits(Rectangle(0, 0, width, height), ImageLockMode::WriteOnly, target->PixelFormat);  

  array<Byte>^ values = gcnew array<Byte>(bytes);  
  Marshal::Copy((IntPtr)bitmap->buffer, values, 0, bytes);
  Marshal::Copy(values, 0, bitmapData->Scan0, bytes);

  target->UnlockBits(bitmapData);

Solution

  • Ah ha. Worked it out.

    FT_Bitmap is an 8bit image, so the correct PixelFormat was 8bppIndexed, which resulted this output. Not aligned to 32byte boundary http://www.freeimagehosting.net/uploads/dd90fa2252.jpg

    System::Drawing::Bitmap needs to be aligned on a 32 bit boundary.

    I was calculating the stride but was not padding it when writing the bitmap. Copied the FT_Bitmap buffer to a byte[] and then wrote that to a MemoryStream, adding the necessary padding.

      int stride = ((width * pixelDepth + 31) & ~31) >> 3;
      int padding = stride - (((width * pixelDepth) + 7) / 8);
    
      array<Byte>^ pad = gcnew array<Byte>(padding);
      array<Byte>^ buffer = gcnew array<Byte>(size);  
      Marshal::Copy((IntPtr)source->buffer, buffer, 0, size);
    
      MemoryStream^ ms = gcnew MemoryStream();
    
      for (int i = 0; i < height; ++i)
      {
        ms->Write(buffer, i * width, width);
        ms->Write(pad, 0, padding);    
      }
    

    Pinned the memory so the GC would leave it alone.

      // pin memory and create bitmap
      GCHandle handle = GCHandle::Alloc(ms->ToArray(), GCHandleType::Pinned);
      target = gcnew Bitmap(width, height, stride, PixelFormat::Format8bppIndexed, handle.AddrOfPinnedObject());   
      ms->Close();
    

    As there is no Format8bppIndexed Grey the image was still not correct.

    alt text http://www.freeimagehosting.net/uploads/8a883b7dce.png

    Then changed the bitmap palette to grey scale 256.

      // 256-level greyscale palette
      ColorPalette^ palette = target->Palette;
      for (int i = 0; i < palette->Entries->Length; ++i)
        palette->Entries[i] = Color::FromArgb(i,i,i);
    
      target->Palette = palette;
    

    alt text http://www.freeimagehosting.net/uploads/59a745269e.jpg


    Final solution.

      error = FT_Load_Char(face, ch, FT_LOAD_RENDER);
      if (error)
        throw gcnew InvalidOperationException("Failed to load and render character");
    
      FT_Bitmap *source = &face->glyph->bitmap; 
    
      int width = (face->glyph->metrics.width / 64);
      int height = (face->glyph->metrics.height / 64);
      int pixelDepth = 8;   
      int size = width * height;
    
      // stride must be aligned on a 32 bit boundary or 4 bytes
      // padding is the number of bytes to add to make each row a 32bit aligned row
      int stride = ((width * pixelDepth + 31) & ~31) >> 3;
      int padding = stride - (((width * pixelDepth) + 7) / 8);
    
      array<Byte>^ pad = gcnew array<Byte>(padding);
      array<Byte>^ buffer = gcnew array<Byte>(size);  
      Marshal::Copy((IntPtr)source->buffer, buffer, 0, size);
    
      MemoryStream^ ms = gcnew MemoryStream();
    
      for (int i = 0; i < height; ++i)
      {
        ms->Write(buffer, i * width, width);
        ms->Write(pad, 0, padding);    
      }
    
      // pin memory and create bitmap
      GCHandle handle = GCHandle::Alloc(ms->ToArray(), GCHandleType::Pinned);
      target = gcnew Bitmap(width, height, stride, PixelFormat::Format8bppIndexed, handle.AddrOfPinnedObject());   
      ms->Close();
    
      // 256-level greyscale palette
      ColorPalette^ palette = target->Palette;
      for (int i = 0; i < palette->Entries->Length; ++i)
        palette->Entries[i] = Color::FromArgb(i,i,i);
    
      target->Palette = palette;
    
      FT_Done_FreeType(library);