Board index » delphi » Bitmap Pointer Problem

Bitmap Pointer Problem

Howdy all, a D1 problem:
  I've figured out how to get a bitmap image of a form to print, but I
have a small problem.
  If it's a <256 color bitmap (i.e. a <256 color video driver) then I
can step through the colors and change silver (the background color) to
white for printing.  However, if it's a >256 color video driver, then
the bitmap is not color-mapped, and the best that I can get out of the
help system is grab a pointer to the bitmap, understanding that the
bitmap is made up of three-byte color maps.  However, I haven't figured
out how to step through that color-map to change silvers to white!
  How do I access each byte of a buffer formed with the alloc() system?
I've tried lots of pointer tricks, and I've got the size of the 'bits'
part of the bitmap, and have created a pointer to the 'buffer which
contains the bits', but the bottom line is I need to step through the
buffer three bytes at a time, check the color, and change it if need
be.  Ring any bells, anyone?
TIA,
=scott

 

Re:Bitmap Pointer Problem


[ Posted and e-mailed. ]

Quote
Scott Stewart wrote:
>   If it's a <256 color bitmap (i.e. a <256 color video driver) then I
> can step through the colors and change silver (the background color) to
> white for printing.  However, if it's a >256 color video driver, then
> the bitmap is not color-mapped, and the best that I can get out of the
> help system is grab a pointer to the bitmap, understanding that the
> bitmap is made up of three-byte color maps.  However, I haven't figured
> out how to step through that color-map to change silvers to white!
>   How do I access each byte of a buffer formed with the alloc() system?
> I've tried lots of pointer tricks, and I've got the size of the 'bits'
> part of the bitmap, and have created a pointer to the 'buffer which
> contains the bits', but the bottom line is I need to step through the
> buffer three bytes at a time, check the color, and change it if need
> be.  Ring any bells, anyone?

I think there's a remote possibility that the bitmap could be 16 or even
32 bits per pixel. In D1 and D2, however, just getting the handle of the
device context converts the bitmap into whatever pixel depth the screen
is at (usually 4-, 8-, or 24-bit).

I've never had any problems just assuming that if it's > 256 colors, it
must be 24-bit. You can always look up the value in the the
TBitmapInfoHeader stuff anyway.

The best method I've found for doing fast bitmap manipulation is to use
the TBitmap's (sort of undocumented) SaveToStream method and get it into
a TMemoryStream.

Once you've done that, you can get the size of the header via the
appropriate cast:

var
  BitmapOffset: LongInt;

begin
  BitmapOffset := PBitmapFileHeader(MemoryStream.Memory)^.bfOffBits;
end;

Then Inc the pointer from there:

var
  P: ^Byte;

begin
  P := MemoryStream.Memory;
  Inc(P, BitmapOffset);
end;

Now you're ready to go Incing through the whole thing, three bytes at a
time. First, however, you should remember that bitmaps are
DWORD-aligned, meaning that if a scan line isn't evenly divisible by
four bytes, you have to add on whatever it takes to get it there (these
bytes are junk, and you'll skip over them). This has already been done
for you, and now you just need to get the number of extra bytes:

{ ScanLines are DWORD aligned. }
{ biWidth is the width of the bitmap in pixels. }
{ BitCount is the number of bits per pixel, in our case, 24. }

var
  BytesPerScanLine: LongInt;
  SignificantBytes: LongInt;
  PaddingBytes: LongInt;

begin
  BytesPerScanLine := ((((biWidth * BitCount) + 31) div 32) *
                      SizeOf(DWORD));
  SignificantBytes := biWidth * BitCount shr 3;
  { Extra bytes required for DWORD aligning. }
  PaddingBytes := BytesPerScanLine - SignificantBytes;
end;

Note that this code doesn't work for bitmaps < 8 bits per pixel. (I have
code to take care of this, but I'll leave it out for brevity. I can send
it to you if you need it.)

Finally, we can step through the pixels, each time copying them into a
LongInt for mathematical evaluation:

var
  Level: LongInt;
  P: ^Byte;

begin
  { Assume PByte is pointing to the bitmap bits. }
  {
   The most significant byte of level won't get touched by the
   memory move, but should be zero for proper evaluation. Just
   set the whole thing to zero.
  }
  Level := 0;
  { Copy the pixel into Level for evaluation. }
  {
   Note that we copy it starting at the very beginning of the LongInt,
   which is a bit counter-intuitive. This is due to the endian
   situation on Intels. The fourth byte into the LongInt, then, is the
   most significant.
  }
  Move(P^, Level, 3);
  { If it's silver, fill it with 1s, thus converting it to white. }
  if Level = clSilver then FillChar( P, 3, High(Byte) );
  { Next! }
  Inc(P, 3);
end;

There is the extra issue of Delphi 1. If you are using Delphi 1, things
get more complicated, because your memory is of the segment:offset
variety. You can't just blindly Inc a pointer, because you might be
crossing segment boundaries. Ray Lischner has some HugeInc routines in
assembler in his Secrets of Delphi 2 book. If you don't own it, you
should. It's one of the best out there.

This whole thing ended up being more complicated-sounding than I
anticipated. It's not that bad, really. If you would like, I can send
you an entire routine to convert silvers to whites, or to do an ordered
dither, or a rotation of 90 degrees.

Dave
daves at cfxc.com

Other Threads