Image Processing
Tech Note

Manipulating Pixels With Delphi's ScanLine Property

by Earl F. Glynn

First appeared in May 1998 Delphi Developer, pp. 1-5, 9
Expanded to include information about all PixelFormats.
Modified and expanded to refer to Lab Reports in efg's Computer Lab.

Contents
Introduction
Scanline and PixelFormat
Additional Examples of Using Scanline
Misuse of Scanline
Optimization
Unexpected Problems Using Scanline


The ScanLine property, new in Delphi 3, allows quick access to individual pixels, but you must know what PixelFormat you're working with before you can access the pixels correctly. In this Tech Note I will demonstrate how to use the ScanLine property to manipulate pixels for a variety of image processing and other applications.   Each of the various PixelFormats is first discussed with some accompanying examples.  After the "theory" about Scanlines, several applications, mostly using pf24bit Scanlines, are described.  Finally, a few unexpected problems using Scanline are described.


How to Use Scanlines by Leonel Togniolli
Intended to be a simpler introduction to scanlines 


Introduction

Delphi 1and 2 provided a Pixels property for direct access to pixels on a canvas. But accessing pixels using this property is very slow. For example, Listing 1 shows how to rotate a bitmap by 90 degrees using the Pixels property. This works fairly well for small bitmaps as shown in the RotatePixels Lab Report but is unacceptably slow for larger bitmaps. The Pixels property, however, did allow treatment of all sizes of pixels (1 bit to 24 bits) in the same way.

Listing 1. Rotating a bitmap using the Canvas.Pixels property
WITH ImageFrom.Canvas.ClipRect DO
BEGIN
  FOR i := Left TO Right DO
    FOR j := Top TO Bottom DO
      ImageTo.Canvas.Pixels[j,Right-i-1] := ImageFrom.Canvas.Pixels[i,j]
END;

(Note: I follow a convention suggested by Jeff Duntemann to capitalize all keywords in Delphi code that I write. I leave code Delphi writes untouched in lowercase.  This capitalization convention was also very common back in the '80s with Pascal to emphasize structure in code -- it is not "shouting" as some may think.)

In Delphi 1 and 2 the alternative to the Pixels property is to use Windows API calls to access the pixel data directly (such as GetDIBits). But accessing pixel data in a DIB (device independent bitmap) using API calls is more than just a little complicated. Once you have the DIB data, you often must get it back into a TBitmap for display in a TImage. Delphi 3's ScanLine and PixelFormat properties of a TBitmap provide a nice alternative.


ScanLine and PixelFormat

A Delphi ScanLine contains the pixel data, but before accessing the pixel data you must understand the ScanLine's layout in memory using the PixelFormat property. Possible PixelFormats defined in GRAPHICS.PAS include pfCustom, pfDevice, pf1bit, pf4bit, pf8bit, pf15bit, pf16bit, pf24bit, and pf32bit.

Kylix Note:  Kylix (K1 - K3) only supports these PixelFormats:  pf1bit, pf8bit, pf16bit, pf32bit, pfCustom (defined in QGraphics.pas).  In Kylix Scanlines operate on Qimage not on QPixmaps.  The first time you call scanline the pixmap is converted to a QImage, which is a bit of a time hit.  See UseNote Post by Mattias Thoma about this conversion.

For all PixelFormats, each Scanline of data is padded to the nearest doubleword boundary.

pfCustom Bitmaps
When Delphi cannot determine the PixelFormat of a bitmap, Delphi assumes the format is pfCustom.   If you try to assign a PixelFormat of pfCustom, an EInvalidGraphic exception is raised.  [See Colin Wilson's UseNet Post about cases involving pfCustom.]

pfDevice Bitmaps
If you create a TBitmap and DO NOT assign either a PixelFormat or the HandleType, a device-dependent bitmap (DDB) is created with a PixelFormat of pfDevice. The HandleType will be bmDDB.  If you set HandleType := bmDIB, or if you specify any PixelFormat (other than pfDevice), you get a Device Independent Bitmap (DIB).

pf1bit Bitmaps
You can save a lot of memory if you only need a single bit per pixel (such as with various masks), but you may need to brush up on bit manipulations in Delphi to access the pixels. With only a single bit of information per pixel, the normal "colors" in a pf1bit bitmap are black and white.  Alternately, a two-color palette can be defined to display any two colors.

Use a TByteArray, which is defined in the Delphi SysUtils.PAS unit, to access a pf1bit ScanLine:

pByteArray = ^TByteArray;
TByteArray = ARRAY[0..32767] OF BYTE;

The width of a pf1bit Scanline in bytes is Bitmap.Width DIV 8 if the width is a multiple of 8. If width is not a multiple of 8, use the following to calculate the number of bytes: 1 + (Bitmap.Width-1) DIV 8.

Using the TByteArray from SysUtils limits a pf1bit Bitmap to be no larger than 32768-by-32768 pixels, which isn't much of a restriction since this would require more memory than is available in many Windows machines at present.  (Actually, very large bitmaps are usually a problem in Windows 95/98.  Size limitations in Windows NT/2000 are not as severe.  See the Very Large Bitmap Lab Report for details.)   I like this construct better than the Borland solution of an ARRAY[0..0] with range checking required to be turned off for all variables (once shown in  Borland FAQ 890D but Borland dropped the page.)

The pf1bit Lab Report demonstrates how to create and work with a pf1bit bitmap, including how to attach two-color palettes.  In this Lab Report, the button tags for the Black (0), White (1). and Stripes buttons are $00, $FF and $55 (01010101 binary). When these buttons are pressed, each byte of each ScanLine is assigned this tag value. Display the Bitmap by assigning it to ImageBits.Picture.Graphic. See Listing 2.

Listing 2. Filling a pf1bit bitmap with a constant mask
procedure TFormPf1bit.ButtonTagFillClick(Sender: TObject);
  VAR
    Bitmap: TBitmap;
    i     : INTEGER;
    j     : INTEGER;
    Row   : pByteArray;
    Value : BYTE;
begin
  // Value = $00 = 00000000 binary for black
  // Value = $FF = 11111111 binary for white
  // Value = $55 = 01010101 binary for black & white stripes
  Value := (Sender AS TButton).Tag;

  Bitmap := TBitmap.Create;
  TRY
    WITH Bitmap DO
    BEGIN
      Width := 32;
      Height := 32;

      // Unclear why this must follow width/height to work correctly.
      // If PixelFormat precedes width/height, bitmap will always be black.
      PixelFormat := pf1bit;

      IF        CheckBoxPalette.Checked
      THEN Bitmap.Palette := GetTwoColorPalette
    END;

    FOR j := 0 TO Bitmap.Height-1 DO
    BEGIN
      Row := pByteArray(Bitmap.Scanline[j]);
      FOR i := 0 TO (Bitmap.Width DIV BitsPerPixel)-1 DO
      BEGIN
        Row[i] := Value
      END
    END;

    ImageBits.Picture.Graphic := Bitmap
  FINALLY
    Bitmap.Free
  END
end;

Danny Thorpe's (Borland R&D) clarification about "Unclear why..." comment above in Listing 2:

The superficial answer is that the sequence changes when the bitmap handle gets created -- as soon as the bitmap has a non zero width and height, it will create a handle.  Setting the PixelFormat after width and height causes a copy to a new handle of the requested pixel format.  Setting the PixelFormat before width and height stores the info until the handle is actually created.

That'd be fine if it weren't for the mono DIB creation code referring to an uninitialized field in the DIB section structure... SrcDIB.dsbm.bmBits = nil is not the case when the internal routine CopyBitmap is called to create a bitmap from scratch (source handle = 0, SrcDIB uninitialized).  This will be fixed in D6.

To create the "g" shown in the pf1bit Lab Report, or to create the arrow, bytes from a constant are assigned to the ScanLines. The following shows the bytes used to create the first two ScanLines of the "g" bitmap:

$00, $FC, $0F, $C0
$07, $FF, $1F, $E0

00000000 11111100 00001111 11000000
00000111 11111111 00011111 11100000

See the ButtonGClick routine for full details.

The Invert button performs a bit-by-bit inversion of the bitmap. The loops that inverts each ScanLine work as follows:

Listing 3. Inverting a pf1bit Bitmap
FOR j := 0 TO Bitmap.Height-1 DO
BEGIN
  RowOut := pByteArray(Bitmap.Scanline[j]);
  RowIn := pByteArray(ImageBits.Picture.Bitmap.Scanline[j]);
  FOR i := 0 TO (Bitmap.Width DIV BitsPerPixel)-1 DO
  BEGIN
    RowOut[i] := NOT RowIn[i]
  END
END;

A pf1bit bitmap has a palette!  See the pf1bit Lab Report  for details.

For a pf1bit bitmap, after a TBitmap is created, its Width and Height must be specified before the PixelFormat.  If not, the pf1bit bitmap will display only in black  (This pf1bit Scanline Enigma was described in a UseNet Post.)  

pf4bit Bitmaps
Working with pf4bit bitmaps is complicated by the fact that each pixel is a nibble within a byte. So like with pf1bit bitmaps, bit manipulations are needed to work with pf4bit bitmaps. With 4 bits of information per pixel, 16 colors are possible. The "standard" 16 VGA colors are often used, but palettes can be a complication of working with pf4bit bitmaps.

For a simple example of a pf4bit bitmap, take a look at the pf4bit sample code.  To see how a pf4bit bitmap, defined from an array of constants, can be used as a brush, see the Brush Bitmaps Lab Report.

For a more informative example, take a look at the Combine pf4bit Bitmaps Lab Report, which shows how to create a pf8bit or pf24bit bitmap from two pf4bit bitmaps. See part of this example in the next section about pf8bit bitmaps.

pf8bit Bitmaps
Working with pf8bit bitmaps is very easy, since each pixel is a byte and can be accessed directly in a TByteArray. Listing 4 shows the nested FOR loops necessary to assign all the byte pixels values in a pf8Bit bitmap. (The code shown in Listing 4 is part of the CycleColors Lab Report, which will be discussed later.)

Listing 4. Assigning values to a pf8bit Bitmap using a TByteArray
VAR
  i  :  INTEGER;
  j  :  INTEGER;
  Row:  pByteArray
. . .
FOR j := 0 TO BitmapBase.Height-1 DO
BEGIN
  Row := BitmapBase.ScanLine[j];
  FOR i := 0 TO BitmapBase.Width-1 DO
  BEGIN
    Row[i] := <pixel value 0..255>;   // assign palette index
  END
END;

ImageShow.Picture.Graphic := BitmapBase;

While working with 8-bit bitmaps with Scanline is easy, the byte value assigned to the Scanline represents the color of the pixel only indirectly.  The Scanline value represents the index into the palette table.   The palette contains the actual R, G and B components of the color.

I have used pf8bit bitmaps in memory out of addressing convenience, but I usually use pf24bit bitmaps so I can control the displayed colors more easily than working with the complexity of palettes in Windows (see example below).

Listing 5 shows how to transfer the Scanline values from a pf4bit Bitmap to a pf8bit Bitmap. See the Combine pf4bit Bitmaps Lab Report for details about how to form a combined palette for the pf8bit bitmap from the two palettes of the original pf4bit bitmaps.

Listing 5. Transferring pf4bit Scanline to a pf8bit Scanline
(palette implications are ignored here)
// Given a pf4bit Bitmap, Bitmap4, transfer the Scanline data to a
// pf8bit Bitmap, Bitmap8.
VAR
  Bitmap4: TBitmap;  // pf4bit Bitmap
  Bitmap8: TBitmap; // pf8bit Bitmap
  i      : INTEGER;
  j      : INTEGER;
  Row4   : pByteArray; // pf4bit Scanline
  Row8   : pByteArray; // pf8bit Scanline
...
Bitmap8 := TBitmap.Create;
TRY
  Bitmap8.Width  := Bitmap4.Width;
  Bitmap8.Height := 2 * Bitmap4.Height;
  Bitmap8.PixelFormat := pf8Bit;

  // Copy pf4bit Scanlines to pf8bit Scanline
  FOR j := 0 TO Bitmap4.Height-1 DO
  BEGIN
    Row4 := Bitmap4.Scanline[j];   // "input" Scanline
    Row8 := Bitmap8.Scanline[j];  // "output" Scanline

    // Width[Bytes] = Width[Pixels] / 2 for pf4bit Bitmap
    // Assume Width is even number
    FOR i := 0 TO (Bitmap4.Width DIV 2)-1 DO
    BEGIN
      Row8[2*i  ] := Row4[i] DIV 16;
      Row8[2*i+1] := Row4[i] MOD 16
    END
  END;

  Image8.Picture.Graphic := Bitmap8;
FINALLY
  Bitmap8.Free
END

Consider another pf8bit (and pf24bit) example: How to use GetPaletteEntries and Convert pf8bit Bitmap to a pf24bit Bitmap Using Scanline.   This example is intended to be instructional, since normally converting from a pf8bit to pf24bit bitmap is as simple as assigning a new value to the PixelFormat property.

In-memory pf8bit bitmaps.  You can create a pf8bit bitmap in memory, along with its palette, but the appearance of this bitmap may depend on the display mode.  If you're using high color (15- or 16-bit color) or true color (24- or 32-bit color) display modes, you can define and see all 256 palette entries in a pf8bit bitmap, as shown in Figure 1.

Figure 1.  256 shades of gray in pf8bit bitmap with high-color display
(Note:  the banding that appears below should not appear when you run the program in true color display mode)

pf8bitHighColorDisplay.jpg (35302 bytes)

However, displaying this same pf8bit bitmap in 256-color mode will result in the loss of 20 of the 256 palette entries as shown in Figure 2.

Figure 2.  256 shades of gray in pf8bit bitmap with 256-color display
(Note:  Windows "took" 20 of the original 256 palette entries!)

pf8bit256ColorDisplay.JPG (38114 bytes)

Windows "takes" 20 of the 256 colors, normally the first 10 and the last 10, for display of panels, buttons, icons, etc. in 256-color mode.  The remaining 236 palette entries can be used by an application.

Figure 3.  Reserved colors in Windows and 236 shades-of-gray palette

ShadesOfGray256ColorModePalette.JPG (18905 bytes)

This D3/D4/D5 pf8bit palette example can be downloaded from here for your experimentation.  Be careful when creating pf8bit bitmaps and their palette entries unless you absolutely know the bitmaps will be displayed in a true color display mode.

See UseNet Posting about how to create TBitmap with 256 shades of gray.

See Dean Verhoeven's E-mail about using SetDIBColorTable to alter the palette of a pf8bit bitmap.

For additional information about palettes, consult Section B, Color, of the Delphi Graphics Algorithms page.

pf15bit and pf16bit Bitmaps
Not all PixelFormats may be available on every machine.  For example, pf15bit bitmaps are not possible on some machines because of limitations in the video adapters/video drivers.  The following check is one way to verify whether your machine supports pf15bit bitmaps:

BEGIN
  bitmap := TBitmap.Create;
  TRY
    bitmap.Width  := 32;
    bitmap.Height := 32;
    bitmap.PixelFormat := pf15Bit;

    IF   bitmap.PixelFormat <> pf15bit
    THEN ShowMessage('Not pf15bit);
...

If you're in doubt, you need to verify the correct PixelFormat was created.  Apparently the PixelFormat is set to pfCustom if the requested PixelFormat is not honored.  See Robert Rossmair's UseNet Post about a possible workaround to this problem.  Also see Colin Wilson's UseNet Post about pf15bit/pf16bit cases involving pfCustom.

Use the pWordArray type defined in SysUtils to access a Scanline of pf15bit or pf16bit pixels. Using this type restricts bitmaps to be no larger than 16384-by-16384 pixels, but this isn't much of a limitation since creating a bitmap that size will exhaust system resources, especially on Windows 95/98 machines.

The logical bit layout within a word for each pixel is

pf15bit:   0 rrrrr ggggg bbbbb
pf16bit:  rrrrr gggggg bbbbb

A pf15bit pixel has 5-bits of R, G and B, but a pf16bit pixel has an extra bit for the color green -- 6 bits instead of just 5. Green has an extra bit since the eye is more sensitive to green than red or blue.

Listing 6 shows how to create a pf15Bit bitmap filled with yellow pixels. Note that pixels with red=31, green=31 and blue=0 are yellow.

Listing 6. Creating a "Yellow" pf15bit Bitmap
VAR
  Bitmap: TBitmap;
  i     : INTEGER;
  j     : INTEGER;

  R     : 0..31; // each RGB component has 5 bits
  G     : 0..31;
  B     : 0..31;

  RGB   : WORD;
  Row   : pWordArray; // from SysUtils
...
Bitmap := TBitmap.Create;
TRY
  Bitmap.Width  := Image1.Width;
  Bitmap.Height := Image1.Height;
  Bitmap.PixelFormat := pf15bit;

  R := 31; // Max Red
  G := 31; // Max Green
  B :=  0; // No Blue

  // "FillRect" using Scanline
  // The bits in each pixel (WORD) are as follows:
  // 0rrrrrgggggbbbbb. The high order bit is ignored.
  RGB := (R SHL 10) OR (G SHL 5) OR B; // "yellow"

  FOR j := 0 TO Bitmap.Height-1 DO
  BEGIN
    Row := Bitmap.Scanline[j];
    FOR i := 0 TO Bitmap.Width-1 DO
      Row[i] := RGB
  END;

  Image1.Picture.Graphic := Bitmap
FINALLY
  Bitmap.Free
END

Also see the similar pf16bit example with R := 31 but G := 63.

When converting each 5-bit color component in a pf15bit bitmap to an 8-bit color component in a pf24bit bitmap, what happens with the "extra" 3 bits?  See efg's UseNet Post about this.  The conversion process is NOT deterministic. The results appear to depend on the video adapter.

According to a UseNet Post by Ian Martin, Photoshop has a problem reading pf16bit bitmaps created by Delphi.  Ian's solution was to used pf24bit bitmaps instead.

Paul Nicholls' UseNet Post about accessing a  pf16bit Scanline using BASM.

pf24bit Bitmaps
For pf24bit bitmaps, I define (I wish Borland would, too) the following in Listing 7, which is similar to the TByteArray type:

Listing 7. TRGBTripleArray Definition
CONST
  PixelCountMax = 32768;

TYPE
  pRGBTripleArray = ^TRGBTripleArray;
  TRGBTripleArray = ARRAY[0..PixelCountMax-1] OF TRGBTriple;

Using a large value for PixelCountMax serves two purposes. No bitmap can be that large (at present), and this effectively turns off range checking on Scanline variables.

While the definition in Listing 7 puts some bounds on the range of pixel index values (and you might even pick a smaller PixelCountMax), the following is perhaps even a simpler definition:

TYPE   TRGBTripleArray = ARRAY[WORD] OF TRGBTriple;

Borland defines a TRGBTripleArray in the Graphics.PAS unit, but it is an ARRAY[BYTE] of TRGBTriple.

Danny Thorpe's (Borland R&D) comments:

That's because that array type is for convenience when dealing with color palettes.   It is not intended for pixel access.  You'll notice that in the evolution from D1 to D4, I've eliminated a lot of temporary memory allocations in the bitmap routines.   One of the big timesavers was not allocating heap memory for temporary color palettes.  Instead I just use a local variable on the stack of the maximum possible size I'll need: 256 RGBTriples.  That burns between 700 and 1k bytes of stack space, but stack is cheap in Win32 and it will be recycled in a few nanoseconds.

That wouldn't work with a TRGBTripleArray type with a WORD range.  Even if it didn't blow the stack, it'd still be prohibitively expensive.

The use of Borland's TRGBTripleArray type will cause problems even for "normal" sized bitmaps since a Byte value must range from 0 to 255. The definition I use above does not have this limitation.

The Borland definition (in the Windows unit) for a TRGBTriple follows:

pRGBTriple = ^TRGBTriple;
TRGBTriple =
PACKED RECORD
  rgbtBlue : BYTE;
  rgbtGreen: BYTE;
  rgbtRed  : BYTE;
END;

Listing 8 shows how to create a pf24Bit array filled with yellow pixels. Note that pixels with red=255, green=255and blue=0 are yellow.

Listing 8A. Creating a "Yellow" pf24bit Bitmap
VAR
  i  :  INTEGER;
  j  :  INTEGER;
  Row:  pRGBTripleArray;
...
Bitmap := TBitmap.Create;
TRY
  Bitmap.PixelFormat := pf24bit;
  Bitmap.Width  := ImageRGB.Width;
  Bitmap.Height := ImageRGB.Height;

  FOR j := 0 TO Bitmap.Height-1 DO
  BEGIN
    Row := Bitmap.Scanline[j];
    FOR i := 0 TO Bitmap.Width-1 DO
    BEGIN
      WITH Row[i] DO
      BEGIN
        rgbtRed   := 255; // yellow pixels
        rgbtGreen := 255;
        rgbtBlue  :=   0;
      END
    END
  END;

  // Display on screen
  ImageRGB.Picture.Graphic := Bitmap;

FINALLY
  Bitmap.Free
END

In Delphi 4.02 or later, this somewhat simpler approach will work using a CONST TRGBTriple:

Listing 8B. Using a CONST TRGBTriple
CONST
  Yellow:  TRGBTriple = 
           (rgbtBlue: 0; rgbtGreen: 255; rgbtRed: 255);
VAR
  i  :  INTEGER;
  j  :  INTEGER;
  Row:  pRGBTripleArray;
...
Bitmap := TBitmap.Create;
TRY
  Bitmap.PixelFormat := pf24bit;
  Bitmap.Width  := ImageRGB.Width;
  Bitmap.Height := ImageRGB.Height;

  FOR j := 0 TO Bitmap.Height-1 DO
  BEGIN
    Row := Bitmap.Scanline[j];
    FOR i := 0 TO Bitmap.Width-1 DO
    BEGIN
      Row[i] := Yellow
    END
  END;

  // Display on screen
  ImageRGB.Picture.Graphic := Bitmap;

FINALLY
  Bitmap.Free
END

How can a TColor be converted to a TRGBTriple?

FUNCTION  ColorToRGBTriple(CONST Color:  TColor):  TRGBTriple;
BEGIN
  WITH RESULT DO
  BEGIN
    rgbtRed   := GetRValue(Color);
    rgbtGreen := GetGValue(Color);
    rgbtBlue  := GetBValue(Color)
  END
END {ColorToRGBTriple};  

Be aware that a TColor can have several internal formats and the above assumes a format of $00bbggrr, which can be created using the RGB function.

How can a pf24bit bitmap be converted to a pf15bit bitmap?   Let's consider the "hard way" using Scanline and the "easy way" using PixelFormat.  See Listing 9.

Listing 9. Conversion of pf24bit bitmap to pf15bit
VAR
  Bitmap15:  TBitmap;
  Bitmap  :  TBitmap;
  i       :  INTEGER;
  j       :  INTEGER;
  Row15   :  pWordArray;
  Row24   :  pRGBTripleArray;
...
Bitmap := TBitmap.Create;
TRY
  Bitmap.LoadFromFile('N:\Images\Flowers\Tulip3.BMP');

  // Convert pf24bit bitmap to pf15bit bitmap the "hard way"
  Bitmap15 := TBitmap.Create;
  TRY
    Bitmap15.Width  := Bitmap.Width;
    Bitmap15.Height := Bitmap.Height;
    Bitmap15.PixelFormat := pf15bit;

    FOR j := 0 TO Bitmap.Height-1 DO
    BEGIN
      Row15 := Bitmap15.Scanline[j];
      Row24 := Bitmap.Scanline[j];
      FOR i := 0 TO Bitmap.Width-1 DO
      BEGIN
        WITH Row24[i] DO
          Row15[i] := (rgbtRed   SHR 3) SHL 10 OR
                      (rgbtGreen SHR 3) SHL  5 OR
                      (rgbtBlue  SHR 3)
      END
    END;

    Bitmap15.SaveToFile('Tulip3-15A.BMP');
  FINALLY
    Bitmap15.Free
  END;

  // Convert pf24bit bitmap to pf15bit bitmap the "easy way"
  ASSERT(Bitmap.PixelFormat = pf24bit); // verify pf24bit
  Bitmap.PixelFormat := pf15bit; // Assignment results in conversion
  Bitmap.SaveToFile('Tulip3-15B.BMP')
FINALLY
  Bitmap.Free
END

// Tulip3-15A.BMP and Tulip3-15B.BMP should be identical bitmaps

In the example above, the two bitmaps are identical on a pixel-by-pixel basis, but they have different CRC-32 values -- so the files are not completely identical.  Perhaps, some "header" value is slightly different in the two files.

A better approach in reducing 24-bit pixels to 15-/16-bit pixels is given in Phil McRevis' UseNet Post where he suggests using Floyd-Steinberg dithering.

Other examples:

   How to create a Bitmap from numeric data? (D5)

efg's UseNet Post about how to flip a TBitmap by writing Scanlines to a MemoryStream and loading the Scanlines of a second TBitmap in the flipped order.

pf32bit Bitmaps
Analogous to the TRGBTriple, define a TRGBQuadArray to work with  pf32bit bitmaps.  See Listing 10.

Listing 10. TRGBQuadArray Definition
CONST
  PixelCountMax = 32768;

TYPE
  pRGBQuadArray = ^TRGBQuadArray;
  TRGBQuadArray = ARRAY[0..PixelCountMax-1] OF TRGBQuad;

A simpler definition that eliminates the need for PixelCountMax would be:

TYPE   TRGBQuadArray = ARRAY[WORD] OF TRGBQuad;

The Borland definition (in the Windows unit) for a TRGBQuad follows:

pRGBQuad = ^TRGBQuad;
TRGBQuad =
PACKED RECORD
  rgbBlue : BYTE;
  rgbGreen: BYTE;
  rgbRed  : BYTE;
  rgbReserved:  BYTE
END;

As far as color information is concerned, note that the TRGBQuad is equivalent to the TRGBTriple -- both records provide for 24-bits of color information with 8 bits for Red, 8 bits for Green and 8 bits for Blue.  The TRGBQuad had an extra rgbReserved field, for what is sometimes called the "alpha" channel (especially in the Mac world).  The alpha channel is used by some applications to pass grayscale mask information, but there is no consistent definition of what the alpha channel may contain.  NOTE:  the contents of the alpha channel are in general unpredictable.  See this UseNet post by Stan that "you NEVER know the state of the alpha channel byte."

Danny Thorpe's (Borland R&D) comments:

NT supports (nearly) arbitrary arrangements of RGB bits.  The only limitation is the bits of each color element must be contiguous.  It would be trivial to load a big-endian RGB file (like Sun's RAW or whatnot) by simply creating the DIBSection with BGR masks.  Just for grins, I once created a 32bpp DIBSection with 24 bits of color info, then created another DIBSection using the same pixel memory buffer but defining the pixels in the "alpha channel" reserved byte of the 32bpp data.

One novel use of a pf32bit Scanline would be to use a TBitmap as a dynamic array of Single values from numerical calculations.  Such large arrays could be saved to disk with the Bitmap SaveToFile method or loaded from disk with the LoadFromFile method.   This numeric array can even be "displayed" in a TImage, where each pixel is the floating-point fraction -- the rgbReserved part of the Single is the exponent of the floating-point number and this would not normally affect the image.  Images created this way from scientific/engineering calculations can show some very interesting patterns.  See an example of using a pf32bit bitmap to hold and display a matrix of IEEE "single" floating-point values in the Lyapunov Exponents  Lab Report.  The Fractals Show 2 Lab Report shows how to use a pf32bit bitmap as a matrix of 4-byte integers.

In limited tests, the use of pf32bit Scanlines was about 5% faster than pf24bit Scanlines in performing an identical task.  This is probably due to alignment of pf32bit values on 32-bit word boundaries.  So using the 3-bytes/pixel pf24bit Scanlines does involve some extra overhead, but usually not enough given the space savings of pf24bit over pf32bit Scanlines.

Ken Florentino's example of using pf32bit bitmaps with assembly language.

efg's UseNet Post about "pf32bit enigma: 'alpha byte' indeterminate in new bitmap?"

efg's UseNet Post about typecasting a shifted TColor to a TRGBQuad:  

VAR
  Color :  TColor;
  yellow:  TRGBQuad;
..
  Color := clYellow;
  yellow := TRGBQuad(Color SHL 8);

So starting with a TColor with an internal format of  $00bbggrr, if you shift this 8 bits left, you get $bbggrr00, which matches the internal format of a TRGBQuad.  A typecast then allows conversion of the shifted TColor to a TRGBQuad.

Kylix Note:  efg's UseNet Post with Kylix pf32bit example

PixelFormat Conversion
Assign a new PixelFormat to convert from one PixelFormat to another.  This works well if converting from a lower bits/pixel to a higher bits/pixel, or when converting between PixelFormats that do not have any palette implications, e.g., pf15bit to/from pf24bit.    But assigning a new PixelFormat does nothing to ensure you have the correct palette if you need one. 

A pf24bit bitmap can have thousands of colors.   For example, the Mandrill monkey bitmap is an array of 512-by-512 = 262,144 pixels, but has 230,427 unique RGB colors!  (The Show Image utility gives the number of unique colors in an image -- look in the lower left corner of the display.)  In 256-color (pf8bit) display mode, Windows normally reserves 20 of the colors for display of buttons, panels, icons, etc., leaving only 236 colors to an application.  To display the Mandrill monkey image, an algorithm is needed to pick the best 236 colors of the 230,427 colors in the image. 

See the Show Demo One Lab Report for an algorithm that creates a palette to display a 24-bits/pixel image in 256-color mode.  What this example does not do, is to take each TRGBTriple in the pf24bit Scanline, and lookup the "nearest" color in the palette -- it does not convert a pf24bit bitmap to a pf8bit bitmap. 

You're extremely lucky if all your pf24bit images display correctly in 256-color mode.


Other Scanline Notes

Question (Paul Nicholls's UseNet Post): "If I wanted to store the pointers to some ScanLines from a bitmap into some variables, when would I have to update these variables to have the pointers be valid again?"

Answer (Steve Schafer's UseNet Post ): "I don't think it's ever really safe to keep a copy of a ScanLine pointer. Even if it were safe to do so under some circumstances in one version of Delphi, it might not be safe under those same circumstances in the next."

"Obtain a ScanLine pointer, use it immediately, and then throw it away."

Peter Haas' UseNet Post about using Bitmap.Dormant after setting PixelFormat to fix a problem in D1-D4 with the TBitmapInfoHeader

Finn Tolderlund's UseNet Post about Scanline and FreeImage

Matthijs Laan's UseNet Post with MMX example to XOR bitmaps

Andrew Rybenkov's UseNet Post about using GetDIBits/SetDIBits as an alternative to Scanline

Danny Thorpe's UseNet Post about Bilinear Interpolation and Scanline


Additional Examples of Using Scanline

The Daisy Program
The DAISY program uses the same technique as shown in Listing 8 to create a pf24bit image. The details in the DrawDaisy method show how this image is created. Briefly, the red and green planes of this image contain all the variations of these colors. The blue plane contains the "daisy" with only the brightest values of blue. You will need an 800-by-600 screen, and 15-bit or higher color for this application.

Displaying a 24-bit true color image is easy in 15-bit or higher color video modes since Windows only has palettes with 256 color or lower video modes. If you try to display a 24-bit bitmap with only 256 colors, you are at the mercy of the current palette in Windows.   (See the Show Demo One Lab Report for an alternative.)


The Split Program
The SPLIT program is a simple image processing program for studying the color planes of an image. The SPLIT program reads the DAISY.BMP file created by the DAISY program — or any other 24-bit color BMP file. Pressing the speed buttons along the left side results in the display of the corresponding red, green and blue color planes.

The SPLIT programs stores the original image in a BitmapRGB bitmap and keeps it available as the base image for creating the other bitmaps.

When the Monochrome Checkbox next to the RGB Composite button is checked, each red, green and blue component of the pixel is assigned the same intensity, where Intensity is defined as (R + G + B) / 3. The resulting bitmap, BitmapGray, is assigned back to Image.Picture.Graphic for display. See Listing 11.

Listing 11. MakeShadesofGrayImage from RGB Composite Image
PROCEDURE TFormSplit.MakeShadesOfGrayImage;
  VAR
    Gray   :  INTEGER;   
    i      :  INTEGER;
    j      :  INTEGER;
    rowRGB :  pRGBTripleArray;
    rowGray:  pRGBTripleArray;
BEGIN
  Screen.Cursor := crHourGlass;
  TRY
    FOR j := BitmapRGB.Height-1 DOWNTO 0 DO
    BEGIN
      rowRGB := BitmapRGB.Scanline[j];
      rowGray := BitmapGray.Scanline[j];
      FOR i := BitmapRGB.Width-1 DOWNTO 0 DO
      BEGIN
        // Intensity = (R + G + B) DIV 3
        WITH rowRGB[i] DO
          Gray := (rgbtRed + rgbtGreen + rgbtBlue) DIV 3;

        WITH rowGray[i] DO
        BEGIN
          rgbtRed   := Gray;
          rgbtGreen := Gray;
          rgbtBlue  := Gray
        END

      END
    END;
  FINALLY
    Screen.Cursor := crDefault
  END
END;

Other "better" methods of creating a gray scale can be used.  See the Spectra Lab Report for two alternatives involve "Y" -- the gray scale used to convert color information (YUV/YIQ) for display on black-and-white TV sets.

To display the Red plane, the red pixel values (that is, the rgbtRed values) are assigned to another bitmap, namely BitmapR, but the pixel values for the blue and green components are assigned a 0 value. When the Monochrome Checkbox is checked, the red pixel values are assign to rgbtRed, rgbtBlue and rgbtGreen resulting in a "shades of gray" intensity map.

The explanation of the conversion of RGB (Red-Green-Blue) to HSV (Hue-Saturation-Value) is in the HSV Lab Report. An excellent book about color conversions (and anything about Computer Graphics) is Computer Graphics Principles and Practice by Foley, et al, Addison-Wesley, 1996. Or, take a look at the general Color Information in efg's Reference Library.   For Delphi color information look at Section B, Color, of the Delphi Graphics Algorithms page.


Rotation of an Image
As discussed earlier, rotation of a bitmap is too slow with the canvas' Pixels property. But the use of the ScanLine property makes rotation of a bitmap any angle fairly fast.   The Rotate Scanline Lab Report shows that the rotation of the 640-by-480 pf24bit bitmap degrees clockwise takes slightly more than a second on a 166 MHz Pentium.

Each pixel is not rotated to a new position in a "forward" direction. You start with the rotated image and consider where each pixel was in the original image. You rotate "back" and take the closest pixel from the original image. Because of the integer math in the rotation, certain artifacts can be introduced with a rotation. Usually anti-aliasing isn't necessary when rotating 24-bit images of most objects. Anti-aliasing may be necessary if rotating thin lines or text, however.

Also see the Flip/Reverse/Rotate Lab Report.

Color Cycling in a pf24bit Bitmap
The old VGA palette tricks won't work with 24-bit color images. Remember: Windows only uses palettes with 256 color display modes or lower. Palettes are not used with high color (15 or 16-bit color) or true color (24-bit color) video modes.

When Windows uses palettes, you only have 236 colors available for your application since usually the bottom 10 and top 10 colors of the palette are fixed by Windows. Even though this technique of color cycling with a pf24bit bitmap is a little slow without using assistance from the hardware, the Color Cycle Lab Report,  uses a lookup table of 1280 colors (5*256), which is a far greater number than in a normal Windows palette.

The FormCreate method defines the entries in the ColorCycle ARRAY OF TRGBTriples. The first 256 colors are shades of red, followed by 256 shades of green and 256 shades of blue. The fourth set of 256 is somewhat similar to a "Fire Storm" palette used by the FractInt fractal program. The fifth set defines shades of gray.

After starting the CycleColors program, the fractal image takes about 90 seconds to create on a 166 MHz Pentium. (The math behind this image is outside the scope of this article.) When the image has completed, the Cycle Colors Checkbox is enabled. This fractal image is a pf8bit bitmap (created much like the method in Listing 4.)

With the Cycle Color checkbox checked, the application's OnIdle hander takes the pf8bit bitmap, BitmapBase, and defines all the RGB pixels in BitmapRGB using the ColorCycle array. See Listing 12 below. Uncheck the Cycle Colors Checkbox to stop the change of colors. While somewhat slow, this color cycling , which is done completely in software, is fairly impressive.

Listing 12.   Using IdleAction to Cycle Colors

PROCEDURE TFormColorCycle.IdleAction(Sender: TObject; VAR Done: BOOLEAN);
  VAR
    i     :  INTEGER;
    index :  INTEGER;
    j     :  INTEGER;
    RowIn :  pByteArray;
    RowRGB:  pRGBTripleArray;
BEGIN
  IF   NOT CheckBoxCycle.Checked
  THEN Done := TRUE
  ELSE BEGIN
    INC (CycleStart);
    IF   CycleStart >= ColorList.Count
    THEN CycleStart := 0;

    LabelCycle.Caption := IntToStr(CycleStart);

    FOR j := 0 TO BitmapBase.Height-1 DO
    BEGIN
      RowIn  := BitmapBase.ScanLine[j];
      RowRGB := BitmapRGB.ScanLine[j];

      FOR i := 0 TO BitmapBase.Width-1 DO
      BEGIN
        index := CycleStart + RowIn[i];
        IF   index >= ColorList.Count
        THEN index := index - ColorList.Count;

        RowRGB[i] := pRGBTriple(ColorList.Items[index])^
      END
    END;

    ImageShow.Picture.Graphic := BitmapRGB;

    Done := FALSE;
  END
END {IdleAction};

Example of image resampling using Scanline (D3-D5)
Shrink/Flip/Reverse image example (D3)
Example of comparing two TBitmaps (D3-D5)


Misuse of Scanline

by Danny Thorpe (Borland R&D)

Many people are too quick to use Scanlines[] to perform operations that are better handled by GDI and raster ops.  Scanlines[]'s main advantage is that it gives you direct access to the pixels with a minimum of overhead.  That doesn't mean that Scanlines[] is the fastest way to manipulate the bitmap contents.  Most video cards today contain very sophisticated, purpose-built blit engines that can do raster ops on bitmap data usually much faster than the main CPU.  I've seen a video card ROP an entire bitmap faster than the main CPU can finish even one scanline.  The CPU can only do one pixel at a time, and each access is a memory reference that could stall the CPU pipeline because of a cache miss.  If the DIBSection lives in video memory, the CPU has to cross the PCI or AGP bus to get to it.  The video card has none of these restrictions - it has immediate and direct access to low-latency video memory and most blit engines are designed to ROP multiple pixels at once.  Filling video memory with zeros or color values (i.e., brush) is also a high priority for video hardware.  I've heard that the secret to one high-end 3D video card's extremely high zero fill rate is that instead of filling the RAM with zeros, they actually turn off the power to the RAM banks!

An example:  the Listing 3 example of inverting a bitmap by iterating through the pixels and individually NOTting each pixel.  I seriously doubt that this will [have the same performance] as a PatBlt(Bitmap.Canvas.Handle, 0, 0, Bitmap.Width, Bitmap.Height, DSTINVERT), even on a plain old VGA frame buffer.  Listing 3 is fine as an educational tool, but as we both know from the Delphi documentation that mentions Canvas.Pixels[], people will adopt sample code as gospel, no matter how inappropriate it is for production work.  ;>

Same observation for Listing 6:  While the function is described as equivalent to "FillRect" with a yellow brush, no mention is made that the FillRect will probably be many times faster or that the example is intended for instruction, not for production.

Similarly, merging paletted bitmaps can be done without pixel manipulations.  The trick is to merge the palettes first, assign the new palette to the destination bitmap, and then blit the source bitmaps to the destination.  GDI and the video hardware will do the color matching for you.

Also, since D4, assigning a palette handle to a bitmap object will re-vector the bitmap pixels to the nearest available colors in the new palette.  D3 and earlier did not do this.


Optimization

1.  Minimizing Scanline Accesses

Several times in the various Delphi newsgroups someone suggests that the number of Scanline accesses should be minimized as an optimization technique since considerable overhead is thought to exist with each call.  For example, instead of accessing Scanline once per row (as in many of the examples here), with some extra work Scanline can be accessed only twice at the beginning of the loop stepping through the rows.  A "pointer" can be incremented using integer arithmetic instead of a Scanline call for each row.  See Listing 13 for an an example.

Listing 13.  Minimizing Scanline Accesses

procedure TForm1.ButtonOptimizedClick(Sender: TObject);
  VAR
    Bitmap       :  TBitmap;
    Delta        :  INTEGER;
    i            :  INTEGER;
    j            :  INTEGER;
    k            :  INTEGER;
    LoopCount    :  INTEGER;
    row          :  pRGBTripleArray;
    ScanlineBytes:  INTEGER;
    StartTime    :  DWORD; // Use DWORD to keep D3 and D4 happy
begin
  LabelOptimized.Caption := '';
  LoopCount := SpinEditTimes.Value;

  StartTime := GetTickCount;
  FOR k := 1 TO LoopCount DO
  BEGIN
    Bitmap := TBitmap.Create;
    TRY
      Bitmap.PixelFormat := pf24bit;
      Bitmap.Width  := 640;
      Bitmap.Height := 480;

      row := Bitmap.Scanline[0];
      ScanlineBytes := Integer(Bitmap.Scanline[1]) - Integer(row); 


      FOR j := 0 TO Bitmap.Height-1 DO
      BEGIN
        FOR i := 0 TO Bitmap.Width-1 DO
        BEGIN
          WITH row[i] DO
          BEGIN
            rgbtRed   := k;
            rgbtGreen := i MOD 256;
            rgbtBlue  := j MOD 256;
          END
        END;

       INC(Integer(Row), ScanlineBytes);
      END;
      ImageOptimized.Picture.Graphic := Bitmap
    FINALLY
      Bitmap.Free
    END
  END;
  Delta := GetTickCount - StartTime; // ms
  LabelOptimized.Caption := IntToStr(Delta) + ' Total ms; ' +
    Format('%.1f', [Delta / LoopCount]) + ' ms/bitmap';

end;

But how effective is this technique?  To determine how effective this technique is, a small D3/D4 program ScanlineTiming was written to compare the "brute force" technique of access the Scanline property for each row of a bitmap with the technique shown above in Listing 13.  The results are shown in the following table:

Time[ms] (Mean Standard Deviation) Per 640-by-480 Bitmap
(based on 5 trials each of which created 100 bitmaps with the ScanlineTiming program)

Pentium Speed [MHz] Brute Force [ms/bitmap] Optimized [ms/bitmap] Savings [ms/bitmap]
120 188.3 0.6    187.9 0.9    0.4 
166       70.9 0.07  69.8 0.04  1.1 
400 16.27 0.02 15.40 0.00 0.87
450 17.69 0.35 16.78 0.32 0.91

In my opinion, this optimization technique is probably the last thing one should worry about while working with a bitmap -- all other algorithms should be scrutinized first.  Rarely is saving 1 millisecond per bitmap worth the optimization -- perhaps with some real-time applications this savings would be significant enough.   On the faster machines this represents only about a 5% savings.

E-Mail comments from Robert Lee about this "Optimization":

...the utility of this technique is likely to vary depending upon what you are doing to/with the bitmap.  For instance, half the time in your example is spent creating and freeing the bitmap itself. If the bitmap(s) in question were already created then the relative effect of the technique would increase to 10%.  Also, if the dimensions were changed to something long and skinny, or if the operation involved two or more bitmaps (say a blend) then the effect would get progressively more significant.  With a little fooling round I got to a 2x difference just by resizing the bitmap.

My preferred strategy for reporting the impact of optimization techniques is to state both some sort of "best case" number and a "typical" number.  Thus the typical number might be 5% (probably even less), but the "best case" effect could be 2-4x."  [Thanks, Robert, for the clarification.]

Danny Thorpe's (Borland R&D) comments:

On the question of performance impact of calling Scanlines[] a lot:   Yes, there is a potential performance hit.  The DIBSection's memory buffer is not guaranteed to be coherent (reflect the most recent GDI operations) unless you call GDIFlush before accessing the pointer.  The ScanLines[] property has to call GDIFlush to ensure the buffer contents are in sync.  If the bitmap has been modified by GDI calls, then the call to GDIFlush could block waiting for the GDI pipeline to empty.   If no modifications are still pending, GDIFlush should return immediately, but there will always be the overhead of making an additional call.

Additionally, TBitmap.GetScanlines() calls TBitmap.Changing, which may have to create a completely new, unshared copy of the image if the bitmap is shared.   That will completely blow out performance on the first touch of Scanlines, and it chips away a little even when the bitmap isn't shared.

The technique shown in Listing 13 for minimizing time lost to ScanLines[] was developed by me while writing the TJPEGImage class.  There is more at work here than meets the eye, though, which should be mentioned with that listing.   The easiest way to eliminate calls to ScanLines[] is to simply do your own pointer arithmetic to advance the start-of-scanline buffer pointer to the next scanline.   There are two hazards in doing that:  1) correctly dealing with the DWORD alignment of scan lines, and 2) the  physical layout of scanlines in the memory buffer.  DIBs can be oriented as "top-down", where the first row of pixels in the bitmap reside in the first bytes of memory in the buffer, or as "bottom-up", where the first row of pixels reside in the last bytes of memory and grow upward in memory.  Oddly enough, "bottom-up" is the more common DIB orientation, possibly due to BMP's OS/2 origins.

The technique of subtracting the address of Scanline[0] from the address of Scanline[1] solves both problems very nicely.  It gives you the distance between scanlines including DWORD padding, if any, and the sign of the delta implicitly indicates the DIB memory orientation.  This eliminates the need for performance-robbing conditional statements in the critical pointer advancement loop.   Just increment the pointer by that delta, and it will put you on the next scanline, be it above or below the current address.

Comments from Nono40 (Nov 2003):

I have one note about the "Optimization" (Accessing ScanLines only twice for a bitmap ). We usually use (0,0) at the top left corner of a bitmap. But in that case points like ( Cos(t) , Sin(t) ) will go clockwise.

If we want to use a "normal" drawing with ( Cos(t) , Sin(t) ) going anti-clockwise ( and (0,0) pixel at bottom left ). There is no need to change "y" by "Height-Y-1" each time. There is only to change line 0 pointer and Delta value between two lines:

PtrBmp:=BitMap.ScanLine[BitMap.Height-1]; Delta:=Integer(BitMap.ScanLine[BitMap.Height-2])-Integer(PtrBmp);

Of course, after the "Y" signification is not the same in ScanLine access and Canvas access. But it's not a problem if we use only ScanLine access.

Just a personnal point of view: I prefere pf32bit, as i use assembly language with ScanLine. It's easier to access pixels with a complete Integer, even if 25% of space is lost. With ScanLine if one register olds the ScanLine pointer of one Line ( ex EDI ), and another register olds "X" value ( ex EAX). Read/Write of the pixel is very simple using bulit-un index multiplier:

MOV Dword Ptr [EDI+EAX*4],clYellow

(example : http://nono40.developpez.com/sources/source0069.html)

Please send me feedback if you think I missed something in this timing test or if you have significantly different results.

2.  Accessing Scanlines via Assembly Language

Paul Nicholls' UseNet Post about accessing a  pf16bit Scanline using BASM.
Robert Rossmair's UseNet Post about calculating the average color of a bitmap
Ken Florentino's example of using pf32bit bitmaps with assembly language.

3.  Copying Raw Data to Scanline Area

In a borland.public.delphi graphics post Mikael Stalvik wanted to create an array of 4-byte integers in memory and then fill a pf32bit bitmap with them.  I created two examples for Mikael, one "unoptimized" and another "optimized."  But the performance of both approaches was nearly the same.

The "unoptimized" approach involved filling the scanlines in increasing order (from top-to-bottom): 

Listing 14.  "Unoptimized" Way to Fill Scanlines from In-Memory Array of Integers

procedure TForm1.Button1Click(Sender: TObject);
 TYPE
    TRGBQuadArray = ARRAY[WORD] OF INTEGER;
    pRGBQuadArray = ^TRGBQuadArray;
  VAR
    i,j   :  INTEGER;
    Bitmap:  TBitmap;
    row   :  pRGBQuadArray;
    Start :  DWORD;
begin
  Start := GetTickCount;
  Bitmap := TBitmap.Create;
  TRY
    Bitmap.PixelFormat := pf32bit;
    Bitmap.Width  := 640;
    Bitmap.Height := 480;
    FOR j := 0 TO Bitmap.Height-1 DO
    BEGIN
      row := Bitmap.Scanline[j];
      FOR i := 0 TO Bitmap.Width-1 DO
      BEGIN       
        row[i] := i*j    // something to make interesting bitmap
      END
    END;
    Image1.Picture.Graphic := Bitmap;
    ShowMessage( IntToStr(GetTickCount-Start) + ' ms')
  FINALLY
    Bitmap.Free;
  END
end;         


The "optimized" approach involved a single "Move" operation of all the in-memory data to the scanline area.  [Note:  There are some scanline padding requirements -- each scanline must be a multiple of 8-bytes long -- that are ignored here.  Again, the following assumes a bitmap has the proper width so no padding occurs at the end of each scanline.]

But what is the memory address of each pf32bit pixel in a TBitmap?  Insert the following IF statement inside the above nested loops to find out:

 // Show "corner" addresses of pixels in bitmap
IF   ((i=0) OR (i=Bitmap.Width-1)) AND
     ((j=0) OR (j=1) OR (j=Bitmap.Height-1))
THEN Memo1.Lines.Add('(' + IntToStr(i) + ', ' +  IntToStr(j) + ')  = ' +
                           InttoHex( Integer(@row[i]),8 ));

Here are the results from a 640-by-480 pf32bit bitmap:

Addresses of certain pixels in a particular pf32bit TBitmap

row \ column 0 ... 639
0 $8374E600 ... $8374EFFC
1 $8374DC00 ... $8374E5FC
... ... ... ...
479 $83623000 ... $836239FC

Note the largest and smallest address values.  The beginning address of the bitmap is the 0th pixel of row 479, and the highest-address pixel is pixel 639 in row 0.  So the memory addresses increase left-to-right, but from bottom to top.

But this isn't the only possibly arrangement in a TBitmap.  As Alex Denissov pointed out in the newsgroup, when the BitmapInfo.bmiHeader.biHeight value is negative, logical (0,0) is the top-left corner, and if positive, it is bottom-left one.   TBitmap normally has (0,0) at the top-left corner.  

One easy to find out if the addresses of the scanlines are increasing or decreasing is to compute:

ScanlineBytes := Integer(Bitmap.Scanline[1]) - Integer(Bitmap.Scanline[0]);

How many bytes are in the bitmap?  Compute ($8374EFFC + 4) - $83623000 = $12C000 = 1,228,800 = 4*640*480, which is the expected value.

To use "Move" we need the pointers for the "from" and "to" addresses.  The "to" address is the address of the 0th pixel of the last row, as shown below: 

Listing 15.  "Optimized" Way to Fill Scanlines from In-Memory Array of Integers
procedure TForm1.Button1Click(Sender: TObject);
  VAR
    Bitmap:  TBitmap;
    x     :  ARRAY OF Integer;
    i,j   :  INTEGER;
    p     :  ^Integer;
    Start :  DWORD;
begin
  SetLength(x, 640*480);
  FOR j := 0 TO 479 DO
    FOR i := 0 TO 639 DO
      x[640*j + i] := i*j;   // something interesting 
  Start := GetTickCount;
  Bitmap := TBitmap.Create;
  TRY
    Bitmap.PixelFormat := pf32bit;
    Bitmap.Width  := 640;
    Bitmap.Height := 480;
    p := Bitmap.Scanline[Bitmap.Height-1];  // starting address of "last" row
    Move(x[0], p^, Bitmap.Width*Bitmap.Height*SizeOf(Integer));
    Image1.Picture.Graphic := Bitmap;
    ShowMessage( IntToStr(GetTickCount-Start) + ' ms')
  FINALLY
    Bitmap.Free;
  END
end;           

GetTickCount was used in both cases to measure how effective the approach was.  I couldn't detect a significant difference between these approaches on a 650 MHz Pentium III.

Two final notes:

1.  The two methods shown above are not identical.  The resulting bitmaps are flipped top-to-bottom, which was ignored above.

2.  The "raw" binary integer data above from the computation of "i*j" (from the pixel's coordinates) results in an interesting bitmap.


Unexpected Problems Using Scanline (D3-D4)

1.  Using Scanline Only after a TBitmap.Assign Will Corrupt the Original Bitmap (problem in D3/D4; fixed in D5)
TBitmap.Assign should be a "deep copy" (all data copied) versus a "shallow copy" (only a pointer copied).  Actually, because of reference counting, the Assign is a shallow copy until something "significant" happens at which time an independent copy of the bitmap is made.  This can be very subtle to catch sometimes.  

Below, the assignment of a single Pixels value changes the "shallow copy" to a "deep copy."  The need for this is either a bug or a bad design.

BitmapAlign := TBitmap.Create;
TRY
  // Assign doesn't directly make an independent copy of a bitmap.
  // Problem affects Delphi 3 and 4.
  BitmapAlign.Assign(Bitmaps[2]);

  // Force BitmapAlign to be totally independent of Bitmaps[2]
  // This is an unfortunate kludge needed if Scanline is the only
  // method to access pixel data. Without this kludge, the
  // original bitmap and "new" bitmap are operating on the same
  // pixel data!!!
  BitmapAlign.Canvas.Pixels[0,0] := Bitmaps[2].Canvas.Pixels[0,0];

<Scanline-only access of pixel data in BitmapAlign here>

FINALLY
  BitmapAlign.Free
END

Here's some sample code that shows this problem:  ScreenBitmapAssignEnigma.PAS.

Finn Tolderlund's solution (after digging through the VCL) was to issue this command:

   BitmapAlign.Assign(Bitmaps[2]);
   BitmapAlign.FreeImage;
   <Scanline-only access of pixel data in BitmapAlign here>

The FreeImage has the effect of converting the shallow bitmap copy to a deep bitmap copy.

[Thanks Finn for this tip!  --efg, 21 Jan 2001]

2.  The pf24bit Scanline Assignment Enigma (a rare Delphi optimization bug)
This problem appears in Delphi 3 and 4, but appears to be fixed with the Delphi 4 Update Pack 2 (Build 5.104).  Thanks to
Marko Peric for informing me that Delphi 4 Update Pack 2 fixes this problem.  [efg, 14 Dec 1998]

The following assignment of a TRGBTriple from one pf24bit Scanline to another will result in an Access Violation if Delphi optimization is enabled:

VAR
  BitmapA  : TBitmap;
  BitmapB  : TBitmap;
  i        : INTEGER;
  j        : INTEGER;
  RGBTriple: TRGBTriple;
  rowInB   : pRGBTripleArray;
  rowInA   : pRGBTripleArray;

// Define a pf24bit BitmapA

BitmapB := TBitmap.Create;
TRY
  BitmapB.Width := BitmapA.Width;
  BitmapB.Height := BitmapA.Height;
  BitmapB.PixelFormat := pf24bit;

FOR j := 0 TO BitmapA.Height-1 DO
BEGIN
  RowInA := BitmapA.ScanLine[j];
  RowInB := BitmapB.Scanline[j];
  FOR i := 0 TO BitmapA.Width-1 DO
  BEGIN
    RowInB[i] := RowInA[i];  // Access Violation
  END
END;

The following is the "best" workaround that I've found (since turning optimization off makes too much of a difference in execution speed):

VAR
  RGBTriple: TRGBTriple; 

// Kludge workaround for best performance
RGBTriple := RowInA[i];
RowInB[i] := RGBTriple

See additional details on this problem. Amazingly, Borland claims this is not a bug. They told me: "We determined that your bug report isn't a bug, but might be due to a misunderstanding of how the product works."

3.  Scanline code fragment that causes Internal Error C1127 (D4) and C1141 (D5)  

4.  Problem with complex implementation that allows DDB and DIB Section to exist simultaneously in Single TBitmap as described in UseNet Post by Takuo Nakamura.


Conclusion
This Tech Note covered a number of examples of using Delphi's ScanLine and PixelFormat properties to manipulate pixels in a variety of ways. Many image processing and graphics enhancements can be written in Delphi with fairly short routines using ScanLine. I hope others also discover how useful Delphi's RAD capabilities are in the area of image processing and computer graphics.


References

Using Device-Independent Bitmaps and Palettes
http://support.microsoft.com/support/kb/articles/q72/0/41.asp

Sample: 16 and 32 Bits-Per-Pel Bitmap Formats
http://support.microsoft.com/support/kb/articles/q94/3/26.asp


Thanks to Danny Thorpe, Senior Engineer, Delphi R&D, for his useful feedback.

Thanks to Gus Elliot for pointing out an omission in Listing 13.  (25 Jan 2001)


Updated 11 Jul 2009
Since 1 Nov 1998