Mega Code Archive

 
Categories / Delphi / Graphic
 

How to Load big bitmaps with few memory requirement

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;