I'd love to be proved wrong, but it seems in rare cases
TBitmap.LoadFromFile (or stream, etc.) mis-aligns bitmap bytes. This
results in, for example, red becoming green, green -> blue, and blue ->
red.
*** IF YOU ARE USING TBITMAP THEN YOUR PROGRAM COULD PRODUCE EMBARRASSING
RESULTS!! ***
I found the problem when loading bitmaps saved by my Kodak DC120 camera.
If anyone wants to confirm the bug, I'll send them a sample bitmap. The
bitmaps load fine in commerical graphics apps.
THE CAUSE: In 24-bit bitmaps, I think TBitmap.LoadFromFile expects the
bitmap data to follow immediately after the headers (BitmapFileHeader and
BitmapInfoHeader) - that is, 54 bytes in. BUT, the BitmapFileHeader
'bfOffBits' field can specify a different starting point. bfOffBits is
usually 54, but doesn't have to be. The DC120 bitmaps have a 58 byte
offset. Because Delphi starts reading at 54 and the bytes are in RGB
triples, the triples are misaligned and RGB becomes GBR!
A SOLUTION: I've implemented a 'TOKBitmap' type that descends from TBitmap
and overrides 'LoadFromFile'. The code is below. I'd like to know from
anyone if the code looks 'solid'. Does it risk creating problems of its
own?
I'd love to hear from Borland on this! I think it is a major bug! It's in
Delphi2 and Delphi3 but I don't have Delphi4.
Hope this helps someone out there!
Regards
Julian Brewer
THE CODE: *** MUST USE THE {$A-} DIRECTIVE ***
unit OKBitmap;
interface
{$A-}
uses
Windows, Graphics, Classes, SysUtils;
type
tBitmapfileheader = record
bfType : word;
bfSize : integer;
bfReserved1 : word;
bfReserved2 : word;
bfOffBits : integer;
end;
tBitmapinfoheader = record
biSize : integer;
biWidth : integer;
biHeight : integer;
biPlanes : word;
biBitCount : word;
biCompression : integer;
biSizeImage : integer;
biXPel{*word*237}eter : integer;
biYPel{*word*237}eter : integer;
biClrUsed : integer;
biClrImportant : integer;
end;
TOKBitmap = class(TBitmap)
private
public
procedure LoadFromFile(const FileName: string); override;
end;
implementation
procedure TOKBitmap.LoadFromFile(const FileName: string);
var
FS,FSout : TFileStream;
BMFH : tBitmapfileheader;
BMIH : tBitmapinfoheader;
NewFileName : string;
CopySize : integer;
StandardHeader : array[0..53] of byte;
WasError : boolean;
begin
if not FileExists(FileName) then exit;
try
WasError := true;
FS := TFileStream.Create(FileName,fmOpenRead);
FS.ReadBuffer(BMFH,sizeof(tBitmapfileheader));
FS.ReadBuffer(BMIH,sizeof(tBitmapinfoheader));
WasError := false;
finally
FS.Free;
end;
if WasError then exit;
// If this is a 24-bit bitmap then Delphi appears to ignore the
// bfOffbits field and starts reading the bits from an offset of
// 54. This offset is most common as the standard size of the
// two bitmap headers is 54 bytes. So, if the offset if greater
// than 54, we need to construct a new file with an offset of 54.
if ( (BMIH.biBitCount = 24) and (BMFH.bfOffBits <> 54) and
((BMIH.biSize < 36) or (BMIH.biClrUsed = 0)) )
then begin
// Create temporary files to house standard bitmap
NewFileName := FileName;
while FileExists(NewFileName) do NewFileName := NewFileName+'x';
// Now write headers clipped or extended to standard size
try
WasError := true;
FSout := TFileStream.Create(NewFileName,fmCreate);
PreBytes := BMFH.bfOffBits;
BMFH.bfOffBits := 54;
FSOut.WriteBuffer(BMFH,SizeOf(BMFH));
FSOut.WriteBuffer(BMIH,SizeOf(BMIH));
// Now copy over the actual bitmap and anything following
CopySize := BMFH.bfSize - PreBytes;
FS := TFileStream.Create(FileName,fmOpenRead);
FS.Seek(PreBytes,soFromBeginning);
FSout.CopyFrom(FS,CopySize);
WasError := false;
finally
FS.Free;
FSout.Free;
end;
if WasError then exit;
// Now load this standardized bitmap before deleting its file
inherited LoadFromFile(NewFileName);
DeleteFile(NewFileName);
end else inherited LoadFromFile(FileName);
end;
end.