理解Delphi和C++ Builder中的TBitmap.Scanline

6
Delphi和C++ Builder有一个TBitmap类,其中有一个Scanline属性返回位图像素的内存。然而,与在BMP文件中查看时似乎不同。
我正在尝试将C++ Builder应用程序移植到Java,并希望了解Scanline算法。如果我有文件,如何生成类似于Scanline的内存数组?Scanline背后的确切规范是什么?
澄清一下:BMP是Windows 24位DIB。在代码中没有提供任何其他信息;C++ Builder似乎将其加载到某种类型的内存结构中,但它并非逐字节加载。希望了解那个结构的规范。

“TBitmap”这个名称并不一定与微软的“位图”文件格式有任何关系,你知道吗!在Delphi中,“TBitmap”的“scanline”属性只是一个光栅扫描线 - 没有更多,也没有更少。 - paulsm4
1
位图文件的像素格式是什么,如何将其加载到TBitmap中?您的位图是自上而下存储还是自下而上存储? - David Heffernan
你想了解结构的哪一部分?是TBitmap字段还是像素数据? - David Heffernan
@paul,Delphi中的TBitmap是一个松散的包装器,围绕着Windows HBITMAP。 - David Heffernan
“Scanline” 是一种属性,它为您提供了对 TBitmap 实例背后的 DIBSection 的每个扫描线的索引访问。您具体不理解什么? - Premature Optimization
1个回答

8
一个位图文件以BITMAPFILEHEADER开头,bfOffBits成员指定了图像数据的起始地址。这是Dh(第11-14个字节)处的一个DWORD。Delphi VCL将此结构定义为'windows.pas'中的TBitmapFileHeaderScanLine的最后一行指向该图像数据(自下而上)。VCL在图像的dsBmBITMAP的成员)成员或DIBSECTION中的bmBits成员中具有该值。当请求扫描行时,VCL根据请求的行、每行的像素数(图像的宽度)以及组成像素的位数计算偏移量,并返回指向添加此偏移量到bmBits的地址的指针。这实际上是字节对字节的图像数据。
以下Delphi示例代码将24位位图读入文件流,并将每个读取的像素与Bitmap.ScanLine对应项的像素数据进行比较:
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;

在十六进制编辑器中查找位图文件像素数据开头的图片如下:

enter image description here

3
非常好的回答!我的理解是否正确,即 TBitmap.ScanLine 是自底向上扫描每一行,而文件则是自顶向下扫描每一行,但它们都将以相同的字节序列表示每一行?(至少对于我处理的24位 DIB 是这样。) - SRobertJames
大多数Windows位图是自下而上的,因此位图文件首先有底行。 bmBits只是指向内存中相同数据的指针(除了行对齐在内存布局方面略有不同)。 当请求扫描线时,VCL会在graphics.pas中的TBitmap.GetScanLine中考虑到“自下而上”的特性。 例如,如果您请求最后一行(Bmp.ScanLine [Bmp.Height-1])并且位图是自下而上的,则VCL将返回指向像素数组的第一行的指针(作为扫描线的最后一行)。 - Sertac Akyuz
仍然有困难理解。我在十六进制编辑器中清楚地看到字节是非常不同的。唯一的区别是行顺序颠倒了吗? - SRobertJames
@SRobert - 你考虑了“小端序”吗?在文件中,你会看到BGR顺序。 - Sertac Akyuz
@SRobert - 我在答案中附上了一张图片,希望能消除困惑。 - Sertac Akyuz

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接