delphidelphi-xe3

Delphi - How to add multiple JPG images to a TCanvas and print them


As a Delphi programmer I'm a bit of a hack - learned bits and pieces along the way, mostly self taught.

I'm working on a program for fun that's a database for an out of print card game. Has info of the cards and links to a JPG of the card image.

Was talking to someone about it and they said "Wouldn't it be cool if you could render multiple card images on a page at 2.5 x 3.5 inches and print them".

Legal issues aside (I won't be distributing this without express permission from the former publisher), I wanted to see if it could be done as an exercise to teach me how to use TCanvas. Unfortunately I've really got no idea how to do this.

I'm working with an 8.5 x 11 inch page, so I'd want a 2.3 x 3.5 card image which gives me about an inch to play with that's not taken up by card images, so like 0.25 inches page margins all around and .25 inches between each image. This would put 9 cards on the page.

For what I have written in my program so far, I can drop a TImage on a form and read the associated jpg file for any given card from disk and show it on the form.

Assume I have built a deck of these cards and want to print the images of my "deck". How do I then take that associated image for each card, resize it so that it would fit into a 2.5 x 3.5 space on a canvas and then continue loading images until I've got 9 on the page. Then I'd want to move to a new page and do it again until I've printed the cards in my deck (usually around 50 of them - so about 6 pages).

I've been doing some searching and I'm not sure how to deal with these aspects

The current images are (for the most part) 390 x 546 pixels. (which is the correct ratio for 2.5 x 3.5 inches).

Also, in order to try to preserve ink & paper, is there a way to "preview" the canvas before you send it to the printer (a print preview if you will), or is it just a huge trial and error thing until you get it right.

While specific code example would be preferred, I'm willing to do some legwork if I could even get a pointer to a website that showed how to work with these objects.

About 15 years ago I mucked around with putting Text onto a TCanvas and printing it, but I'm not sure how to make this work with a JPG after reading from disk and then resizing it to print to the scale I want here.

Anyone able to offer some pointers for me here ?

Oh if it matters, I'm working with Delphi XE3 Enterprise / Windows 10.

Thanks.


Solution

  • There is some rude way to make it work.

    First of all, I see at least two ways to make it:

    Which of them the better one? I don't know. I'd prefer second because it will take less RAM I suppose.

    uses ...,jpeg, SysUtils, Graphics;
    
    ...
    
    const CARD_WIDTH:integer = /*your card width*/;
    const CARD_HEIGHT:integer = /*your card height*/;
    const MARGIN_X:integer = 0; //you can change it if you want to
    const MARGIN_Y:integer = 0; //you can change it if you want to
    ...
    
    //Load images. You can call it from button's OnClick event;
    function LoadImages(aWidth, aHeight:integer):TBitmap;
    var i:integer;
        lImage:TJpegImage;
        lResizedBmp:TBitmap;
        lPosX,lPosY:integer; //it can be TPoint as well       
    begin
    result := TBitmap.Create;
    lPosX := MARGIN_X;
    lPosY := MARGIN_Y;
    try
    result.Width := aWidth;
    result.Height := aHeight;
    //I don't know how you will get filenames, so I'll make some dummy code
    for i := 0 to 10 do
       begin
          lImage := TJpegImage.Create;
          lResizedBmp := nil;
          try
             lImage.LoadFromFile('C:\' + inttostr(i) + '.jpg'); //it's bad code just to demonstrate the way we load file. I don't remember if "i.toString()" is acceptable in XE3.
             lResizedBmp := ResizeJpeg(lImage, CARD_WIDTH, CARD_HEIGHT);
             result.Canvas.Draw(lPosX, lPosY, lResizedBmp);
             //let's calculate next card position
             lPosX := lPosX + CARD_WIDTH + MARGIN_X;
             if (lPosX + CARD_WIDTH + MARGIN_X > aWidth) then
                begin
                lPosX := MARGIN_X;
                lPosY := lPosY + MARGIN_Y + CARD_HEIGHT;
                end; 
          finally
             FreeAndNil(lImage); 
             if assigned(lResizedBmp) then
                FreeAndNil(lResizedBmp)
          end;
       end;
    except 
      on (e:Exception) do
       begin
          FreeAndNil(result);
          raise e;
       end; 
    end;
    end;
    
    //Resize image and convert it into Bitmap
    function ResizeJpeg(aJpg:TJpegImage; aWidth, aHeight:integer):TBitmap;
    var lProxyBmp:TBitmap;
    begin
    result := TBitmap.Create;
    try
       result.Width := aWidth;
       result.Height := aHeight;
    
       lProxyBmp := TBitmap.Create;
       try
          lProxyBmp.Assign(aJpg);    
          result.Canvas.StretchDraw(result.Canvas.ClipRect, lProxyBmp);
       finally
          FreeAndNil(lProxyBmp);
       end;
    
    except
       on e:Exception do
       begin
          FreeAndNil(result);
          raise e;
       end;
    end;
    end;
    

    So, you have all procedures to make your page. Just make Form, place TImage and two buttons on in. Set TImage's Stretched and Proportional properties to true. Set buttons' captions to Load and Print. Don't forget to add unit with procedures to uses or make them as Form's methods. Add private field _bmp:TBitmap to your Form;

    For Load button:

    //Page preview. 
    procedure TForm1.Button1Click(Sender:TObject)
    begin
    if assigned(_bmp) then
       FreeAndnIl(_bmp);
    
    _bmp := LoadImages(2000,3000);
    Image1.Picture.Assign(_bmp);
    end;
    

    For Print button:

    procedure TForm1.Button2Click(Sender:TObject)
    begin
    if not assigned(_bmp) then
    begin
      ShowMessage('Click "Load" first');
      Exit;
    end;
    
    with TPrintDialog.Create(nil) do
    try
    if not Execute then
      Exit;
    finally
      Free;
    end;
    
    Printer.BeginDoc;
    try
       Printer.Canvas.Draw(0,0,_bmp);
    finally
       Printer.EndDoc;
    end;
    end;
    

    After I checked this code on Delphi 10.1 using PDF printer I got my pdf file.

    I've tried to make it as simple as possible, but there is plenty of code. I could miss something, but I'm ready to help. All constants can be made as variables and passed as function's params, it's up to you.