Delphi print a custom area of a form - delphi

I have a form which I need to print but only a certain section of it and then enlarge it (increase the scale). So far I have the following code:
procedure TForm1.PrintButtonClick(Sender: TObject);
var
printDialog : TPrintDialog;
begin
printDialog := TPrintDialog.Create(Form1);
if printDialog.Execute then
begin
Printer.Orientation := poLandscape; //Better fit than portrait
Form1.PrintScale:=poPrintToFit;
Form1.Print;
end;
end;
However, this prints the whole form. I've googled around and found a few different things that might help but I'm not sure how to use them:
GetFormImage - Is there a way of selecting a specific area with this or does it just take the whole form?
Using a rectangle with given coordinates e.g rectangle1:= rect(Left, Top, Right, Bottom); but then how do I print scale the rectangle to a larger size and print it? As well, seen as though Delphi only gives Left and Top properties, is Right just another name for the furthest left value you want to go to?
UPDATE:
I have tried to create a custom bitmap and then stretch it but I'm not using the strechdraw correctly. It doesn't actually stretch when printed:
procedure TForm1.PrintButtonClick(Sender: TObject);
var
printDialog: TPrintDialog;
Rectangle, stretched: TRect;
Bitmap: TBitmap;
begin
Bitmap := TBitmap.Create;
try
Rectangle := Rect(0, 90, 1450, 780);
stretched := Rect(0, 0, 5000, 3000); //what numbers do i put in here for streching it?
Bitmap.SetSize(Form1.Width, Form1.Height);
Bitmap.Canvas.CopyRect(Rectangle, Form1.Canvas, Rectangle);
Bitmap.Canvas.StretchDraw(stretched, Bitmap); //not sure how to use this
finally
printDialog := TPrintDialog.Create(Form1);
if printDialog.Execute then
begin
with printer do
begin
BeginDoc;
Canvas.Draw(0, 90, Bitmap);
EndDoc;
end;
end;
Bitmap.Free;
end;
end;
Is the try and finally necessary?
When I printed without the stretchdraw it was really small but when I printed with the stretchdraw, a lot of the image was missing so I must be using it wrong

Get rid of your stretched variable and the Bitmap.Canvas.StretchDraw (you can also get rid of the TPrintDialog if you'd like).
// Capture your bitmap content here, and then use this code to scale and print.
Printer.Orientation := poLandscape;
Printer.BeginDoc;
Printer.Canvas.StretchDraw(Rect(0, 0, Printer.PageWidth, Printer.PageHeight), Bitmap);
Printer.EndDoc;

Related

Take a screenshot of a particular area

I am using Lazarus and I have a TImage inside a form. The black table is a TImage and the numbers are labels. I need to take a screenshot of the red area I drew.
How can I perform this?
I have Lazarus 1.0.14 and I didn't find any example about this. Any suggestion?
This is a painful design, but well, one simple way might be to put all the controls on a common container and copy its canvas to a bitmap. The following example assumes, that you have put your image and all the labels on a common TPanel control (Panel1):
procedure TForm1.Button1Click(Sender: TObject);
var
R: TRect;
Bitmap: TBitmap;
begin
Bitmap := TBitmap.Create;
try
R := Rect(0, 0, Panel1.Width, Panel1.Height);
Bitmap.SetSize(Panel1.Width, Panel1.Height);
Bitmap.Canvas.CopyRect(R, Panel1.Canvas, R);
Bitmap.SaveToFile('C:\Screenshot.bmp');
finally
Bitmap.Free;
end;
end;
You can use GetFormImage to get the form image, and keep the part that corresponds to your image area in it:
var
Bmp: TBitmap;
begin
Bmp := GetFormImage;
try
Bmp.Canvas.CopyRect(Image1.ClientRect, Bmp.Canvas, Image1.BoundsRect);
Bmp.SetSize(Image1.Width, Image1.Height);
Bmp.SaveToFile('....');
finally
Bmp.Free;
end;

BitBlt Printer.Canvas to a TBitMap displays as solid white

I am trying to capture Printer.Canvas as a Bitmap using BitBlt. I want to then take that Bitmap and display it on a paintbox. However, when I attempt this I am given only a white rectangle proportionate to the values I entered for Bitmap.SetSize. My printout looks correct, so I am almost positive the canvas of the printer is being properly drawn to. I attempted the following code using the variable bitmap as the destination and the paintbox as the source (in essence I was drawing a simple rectangle and line of text to the Paintbox, bitblt-ing it to a bitmap, clearing it, and then posting it back to the paintbox), but now that Printer.Canvas.Handle is the source it doesn't display.
I understand that the dimensions between the screen and printer are different so I will clearly indicate dimensions, just in case I am doing it wrong.
procedure TForm2.btnDrawClick(Sender: TObject);
begin
Printer.BeginDoc;
Printer.Canvas.Font.Size := 10; //Not Sure if this is necessary
Printer.Canvas.Font.Name := 'Arial'; //Not Sure if this is necessary
Printer.Canvas.Font.Color := clBlack; //Not Sure if this is necessary
Printer.Canvas.Rectangle(100,100,200,200); //Should print very tiny to paper
//But will look bigger when posted to
//The Paintbox
Printer.Canvas.TextOut(120,120,'XRay-Cat');
PCBitmap.SetSize(Paintbox1.Width,Paintbox1.Height); //Paint box is 300W,300H
Application.ProcessMessages;
BitBlt(PCBitmap.Canvas.Handle, //PCBitmap, is created on create, freed on destroy,
//Defined in the private section
0,
0,
PCBitmap.Width, //300
PCBitmap.Height, //300
Printer.Canvas.Handle,
0,
0,
SRCCOPY);
Application.ProcessMessages;
Printer.EndDoc;
procedure TForm2.btnPostBMClick(Sender: TObject);
begin
PaintBox1.Canvas.Draw(0,0,PCBitmap);
end;
I expect that the canvas would be written too, the canvas would be copied to a bitmap, then be available to be drawn on the paintbox. However all I see is a white rectangle. I am setting the dimensions of the bitmap to be the entire paintbox rather than the entire canvas of the printer. I am doing this because if I understand it correctly I should be only be drawing between the printer canvas's TopLeft 0,0 and BottomRight 300,300 the same way I would on my paint box. I would expect to see the same results as I would if I did this directly to the Paintbox.
Any help would be greatly appreciated. Thanks in advance.
Given the comments I've received it seems what I was trying to do was not possible. What I wanted to do was to write to a printers canvas and then get the image data of that canvas and store it in a bitmap. Since BitBlt can't be used is there a way to do what I wanted? I assume not, as I was told Printer.Canvas cannot be read from. At this point I have Found a way around it but I am just curious.
Switch your logic...draw to the PaintBox...and Print the PaintBox
procedure TForm55.Button1Click(Sender: TObject);
var
a_BM: TBitMap;
begin
a_BM := TBitmap.Create;
try
PaintBox1.Canvas.Font.Size := 10; //Not Sure if this is necessary
PaintBox1.Canvas.Font.Name := 'Arial'; //Not Sure if this is necessary
PaintBox1.Canvas.Font.Color := clBlack; //Not Sure if this is necessary
PaintBox1.Canvas.Rectangle(0,0,300,300); //Should print very tiny to paper
//But will look bigger when posted to
//The Paintbox
PaintBox1.Canvas.TextOut(120,120,'XRay-Cat');
PaintBox1.Width := 300;
PaintBox1.Height := 300;
a_BM.SetSize(PaintBox1.Width, PaintBox1.Height);
BitBlt(a_BM.Canvas.Handle, 0, 0, a_BM.Width, a_BM.Height, PaintBox1.Canvas.Handle, 0, 0, SRCCOPY);
Application.ProcessMessages;
Printer.BeginDoc;
Printer.Canvas.Draw(a_BM.Canvas.ClipRect.Left, a_BM.Canvas.ClipRect.Top, a_BM);
Printer.EndDoc;
Application.ProcessMessages;
finally
a_BM.Free;
end;
end;

Is there a reliable way to draw a bitmap on a Tprinter canvas?

I have created a print preview object which allows me to organise several graphic objects from different sources onto a printed page.
In order to render these objects on the printer canvas I have to first render them to a bitmap of the calculated size and then render the bitmap to the Tprinter canvas at the correct positions using the canvas.draw(x,y,bitmap) method.
I found out early on that a device independent bitmap is required and set the pixel format to pf24bit.
Tried this on the office HP laserjet, works fine, HP colour Deskjet works fine, Cannon colour printer however does not work or occasionally gives intermittent graphics at random points.
Now if I get the graphical objects to render directly to the printer canvas, then all work OK.
Rendering directly to the printer canvas however is not a workable long term solution for me for various reasons.
So, my question is, what is it about the Tprinter.canvas that makes it hardware dependent and is there any workarounds ?
Some example code which divides the page up into 4 quadrants with margins and cell padding and puts a graphical object in each quadrant. Each graphical object has a RenderToBitmap method which draws the graphics.
procedure TMultiPrintForm.PrintBtnClick(Sender: TObject);
var w,h,h2,w2,mv,iw,ih,pv,cw,ch:integer; Abmp:Tbitmap;
begin
Abmp:=Tbitmap.create;
Abmp.PixelFormat := pf24bit;
try
with Printer do
begin
w:=pagewidth;
h:=pageheight;
h2:=h div 2;
w2:=w div 2;
mv:=h*margin.value div 200; //margin percentage div 2
pv:=h*padding.value div 400; //padding percentage div 4
iw:=w-mv; //internal width
ih:=h-mv; //internal height
cw:=(iw-mv) div 2-pv; //Quadrant Cell Width
ch:=(ih-mv) div 2-pv; //Quadrant Cell Height
Abmp.width:=cw;
Abmp.height:=ch;
If Fsources[0]<>nil then
begin
Fsources[0].rendertoBitmap(Abmp);
canvas.draw(mv,mv,Abmp);
end;
If Fsources[1]<>nil then
begin
Fsources[1].rendertoBitmap(Abmp);
canvas.draw(w2+pv,mv,Abmp);
end;
If Fsources[2]<>nil then
begin
Fsources[2].rendertoBitmap(Abmp);
canvas.draw(mv,h2+pv,Abmp);
end;
If Fsources[3]<>nil then
begin
Fsources[3].rendertoBitmap(Abmp);
canvas.draw(w2+pv,h2+pv,Abmp);
end;
end;
finally
printer.Enddoc;
Abmp.free;
end
end;
On further investigation around the net, I accumulated some suggestions into a PrintBitmap procedure which uses a technique recommended by Microsoft.
I replaced all the Canvas.draw calls with PrintBitmap calls (it assumes begindoc and enddoc are set outside the calls).
Being as its the weekend, I can't test it on the problem printers in the office but it does at least not break the printing on the home HP Deskjet printer (which works anyway with both methods).
procedure PrintBitmap(X,Y:integer; Abmp:Tbitmap);
var Info: PBitmapInfo;
InfoSize,ImageSize: DWORD; Image: Pointer;
DIBWidth, DIBHeight: LongInt;
begin
with Printer do
begin
GetDIBSizes(Abmp.Handle, InfoSize, ImageSize);
Info := AllocMem(InfoSize);
Image := AllocMem(ImageSize);
try
GetDIB(Abmp.Handle, 0, Info^, Image^);
with Info^.bmiHeader do
begin
DIBWidth := biWidth;
DIBHeight := biHeight;
end;
StretchDIBits(Canvas.Handle, X, Y, DIBWidth, DIBHeight, 0, 0,
DIBWidth, DIBHeight, Image, Info^, DIB_RGB_COLORS, SRCCOPY);
finally
FreeMem(Image, ImageSize);
FreeMem(Info, InfoSize);
end
end;
end;

CopyRect (scaling) with the correct colors in Delphi

In this question I asked about the correct use of the CopyRect method. I got an answer which fixed my problem, but now the colors of the copied rectangle are wrong (limited to 256 values?).
This is the code:
var
Bmp: TBitmap;
begin
Image1.Picture.LoadFromFile(SomeJPGimage);
Bmp := TBitmap.Create;
try
Bmp.Assign(Image1.Picture.Graphic);
with Bmp do
Image2.Canvas.CopyRect(Image2.Canvas.ClipRect, Canvas, Canvas.ClipRect);
finally
Bmp.Free;
end;
end;
The inset with the false colors is Image2. The colors are right if I don't resize.
How do I get the 24 bit color of the source image (a JPG) when resizing?
edit
Draw is not an alternative; I want to copy a scaled version of part of the source image.
This is not caused because of color reduction, or a wrong pixelformat etc.. You're probably shrinking the image while copying and 'StretchBlt' compresses the image to fit in, and depending on the mode, produces some artifacts. For instance the below 128x128 image    
is displayed exactly the same if no resizing is applied. However if it is applied on a 90x100 image for instance, the output is   .
You can change the stretching mode for a slightly better result:
var
Bmp: TBitmap;
begin
Image1.Picture.LoadFromFile(SomeJPGimage);
Bmp := TBitmap.Create;
try
Bmp.Assign(Image1.Picture.Graphic);
SetStretchBltMode(Image2.Canvas.Handle, HALFTONE); // <- here
with Bmp do
Image2.Canvas.CopyRect(Image2.Canvas.ClipRect, Canvas, Canvas.ClipRect);
finally
Bmp.Free;
end;
end;
For the above source picture the output now becomes:
(Having browsed a little 'graphics.pas', the VCL seems to be using halftone only for 8-bit images. I may be wrong or right in this assessment, but in any case halftone stretching mode has no such constraint.)
For anything better, I believe, you have to use a proper graphics library.
Edited again:
Turns out the issue is going against the WRONG canvas (too easy with TImage if you're not used to it). Tried to save files on my last sample and got a huge file on the one I assigned. So I Started looking into some of the other values and found that you need to work against the Bitmap Canvas...
var
BMP: TBitmap;
MyClipRect: TRect;
begin
if OpenDialog1.Execute then
begin
Image1.Picture.LoadFromFile(OpenDialog1.FileName);
Bmp := TBitmap.Create;
try
Bmp.Assign(Image1.Picture.Graphic);
myClipRect.Left := (Bmp.Width div 2);
myClipRect.Top := (Bmp.Height div 2);
myClipRect.Right := (Bmp.Width);
myClipRect.Bottom := (Bmp.Height);
with Image2.Picture.Bitmap do
begin
Width := Bmp.Width div 2;
Height := Bmp.Height div 2;
Canvas.CopyRect(Canvas.ClipRect, Bmp.Canvas, MyClipRect);
end;
Image2.Picture.SaveToFile('image2.bmp');
finally
Bmp.Free;
end;
end;
end;
Hope that finally got it. Yeesh.

Setting up background images for forms in Delphi

How can I make my program load an image and make it the background for a form?
I need the exact code for it. I've looked all over the internet and the only things I've found are various tweaks and fixes to make backgrounds work as intended in special circumstances. I've also tried some Delphi books I have and I can't find it anywhere.
Put a TImageon your form. Make sure it's behind all other controls on the form. You can right-click it and choose the "send to back" menu option.
Load a graphic.
var
img: TBitmap;
begin
img := TBitmap.Create;
try
img.LoadFromFile('S:\background.bmp');
Assign it to the image control.
Image1.Picture := img;
Clean up.
finally
img.Free;
end;
end;
You can also combine the last three steps to load the graphic and put it in the image control all at once. Thanks to Jon for the suggestion.
Image1.Picture.LoadFromFile('B:\background.bmp');
See also: How to add background images to Delphi forms
What I would do is use the forms OnPaint event, get the canvas (Form1.Canvas), and then use the Draw method (which takes an image) to draw the image you want. Something like the following:
procedure TForm1.FormPaint(Sender: TObject);
var
mypic: TBitMap;
begin
mypic := TBitMap.Create;
try
mypic.LoadFromFile('cant.bmp');
Form1.Canvas.Draw(0, 0, mypic);
finally
FreeAndNil(mypic);
end;
end;
Note that this could be extremely slow.
This is the way all my applications show a form image. I load the image at form creation or when the application calls a specific showing event
var
vDest, vRect: TRect;
begin
vRect := Rect(0, 0, FBackgroundImage.Width, FBackgroundImage.Height);
vDest := Rect(0,0,Self.Width, Self.Height);
Canvas.StretchDraw(vDest, FBackgroundImage);
if FileExists(this) then
FBackgroundImage.LoadFromFile(this);
#Brendan
thanks
//from Brendan code;
var
vDest, vRect: TRect;
FBackgroundImage: TGraphic;
begin
FBackgroundImage := image1.Picture.Graphic; //LOAD from invisible image
vRect := Rect(0, 0, FBackgroundImage.Width, FBackgroundImage.Height);
vDest := Rect(0,0,Self.Width, Self.Height);
Canvas.StretchDraw(vDest, FBackgroundImage);
end;

Resources