E' un po' lunghetto, metto i passi principali
codice:
var
   frxReport1: TfrxReport;
   quanteRighe:integer;
   quantecolonne:integer;

   offsetXmillimetri:double;
   offsetYmillimetri:double;

   larghezzaPagina:double;
   altezzaPagina:double;

   larghezzaFoto:double;
   altezzaFoto:double;
   i,j,k:integer;
   numerofoto:integer;

   quantePagine:integer;

   myPic:array of TfrxPictureView;
   arrayMemo:array of TfrxMemoView;
   arrayPage:array of TFrxReportPage;
   arrayBand:array of TfrxBand;

   FlagPaginaSIngola:boolean;
   ArraymemoID:array of TfrxMemoView;
   ArrayHeader:array of TfrxMemoView;
   offsetHeader:double;


begin
  flagPaginaSingola:=false;

   offsetXMillimetri:=3;
   offsetYMillimetri:=10;
   quanteRighe:=<quantenevuoi>;
   quanteColonne:=<quantenevuoi>

// in lbfotodalavorare indovina cosa c'è

   quantePagine:=lbfotodalavorare.items.count div (quanteRighe*QuanteColonne)+1;

   if (lbfotodalavorare.items.count mod (quanteRighe*QuanteColonne))=0 then
   	dec(quantepagine);

   frxReport1:= tfrxreport.create(nil);
   frxReport1.Clear;

   // (+ per sicurezza)

   setlength(myPic,lbfotodalavorare.items.count+5);
   setlength(arraymemo,lbfotodalavorare.items.count+5);
   setlength(arrayPage,quantepagine+5);
   setlength(arrayBand,quantepagine+5);
   setlength(arrayMemoID,quantepagine+5);
   setlength(arrayHeader,quantepagine+5);


   numerofoto:=0;

   offsetHeader:=0;
   if edtHeader.text<>'' then
       offsetHeader:=40;

   for k:=1 to quantePagine do
   begin
       arrayPage[k]:=TfrxReportPage.Create(frxReport1);
       arrayPage[k].CreateUniqueName;
       arraypage[k].SetDefaults;
       arraypage[k].bottommargin:=20;
       arrayBand[k] := TfrxReportTitle.Create(arrayPage[k]);
       arrayBand[k].CreateUniqueName;
       arrayband[k].Top := 0;
       arrayband[k].Height := 20;

       larghezzaPagina:=arraypage[k].paperwidth;
       altezzaPagina:=arraypage[K].paperheight - (offsetHeader /2);

       larghezzaFoto:=(larghezzaPagina - arraypage[k].leftmargin - arraypage[k].rightmargin - ((quanteColonne-1)*offsetXMillimetri)) / quanteColonne;
       altezzaFoto:=(AltezzaPagina - arraypage[k].topmargin - arraypage[k].bottommargin - ((quanteRighe-1)*offsetYMillimetri)) / quanteRighe;

   for i:=1 to quanteRighe do
   begin
        for j:=1 to quanteColonne do
       begin
	if numeroFoto<lbfotodalavorare.items.count then
           begin
   	myPic[numerofoto]:=TfrxPictureView.Create(arrayband[k]);
           	myPic[numerofoto].CreateUniqueName;
   	mypic[numerofoto].width:=fr01cm*larghezzaFoto; //millimetri
   	myPic[numerofoto].height:=fr01cm*AltezzaFoto;
   	mypic[numerofoto].top:=fr01cm*(i-1)*(altezzaFoto+offsetYMillimetri)+offsetheader;
   	mypic[numerofoto].left:=fr01cm*(j-1)*(larghezzaFoto+offsetXMillimetri);
   	mypic[numerofoto].autosize:=false;
   	mypic[numerofoto].center:=true;
   	mypic[numerofoto].filelink:=lbfotodalavorare.items[numerofoto];
             mypic[numerofoto].frame.typ:=[ftleft]+[ftright]+[fttop]+[ftbottom];

             if <mettinumerofoto>then
              begin
                  arrayMemo[numerofoto] := TfrxMemoView.Create(arrayband[k]);
                  arrayMemo[numerofoto].CreateUniqueName;
                 arrayMemo[numerofoto].Height := 20;
           	arrayMemo[numerofoto].AutoWidth := true;
	arrayMemo[numerofoto].top := fr01cm*((altezzaFoto+1)+(i-1)*(altezzaFoto+offsetYMillimetri))+offsetheader;//fr01cm*(i-1)*(altezzaFoto+offsetYMillimetri);;
            arrayMemo[numerofoto].left := fr01cm*(j-1)*(larghezzaFoto+offsetXMillimetri);
            arrayMemo[numerofoto].Text := 'Foto '+inttostr(numerofoto+1);
               end;
           	INC(Numerofoto);
           end;
   	end;

   end;

    if <mettididascalia>then
       if <i_ID numeropdidascaliasolcaxxo> >0 then
       begin
       	arrayMemoID[k] := TfrxMemoView.Create(arrayband[k]);
           arrayMemoID[k].CreateUniqueName;
           arrayMemoID[k].Height := 35;
           arrayMemoID[k].AutoWidth := true;
           arrayMemoID[k].top := 0; //fr01cm*((altezzaFoto+1)+(i-1)*(altezzaFoto+offsetYMillimetri));;//fr01cm*(i-1)*(altezzaFoto+offsetYMillimetri);;
           arrayMemoID[k].Text := 'ID '+inttostr(i_id);
           arrayMemoID[k].font.size:=20;
           arrayMemoID[k].frame.typ:=[ftleft]+[ftright]+[fttop]+[ftbottom];
           arraymemoID[k].frame.dropshadow:=true;
           arrayMemoID[k].left := 600-arrayMemoID[k].width; //fr01cm*((altezzaFoto+1)+(i-1)*(altezzaFoto+offsetYMillimetri));;//fr01cm*(i-1)*(altezzaFoto+offsetYMillimetri);;
           arraymemoID[k].brushstyle:=bsclear;
       end;


    if <intestazione da mettere><>'' then
    begin
       arrayheader[k] := TfrxMemoView.Create(arrayband[k]);
       arrayheader[k].CreateUniqueName;
       arrayheader[k].Height := 35;
       arrayheader[k].align:=tfrxalign(bawidth);
       arrayheader[k].HAlign := haCenter;
       arrayheader[k].VAlign := vaCenter;

       arrayheader[k].top := 0; //fr01cm*((altezzaFoto+1)+(i-1)*(altezzaFoto+offsetYMillimetri));
       arrayheader[k].Text := edtHeader.Text;
       arrayheader[k].font.size:=15;
       arrayheader[k].frame.typ:=[ftleft]+[ftright]+[fttop]+[ftbottom];
       arrayheader[k].brushstyle:=bsclear;
    end;
Non è difficile, in fondo, no?