Title: How to Load big bitmaps with few memory requirement
function MyGetMem(Size: DWORD): Pointer;
begin
Result := Pointer(GlobalAlloc(GPTR, Size));
end;
procedure MyFreeMem(p: Pointer);
begin
if p = nil then Exit;
GlobalFree(THandle(p));
end;
{ This code will fill a bitmap by stretching an image coming from a big bitmap on disk.
FileName.- Name of the uncompressed bitmap to read
DestBitmap.- Target bitmap where the bitmap on disk will be resampled.
BufferSize.- The size of a memory buffer used for reading scanlines from the physical bitmap on disk.
This value will decide how many scanlines can be read from disk at the same time, with always a
minimum value of 2 scanlines.
Will return false on error.
}
function GetDIBInBands(const FileName: string;
DestBitmap: TBitmap; BufferSize: Integer;
out TotalBitmapWidth, TotalBitmapHeight: Integer): Boolean;
var
FileSize: integer; // calculated file size
ImageSize: integer; // calculated image size
dest_MaxScans: integer; // number of scanline from source bitmap
dsty_top: Integer; // used to calculate number of passes
NumPasses: integer; // number of passed needed
dest_Residual: integer; // number of scanlines on last band
Stream: TStream; // stream used for opening the bitmap
bmf: TBITMAPFILEHEADER; // the bitmap header
lpBitmapInfo: PBITMAPINFO; // bitmap info record
BitmapHeaderSize: integer; // size of header of bitmap
SourceIsTopDown: Boolean; // is reversed bitmap ?
SourceBytesPerScanLine: integer; // number of bytes per scanline
SourceLastScanLine: Extended; // last scanline processes
SourceBandHeight: Extended; //
BitmapInfo: PBITMAPINFO;
img_start: integer;
img_end: integer;
img_numscans: integer;
OffsetInFile: integer;
OldHeight: Integer;
bits: Pointer;
CurrentTop: Integer;
CurrentBottom: Integer;
begin
Result := False;
// open the big bitmap
Stream := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite);
// total size of bitmap
FileSize := Stream.Size;
// read the header
Stream.ReadBuffer(bmf, SizeOf(TBITMAPFILEHEADER));
// calculate header size
BitmapHeaderSize := bmf.bfOffBits - SizeOf(TBITMAPFILEHEADER);
// calculate size of bitmap bits
ImageSize := FileSize - Integer(bmf.bfOffBits);
// check for valid bitmap and exit if not
if ((bmf.bfType $4D42) or
(Integer(bmf.bfOffBits) 1) or
(FileSize 1) or (BitmapHeaderSize 1) or (ImageSize 1) or
(FileSize (SizeOf(TBITMAPFILEHEADER) BitmapHeaderSize ImageSize))) then
begin
Stream.Free;
Exit;
end;
lpBitmapInfo := MyGetMem(BitmapHeaderSize);
try
Stream.ReadBuffer(lpBitmapInfo^, BitmapHeaderSize);
// check for uncompressed bitmap
if ((lpBitmapInfo^.bmiHeader.biCompression = BI_RLE4) or
(lpBitmapInfo^.bmiHeader.biCompression = BI_RLE8)) then
begin
Exit;
end;
// bitmap dimensions
TotalBitmapWidth := lpBitmapInfo^.bmiHeader.biWidth;
TotalBitmapHeight := abs(lpBitmapInfo^.bmiHeader.biHeight);
// is reversed order ?
SourceIsTopDown := (lpBitmapInfo^.bmiHeader.biHeight 0);
// calculate number of bytes used per scanline
SourceBytesPerScanLine := ((((lpBitmapInfo^.bmiHeader.biWidth *
lpBitmapInfo^.bmiHeader.biBitCount) 31) and not 31) div 8);
// adjust buffer size
if BufferSize Abs(SourceBytesPerScanLine) then
BufferSize := Abs(SourceBytesPerScanLine);
// calculate number of scanlines for every pass on the destination bitmap
dest_MaxScans := round(BufferSize / abs(SourceBytesPerScanLine));
dest_MaxScans := round(dest_MaxScans * (DestBitmap.Height / TotalBitmapHeight));
if dest_MaxScans 2 then
dest_MaxScans := 2; // at least two scan lines
// is not big enough ?
if dest_MaxScans TotalBitmapHeight then
dest_MaxScans := TotalBitmapHeight;
{ count the number of passes needed to fill the destination bitmap }
dsty_top := 0;
NumPasses := 0;
while (dsty_Top dest_MaxScans) = DestBitmap.Height do
begin
Inc(NumPasses);
Inc(dsty_top, dest_MaxScans);
end;
if NumPasses = 0 then Exit;
// calculate scanlines on last pass
dest_Residual := DestBitmap.Height mod dest_MaxScans;
// now calculate how many scanlines in source bitmap needed for every band on the destination bitmap
SourceBandHeight := (TotalBitmapHeight * (1 - (dest_Residual / DestBitmap.Height))) /
NumPasses;
// initialize first band
CurrentTop := 0;
CurrentBottom := dest_MaxScans;
// a floating point used in order to not loose last scanline precision on source bitmap
// because every band on target could be a fraction (not integral) on the source bitmap
SourceLastScanLine := 0.0;
while CurrentTop .Height do
begin
// scanline start of band in source bitmap
img_start := Round(SourceLastScanLine);
SourceLastScanLine := SourceLastScanLine SourceBandHeight;
// scanline finish of band in source bitmap
img_end := Round(SourceLastScanLine);
if img_end TotalBitmapHeight - 1 then
img_end := TotalBitmapHeight - 1;
img_numscans := img_end - img_start;
if img_numscans 1 then Break;
OldHeight := lpBitmapInfo^.bmiHeader.biHeight;
if SourceIsTopDown then
lpBitmapInfo^.bmiHeader.biHeight := -img_numscans
else
lpBitmapInfo^.bmiHeader.biHeight := img_numscans;
// memory used to read only the current band
bits := MyGetMem(Abs(SourceBytesPerScanLine) * img_numscans);
try
// calculate offset of band on disk
OffsetInFile := TotalBitmapHeight - (img_start img_numscans);
Stream.Seek(Integer(bmf.bfOffBits) (OffsetInFile * abs(SourceBytesPerScanLine)),
soFromBeginning);
Stream.ReadBuffer(bits^, abs(SourceBytesPerScanLine) * img_numscans);
SetStretchBltMode(DestBitmap.Canvas.Handle, COLORONCOLOR);
// now stretch the band readed to the destination bitmap
StretchDIBits(DestBitmap.Canvas.Handle,
0,
CurrentTop,
DestBitmap.Width,
Abs(CurrentBottom - CurrentTop),
0,
0,
TotalBitmapWidth,
img_numscans,
Bits,
lpBitmapInfo^,
DIB_RGB_COLORS, SRCCOPY);
finally
MyFreeMem(bits);
lpBitmapInfo^.bmiHeader.biHeight := OldHeight;
end;
CurrentTop := CurrentBottom;
CurrentBottom := CurrentTop dest_MaxScans;
if CurrentBottom DestBitmap.Height then
CurrentBottom := DestBitmap.Height;
end;
finally
Stream.Free;
MyFreeMem(lpBitmapInfo);
end;
Result := True;
end;
// example of usage
procedure TForm1.Button1Click(Sender: TObject);
var
bmw, bmh: Integer;
Bitmap: TBitmap;
begin
Bitmap := TBitmap.Create;
with TOpenDialog.Create(nil) do
try
DefaultExt := 'BMP';
Filter := 'Bitmaps (*.bmp)|*.bmp';
Title := 'Define bitmap to display';
if not Execute then Exit;
{ define the size of the required bitmap }
Bitmap.Width := Self.ClientWidth;
Bitmap.Height := Self.ClientHeight;
Bitmap.PixelFormat := pf24Bit;
Screen.Cursor := crHourglass;
// use 100 KB of buffer
if not GetDIBInBands(FileName, Bitmap, 100 * 1024, bmw, bmh) then Exit;
// original bitmap width = bmw
// original bitmap height = bmh
Self.Canvas.Draw(0,0,Bitmap);
finally
Free;
Bitmap.Free;
Screen.Cursor := crDefault;
end;
end;