【问题标题】:Understanding TBitmap.Scanline in Delphi & C++ Builder了解 Delphi & C++ Builder 中的 TBitmap.Scanline
【发布时间】:2011-11-19 22:46:28
【问题描述】:

Delphi & C++ Builder 有一个带有 Scanline 属性的 TBitmap 类,它返回位图像素的内存。当我查看 BMP 文件的十六进制编辑器时,这似乎有所不同。

我正在尝试将 C++ Builder 应用程序移植到 Java,并希望了解 Scanline 中的算法。如果我有文件,如何像 Scanline 那样生成内存阵列? Scanline 背后的具体规格是什么?

澄清:BMP 是 Windows 24 位 DIB。我没有在代码中提供任何其他信息; C++ Builder 似乎将它加载到某种类型的内存结构中,但它不是逐字节的。想知道那个结构的规格是什么。

【问题讨论】:

  • “TBitmap”这个名字并不一定与微软的“位图”文件格式有关,你知道的! Delphi“TBitmap”中的“扫描线”属性只是一条光栅线——不多也不少。
  • 位图文件的像素格式是什么,如何将其加载到 TBitmap 中?您的位图是自上而下存储还是自下而下存储?
  • 你想了解结构的哪一部分? TBitmap 字段,还是像素数据?
  • @paul Delphi TBitmap 是 Windows HBITMAP 的松散包装器
  • Scanline 是一个属性,它为您提供每个扫描线基本的索引访问到 TBitmap 实例后面的 DIBSection。你具体有什么不明白的?

标签: delphi bitmap c++builder porting vcl


【解决方案1】:

位图文件以BITMAPFILEHEADER开头,bfOffBits成员指定图像数据的起始地址。这是 Dh 处的 DWORD(第 11-14 个字节)。 Delphi VCL 的结构在'windows.pas'中定义为TBitmapFileHeader

ScanLine 的最后一行指向此图像数据(自下而上)。 VCL 在dsBm(a BITMAP) 成员的bmBits 成员或图像的DIBSECTION 中具有此值。当请求扫描线时,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;



关于在十六进制编辑器中查找位图文件的像素数据开始的图片:

【讨论】:

  • 超级答案!我是否正确理解唯一的区别是 TBitmap.ScanLine 是底行优先,而文件是顶行优先,但每个都将行表示为相同的字节序列? (至少对于 24 位 DIB,这是我正在处理的。)
  • 大多数 Windows 位图是自下而上的,因此位图文件的底行在前。 bmBits 只是一个指向内存中相同数据的指针(除了行对齐对于内存布局有点不同)。当请求扫描线时,VCL 会在 graphics.pas 中的TBitmap.GetScanLine 中考虑“自下而上”。如果您请求最后一行 (Bmp.ScanLine[Bmp.Height - 1]) 并且位图是自下而上的,VCL 会返回一个指向像素数组第一行的指针(作为扫描线的最后一行)。
  • 仍然无法理解。我在十六进制编辑器中清楚地看到字节完全不同。唯一的区别是行顺序颠倒了吗?
  • @SRobert - 你在考虑“小端”吗?在您按 BGR 顺序看到的文件中。
  • @SRobert - 我在答案中附上了一张图片,希望它能消除混乱。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-08-25
  • 2014-07-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-03-16
相关资源
最近更新 更多