Delphi TBitmap - why are Pixels and ScanLine diffe

2019-02-23 18:05发布

问题:

While using a 32 bit TBitmap, I switched from Canvas.Pixels to ScanLine.

I then set the value to Red, only to find it was displayed as blue.

Any idea why?

Here's a code excerpt:

procedure TForm1.FormPaint(Sender: TObject);
var
  varBitmap: TBitmap;
  pLock: PIntegerArray;
  iColor: integer;
begin
  varBitmap := TBitmap.Create;
  varBitmap.PixelFormat := pf32bit;
  varBitmap.Width := 800;
  varBitmap.Height := 600;

  // Set Pixels to Red
  varBitmap.Canvas.Pixels[0, 0] := $0000FF;

  // Shows $FF0000 (blue)
  pLock := varBitmap.ScanLine[0];
  iColor := pLock[0];
  ShowMessageFmt('%x', [iColor]);

  // Set ScanLine to Red
  pLock[0] := $0000FF;

  // Displays a blue pixel
  Canvas.Draw(0, 0, varBitmap);
end;

It seems that somehow TColor is not the same as what is in memory, but that makes no sense.

Any suggestions welcome. ;)

回答1:

32bit pixel data is in $AARRGGBB format. You are setting the Blue component, not the Red component. Use $FF0000 instead of $0000FF. Or better, use the RGB() function.



回答2:

The VCL bitmap class, TBitmap is a wrapper around the Windows native device independent bitmap (DIB). These bitmap objects can store bitmaps in a wide variety of different pixel formats. They can be monochrome with a single bit per pixel, up to 32 bits per pixel, the format that you are using. They can also be used to store palette based bitmaps where each pixel holds an index into a color table.

The two methods of accessing the pixel data that you refer to are the Pixels property of TCanvas and the ScanLine property of TBitmap.

The Pixels property of TCanvas is a wrapper around the GDI GetPixel and SetPixel functions. These is a high-level functions that operate on COLORREF values. The documentation for COLORREF says:

The low-order byte contains a value for the relative intensity of red; the second byte contains a value for green; and the third byte contains a value for blue. The high-order byte must be zero. The maximum value for a single byte is 0xFF.

In other words, a COLORREF value has a fixed way of encoding the pixel color. The GetPixel and SetPixel functions much handle the transformation between the fixed COLORREF form and the underlying raw bitmap pixel data. Note also that a COLORREF cannot represent the alpha value. The COLORREF value is in $00BBGGRR format.

On the other hand, the ScanLine property of TBitmap returns you a pointer into the raw pixel data of the underlying DIB object. The data you work with here is 32bpp pixel data and the convention for that data is that it is stored in $AARRGGBB format. The Windows documentation for 32bpp data says:

The bitmap has a maximum of 2^32 colors. If the biCompression member of the BITMAPINFOHEADER is BI_RGB, the bmiColors member of BITMAPINFO is NULL. Each DWORD in the bitmap array represents the relative intensities of blue, green, and red for a pixel. The value for blue is in the least significant 8 bits, followed by 8 bits each for green and red. The high byte in each DWORD is not used.

So in fact this text is incorrect and out-of-date. The high byte in each DWORD is, in fact, the alpha channel, if it is used.