Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Understanding TBitmap.Scanline in Delphi & C++ Builder

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.

like image 768
SRobertJames Avatar asked Sep 19 '11 04:09

SRobertJames


1 Answers

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

like image 109
Sertac Akyuz Avatar answered Nov 14 '22 21:11

Sertac Akyuz