6

Delphi & C++ Builder have a TBitmap class with a Scanline property which returns the memory of pixels of the bitmap. This seems to be different when I look in a hex editor of the BMP file.

I'm trying to port a C++ Builder app to Java, and would like to understand the algorithm in Scanline. If I have the file, how do I generate the memory array like Scanline does? What is the exact spec behind Scanline?

Clarifcation: The BMP is a Windows 24bit DIB. I don't provide any other info in the code; C++ Builder seems to load it into some type of memory structure, but it's not byte-for-byte. Would like to know what the spec of that structture is.

SRobertJames
  • 8,210
  • 14
  • 60
  • 107
  • The name "TBitmap" doesn't necessarily have anything to do with the Microsoft "bitmap" file format, you know! The "scanline" property in a Delphi "TBitmap" is just a raster line - nothing more, nothing less. – paulsm4 Sep 19 '11 at 04:20
  • 1
    what is the pixel format of the bitmap file and how do you load it into a TBitmap? Is your bitmap stored top down or bottom down? – David Heffernan Sep 19 '11 at 08:46
  • Which part of the structure do you want to understand? The TBitmap fields, or the pixel data? – David Heffernan Sep 19 '11 at 09:28
  • @paul A Delphi TBitmap is a loose wrapper around a Windows HBITMAP – David Heffernan Sep 19 '11 at 09:54
  • `Scanline` is property which provides you indexed access on per scanline basic into DIBSection behind TBitmap instance. What specifically you do not understand? – Premature Optimization Sep 19 '11 at 12:30

1 Answers1

8

A bitmap file starts with a BITMAPFILEHEADER, the bfOffBits member specifies the starting address of image data. This is a DWORD at Dh (11-14th bytes). Delphi VCL has the structure defined as TBitmapFileHeader in 'windows.pas'.

The last row of the ScanLine points to this image data (bottom-up). The VCL has this value in bmBits member of the dsBm(a BITMAP) member or the DIBSECTION of the image. When a scan line is requested, the VCL calculates an offset depending on the requested row, number of pixels in a row (width of the image) and how many bits make up a pixel, and returns a pointer to an address adding this offset to bmBits. It's really byte-by-byte image data.

The below Delphi sample code reads a 24bit bitmap to a file stream and compares each read pixel with the pixel data of the Bitmap.ScanLine counterpart:

procedure TForm1.Button1Click(Sender: TObject);
var
  BmpFile: string;
  Bmp: TBitmap;

  fs: TFileStream;
  FileHeader: TBitmapFileHeader;
  InfoHeader: TBitmapInfoHeader;
  iHeight, iWidth, Padding: Longint;

  ScanLine: Pointer;
  RGBFile, RGBBitmap: TRGBTriple;
begin
  BmpFile := ExtractFilePath(Application.ExeName) + 'Attention_128_24.bmp';

  // laod bitmap to TBitmap
  Bmp := TBitmap.Create;
  Bmp.LoadFromFile(BmpFile);
  Assert(Bmp.PixelFormat = pf24bit);

  // read bitmap file with stream
  fs := TFileStream.Create(BmpFile, fmOpenRead or fmShareDenyWrite);
  // need to get the start of pixel array
  fs.Read(FileHeader, SizeOf(FileHeader));
  // need to get width and height of bitmap
  fs.Read(InfoHeader, SizeOf(InfoHeader));
  // just a general demo - no top-down image allowed
  Assert(InfoHeader.biHeight > 0);
  // size of each row is a multiple of the size of a DWORD
  Padding := SizeOf(DWORD) -
      (InfoHeader.biWidth * 3) mod SizeOf(DWORD); // pf24bit -> 3 bytes

  // start of pixel array
  fs.Seek(FileHeader.bfOffBits, soFromBeginning);


  // compare reading from file stream with the value from scanline
  for iHeight := InfoHeader.biHeight - 1 downto 0  do begin

    // get the scanline, bottom first
    ScanLine := Bmp.ScanLine[iHeight];

    for iWidth := 0 to InfoHeader.biWidth - 1 do begin

      // read RGB from file stream
      fs.Read(RGBFile, SizeOf(RGBFile));

      // read RGB from scan line
      RGBBitmap := TRGBTriple(Pointer(
                      Longint(ScanLine) + (iWidth * SizeOf(TRGBTriple)))^);

      // assert the two values are the same
      Assert((RGBBitmap.rgbtBlue = RGBFile.rgbtBlue) and
             (RGBBitmap.rgbtGreen = RGBFile.rgbtGreen) and
             (RGBBitmap.rgbtRed = RGBFile.rgbtRed));
    end;
    // skip row padding
    fs.Seek(Padding, soCurrent);
  end;
end;



A picture about finding the starting of pixel data of a bitmap file in a hex-editor:

enter image description here

Sertac Akyuz
  • 54,131
  • 4
  • 102
  • 169
  • 3
    Super answer! Am I correct in understanding that the only difference is that TBitmap.ScanLine is bottom-row-first, whereas the file is top-row-first, but each will represent the row as the same sequence of bytes? (At least for 24bit DIB, which is what I'm dealing with.) – SRobertJames Sep 19 '11 at 20:02
  • Most windows bitmaps are bottom-up so a bitmap file has the bottom row first. `bmBits` is just a pointer to the same data which is in-memory (except aligning of rows is a little bit different for memory layout). When a scanline is requested the VCL accounts for 'bottom-up'ness in `TBitmap.GetScanLine` in graphics.pas. If you, f.i., request the last row (`Bmp.ScanLine[Bmp.Height - 1]`) and the bitmap is bottom-up, VCL returns a pointer to the first row of the pixel array (as the last row of the scanline). – Sertac Akyuz Sep 19 '11 at 20:24
  • Still having trouble understanding. I see clearly in my hex editor that the bytes are quite different. Is the only difference the row order is inverted? – SRobertJames Sep 19 '11 at 21:00
  • @SRobert - You're taking 'little-endian'ness into account? In the file you'd see in BGR order. – Sertac Akyuz Sep 19 '11 at 21:08
  • @SRobert - I attached a picture to the answer, I hope it clears up confusion. – Sertac Akyuz Sep 19 '11 at 21:53