unsigned char* buffer to System::Drawing::Bitmap
Asked Answered
W

2

4

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);
Wini answered 1/5, 2010 at 14:43 Comment(2)
bitmapData->Scan0 is the address of the first pixel data in the bitmap.Wini
The FT_Bitmap is FT_PIXEL_MODE_GRAY (8-bit bitmap). bit.ly/9lgh69.Wini
W
7

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);
Wini answered 3/5, 2010 at 7:33 Comment(0)
C
1

Your "depth" value doesn't match the PixelFormat of the Bitmap. It needs to be 24 to match Format24bppRgb. The PF for the bitmap needs to match the PF and stride of the FT_Bitmap as well, I don't see you take care of that.

Correggio answered 1/5, 2010 at 16:56 Comment(3)
Thanks for your reply. The FT_Bitmap is FT_PIXEL_MODE_GRAY (8-bit bitmap). bit.ly/9lgh69. What is the correct stride and PixelFormat that I should use?Wini
There isn't, a 8bpp format requires a palette to map a byte to a color. GDI+ does not deal with palettes well, stick with .tga. Or tinker from the top, it is a pretty useless pixel format for nicely anti-aliased text that displays well on LCD monitors.Correggio
I appreciate the help Hans, I managed to figured out it. Writing it as a *.tga was simply to test if the output was correct. This was a part of a larger task and needed to get the image into a Bitmap to process it further in C#.Wini

© 2022 - 2024 — McMap. All rights reserved.