-2

I want to paint a monochome bitmap stretched at 200% with two colors: pure black and pure white.

I use the following code, but nothing gets displayed.
If I replace SRCCOPY with SRCPAINT I get a white rectangle, but still no random 2x2 blocks get painted as is supposed to happen.

procedure TForm1.Button1Click(Sender: TObject);
begin
  ShowCell(Form1.Canvas);  //Using another canvas does not help.
end;

procedure ShowCell(Canvas: TCanvas);
const
  cHeight = 100;
  cWidth = 50;    //50 * 8 = 400 pixels
var
  bmpinfo: PBitmapInfo;
  color: PRGBQUAD;
  i: Integer;
  x,y,h: integer;
  DataBuffer: array[0..cHeight-1,0..cWidth-1] of byte;
  ScanLineWidth: integer;
  Cell: TLifeCell;
  Coordinate: TCoordinate;
begin
  GetMem(bmpinfo, SizeOf(TBitmapInfo) + SizeOf(TRGBQUAD)*2);
  color:= @bmpinfo^.bmiColors[0];
  color^.rgbRed:= 255;
  color^.rgbBlue:= 255;
  color^.rgbGreen:= 255;
  color^.rgbReserved:= 0;
  Inc(color);
  color^.rgbRed:= 0;
  color^.rgbBlue:= 0;
  color^.rgbGreen:= 0;
  color^.rgbReserved:= 0;

  with bmpinfo.bmiHeader do begin
    biSize:= SizeOf(bmpinfo.bmiHeader);
    biWidth:= cWidth*8;   //8 pixels per byte
    biHeight:= cHeight;
    biPlanes:= 1;
    biBitCount:= 1;
    biCompression:= BI_RGB;
    biSizeImage:= 0;
    biXPelsPerMeter:= 0;
    biYPelsPerMeter:= 0;
    biClrUsed:= 0;
    biClrImportant:= 0;
  end;

  ScanlineWidth:= cWidth div 8;
  if (ScanlineWidth mod 4) <> 0 then Inc(ScanlineWidth, 4 - ScanlineWidth mod 4);

  for x:= 0 to cwidth-1 do begin
    for y:= 0 to cheight-1 do begin
      DataBuffer[x][y]:= Random(255);
    end;
  end;

    StretchDIBits(Canvas.Handle,  0, 0, cHeight*2, cWidth*2*8, 0, 0, cHeight, cWidth*8,
             @DataBuffer, bmpinfo^, DIB_RGB_COLORS, SRCCOPY);
  FreeMem(bmpinfo);
end;

What am I doing wrong here?

Johan
  • 74,508
  • 24
  • 191
  • 319

2 Answers2

1

It works for me with some corrections - cwidth/cheight in the cycle and main - width and height arguments of StretchDiBits function were exchanged. Has GetLastError reported wrong param values? (In my case - not)

  for x:= 0 to cwidth-1 do begin
    for y:= 0 to cheight-1 do begin
      DataBuffer[x][y]:= Random(255);
    end;
  end;

  StretchDIBits(Canvas.Handle, 0,0,cWidth*2*8,cHeight*2,0,0,cwidth*8,cHeight,@DataBuffer,
    bmpinfo^, DIB_RGB_COLORS, SRCCOPY);

Another possible issue - you defined cWidth (data buffer width) independently of ScanlineWidth calculation.

MBo
  • 77,366
  • 5
  • 53
  • 86
  • Error codes are not signaled through GDI for this function. As is typical with GDI. All you can do is compare return value against 0. – David Heffernan May 16 '16 at 11:52
  • @David Heffernan It is not typical. Some GDI functions set LastError (while message usually is not informative like "wrong parameter") – MBo May 16 '16 at 11:57
  • Hmm, most GDI functions that I use don't call `SetLastError`. Which ones do? – David Heffernan May 16 '16 at 11:59
  • Long time ago found periodic bug with some Get/SetDiBits function (memory corruption,bad pointer to data,"ERROR_INVALID_PARAMETER"). Did not find any such pair in my archives though. Old MSDN SetDiBits page had explicit directive to check GetLastError to get additional information when result is 0 – MBo May 16 '16 at 12:29
  • @MBo, note that in addition to returning 0 for error, the function can also return `GDI_ERROR` which is a negative number if it cannot decode PNG or JPG input data. So if you want to be complete, you need to check for both return values if you want to be sure the function succeeded. Of course a negative return value is allowed if the function mirrors data. – Johan May 16 '16 at 12:40
  • @DavidHeffernan, Dunno, but this function does not call SetLastError. It returns either 0 for `bad arguments` or `GDI_ERROR` for cannot decode PNG/JPG data. – Johan May 16 '16 at 12:41
1

There are a number of errors:

  • Bitmap declaration does not match StretchDIBits call.
  • Bitmap is upside down
  • Loop has x and y reversed
  • Status code is not checked (and finally)
  • For performance reasons the width of a bitmap should be a multiple of 4 (or 8) bytes

Bitmap declaration does not match StretchDIBits call
The problem is that the declaration of the bitmap must match the arguments of StretchDIBits. If these do not match you'll get a silent error and nothing will get displayed.

Here are the problem lines:

with bmpinfo.bmiHeader do begin
  biSize:= SizeOf(bmpinfo.bmiHeader);
  biWidth:= cWidth*8;   //8 pixels per byte   must match srcWidth.
  biHeight:= cHeight;   // must match srcHeight below.

StretchDIBits(Canvas.Handle,0,0,cWidth*2*8,cHeight*2  
                           ,0,0,cwidth*8,cHeight,   //srcWidth,srcHeight 
              @DataBuffer, bmpinfo^, DIB_RGB_COLORS, SRCCOPY);

If either the srcWidth or srcHeight parameter exceed the dimensions of the bitmap the call will fail.
In the call to StretchDIBits in the question Height and Width are reversed, making the bitmap too large and forcing an error, preventing display.

Bitmap is upside down
Because IBM has had it's grubby hands on the bitmap format logic went out the window and the default for bitmaps is to be upside down.

BITMAPINFOHEADER
biHeight The height of the bitmap, in pixels. If biHeight is positive, the bitmap is a bottom-up DIB and its origin is the lower-left corner. If biHeight is negative, the bitmap is a top-down DIB and its origin is the upper-left corner.

Unless you want your data to be upside down, you'd better make biHeight negative, like so:

with bmpinfo.bmiHeader do begin
  biSize:= SizeOf(bmpinfo.bmiHeader);
  biWidth:= cWidth*8;   //8 pixels per byte   must match srcWidth.
  biHeight:= -cHeight;   // "-" = TopDown: must match srcHeight below.

Loop has x and y reversed
In the loop, take note that x and y are reversed in the buffer.

  for y:= 0 to cHeight-1 do begin
    for x:= 0 to cWidth-1 do begin   //fill scanlines in the inner loop.
      DataBuffer[y][x]:= Random(256);  //y,x must be reversed!
    end; {for x}
  end; {for y}

Status code is not checked
If I had bothered to check the return value of StretchDIBits than I could have saved myself the bother. I would have known there was an error.

If the function succeeds, the return value is the number of scan lines copied. Note that this value can be negative for mirrored content.

If the function fails, or no scan lines are copied, the return value is 0.

Success:= StretchDIBits(.....
Assert(Success <> 0,'StretchDIBits error, check your arguments');  

For performance reasons the width of a bitmap should be a multiple of 4 bytes
If you are going to write to your bitmap buffer using (32-bit) integers, you'd better make sure your bitmap width is a multiple of 4 bytes, or you're going to suffer delays due to misaligned writes.
If you use 64-bit Int64 writes, make it a multiple of 8 bytes.

Windows only enforces a 2-byte alignment. This is because the bitmaps need to stay compatible with 16-bit Windows bitmaps.

bmWidthBytes The number of bytes in each scan line. This value must be divisible by 2, because the system assumes that the bit values of a bitmap form an array that is word aligned

Community
  • 1
  • 1
Johan
  • 74,508
  • 24
  • 191
  • 319
  • You've fixed the loop the wrong way. You want to access memory sequentially for efficiency. This doesn't seem to me to scale by 200%. – David Heffernan May 16 '16 at 12:21
  • Yes, thanks. Not a problem in the real code, because that does not use a loop, that one blits cells into a buffer, but yes always work with the scanlines. – Johan May 16 '16 at 12:25
  • Oh, but it does, check the arguments in the `StretchDIBits`. The dstWidth and dstHeight are twice the size of the srcWidth and srcHeight. – Johan May 16 '16 at 12:27
  • So you want to scale by 200% but draw 16 of squares. Maybe I've got it wrong and should delete my answer. – David Heffernan May 16 '16 at 12:30
  • I'm drawing a 400x100 pixel bitmap at 200%, (each pixel is displayed as a 2x2 square). This takes 50x100 bytes, because I'm using a 1 bit bitmap. Dunno where you get the 16 squares from. – Johan May 16 '16 at 12:31
  • Ok, I give up on this – David Heffernan May 16 '16 at 12:59
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/112060/discussion-between-johan-and-david-heffernan). – Johan May 16 '16 at 13:14