Delphi Image Print - delphi

I have an array of TImages each one containing thumbnail of a Image file in a specified directory and their Hint property set to their Image Filename for printing purpose.
all files are located on a remote server in a shared directory (Example: \192.168.1.50\imgscan\12-14-54\ *.jpg).
also each Image has a corresponding TCheckBox that users can check to mark images for printing.
I use the following code for printing (variable images_index holds the number of images in the selected directory)...
procedure PrintSelectedImages;
var
i: integer;
R1, R2: TRect;
Picture: TPicture;
Bitmap: TBitmap;
Total, done: integer;
begin
Total := 0;
done := 0;
for i := 0 to images_index - 1 do
if Checks[i].Checked then
INC(Total);
if Total = 0 then
begin
MessageDlg('No Images Selected!', mtInformation, [mbOK], 0);
Exit;
end;
Printer.BeginDoc;
if PrintDialog1.Execute then
begin
for i := 0 to images_index - 1 do
begin
if Checks[i].Checked then
begin
try
Picture := TPicture.Create;
Picture.LoadFromFile(images[i].Hint);
Bitmap := TBitmap.Create;
try
Bitmap.Width := Picture.Width;
Bitmap.Height := Picture.Height;
Bitmap.Canvas.Draw(0, 0, Picture.Graphic);
R1 := Rect(0, 0, Bitmap.Width, Bitmap.Height);
R2 := Rect(0, 0, Printer.PageWidth, Printer.PageHeight);
Printer.Canvas.CopyRect(R2, Bitmap.Canvas, R1);
INC(done);
finally
Bitmap.Free;
end;
finally
Picture.Free;
end;
if done < Total then
Printer.NewPage;
end; // if
end; // i
end; // if
Printer.EndDoc;
end;
Now...
On Microsoft XPS Document Writer I have no problems and all the pages are printed fine, but on real printers most of the time white papers come out and sometimes only some of the selected images are printed (for example 4 of 10 selected files).
What is the problem with my code? i googled a lot and found nothing!
Thanks.

The Canvas CopyRect function uses StretchBLT. We have had better results using the DIBits functions SetDIBitsToDevice or StretchDIBits. Here is our draw code. We have a DrawParams struct that is passed in with the details on how this image should be drawn.
The code below is using a TBitmap32 from graphics32. We use that because of some other drawing and resize routines we find useful. But the same code will work with a normal TBitmap.
{ TDrawParamsRecord }
TDrawParamsRecord = record
private
function GetHeight(): integer;
function GetWidth(): integer;
public
PictureZoom: integer;
Stretch: boolean;
Center: boolean;
KeepAspectRatio: boolean;
OutputRect: TRect;
ResizeMode: TResizeMode;
property Height: integer read GetHeight;
property Width: integer read GetWidth;
function Equal(OtherParams: TDrawParamsRecord): boolean;
end;
{
TCFImage.OutputToCanvas
---------------------------------------------------------------------------
When writing to the canvas we could have a Screen canvas, a metafile canvas
used to create a PDF file, or a printer canvas. Because of this we want to
make sure we are using the DIBits functions. Many printer drivers can't use
the StretchBLT function because of color space changes. Everyone should
support StretchDIBits.
When resizing the image we sometimes will resize it internally to match the
output size and other times we will let StretchDIBits handle the conversion.
}
procedure TCFImage.OutputToCanvas(Canvas: TCanvas; Image: TBitmap32; DrawParams: TDrawParamsRecord);
var
// StretchDIBits has BmpInfo passed in as a Var parameter so we can't
// use the read only property.
BmpInfo: TBitmapInfo;
begin
BmpInfo := Image.BitmapInfo;
// If th output matches the current image size then we can just move the bits,
// no reason for "Stretch"
if (DrawParams.Height = Image.Height) and (DrawParams.Width = Image.Width) then
begin
SetDIBitsToDevice(Canvas.Handle,
DrawParams.OutputRect.Left, DrawParams.OutputRect.Top,
DrawParams.Width, DrawParams.Height,
0, 0, 0, Image.Height, Image.Bits, BmpInfo, DIB_RGB_COLORS);
end
else
begin
StretchDIBits(Canvas.Handle,
DrawParams.OutputRect.Left, DrawParams.OutputRect.Top,
DrawParams.Width, DrawParams.Height,
0, 0, Image.Width, Image.Height,
Image.Bits, BmpInfo, DIB_RGB_COLORS, SRCCOPY);
end;
end;

Related

How do I properly display a combined image in a Delphi `tstringgrid` component

I have a game program that requires the user to select from 50+ images to place on a 9x9 game board grid with a timage in each position. For the user to place the images, I am providing a tstringgrid which displays various images from a timagelist. The actual images are graphic symbols created in .png format with transparent regions to allow the background color of the image's parent to show through when displayed. The image selected from the tstringgrid displays correctly on the game board timage components, but not in the 'tstringgrid'. The tstringgrid displays an image's transparent areas as black which is unsightly and makes many of the symbols unreadable.
I have used the following code to load the tstringgrid:
procedure TImageForm.FormCreate(Sender: TObject);
var
r, c, n : Integer;
img:TImage;
begin
//assign a value to each cell to connect with imagelist.
//see StringGrid1DrawCell
img := timage.Create(nil);
try
n := -1;
with StringGrid1 do begin
for r := 0 to RowCount - 1 do begin
for c:= 0 to ColCount - 1 do begin
inc(n);
Cells[c,r] := IntToStr(n);
ImageList1.GetBitmap(n, img.Picture.Bitmap);
// ImageList1.AddMasked(Img.Picture.Bitmap, clBlack);
end;
end;
end;
finally
img.Free;
end;
end;
What I need to do is revise the bitmap retrieved from the list before it is displayed.
I am attempting to do this as follows:
procedure TForm1.FillBkgd (bmp : tbitmap;clr : tcolor);
//which is the imagelist index for the image
var
bmp1 : tbitmap;
begin
bmp1 := tbitmap.create;
try
bmp1.Width := 50;
bmp1.Height := 50;
with Bmp1.Canvas do begin
Brush.Color := clr; //stringgrid1.color;
Brush.Style := bsSolid;
FillRect(rect(0,0,50, 50));
end;
bmp1.Canvas.Draw(0,0, bmp);
bmp:= bmp1;
finally
bmp1.free;
end;
end;
procedure TForm1.StringGrid1DrawCell(Sender: TObject; ACol, ARow: Integer;
Rect: TRect; State: TGridDrawState);
{https://www.youtube.com/watch?v=LOEP312NzsE}
var
bmp : tbitmap;
s : string;
aCanvas: TCanvas;
n : integer;
begin
inherited;
if not Assigned (Imagelist1) then //or (Arow = 0) or (aCol in [0,5])
exit;
bmp := tbitmap.create;
try
s := (Sender as TStringGrid).Cells [aCol, ARow];
// Draw ImageX.Picture.Bitmap in all Rows in Col 1
aCanvas := (Sender as TStringGrid).Canvas;
// Clear current cell rect
aCanvas.FillRect(Rect);
// Draw the image in the cell
n := strtoInt (s);
ImageList1.GetBitmap(n, Bmp);
FillBkgd (bmp,clRed); //stringgrid.color
aCanvas.StretchDraw(Rect,bmp);
finally
end;
end;
My attempts to combine the symbol image with a colored background before placing it in the stringgrid have failed. It is unclear to me whether I am failing to create a solid colored bitmap or am not successfully joining the image to the background.
Function FillBkgd has big problems with the quality of the code.
bmp:= bmp1;
finally
bmp1.free;
end;
All object variables are pointers. bmp and bmp1 objects point to one area of ​​memory that you are freeing. This leads to Access Violation. You are lucky that the pointer is not returned from the function. Function FillBkgd does not work. To get the result, you could use bmp.Assign(bmp1);.
I see a lot of redrawing the picture of ImageList (draw to Bmp, draw to Bmp1, draw ACanvas). After the first transformation transparency information is lost. Therefore at this moment it is necessary to change the background color.
s := (Sender as TStringGrid).Cells [aCol, ARow];
// Draw ImageX.Picture.Bitmap in all Rows in Col 1
aCanvas := (Sender as TStringGrid).Canvas;
// Clear current cell rect
aCanvas.FillRect(Rect);
// Draw the image in the cell
n := strtoInt (s);
//new lines
bmp.Canvas.Brush.Color := clRed;
bmp.Canvas.FillRect(TRect.Create(0, 0, ImageList1.Width, ImageList1.Height));
//new lines
ImageList1.GetBitmap(n, Bmp);
//deleted lines
//FillBkgd (bmp,clRed); //stringgrid.color
aCanvas.StretchDraw(Rect,bmp);
And do not forget to fill out finally to free bmp.

How set DPI resolution of a TIFF image with Delphi?

I use this code to draw a image and save it as TIFF with Delphi 2006:
var Bmp: TBitmap;
MF: TMetaFile;
MetafileCanvas: TMetafileCanvas;
begin
Gdip := TGDIPlusFull.Create('gdiplus.dll');
MF := TMetaFile.Create;
MF.Width := 1000;
MF.Height := 1100;
MetafileCanvas := TMetafileCanvas.Create(MF, 0);
MetafileCanvas.Brush.Color := clRed;
MetafileCanvas.Brush.Style := bsDiagCross;
MetafileCanvas.Ellipse(50, 50, 300 - 50, 200 - 50);
MetafileCanvas.Free;
Bmp := Gdip.DrawAntiAliased(MF);
Image1.Picture.Assign(Bmp);
SynGDIPlus.SaveAs(Bmp, 'c:\test.tif', gptTIF);
Bmp.Free;
MF.Free;
FreeAndNil(GdiP);
end;
NOTE I use free framework fromhttp://www.synopse.info.
The code works very well. However I have a problem. How can I set the TIFF resolution.
My test.tif image have 96 DPI (screen resoltion), but I need of 200 DPI.
Note I cannot want change the image dimensions (width and heght), becuase there are right, I want change only DPI resolution.
I have found many answer about this question but nothing about Delphi.
I've added the following method:
procedure TSynPicture.BitmapSetResolution(DPI: single);
begin
if (fImage<>0) and fAssignedFromBitmap and (DPI<>0) then
Gdip.BitmapSetResolution(fImage,DPI,DPI);
end;
Which will call the corresponding GDI+ API for setting a bitmap resolution.
Then it should be specified when saving:
procedure SaveAs(Graphic: TPersistent; const FileName: TFileName;
Format: TGDIPPictureType; CompressionQuality: integer=80;
MaxPixelsForBiggestSide: cardinal=0; BitmapSetResolution: single=0); overload;
var Stream: TStream;
begin
Stream := TFileStream.Create(Filename, fmCreate);
try
SaveAs(Graphic,Stream,Format,CompressionQuality,MaxPixelsForBiggestSide,
BitmapSetResolution);
finally
Stream.Free;
end;
end;
So you could be able to write in your code:
Bmp := Gdip.DrawAntiAliased(MF);
Image1.Picture.Assign(Bmp);
SynGDIPlus.SaveAs(Bmp, 'c:\test.tif', gptTIF, 80, 0, 200); // force 200 DPI
Bmp.Free;
See this commit.
TWICImage class is able to save DPI information for TIF files, but the access to this feature is not apparent at first glance. Just call the SetResolution function of the Handle.
tif := TWICImage.Create;
...
tif.Handle.SetResolution( DPI_X, DPI_Y);

Replacing StretchDIBits with GDI+ (while drawing an Image to Printer's canvas)

My application is developed in Delphi 6. This is a resource intesive application due to background processing and large volume of data (It consumes around 60MB - 120MB of physical memory). One of the functionality of this application is to create barcode images and print them. If user keeps on generating the Barcodes, then at least One out of Ten Barcode has missing lines in it.
I was able to resolve this issue using TExcellenImagePrinter component. But, it diminished the performance a lot. This resolution was rejected by my client and hence, now I am trying to replace the WinAPI StretchDIBits call with GDI+.
The original source code is as following:
procedure PrintBitmap(ARect:TRect; Bitmap:TBitmap);
var
Info: PBitmapInfo;
InfoSize: dword{Integer};
Image: Pointer;
ImageSize: dword{ integer};
iWidth,iHeight :integer;
iReturn : integer ;
begin
GetDIBSizes(Bitmap.handle,InfoSize,ImageSize);
if (LoadDIBFromTBitmap(Bitmap,Pointer(Info),Image,iWidth,iHeight)) then
begin
SetStretchBltMode(Printer.Canvas.handle,STRETCH_HALFTONE);
SetBrushOrgEx(Printer.Canvas.handle, 0, 0, NIL);
iReturn := StretchDIBits(Printer.Canvas.Handle, ARect.Left, ARect.Top,
ARect.Right - ARect.Left, ARect.Bottom - ARect.Top,
0, 0, Info^.bmiHeader.biWidth,
Info^.bmiHeader.biHeight, Image, Info^,DIB_RGB_COLORS, SRCCOPY);
end;
FreeMemEx(Info);
FreeMemEx(Image);
end;
I got the GDI+ header from the (http://www.progdigy.com/?page_id=7) as suggested by Joe in embarcadero forums (https://forums.embarcadero.com/thread.jspa?messageID=471501#471501).
I have modified my source code as following :
Created an object of TGPGraphics class and assigned the printer's handle to it.
gp := TGPGraphics.Create(Printer.Canvas.Handle);
Created an onject of TGPBitmap class and assigned the barcode image to it.
bmp := TGPBitmap.Create(Info^,Image);
Info is TBitmapInfo and Image is an pointer.
Assigned the Printer's dimension to the an instance rect of TGPRect record
Called the DrawImage function :
gp.DrawImage(bmp,rect);
But, after making these changes, empty image is shown in the printer's output.
Can you point out if I have missed something or my implementation is wrong.
Can you provide any pointers regarding this?
I have found the following code demonstrating creation of compatible bitmap (DBB) from a dib.
That should work for you. Probably it could be written better but all in all it just works ... at least for me..
procedure PRPrintBitmapOnCanvas(Canvas: TMetafileCanvas; Bitmap: TBitmap; posLeft, posTop: Integer);
var
lpbmih: TBitmapInfoHeader;
lpbmi: TBitmapInfo;
aBitmap: HBITMAP;
aDC: LongWord;
begin
Fillchar(lpbmih, SizeOf(lpbmih), 0);
lpbmih.biSize := SizeOf(lpbmih);
lpbmih.biWidth := bitmap.width;
lpbmih.biHeight := bitmap.height;
lpbmih.biPlanes := 1;
lpbmih.biBitCount := 32;
lpbmih.biCompression := BI_RGB;
Fillchar(lpbmi, SizeOf(lpbmi), 0);
lpbmi.bmiHeader.biSize := SizeOf(lpbmi.bmiHeader);
lpbmi.bmiHeader.biPlanes := 1;
lpbmi.bmiHeader.biBitCount := 32;
lpbmi.bmiHeader.biCompression := BI_RGB;
aBitmap := CreateDIBitmap(Canvas.Handle, lpbmih, 0, nil, lpbmi, DIB_RGB_COLORS);
if aBitmap = 0 then RaiseLastOSError;
try
aDC := CreateCompatibleDC(Canvas.Handle);
SelectObject(aDC, aBitmap);
BitBlt(aDC, 0, 0, bitmap.Width, bitmap.Height, bitmap.Canvas.Handle, 0, 0, SRCCOPY);
BitBlt(canvas.handle, posLeft, posTop, bitmap.Width, bitmap.Height, aDC, 0, 0, SRCCOPY);
DeleteDC(aDC);
finally
DeleteObject(aBitmap)
end;
end;

TBitMap to PBitMap KOL

I would like to convert a TBitMap to a PBitMap in KOL.
I tried this but I get a black picture as an output:
function TbitMapToPBitMap (bitmap : TBitMap) : PbitMap;
begin
result := NIL;
if Assigned(bitmap) then begin
result := NewBitmap(bitmap.Width, bitmap.Height);
result.Draw(bitmap.Canvas.Handle, bitmap.Width, bitmap.Height);
end;
end;
Any idea what's wrong with it? I am using Delphi7.
Thank you for your help.
EDIT: New CODE:
function TbitMapToPBitMap (const src : TBitMap; var dest : PBitMap) : Bool;
begin
result := false;
if (( Assigned(src) ) and ( Assigned (dest) )) then begin
dest.Draw(src.Canvas.Handle, src.Width, src.Height);
result := true;
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
TBitMapTest : TBitMap;
PBitMapTest : PBitMap;
begin
TBitMapTest := TBitMap.Create;
TBitMapTest.LoadFromFile ('C:\test.bmp');
PBitMapTest := NewBitMap (TBitMapTest.Width, TBitMapTest.Height);
TbitMapToPBitMap (TBitMapTest, PBitMapTest);
PBitMapTest.SaveToFile ('C:\test2.bmp');
PBitMapTest.Free;
TBitMapTest.Free;
end;
To answer your question why are your target images black; it's because you were drawing those target images to source and black they were because the NewBitmap initializes images to black.
How to copy or convert if you want a TBitmap to KOL PBitmap I found only one way (maybe I missed such function in KOL, but even if so, the method used in the following code is very efficient). You can use the Windows GDI function for bit-block transfer, the BitBlt, which just copies the specified area from one canvas to another.
The following code, when you click on the button creates the VCL and KOL bitmap instances, loads the image to a VCL bitmap, call the VCL to KOL bitmap copy function and if this function succeed, draw the KOL bitmap to the form canvas and free both bitmap instances:
uses
Graphics, KOL;
function CopyBitmapToKOL(Source: Graphics.TBitmap; Target: PBitmap): Boolean;
begin
Result := False;
if Assigned(Source) and Assigned(Target) then
begin
Result := BitBlt(Target.Canvas.Handle, 0, 0, Source.Width, Source.Height,
Source.Canvas.Handle, 0, 0, SRCCOPY);
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
KOLBitmap: PBitmap;
VCLBitmap: Graphics.TBitmap;
begin
VCLBitmap := Graphics.TBitmap.Create;
try
VCLBitmap.LoadFromFile('d:\CGLIn.bmp');
KOLBitmap := NewBitmap(VCLBitmap.Width, VCLBitmap.Height);
try
if CopyBitmapToKOL(VCLBitmap, KOLBitmap) then
KOLBitmap.Draw(Canvas.Handle, 0, 0);
finally
KOLBitmap.Free;
end;
finally
VCLBitmap.Free;
end;
end;

Painting TRichEdit to a canvas

I'm trying to implement an RTF-capable tool tip window in Delphi XE. To render the rich text, I'm using an off-screen TRichEdit. I need to do two things:
Measure the size of the text.
Paint the text
To accomplish both tasks, I wrote this method:
procedure TLookupHintWindow.CallFormatRange(R: TRect; var Range: TFormatRange;
MustPaint: Boolean);
var
TextRect: TRect;
begin
RichText.SetBounds(R.Left, R.Top, R.Right, R.Bottom);
TextRect := Rect(0, 0,
RichText.Width * Screen.Pixelsperinch,
RichText.Height * Screen.Pixelsperinch);
ZeroMemory(#Range, SizeOf(Range));
Range.hdc := Canvas.Handle;
Range.hdcTarget := Canvas.Handle;
Range.rc := TextRect;
Range.rcpage := TextRect;
Range.chrg.cpMin := 0;
Range.chrg.cpMax := -1;
SendMessage(RichText.Handle, EM_FORMATRANGE,
NativeInt(MustPaint), NativeInt(#Range));
SendMessage(RichText.Handle, EM_FORMATRANGE, 0, 0);
end;
The Range parameter is passed in, so I can use the calculated dimensions outside this method. The MustPaint parameter determines if the range should be calculated (False) or painted (True).
To calculate the range, I call this method:
function TLookupHintWindow.CalcRichTextRect(R: TRect; const Rtf: string): TRect;
var
Range: TFormatRange;
begin
LoadRichText(Rtf);
CallFormatRange(R, Range, False);
Result := Range.rcpage;
Result.Right := Result.Right div Screen.PixelsPerInch;
Result.Bottom := Result.Bottom div Screen.PixelsPerInch;
// In my example yields this rect: (0, 0, 438, 212)
end;
To paint it:
procedure TLookupHintWindow.DrawRichText(const Text: string; R: TRect);
var
Range: TFormatRange;
begin
CallFormatRange(R, Range, True);
end;
The problem is that while it calculates a rectangle that is 438 pixels wide and 212 high, it actually paints one that is very wide (gets clipped) and only 52 pixels high.
I have word wrap turned on, although it was my impression that that should not be needed.
Any ideas?
Your units are off. Consider this expression from your code, for example:
RichText.Width * Screen.Pixelsperinch
The left term is in pixels, and the right term is in pixels/inch, so the units of the result are pixels²/inch. The expected unit for the rectangles used in em_FormatRange is twips. If you want to convert pixels to twips, you need this:
const
TwipsPerInch = 1440;
RichText.Width / Screen.PixelsPerInch * TwipsPerInch
You don't need an off-screen rich-edit control. You just need a windowless rich-edit control, which you can instruct to paint directly onto your tool-tip. I've published some Delphi code that makes the basics straightforward. Beware that it's not Unicode-aware, and I have no plans to make it so (although it might not be too complicated to do).
The main function from my code is DrawRTF, shown below, in RTFPaint.pas. It doesn't quite fit your needs, though; you want to discover the size before drawing it, whereas my code assume you already know the dimensions of the drawing target. To measure the size of the RTF text, call ITextServices.TxGetNaturalSize.
Word wrapping is important. Without it, the control will assume it has infinite width to work with, and it will only start a new line when the RTF text requests it.
procedure DrawRTF(Canvas: TCanvas; const RTF: string; const Rect: TRect;
const Transparent, WordWrap: Boolean);
var
Host: ITextHost;
Unknown: IUnknown;
Services: ITextServices;
HostImpl: TTextHostImpl;
Stream: TEditStream;
Cookie: TCookie;
res: Integer;
begin
HostImpl := TDrawRTFTextHost.Create(Rect, Transparent, WordWrap);
Host := CreateTextHost(HostImpl);
OleCheck(CreateTextServices(nil, Host, Unknown));
Services := Unknown as ITextServices;
Unknown := nil;
PatchTextServices(Services);
Cookie.dwCount := 0;
Cookie.dwSize := Length(RTF);
Cookie.Text := PChar(RTF);
Stream.dwCookie := Integer(#Cookie);
Stream.dwError := 0;
Stream.pfnCallback := EditStreamInCallback;
OleCheck(Services.TxSendMessage(em_StreamIn, sf_RTF or sff_PlainRTF,
lParam(#Stream), res));
OleCheck(Services.TxDraw(dvAspect_Content, 0, nil, nil, Canvas.Handle,
0, Rect, PRect(nil)^, PRect(nil)^, nil, 0, txtView_Inactive));
Services := nil;
Host := nil;
end;

Resources