Link to home
Start Free TrialLog in
Avatar of HenryM2
HenryM2

asked on

Delphi : How do you print a Bitmap with TPanels superimposed on it, on a RAVE Report.

I have a TPanel (FloorPlanPnl) with a TImage (FloorPlanImg) as child.  FloorPlanImg has a picture of TBitmap, of a a floor plan.  FloorPlanPnl contains several children of TPanel representing furniture ojects on a floor plan which can be positioned by dragging around with the mouse.

I need to print the FloorPlanImg with TBitmap background including the TPanel object representing the furniture on a Rave Report, using code.
Avatar of Emmanuel PASQUIER
Emmanuel PASQUIER
Flag of France image

you have first to merge your furnitures images on the floor image, and set that new image as source for an image report component.

Here is how to merge your image :
Var
 MergedBMP:TBitmap;
begin
 MergedBMP:=TBitmap.Create;
 MergedBMP.Assign(FloorPlanImg.Picture.Bitmap);
 for i:=0 to NbFurnitures-1 do
  begin
   MergedBMP.Canvas.Draw(X,Y,FurnitureImage[i].Picture.Graphic);
// Where X,Y are the positions of each FurnitureImages relative to the floor image
  end;
end;

Open in new window

Just one question : do you have, as I understand, your furnitures images on TPanels ? like :

FloorPlanPnl
  + FloorPlanImg
  + Furniture1Panel
     + Furniture1Image
  + Furniture2Panel
     + Furniture2Image

If so, why not having the furnitures images directly as TImages at the same level than the Floor image ?

The above code would be then the function below, that you call with your panel and the floor image

Function MergeAllImages(Parent:TPanel;Floor:TImage):TBitmap;
Var
 Img:TImage;
begin
 Result:=TBitmap.Create;
 Result.Assign(FloorPlanImg.Picture.Bitmap);
 for i:=0 to Parent.ComponentCount-1 do 
  if (Parent.Components[i]<>Floor) 
      And Parent.Components[i] Is TImage Then 
  begin
   Img:=TImage(Parent.Components[i]);
   Result.Canvas.Draw(
    Img.Left-FloorPlanImg.Left,
    Img.Top -FloorPlanImg.Top,
    Img.Picture.Graphic);
  end;
end;

Open in new window

Avatar of HenryM2
HenryM2

ASKER

The furniture objects are text only and not a graphical immages, hence just a label with a name representing the furniture code.  It would have been nice to have a graphical immage of the furniture, but space on the floor plan is a problem as it needs to print on A4 paper.  Below, a sample of the floor plan.
Floor-Plan-Example.png
then you can use PaintTo to make the panel (and everything it might contain) to paint itself on the bitmap canvas. Let's say we keep the TImages draw as well, just in case you use it one day.
And let's say that instead of managing only TPanel, we will manage all windowed controls (TWinControl descendants, including TPanel)
Function MergeAllImages(Parent:TPanel;Floor:TImage):TBitmap;
Var
 Img:TImage;
 WinCtrl:TWinControl;
begin
 Result:=TBitmap.Create;
 Result.Assign(FloorPlanImg.Picture.Bitmap);
 for i:=0 to Parent.ComponentCount-1 do 
  if Parent.Components[i]<>Floor Then
   begin
    if Parent.Components[i] Is TImage Then 
     begin
      Img:=TImage(Parent.Components[i]);
      Result.Canvas.Draw(
       Img.Left-FloorPlanImg.Left,
       Img.Top -FloorPlanImg.Top,
       Img.Picture.Graphic);
     end;
    if Parent.Components[i] Is TWinControl Then 
     begin
      WinCtrl:=TWinControl (Parent.Components[i]);
      WinCtrl.PaintTo(Result.Handle,
       WinCtrl.Left-FloorPlanImg.Left,
       WinCtrl.Top -FloorPlanImg.Top,
     end;
   end;
end;

Open in new window

Just programmatically do a screen shot :-)
Avatar of HenryM2

ASKER

Epasquier, I now have my code as below.

In line 24 of your code above, I ended the line with ).  Not sure if this is correct.

I added a TImage (ReportImg) in my Reports Unit.

As code as now produces a compiler error:
[DCC Error] SiteDesignerMainUnt.pas(18494): E2010 Incompatible types: 'TPicture' and 'TBitmap' on line
  ReportsFrm.ReportImg.Picture := MergeAllImages(FloorPlanPnl, FloorPlanImg);
 in my calling procedure to TMainFrm.MergeAllImmages


Function TMainFrm.MergeAllImages(Parent:TPanel;Floor:TImage):TBitmap;
Var
 Img:TImage;
 WinCtrl:TWinControl;
 i : Integer;
begin
 Result:=TBitmap.Create;
 Result.Assign(FloorPlanImg.Picture.Bitmap);
 for i:=0 to Parent.ComponentCount-1 do
   if Parent.Components[i]<>Floor Then
   begin
     if Parent.Components[i] Is TImage Then
     begin
      Img:=TImage(Parent.Components[i]);
      Result.Canvas.Draw(
       Img.Left-FloorPlanImg.Left,
       Img.Top -FloorPlanImg.Top,
       Img.Picture.Graphic);
     end;
     if Parent.Components[i] Is TWinControl Then
     begin
       WinCtrl:=TWinControl (Parent.Components[i]);
       WinCtrl.PaintTo(Result.Handle,
       WinCtrl.Left-FloorPlanImg.Left,
       WinCtrl.Top -FloorPlanImg.Top)
     end;
   end;
end;

procedure TMainFrm.PrintFloorPlanWithEquipmentClick(Sender: TObject);
begin
  ReportsFrm.ReportImg.Picture := MergeAllImages(FloorPlanPnl, FloorPlanImg);
end;

Open in new window

except that a screenshot will have difficulties with scrollbars, and most of the time, when an interface let the user move objects around with the mouse, there are some GUI elements that show the user which element is selected (like the 8 black squares in Delphi IDE), and/or a grid etc... all these things that are not welcome in the final bitmap.
BTW, a screen capture will do about the same, drawing all the elements one after another using the Z-Order.
Now that we talk about Z-Order, the above function does not manage the relative Z position of the elements while rendering the final bitmap, so if you happen to have some panels overlapping, the result might differ in that regard. The solution for that would be not to loop through all controls with Components array property but with Controls array property.

I tested it, and fixed a few mistakes. This one is working like a charm (and if you have images that are stretched, it is handled)
Function MergeAllImages(Parent:TWinControl;Floor:TImage):TBitmap;
Var
 Img:TImage;
 WinCtrl:TWinControl;
 i,X,Y:integer;
begin
 Result:=TBitmap.Create;
 Result.Width:=Floor.Width;
 Result.Height:=Floor.Height;
 Result.Canvas.StretchDraw(0,0,Floor.Picture.Graphic);
 for i:=0 to Parent.ControlCount-1 do
  if Parent.Controls[i]<>Floor Then
   begin
    if Parent.Controls[i] Is TImage Then
     begin
      Img:=TImage(Parent.Controls[i]);
      X:=Img.Left-Floor.Left;
      Y:=Img.Top -Floor.Top;
      Result.Canvas.StretchDraw(
       Rect(X,
            Y,
            X+Img.Width,
            Y+Img.Height),
       Img.Picture.Graphic);
     end;
    if Parent.Controls[i] Is TWinControl Then
     begin
      WinCtrl:=TWinControl (Parent.Controls[i]);
      Result.Canvas.Lock;
      WinCtrl.PaintTo(Result.Canvas.Handle,
       WinCtrl.Left-Floor.Left,
       WinCtrl.Top -Floor.Top);
      Result.Canvas.Unlock;
     end;
   end;
end;

Open in new window

Usage :

ReportsFrm.ReportImg.Picture.Bitmap := MergeAllImages(FloorPlanPnl, FloorPlanImg);
Avatar of HenryM2

ASKER

I am getting a copile error
[DCC Error] SiteDesignerMainUnt.pas(18475): E2010 Incompatible types: 'TRect' and 'Integer'
for line
 Result.Canvas.StretchDraw(0,0,Floor.Picture.Graphic);
Oups sorry, how come I let this one slip through

Ah, yes I know, I commented this line to test something before I changed Draw to StretchDraw
Result.Canvas.StretchDraw(Rect(0,0,Floor.Width,Floor.Height),Floor.Picture.Graphic);

Open in new window

Avatar of HenryM2

ASKER

Thanks, its working very well.  I am just having a problem now to align the TPanel componets with the immages on the bitmap.  Hence, my ReportsFrm.ReportImg.Picture.Bitmap must be exactly the same dimentions as the original FloorPlanImg picture.

Then I have to get this to the RaveReport canvas I guess.
I'm not sure I understand what your problem is. Do you still need help ?
Avatar of HenryM2

ASKER

I was trying to figure it out myself, but yes, I need help:

On the attached scrern print the left part of the picture shows the original bitmap immage with the TPanels on it.  The right part of the image shows the ReportsFrm with the Merged Panels on it.  The panels are exactly right in size and position but for some reason, which I can't spot, the newly created bitmap is stretcehd in the vertical but the horizontal seem fine.  I would have immagined that the code:
  Result.Width:=Floor.Width;
  Result.Height:=Floor.Height;
would have taken care of this but that does not seem to be the case.
Avatar of HenryM2

ASKER

Here is the screen print:

Floor-Plan-Stretched.png
That is why I suggested a simple screen capture :-)
That is probably because your report image component has a Stretch property to true
these lines :
  Result.Width:=Floor.Width;
  Result.Height:=Floor.Height;
are only setting the size of the bitmap

but when you assign a bitmap to a TImage, the bitmap can be resized to fit the TImage width/height (if Stretch = True )
Only if you have TImage.AutoSize = True can you be sure that the TImage will be exactly the same size of it's inner image source (the bitmap, or gif, jpeg, whatever image format...)
Of course, epasquier is right in saying that the screen capture will have problem with scrollbars. But if your interface currently limits the floorplan drawing to only the visible form portion, then the screenshot will be an easier quickfix.
>> That is why I suggested a simple screen capture :-)
The problem would be exactly the same. That is the TImage component that is responsible for the actual display size/proportions of the image.

I finally found Rave Report help, and it would seem that the TRaveBitmap property that control this is MatchSide, and it should be = msInside (fit proportionally within the designated component area).
You have probably the value msBoth (=Stretch true for TImage)
If it's a  screen capture, at least the whole thing will be scaled proportionately?
Henry, there might be some misunderstanding.

Check that your FloorPlanImg.AutoSize:=True , because if its display size is not = to the component size, the function would not work properly. You have 2 possibilities : either use Stretch = True, or AutoSize=True. But not Align=alClient without Stretch nor Autosize, that would be wrong

Also, when you have checked the above , can you do this test :
With MergeAllImages(FloorPlanPnl, FloorPlanImg) do
 begin
  SaveToFile('C:\Merge.Bmp');
  Free;
 end;

and post the image ? so that we know if the problem is from the Bitmap generation or the Bitmap display in the report ? 

Open in new window

Avatar of HenryM2

ASKER

DragonSlayer, It would have been nice to do a simple screen capture.  Unfortunately, the bitmap is not entirely visible as it has a ScrollBox base beneath the FloorPlanPnl..

Epasquier, attached the BMP as requested.. The problem persists where the BMP immage is stretced in the vertical but not in the horizontal.

The parent structure for the BMP is as follows:
MainFrm
  ScrollBox2  > alClient, AutoScroll = True, Auto Size = False,
    FloorPlanPnl > alNone,  AutoSize = False, Height = 920, Width = 1210
      FloorPlanImg > alClient, AutoSize = True, Height = 920, Stretch  = False, Width = 1200

When I did this, I had quite a bit of problems to keep the TPanel object on FloorPlanPnl aligned with the backdrop of the bit map.  The above combination is about all that worked reliably.



Merge.Bmp
ASKER CERTIFIED SOLUTION
Avatar of Emmanuel PASQUIER
Emmanuel PASQUIER
Flag of France image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
And that would also give better results : FloorPlanPnl > alNone, AutoSize := True
to make sure that it encapsulate nicely the floor.
By the way, I'm thinking that this panel is probably useless, except if you wanted to display borders around the image. If not, then you can call the MergeAllImages with the scrollbox as parent
Avatar of HenryM2

ASKER

Its working extremely well now. Making  FloorPlanPnl > AutoSize := True didn't realy make a difference but I am leaving that one True now.

Yes, the Panel originally was before I had the Scrollbox, so I will try it without it.

Whats now the best way to get this merged.bmp to a printer, is it Rave reports or is there a simpler way?
If that is the only thing you want to print in the page, you could well print the image directly on the printer. That is not that hard to do, but a report tool will avoid you the hassle to compute the optimum size & position depending on the printer resolution, page orientation, margins etc...
So use the TRaveBitmap object on a report and set its content to the bitmap you just created :

Bmp:=MergeAllImages(FloorPlanPnl, FloorPlanImg);
RaveBitmap.Image.Assign(Bmp);
Bmp.Free;

that should be it
Avatar of HenryM2

ASKER

I dont quite get it when you say: RaveBitmap.Image.Assign(Bmp);

I put the code below together from example snips.  It works butt only the left half of the Bitmap image is visible.

I have a  MyRvSystem of TRvSystem on the form.

In the OnPrint event of MyRvSystem I have:

procedure TReportsFrm.MyRvSystemPrint(Sender: TObject);
Var Report: TBaseReport;
begin
  PrintGraphicsReport(Sender as TBaseReport)
end;

procedure TReportsFrm.PrintGraphicsReport(Report: TBaseReport);
var
  Bitmap : TBitmap;
begin
  with Report do
  begin
    Bitmap := TBitmap.Create;
    try
      BitMap := MainFrm.MergeAllImages(MainFrm.FloorPlanPnl, MainFrm.FloorPlanImg);
      PrintBitmap(3.5,0.3,1,1, Bitmap); // Obviously this is the problem but how do I fix it?
    finally
      Bitmap.Free;
    end;
  end;
end;
first, this is an error :

   Bitmap := TBitmap.Create; // You create a bitmap object an dstore its ref in Bitmap variable
    try
 // and here you overwrite this reference with the bitmap created by the function : you old bitmap ref is lost and will never be destroyed
      BitMap := MainFrm.MergeAllImages(MainFrm.FloorPlanPnl, MainFrm.FloorPlanImg);
      PrintBitmap(3.5,0.3,1,1, Bitmap); // Obviously this is the problem but how do I fix it?
    finally
      Bitmap.Free;
    end;

This is how it's done :
// Bitmap := TBitmap.Create; // No creation here

 BitMap := MainFrm.MergeAllImages(MainFrm.FloorPlanPnl, MainFrm.FloorPlanImg);
 PrintBitmap(3.5,0.3,1,1, Bitmap);
 Bitmap.Free;

Simply... As for the thing that needs fixing, I don't know rave enough. Maybe I can have a look tomorrow, but you'd better ask in another post how to assign a bitmap to a rave report. That question is too long for another expert come and look here

Open in new window

Avatar of HenryM2

ASKER

epasquier  My question originally was to get the whole thing to Rave.  I however do agree that the whole thing escalated beyond expectation to get the merging right which I think was the bulk of the problem.  Thanks for your help, I will address this in a separate post tomorrow if I don't come right.  Been a long day.
Thank you, yes that has been a long day.
Just for the record, you should have selected this post #33609596 as answer, if somebody else need a solution for this same kind of problem, that is the real thing.

But don't bother really - good night
Avatar of HenryM2

ASKER

Thanks for pointing out the correct post that I should have selected.  I thaught I should select the last post that brough the problem to finality.
You can select multiple answers, but the big one with code solving 95% of the problem should be awarded more points to be sure it will popup as THE answer. Other users looking for solutions will see this one first, and that will have to be the most complete to make them open the post and read all others details. And they will start by reading all the selected posts before reading all the "work in progress posts" - if they ever need those.

That is all the more true when the post selected as parts of solution comes from different expert. The balancing of points is best when proportional to the help you feel they brought (on complex issues at least, sometimes an automated equal sharing is good enough)

If you follow those guide lines, you will be a very effective asker :o)