DrawBlock to bitmap

I am using DrawBlock to dynamically overlay simple line graphics (scales, etc) over another block in a group that contains a bitmap. This works fine on the atDiagram but I also need to export just the group (i.e. the underlay bitmap with the overlayed line graphics) to an image. Getting the underlying bitmap is no problem but I can't find a way to get the line graphics onto an image.

I tried the DrawBlockInCanvas method but that treats the target canvas as a scaled version of the whole atDiagram the block was on, so the drawn block is just floating around somewhere within the canvas.
The only thing I can think of is to create an offscreen atDiagram, set its' size to be the same as the block, copy the block to it, draw the line graphics there, and finally use DrawBlockInCanvas to transfer the whole diagram to am external image. But this seems like a very crude hammer for what should be a small nut!

Is there some way to either transfer the DrawBlock graphic directly to an image or convert the dynamically drawn graphic into the block's static Picture?

DrawBlockInCanvas allow you to pass a TRect with the block coordinates. Doesn't it help?

No, It appears to me that the TRect determines the size of the image the block in drawn into. But the block is drawn in that image in the same proportions and position it had on the source atDiagram.
I have attached a test app I made to try to illustrate the problem (or highlight where my code is going wrong!). Move and zoom the group block on the left then click Export to convert the group to a single, stand-alone image. You will see that the jpg is converted but the red diamond drawn over it moves about and scales depending on the position of the group in the atDiagram and the zoomratio.


testDrawBlockInCanvas.zip (461.8 KB)

You will need to add this procedure to DiagramUtils:

procedure TBlockDrawer.AutoRotLineTo(X, Y: double);
var
  P: TPoint;
begin
  if (Canvas <> nil) then
  begin
    P := RoundPoint(RotX(PP(X, Y)));
    Canvas.LineTo(P.X, P.Y);
  end;
end;

Try this (it's not checking the Visible properties, :grin:so please don't touch your checkboxes):

procedure TForm1.Button2Click(Sender: TObject);
var
  xBMP : TBitmap;
  xCanvas : TCanvas;
  xRect : TRect;
begin
//  panel2.Width := **whatever**
//  panel2.Height := **whatever**

  xRect.Left := trunc(min(picblk.Left, lineblk.Left));
  xRect.Top := trunc(min(picblk.Top, lineblk.Top));
  xRect.Right := Trunc(Max(picblk.Left + picblk.Width, lineblk.Left + lineblk.Width)) + 1;
  xRect.Bottom := Trunc(Max(picblk.Top + picblk.Height, lineblk.Top + lineblk.Height)) + 1;

  xBMP := TBitmap.Create;
  xCanvas := TCanvas.Create;
  xCanvas.Handle := GetWindowDC(WindowHandle);

  xBMP.Height := xRect.Bottom - xRect.Top;
  xBMP.Width := xRect.Right - xRect.Left;

  atDiagram1.PaintTo(xCanvas, 0, 0);
  xBMP.Canvas.CopyRect(rect(0,0,xBMP.Width,xBMP.Height), xCanvas, xRect);
  image1.canvas.StretchDraw(rect(0,0, image1.Width - 1,image1.Height - 1), xBMP);

  xBMP.DisposeOf;
  ReleaseDC(WindowHandle, xCanvas.Handle);
  xCanvas.DisposeOf;

end;

(must add uses System.Math;)

Something had to do with:
1 - The original picture is very large, and after assigning it to Image1, some kind of scaling applied. Maybe something with pixel formats, I really don't know.
2 - lineblk and picblk have Top/Left = 100. You were directly assigning the picture to Image1, so at (0,0), but lineblk was drawn at some scaled 100,100

The above approach worked better, with no hidden scaling. It paints the whole diagram to a Canvas, then extracts your desired rectangle.
Hope it helped.

DrawBlockInCanvas should also be called from grp , but I could not make it work on a foreign Canvas. Raised invalid pointer operation.

Hi Julio,
Thanks for the suggestion however it does not really do what I need. Firstly the atDiagram.PaintTo( procedure returns a simple screenshot, so if the block is partially outside the visible window, the copied rectangle includes any borders, scrollbars, adjacent controls, etc. - not the actual block being copied. Using atDiagram.PaintToBitmap( does solve this problem however it still returns essentially a screenshot of the block and any other blocks that happen to intersect it. I need to return just the result of the block's DrawShape / DrawBlock procedure.

.... I realised that Julio was on the right track but rather than using PaintTo or PaintToBitmap we can use DrawBlockInCanvas:

procedure TForm1.DrawBlockToBitmap(ADiagram: TatDiagram; ABlock: TCustomDiagramBlock; var BMP : TBitmap);
var
  xRect : TRect;
  xDiagramSize : TPoint;
  xBMP : TBitmap;
begin
  xRect.Left := trunc(ABlock.Left * ADiagram.ZoomRatio);
  xRect.Top := trunc(ABlock.Top * ADiagram.ZoomRatio);
  xRect.Right := Trunc((ABlock.Left + ABlock.Width) * ADiagram.ZoomRatio);
  xRect.Bottom := Trunc((ABlock.Top + ABlock.Height) * ADiagram.ZoomRatio);

  xDiagramSize.X := round(ADiagram.DiagramWidth * ADiagram.ZoomRatio);
  xDiagramSize.Y := round(ADiagram.DiagramHeight * ADiagram.ZoomRatio);

  xBMP := TBitmap.Create;
  xBMP.PixelFormat := pf32bit;
  xBMP.Width := xDiagramSize.X;
  xBMP.Height := xDiagramSize.Y;

  BMP.Height := xRect.Bottom - xRect.Top;
  BMP.Width := xRect.Right - xRect.Left;

  ABlock.DrawBlockInCanvas(xBMP.Canvas,rect(0,0,xDiagramSize.X,xDiagramSize.y));
  BMP.Canvas.CopyRect(rect(0,0,BMP.Width,BMP.Height), xBMP.Canvas, xRect);
  xBMP.Free;
end;

This could probably be tidied up but it works for me (so far...).

I tried in many ways this DrawBlockInCanvas, but it didn't respect the Rects I've been trying to cut.

Try drawing lineblk as you did, but without assigning picblk to Image1. You'll see DrawBlockInCanvas works for lineblk, but always draws starting from 0,0 and the lines starting at 100,100. I could not cut the rect starting at 100,100.

And trying grp.DrawBlockInCanvas, I could not draw in Image1.Canvas netiher xCanvas.
Good luck! :smiley:

I think I would brute force make all other blocks&shapes invisible, paint to bitmap/canvas, and then make all them visible again. :slightly_smiling_face:

Using DrawBlockInCanvas to transfer the bitmap of the DrawwBlock/DrawShape to a temporary bitmap, then trimming that bitmap to the block size does work. But I now see that there are other problems to overcome such as transparency. I am experimenting with assigning the bmp to a png, making that transparent then using the png as my final result. But that has anti-aliasing problems :disappointed:

I think this is the best path. Use the diagram as the painter. Set the blocks visibility/position as you wish (hiding the other blocks, for example), export, and that's it.