Teknisk artikkel

Gjengiv PDF-sider til JPEG-bilder i Delphi med PDFium-komponent

Å gjengi en PDF-side til en JPEG er to operasjoner som folk har en tendens til å kjøre sammen og deretter feilsøke separat. Først rasteriserer du siden inn i et pikselbitkart med en oppløsning du velger. Så gir du det bitkartet til en JPEG-koder og velger en kvalitet. PDFium-komponenten eier den første halvdelen gjennom RenderPage; den andre halvdelen er ren VCL, TJPEGImage fra Vcl.Imaging.jpeg. Sømmen mellom dem er der de interessante beslutningene bor, fordi oppløsningen du velger på gjengivelsessiden og kvaliteten du velger på kodingssiden veies mot hverandre og mot filstørrelsen på måter som er enkle å få feil

Tingen å internalisere før noen kode: en PDF-side har ingen piksler. Den er beskrevet i punkter (points), der ett punkt er 1/72 tomme, og siden er en vektortegning målt i de punktene. Når du ber PDFium om å gjengi, velger du hvor mange piksler den tegningen skal projiseres over på, og det valget er DPI-en. Får du aritmetikken feil, gjengir du enten et uskarpt miniatyrbilde når du ville ha en utskriftsmester, eller du tildeler et 200 megapiksels bitkart for noe som er bestemt til å være en 120 piksels forhåndsvisning

Fra DPI til pikseldimensjoner

RenderPage vil ha heltallige pikselverdier for Width og Height, ikke en DPI. Så den første jobben er konvertering. En side rapporterer størrelsen sin i punkter gjennom PageWidth og PageHeight (begge Double), og konverteringen er den samme enhver rasterisator bruker: piksler tilsvarer punkter ganger mål-DPI delt på 72. En US Letter-side er 612 ganger 792 punkter. Ved 150 DPI blir det 1275 ganger 1650 piksler; ved 72 DPI forblir det 612 ganger 792, én piksel per punkt, som er tilfellet folk glemmer bare er identiteten

// Pdf.PageNumber must already point at the page you want.
PixelW := Round(Pdf.PageWidth  * Dpi / 72);
PixelH := Round(Pdf.PageHeight * Dpi / 72);
Bitmap := Pdf.RenderPage(0, 0, PixelW, PixelH, ro0, [], clWhite);
// ... use Bitmap ...
Bitmap.Free;   // the function-form RenderPage hands you ownership

To detaljer i de fire linjene avgjør om koden er riktig. Den første er at funksjonsformen av RenderPage returnerer en TBitmap som du eier. PDFium tildelte den og gikk sin vei; hvis du ikke bruker Free på den ved hver iterasjon, vil en batch over noen hundre sider lekke noen hundre bitkart og prosessen eser ut inntil noe velter. Den andre er Color-argumentet, clWhite her. PDF-sider tegnes vanligvis med en antagelse om et ugjennomsiktig hvitt underlag, og en side med åpenhet (transparency) gjengitt på feil bakgrunnsfarge produserer gjørmete kanter eller forvillede mørke glorier. Hvitt er riktig standard for nesten hvert dokument; parameteren eksisterer for det sjeldne tilfellet der det ikke er det

0, 0 er Left- og Top-forskyvningene (offsets) inn i siden, i det skalerte koordinatrommet, og du lar dem stå på null med mindre du beskjærer. ro0 er rotasjon: la den stå på null og PDFium respekterer uansett rotasjon siden allerede erklærer i sin /Rotate-oppføring, så en side forfattet som landskap kommer ut som landskap uten at du gjør noe

Koding av bitkartet som JPEG

Når bitkartet eksisterer, er JPEG den enkle delen, og det er ren Delphi. TJPEGImage.Assign kopierer bitkartet inn, CompressionQuality setter kvaliteten på en skala fra 1 til 100, og SaveToFile skriver filen. Den eneste rekkefølgeregelen er at kvaliteten må settes før du lagrer, fordi det styrer kodingen som SaveToFile utløser

uses
  Vcl.Graphics, Vcl.Imaging.jpeg, PDFium;

procedure SavePageAsJpeg(Pdf: TPdf; PageNumber, Dpi, Quality: Integer;
  const FileName: string);
var
  Bitmap: TBitmap;
  Jpeg: TJPEGImage;
begin
  Pdf.PageNumber := PageNumber;
  Bitmap := Pdf.RenderPage(0, 0,
    Round(Pdf.PageWidth  * Dpi / 72),
    Round(Pdf.PageHeight * Dpi / 72),
    ro0, [], clWhite);
  try
    Jpeg := TJPEGImage.Create;
    try
      Jpeg.Assign(Bitmap);
      Jpeg.CompressionQuality := Quality;   // 1..100
      Jpeg.SaveToFile(FileName);
    finally
      Jpeg.Free;
    end;
  finally
    Bitmap.Free;
  end;
end;

Den nestede try/finally-en ser pirkete ut for en enkeltsideshjelper, og det er helt riktig for en batch. Den indre blokken frigjør koderen, den ytre blokken frigjør bitkartet, og hvis en av dem avfyres på et unntak, frigir de likevel det de eier. Slå dem sammen til én, og et unntak under koding kan strande bitkartet. Over en lang kjøring er det forskjellen mellom en konverterer som fullfører og en som dør på side 300 med en korrupt fil og en tom-for-minne-dialog

Velge DPI og kvalitet sammen

De to skruknappene er ikke uavhengige av utdataenes formål, og den vanlige feilen er å skru opp begge av forsiktighet. Et nett-miniatyrbilde gjengitt ved 300 DPI og lagret på kvalitet 95 er flere hundre kilobyte som utgir seg for å være et 120 piksels bilde; nettleseren kaster nesten alt sammen ved nedskaleringen. Tilpass oppløsningen til de pikslene utdataene faktisk trenger, og velg deretter en kvalitet som overlever JPEG sin tapstunge komprimering uten synlige artefakter

UtdataDPIJPEG-kvalitet
Miniatyrbilde for liste7260-70
Forhåndsvisning på skjerm96-15080-85
Visning med høy detaljgrad200-30085-95
Utskriftsmester300-60090-100

JPEG-kvalitet er verdt et forsiktighetsord for seg selv. Det er ikke en lineær skala. Hoppet fra 70 til 85 kjøper en reell visuell forbedring for beskjeden filvekst; hoppet fra 95 til 100 dobler omtrent filen for en forskjell nesten ingen kan se, fordi kvalitet 100 fortsatt ikke er tapsfri, den slutter bare å forkaste mye. For teksttunge sider smører JPEGs blokkbaserte komprimering de skarpe kantene til glyfer ut i svake ringer (ringing), som er grunnen til at kvalitet under ca. 80 gjør skannet-utseende tekst på det som burde være krystallklare utdata. Hvis sidene stort sett er tekst og du kan bytte formater, gjengir PNG den teksten uten ringingen; JPEG tjener sin plass på fotografisk og blandet innhold der komprimeringen genuint er mindre

Raskere, mindre miniatyrbilder

Når målet er et miniatyrbilde i stedet for en trofast reproduksjon, kan du fortelle gjengiveren om å gjøre mindre arbeid. Options-parameteren tar et sett med TRenderOption-flagg, og noen av dem bytter ut troskap mot hastighet akkurat slik en liten forhåndsvisning ønsker. reGrayscale dropper farge, som både gjengir raskere og produserer et mindre bitkart å kode. reNoSmoothImage og reNoSmoothPath hopper over anti-aliasing som er usynlig på miniatyrbildeskala uansett

function RenderThumbnail(Pdf: TPdf; PageNumber, MaxW, MaxH: Integer): TBitmap;
var
  Scale: Double;
begin
  Pdf.PageNumber := PageNumber;
  // Fit the page inside MaxW x MaxH while preserving aspect ratio.
  Scale := Min(MaxW / Pdf.PageWidth, MaxH / Pdf.PageHeight);
  Result := Pdf.RenderPage(0, 0,
    Round(Pdf.PageWidth  * Scale),
    Round(Pdf.PageHeight * Scale),
    ro0, [reGrayscale, reNoSmoothImage], clWhite);
end;

Miniatyrbildetilfellet viser også den renere måten å tenke på dimensjonering. I stedet for å gå via DPI, beregner du en enkelt skaleringsfaktor som passer siden inni en avgrensende boks og bevarer sideforholdet, som er det Min av de to forholdene gjør. En portrettside og en landskapsside ender begge inni den samme boksen uten forvrengning, og du trenger aldri å resonnere om hvilken DPI som tilsvarer "pass inni 200 ganger 280." Én advarsel med reGrayscale: den konverterer rasterbildeinnhold til grått, men vektorfyll og tekst beholder fargeverdiene sine i motoren, så en side som stort sett er vektorkunst kan komme tilbake mindre monokrom enn flaggets navn antyder. For et ekte full-gråskala-resultat er konvertering av det gjengitte bitkartet med GrayscalePdfBitmap den pålitelige banen

Batching av et helt dokument

Å sette det sammen for et fullt dokument er en løkke over PageCount, med PageNumber flyttet én side av gangen. Sider er 1-baserte: side én er PageNumber := 1, og løkken kjører til PageCount inkludert, ikke PageCount - 1. Den andre tingen batchen må respektere er avtalen om stille innlasting. Å sette Active := True hever aldri på en skadet fil eller et feil passord; det lar bare Active stå på False. Sjekk den før du gjengir en eneste side, ellers virker den første RenderPage mot et dokument som aldri ble åpnet

procedure ExportAllPages(const PdfPath, OutDir: string; Dpi, Quality: Integer);
var
  Pdf: TPdf;
  I, Digits: Integer;
begin
  Pdf := TPdf.Create(nil);
  try
    Pdf.FileName := PdfPath;
    Pdf.Active := True;
    if not Pdf.Active then
      raise Exception.Create('Could not open ' + PdfPath);

    Digits := Length(IntToStr(Pdf.PageCount));   // zero-pad so files sort right
    for I := 1 to Pdf.PageCount do
      SavePageAsJpeg(Pdf, I, Dpi, Quality,
        Format('%s\page_%.*d.jpg', [OutDir, Digits, I]));
  finally
    Pdf.Active := False;
    Pdf.Free;
  end;
end;

Nullutfyllingen gjennom Digits er en liten ting som sparer en ettermiddag senere. Navngi filene page_1.jpg gjennom page_10.jpg og ethvert verktøy som sorterer dem som strenger setter page_10 rett etter page_1, og roter til rekkefølgen. Utfylling til bredden av det høyeste sidenummeret, slik at et 300-siders dokument gir page_001.jpg, holder leksikal rekkefølge og siderekkefølge identisk overalt nedstrøms

For dokumenter som er store nok til at konverteringen tar merkbar tid, kjør den utenfor brukergrensesnitt-tråden (UI thread) eller pump meldinger mellom sidene slik at applikasjonen forblir responsiv, og gi brukeren en måte å stoppe på. Hvis du gjengir veldig store sider og vil ha en avbrytelse som biter midt på siden i stedet for bare mellom sider, har PDFium-komponenten en progressiv gjengivelsesbane med et avbrytelsessymbol; det er en tyngre mekanisme enn de fleste batcheksporter trenger, men den er der når en enkelt side på 600 DPI i seg selv er langsom nok til å blokkere

Ett siste par som er verdt å kjenne til. Å rasterisere en side kaster tekstlaget: JPEG-en er piksler, og ordene i den er ikke lenger valgbare eller søkbare. Når du trenger både et bilde og den underliggende teksten, gjengi for bildet og trekk ut teksten separat, noe ledsagerstykket om å trekke ut tekst fra PDF-dokumenter med PDFium-komponent dekker. RenderPage-overbelastningene (overloads) og gjengivelsesalternativene vist her er en del av PDFium-komponenten for Delphi og C++Builder