Drawing a Bitmap on a Paintbox in Delphi

Yes, I know, this is far from news, but since I just spent several hours trying to find out why my code didn’t work, I’ll document it here so I can look it up later.

There are various ways to draw a bitmap on a paintbox in Delphi. I’ll focus on the Windows API function StretchBlt.

  // first, get the bitmap from somewhere
  if not GetBitmap(FPreviewBmp) then
    FPreviewBmp.Assign(nil);

  // calculate the available size to display the bitmap
  // keeping the aspect ratio
  cnv := pb_Preview.Canvas;
  BoxWidth := pb_Preview.Width;
  BoxHeight := pb_Preview.Height;
  BmpWidth := FPreviewBmp.Width;
  BmpHeight := FPreviewBmp.Height;
  HeightFactor := BoxHeight / BmpHeight;
  WidthFactor := BoxWidth / BmpWidth;
  if HeightFactor > WidthFactor then
    Factor := WidthFactor
  else
    Factor := HeightFactor;
  // we do not want to enlarge the picture
  if Factor > 1 then
    Factor := 1;
  // calculate size and offsets so it is shown centered
  BmpWidth := Trunc(BmpWidth * Factor);
  BmpHeight := Trunc(BmpHeight * Factor);
  BmpTop := (BoxHeight - BmpHeight) div 2;
  BmpLeft := (BoxWidth - BmpWidth) div 2;
  // set the StretchBlt mode (you basically always want COLORONCOLOR)
  SetStretchBltMode(cnv.Handle, COLORONCOLOR);
  // draw the bitmap to the paintbox
  // note that you need to use the Canvas handle of the bitmap,
  // not the handle of the bitmap itself
  Res := StretchBlt(
    cnv.Handle, BmpLeft, BmpTop, BmpWidth, BmpHeight,
    FPreviewBmp.Canvas.Handle, 0, 0, FPreviewBmp.Width, FPreviewBmp.Height,
    SRCCOPY);
    if not Res then begin
      err := GetLastError;
      cnv.TextOut(0, 0, Format('LastError: %d %s', [err, SysErrorMessage(err)]));
    end;

It took me hours to find out why in my original code StretchBlt failed with error code 6 (The handle is invalid). It was very simple:
StretchBlt must be passed FPreviewBmp.Canvas.Handle rather than FPreviewBmp.Handle.

After changing that, it simply worked.