Tekninen artikkeli

Hahmonna (Render PDF Pages to JPEG Images in Delphi with PDFium Component) PDF-sivut JPEG-kuviksi Delphissä PDFium Component -komponentilla

PDF-sivun hahmontaminen JPEG-kuvaksi koostuu kahdesta toiminnosta, joita ihmisillä on tapana ajaa yhdessä ja sitten virheenkorjata erikseen. Ensin rasteroit sivun pikselibittikartaksi valitsemallasi resoluutiolla. Sitten luovutat kyseisen bittikartan JPEG-kooderille ja valitset laadun. PDFium Component omistaa ensimmäisen puoliskon RenderPage-kutsun kautta; toinen puolisko on puhdasta VCL:ää, TJPEGImage Vcl.Imaging.jpeg-yksiköstä. Sauma niiden välillä on paikka, jossa mielenkiintoiset päätökset piilevät, koska hahmonnuspuolella valitsemasi resoluutio ja koodauspuolella valitsemasi laatu tekevät kauppoja toisiaan vastaan ja tiedostokokoa vastaan tavoilla, joissa on helppo tehdä virheitä

Asia, joka on sisäistettävä ennen mitään koodia: PDF-sivulla ei ole pikseleitä. Se on kuvattu pisteissä, joissa yksi piste on 1/72 tuumaa, ja sivu on niissä pisteissä mitattu vektoripiirros. Kun pyydät PDFiumia hahmontamaan, valitset, kuinka monelle pikselille projisoit tuon piirroksen, ja tuo valinta on DPI. Laske väärin, ja joko hahmonnat sumean pikkukuvan, kun halusit tulostusmestarin, tai varaat 200 megapikselin bittikartan jollekin, jonka on tarkoitus olla 120 pikselin esikatselu

PDFium Component RenderPage -kutsu rasteroi PDF-sivun TBitmapiksi, jonka Delphi VCL -koodi koodaa JPEG:ksi TJPEGImagella
RenderPage omistaa rasterointipuoliskon ja ojentaa sinulle TBitmap:n; VCL-kooderi muuntaa sen JPEG:ksi, kun laatu on asetettu

DPI:stä pikselimittoihin

RenderPage haluaa kokonaislukupikseliset Width- ja Height-arvot, ei DPI:tä. Joten ensimmäinen tehtävä on muuntaminen. Sivu ilmoittaa kokonsa pisteinä PageWidth- ja PageHeight-ominaisuuksien kautta (molemmat Double-tyyppiä), ja muunnos on sama, jota jokainen rasteroija käyttää: pikselit ovat yhtä kuin pisteet kertaa kohde-DPI jaettuna 72:lla. Yhdysvaltalainen Letter-sivu on 612 kertaa 792 pistettä. 150 DPI:llä siitä tulee 1275 kertaa 1650 pikseliä; 72 DPI:llä se pysyy arvossa 612 kertaa 792, yksi pikseli pistettä kohden, mikä on se tapaus, jonka ihmiset unohtavat olevan pelkkä identiteetti

// Pdf.PageNumber-arvon on jo osoitettava haluamaasi sivuun.
PixelW := Round(Pdf.PageWidth  * Dpi / 72);
PixelH := Round(Pdf.PageHeight * Dpi / 72);
Bitmap := Pdf.RenderPage(0, 0, PixelW, PixelH, ro0, [], clWhite);
// ... käytä Bitmap-muuttujaa ...
Bitmap.Free;   // funktiomuotoinen RenderPage antaa omistajuuden sinulle

Kaksi yksityiskohtaa noilla neljällä rivillä ratkaisevat, onko koodi oikein. Ensimmäinen on se, että RenderPage-kutsun funktiomuoto palauttaa TBitmap-objektin, jonka sinä omistat. PDFium varasi sen ja käveli pois; jos et vapauta sitä Free-kutsulla jokaisella iteraatiolla, muutaman sadan sivun erä vuotaa muutaman sadan bittikartan verran, ja prosessi paisuu, kunnes jokin kaatuu. Toinen on Color-argumentti, tässä clWhite. PDF-sivut piirretään yleensä olettaen läpinäkymättömän valkoisen alustan, ja läpinäkyvyyttä sisältävä sivu, joka hahmonnetaan väärälle taustavärille, tuottaa mutaisia reunoja tai satunnaisia tummia haloja. Valkoinen on oikea oletus lähes jokaiselle asiakirjalle; parametri on olemassa niitä harvinaisia tapauksia varten, joissa se ei ole

Kohteet 0, 0 ovat Left- ja Top-poikkeamat sivulle skaalatussa koordinaattiavaruudessa, ja jätät ne nollaan, ellet ole rajaamassa. ro0 on kierto: jätä se nollaan, ja PDFium kunnioittaa mitä tahansa kiertoa, jonka sivu jo ilmoittaa /Rotate-merkinnässään, joten vaakasuuntaiseksi tehty sivu tulee ulos vaakasuuntaisena ilman että teet mitään

Bittikartan koodaaminen JPEG-muotoon

Kun bittikartta on olemassa, JPEG on helppo osa, ja se on puhdasta Delphiä. TJPEGImage.Assign kopioi bittikartan sisään, CompressionQuality asettaa laadun asteikolla 1–100, ja SaveToFile kirjoittaa tiedoston. Ainoa järjestyssääntö on, että laatu on asetettava ennen tallennusta, koska se ohjaa koodausta, jonka SaveToFile laukaisee

Pisteet pikseleiksi -kaava kertoo sivun pisteet DPI:llä jaettuna 72:lla mitoittaakseen RenderPage-tulosteen Delphissä
RenderPage haluaa kokonaisia pikseleitä, joten sivupisteet kerrotaan DPI:llä ja jaetaan 72:lla ennen jokaista kutsua
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;

Tuo sisäkkäinen try/finally näyttää pikkutarkalta yksisivuiselle apulaiselle, ja se on täsmälleen oikein eräajolle. Sisempi lohko vapauttaa kooderin, ulompi lohko vapauttaa bittikartan, ja kumpi tahansa niistä laukeaa poikkeuksessa ja vapauttaa silti sen, mitä se omistaa. Kutista ne yhdeksi, ja koodauksen aikainen poikkeus voi jättää bittikartan orvoksi. Pitkässä juoksussa tuo on ero sellaisen muuntimen, joka valmistuu, ja sellaisen välillä, joka kuolee sivulla 300 vioittuneen tiedoston ja muistin loppumisesta kertovan valintaikkunan kera

DPI:n ja laadun valitseminen yhdessä

Nämä kaksi säädintä eivät ole riippumattomia tulosteen käyttötarkoituksesta, ja yleinen virhe on vääntää molempia ylöspäin varovaisuudesta. 300 DPI:llä hahmonnettu ja laadulla 95 tallennettu verkkopikkukuva on useiden satojen kilotavujen kokoinen tiedosto, joka teeskentelee olevansa 120 pikselin kuva; selain heittää lähes kaiken siitä menemään pienentäessään kokoa. Sovita resoluutio pikseleihin, joita tuloste todella tarvitsee, ja valitse sitten laatu, joka selviytyy JPEG:n häviöllisestä pakkaamisesta ilman näkyviä virheitä

TulosteDPIJPEG-laatu
Luettelon pikkukuva7260-70
Esikatselu näytöllä96-15080-85
Yksityiskohtainen katselu200-30085-95
Tulostuksen alkuperäiskappale300-60090-100

JPEG-laatu ansaitsee oman varoituksensa. Se ei ole lineaarinen säädin. Hyppy 70:stä 85:een ostaa todellisen visuaalisen parannuksen kohtuullisella tiedoston kasvulla; hyppy 95:stä 100:aan kaksinkertaistaa tiedoston koon suunnilleen erolla, jota tuskin kukaan huomaa, koska laatu 100 ei silti ole häviötön, se vain lakkaa hylkäämästä paljoakaan. Paljon tekstiä sisältävillä sivuilla JPEG:n lohkopohjainen pakkaus tuhrii glyyfien terävät reunat haaleaksi soimiseksi, minkä vuoksi alle noin 80:n laatu tekee skannatun näköistä tekstiä tulosteeseen, jonka pitäisi olla terävä. Jos sivut ovat enimmäkseen tekstiä ja voit vaihtaa muotoa, PNG hahmontaa tuon tekstin ilman soimista; JPEG ansaitsee paikkansa valokuvamaisessa ja sekasisällössä, jossa sen pakkaus on aidosti pienempi

Nopeammat, pienemmät pikkukuvat

Kun kohteena on pikkukuva uskollisen toisinnon sijaan, voit kertoa hahmontajalle, että sen tulee tehdä vähemmän työtä. Options-parametri ottaa joukon TRenderOption-lippuja, ja muutama niistä vaihtaa uskollisuutta nopeuteen täsmälleen sillä tavalla, jolla pieni esikatselu haluaa. reGrayscale pudottaa värit, mikä sekä hahmontaa nopeammin että tuottaa pienemmän bittikartan koodattavaksi. reNoSmoothImage ja reNoSmoothPath ohittavat reunanpehmennyksen, joka on joka tapauksessa näkymätön pikkukuvakoossa

Vaaka-asentoinen PDF-sivu sovitetaan 200 × 280:n pienoiskuulaatikkoon PDFium Componentin reGrayscale- ja reNoSmoothImage-asetuksilla
Kahden sovitus-suhteen Min pitää vaaka- ja pystysivut saman rajauslaatikon sisällä, ja renderöintiliput vaihtavat tarkkuuden nopeuteen
function RenderThumbnail(Pdf: TPdf; PageNumber, MaxW, MaxH: Integer): TBitmap;
var
  Scale: Double;
begin
  Pdf.PageNumber := PageNumber;
  // Sovita sivu kokoon MaxW x MaxH säilyttäen kuvasuhde.
  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;

Pikkukuvatapaus osoittaa myös puhtaamman tavan ajatella kokoa. Sen sijaan, että mennään DPI:n kautta, lasketaan yksi skaalakerroin, joka sovittaa sivun rajauslaatikon sisään ja säilyttää kuvasuhteen, mikä on se, mitä näiden kahden suhdeluvun Min tekee. Sekä pystysuuntainen että vaakasuuntainen sivu päätyvät saman laatikon sisään ilman vääristymiä, eikä sinun tarvitse koskaan miettiä, mikä DPI vastaa arvoa "sovita 200 kertaa 280". Yksi varoitus koskien reGrayscale-lippua: se muuntaa rasterikuvien sisällön harmaaksi, mutta vektoritäytöt ja teksti pitävät väriarvonsa moottorissa, joten sivu, joka on enimmäkseen vektoritaidetta, voi tulla takaisin vähemmän yksivärisenä kuin lipun nimi antaa ymmärtää. Todelliseen, täysin harmaasävyiseen tulokseen hahmonnetun bittikartan muuntaminen GrayscalePdfBitmap-funktiolla on luotettava polku

Koko asiakirjan eräajo

Sen kokoaminen kokonaista asiakirjaa varten on silmukka PageCount-ominaisuuden yli, jolloin PageNumber liikkuu yhden sivun kerrallaan. Sivut ovat 1-pohjaisia: sivu yksi on PageNumber := 1, ja silmukka suoritetaan PageCount-arvoon asti mukaan lukien, ei arvoon PageCount - 1. Toinen asia, jota eräajon on kunnioitettava, on hiljaisen latauksen sopimus. Active := True-arvon asettaminen ei koskaan nosta virhettä vaurioituneesta tiedostosta tai väärästä salasanasta; se vain jättää Active-tilan arvoon False. Tarkista se ennen kuin hahmonnat yhtäkään sivua, tai ensimmäinen RenderPage-kutsu toimii asiakirjaa vastaan, jota ei koskaan avattu

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));   // nollatäyttö, jotta tiedostot lajittuvat oikein
    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;

Nollatäyttö Digits-muuttujan kautta on pieni asia, joka säästää iltapäivän myöhemmin. Nimeä tiedostot väliltä page_1.jpgpage_10.jpg, ja mikä tahansa työkalu, joka lajittelee ne merkkijonoina, laittaa tiedoston page_10 aivan tiedoston page_1 perään, sekoittaen järjestyksen. Täyttäminen suurimman sivunumeron leveyteen, jolloin 300-sivuinen asiakirja tuottaa tiedoston page_001.jpg, pitää leksikaalisen järjestyksen ja sivujärjestyksen identtisenä kaikkialla myöhemmin

Asiakirjoille, jotka ovat riittävän suuria, jotta muuntaminen vie huomattavasti aikaa, aja se käyttöliittymäsäikeen ulkopuolella tai pumppaa viestejä sivujen välillä, jotta sovellus pysyy vastaavana, ja anna käyttäjälle tapa pysäyttää se. Jos hahmonnat erittäin suuria sivuja ja haluat peruutuksen, joka puree kesken sivun eikä vain sivujen välillä, PDFium Component -komponentilla on progressiivinen hahmonnuspolku peruutustunnuksella; se on raskaampi mekanismi kuin useimmat eräviennit tarvitsevat, mutta se on olemassa, kun yksittäinen sivu 600 DPI:llä on itsessään riittävän hidas estääkseen toiminnan

Yksi viimeinen pariliitos, joka on hyvä tietää. Sivun rasterointi hylkää sen tekstikerroksen: JPEG on pikseleitä, eivätkä sen sisällä olevat sanat ole enää valittavissa tai haettavissa. Kun tarvitset sekä kuvan että sen alla olevan tekstin, hahmonna kuva ja vedä teksti erikseen, mitä kumppaniartikkeli tekstin purkaminen PDF-asiakirjoista PDFium Component -komponentilla käsittelee. Tässä esitetyt RenderPage-ylikuormitukset ja hahmonnusvaihtoehdot ovat osa PDFium Component -komponenttia Delphille ja C++Builderille