Viikko pakkaustoiminnon julkaisun jälkeen saapuu kaksi valitusta: skannatun sopimuksen kirjainmuodot ovat portaittaisia ja karheita, ja kansilehden läpinäkyvä logo makaa vaalean halon sisällä. PDFiumPas vastaa molempiin yhdessä paikassa. TPdf.OptimizeImages mittaa jokaisen kuvan ennen kuin pienentää sen, ja valitsee sitten uudelleenotantakernelin ja akumuloi värit alfaa huomioivassa muodossa
Näin ei aina ollut. Ennen versiota v3.100.0 sama metodi pienensi jokaisen ei-kaksitasoisen kuvan kiinteällä nearest neighbour -vaiheella, joka on täsmälleen se algoritmi, joka tuottaa molemmat valitukset: se ottaa pistenäytteen yhdestä lähdepikselistä jokaista kohdepikseliä kohden ja kohtelee täysin läpinäkyvän pikselin alla olevaa RGB-arvoa ikään kuin lukija näkisi sen koskaan. v3.100.0:n uudelleenkirjoitus korvaa sen yhden polun viidellä kernelillä, mitatulla valintasäännöllä ja eksplisiittisellä työmuistibudjetilla
Miksi pienentäminen tekee skannatusta tekstistä rosoista?
Koska pistenäytteistys vastaa väärään kysymykseen. Kun 300 DPI:n skannaus kohdistetaan 150 DPI:hen, jokainen kohdepikseli edustaa kahden kertaa kahden lähdepikselin lohkoa, ja nearest neighbour säilyttää yhden neljästä ja hylkää loput. Mikä niistä jää henkiin riippuu pyöristyksestä, joten viivan reuna, joka oli lähteessä tasaisen antialiasoitu, muuttuu pikselikohtaiseksi kolikonheitoksi. Tuloksena on klassinen portaittainen aliasing glyyfien reunoilla sekä moiré rasterialueilla, joissa hylätyt näytteet sattuivat kantamaan kuviota. Tämä merkitsee PDF:ssä enemmän kuin näytöllä, koska vahinko on pysyvä. Kuva-XObject kantaa näytetietonsa /Width-, /Height- ja /BitsPerComponent-arvojen ohella (ISO 32000-1 §8.9.5), ja uudelleenotanta kirjoittaa kaikki kolme tiedoston sisällä. Huono zoomaus katselimessa on kehys, jonka voi piirtää uudelleen, ja PDFiumPasilla on siihen erillinen koneisto artikkelissa renderöintivälimuisti ja zoomauksen suorituskyky. Huono pienentäminen on uusi asiakirja, jonka toimitat asiakkaalle
Miten PDFiumPas mittaa yksityiskohtaisuuden ja valitsee kernelin
PDFiumPas päättää kuvittain, ei asiakirjittain. Ennen kernelin valintaa se laskee normalisoidut luminanssin yksityiskohtapisteet rajatulta näytteistysruudukolta: vaakasuora ja pystysuora askel ovat (Width + 63) div 64 ja (Height + 63) div 64, joten 12000 pikselin skannaus ja 300 pikselin pikkukuva maksavat molemmat suunnilleen saman 64 kertaa 64 -kierroksen. Jokaisessa näytteistetyssä kohdassa se summaa itseisarvoeron oikeanpuoleiseen naapuriin ja alla olevaan naapuriin korkeintaan kolmen kanavan yli ja jakaa sitten tuloksen näytemäärän kertaa 255 tulolla. Pisteet asettuvat välille 0–1, jossa tasaiset liiketoimintagrafiikat pysyttelevät nollan lähellä ja tiheä valokuvamainen tekstuuri kiipeää ylöspäin
Valintatikapuu kulkee sitten kiinteässä järjestyksessä. Jos ResampleFilter on mikä tahansa muu kuin pirfAdaptive, kyseistä suodatinta käytetään sellaisenaan. Muussa tapauksessa: 1-bittinen sisältö saa suodattimen pirfBilevel; ContentClass-arvo piccLineArt saa suodattimen pirfBox; skaalauskerroin 4 tai suurempi saa myös suodattimen pirfBox, koska kyseisessä pienennyksessä aluekeskiarvo on sekä halvin että oikein vastaus; piccPhoto, yksityiskohtapisteet 0.08 tai enemmän tai PreferredQuality 0.9 tai enemmän saa suodattimen pirfLanczos kolmihuippuisella kernelillään; skaala 2 tai enemmän tai laatu 0.7 tai enemmän saa suodattimen pirfBicubic säteellä 2; kaikki jäljelle jäävä saa suodattimen pirfBilinear. Koska TPdfImageOptimizeOptions.Default asettaa arvon PreferredQuality 0.85, oletusajo ei koskaan palaudu bilineariin, ellei pienennys ole lievä ja sisältö tasaista
uses
PDFium;
procedure ShrinkScannedPdf(const InputFile, OutputFile: string);
var
Pdf: TPdf;
Options: TPdfImageOptimizeOptions;
Report: TPdfImageOptimizeReport;
begin
Pdf := TPdf.Create(nil);
try
Pdf.FileName := InputFile;
// Oletukset: TargetDpi 150, MinDpiRatio 1.5, PreserveBilevel True,
// MinDimension 8, pirfAdaptive, piccAuto, laatu 0.85, 64 MiB budjetti.
Options := TPdfImageOptimizeOptions.Default;
Options.TargetDpi := 150;
Options.MinDpiRatio := 1.5;
Options.ContentClass := piccAuto;
Options.PreferredQuality := 0.85;
if Pdf.OptimizeImages(Options, Report) and (Report.OptimizedCount > 0) then
Pdf.SaveAs(OutputFile);
finally
Pdf.Free;
end;
end;
Kuvaan kosketaan vain, kun sen vaakasuoran ja pystysuoran sijoitus-DPI:n suurempi, jaettuna TargetDpi-arvolla, saavuttaa arvon MinDpiRatio. Se suojaus on olemassa, jotta 160 DPI:n valokuvaa, joka tähtää 150 DPI:n kohteeseen, ei uudelleenkoodata kuuden prosentin hyödyn takia, joka maksaisi yhden laatusukupolven. Kuvat, jotka ovat kummallakin akselilla alle arvon MinDimension (oletuksena 8), ohitetaan kuvakkeina tai viivoina
Miksi läpinäkyvät logot saavat valkoisen halon?
Koska täysin läpinäkyvän pikselin alla oleva väri on mielivaltainen, ja tavallinen painotettu keskiarvo antaa sen äänestää. Vie logo suunnittelutyökalusta ja näkymätön reunus on usein valkoinen, tai musta, tai mitä kangas sattui olemaan; alfakanava piilottaa sen, ja suora summa kernelin jalanjäljen yli sekoittaa sen nopeasti takaisin näkyvään reunaan. PDFiumPas välttää tämän akumuloimalla BGRA-näytteet esikerroittuneessa muodossa ja kumoamalla esikerroittumisen vasta kohdepikselissä
Konkreettisesti jokainen osallistuva näyte lisää channel * alpha * weight väriakumulaattoriin, alpha * weight alfa-akumulaattoriin ja weight painosummaan. Kohdeväri jaetaan sitten alfa-akumulaattorilla eikä painosummalla, ja se on askel, jolla on merkitystä: jakaminen painosummalla vetäisi värin kohti näkymättömiä pikseleitä, kun taas jakaminen akumuloidulla alfalla rakentaa uudelleen värin, josta näkyvät näytteet oikeasti olivat yhtä mieltä. Kohdealfa on erillinen suure, 255 * AlphaSum / WeightSum. Ei-alfa-formaatit jakavat painosummalla kuten yleensä, FPDFBitmap_BGRx-kohteen täyttötavu kirjoitetaan vakioksi 255, ja jokainen kanava puristetaan väliin 0–255 ennen tallennusta. Kyseinen alfa alkaa yleensä kuvasanakirjan soft mask -merkinnästä (ISO 32000-1 §11.4), jonka PDFium on jo yhdistänyt siihen BGRA-puskuriin, jonka uudelleenotostin vastaanottaa
// Sisimmän akumulointisilmukan muoto, jokaista osallistuvaa lähdenäytettä kohden
if SrcFormat = FPDFBitmap_BGRA then
Alpha := PByte(PAnsiChar(Pixel) + 3)^ / 255
else
Alpha := 1;
for Channel := 0 to Min(BytesPerPixel, 3) - 1 do
Accumulated[Channel] := Accumulated[Channel] +
PByte(PAnsiChar(Pixel) + Channel)^ * Alpha * Weight;
AlphaSum := AlphaSum + Alpha * Weight;
WeightSum := WeightSum + Weight;
// ... ja kohdepikselissä esikerroittuminen kumotaan alfsummaa vasten
if SrcFormat = FPDFBitmap_BGRA then
begin
if Abs(AlphaSum) > 1E-12 then
ValueSum := Accumulated[Channel] / AlphaSum
else
ValueSum := 0;
end
else
ValueSum := Accumulated[Channel] / WeightSum;
1-bittinen viivapiirros pysyy harmausalueen ulkopuolella
Mikä tahansa jatkuva kerneli kaksitasoiseen skannaukseen sovellettuna tuottaa harmaata, ja harmaa on täsmälleen se, mitä faksityylinen kuva ei saa sisältää. PDFiumPas jättää siksi 1-bittiset kuvat rauhaan oletuksena: PreserveBilevel on True asetuksessa TPdfImageOptimizeOptions.Default, ja tällaiset kuvat päätyvät koskemattomina laskuriin SkippedCount. Aseta arvoksi False, niin pirfBilevel-polku ottaa ohjat tasoittavan kernelin sijaan. Se kulkee sen tarkan lähdesuorakulmion läpi, joka kattaa jokaisen kohdepikselin, keskiarvoistaa luminanssin painoilla 0.114, 0.587 ja 0.299 BGR-muistijärjestyksessä ja kynnystää tuloksen arvossa 127.5 puhtaaksi 0:ksi tai 255:ksi. Mitään välisävyä ei voi kirjoittaa, joten reunat pysyvät terävinä eikä harmaata haloa muodostu ohuiden viivojen ympärille; BGRA-lähteen alfakanava keskiarvoistetaan tavallisesti, ja BGRx-kohde saa vakion 255. Jos tarvitset taustalla olevat pikselit eikä pienempää asiakirjaa, kuvien poimiminen PDF-asiakirjoista on erillinen polku
Mitä tapahtuu, kun kuva ylittää työmuistibudjetin?
Se jätetään täsmälleen sellaiseksi kuin se oli, ja se lasketaan. MaxWorkingBytes on oletuksena 64 MiB ja sitä valvotaan kahdesti. Ennen kohdebittikartan luomista PDFiumPas hylkää kuvan, jos leveys kertaa korkeus kertaa tavua pikseliä kohden ylittää budjetin. Sen jälkeen kun FPDFBitmap_CreateEx on onnistunut, se tarkistaa uudelleen käyttäen oikeaa strideä kertaa korkeus, koska rivitäyttö voi työntää allokoinnin ohi rajan, jonka naiivi tulo päästi läpi. Kumpikin hylkäys tuhoaa kohteen eikä palauta mitään. Ole selkeä siitä heikkenemisestä, mitä tämä merkitsee: budjetin ylittänyttä kuvaa ei uudelleenotosteta alhaisemmalla laadulla, eikä sitä jaeta laatoiksi. Alkuperäinen jää asiakirjaan, BudgetExceededCount ja SkippedCount kasvavat molemmat, ja ajo voi siksi ilmoittaa onnistumisen, vaikka asiakirja on optimoitu vain osittain. Kyseessä on tahallinen fail-safe-käyttäytyminen, mutta se tarkoittaa, ettei raportti ole valinnaista luettavaa. Myös erillinen vikatila on olemassa: kuvat, joiden bittikarttaa PDFium ei pysty tuottamaan lainkaan, kuten CMYK-, JPX-, JBIG2- tai maskatut lähteet, kasvattavat laskuria FailedCount ja jätetään vastaavasti koskemattomiksi
procedure OptimizeBatch(const Files: array of string);
var
Pdf: TPdf;
Options: TPdfImageOptimizeOptions;
Report: TPdfImageOptimizeReport;
I: Integer;
begin
Options := TPdfImageOptimizeOptions.Default;
Options.PreserveBilevel := False; // käytä bilevel-alueen äänestystä
Options.ContentClass := piccPhoto; // pakota Lanczos valokuvajoukoille
Options.MaxWorkingBytes := 256 * 1024 * 1024; // varatilaa suurille skannauksille
Pdf := TPdf.Create(nil);
try
for I := Low(Files) to High(Files) do
begin
Pdf.FileName := Files[I];
if not Pdf.OptimizeImages(Options, Report) then
begin
WriteLn('optimize failed: ', Report.ErrorMessage);
Continue;
end;
if Report.BudgetExceededCount > 0 then
WriteLn(Files[I], ': ', Report.BudgetExceededCount,
' image(s) over budget and kept at full size');
if Report.FailedCount > 0 then
WriteLn(Files[I], ': ', Report.FailedCount,
' image(s) could not be decoded to a bitmap');
if Report.OptimizedCount > 0 then
Pdf.SaveAs(ChangeFileExt(Files[I], '.opt.pdf'));
end;
finally
Pdf.Free;
end;
end;
Raportin lukeminen ennen tiedoston toimittamista
TPdfImageOptimizeReport on rakennettu diagnosoitavaksi, ei pelkästään lokitettavaksi. Arvojen OptimizedCount, SkippedCount ja FailedCount ohella se näyttää yhden laskurin per kerneli, joten BoxFilterCount, BilinearFilterCount, BicubicFilterCount, LanczosFilterCount ja BilevelFilterCount kertovat, mihin adaptiivinen sääntö aineistosi suhteen oikeasti päätyi. Kaikki-box-tulos tarkoittaa, että pienennykset olivat jyrkkiä tai sisältö luokiteltiin viivapiirrokseksi; kaikki-Lanczos-tulos asiakirjassa, jonka uskoit olevan viivapiirros, on merkki siitä, että ContentClass kannattaa asettaa eksplisiittisesti. AverageDetailScore on luku, jota verrataan Lanczos-kynnykseen 0.08 säädetessä asetusta PreferredQuality, ja PeakWorkingBytes näyttää, kuinka suuren osan arvosta MaxWorkingBytes ajo oikeasti tarvitsi. Virheelliset asetukset epäonnistuvat äänekkäästi eivätkä hiljaa: ei-positiivinen TargetDpi, MinDpiRatio alle 1, PreferredQuality välin 0–1 ulkopuolella tai ei-positiivinen MaxWorkingBytes nostaa poikkeuksen EPdfError ennen kuin yhtään sivua on koskettu. Ja OptimizeImages muokkaa vain muistissa olevaa asiakirjaa; jokainen muokattu sivu vahvistetaan funktiolla FPDFPage_GenerateContent, minkä jälkeen kutsut silti itse funktiota SaveAs. Nähdäksesi silmämääräisesti, mitä muuttui, renderöi alkuperäisen ja lopullisen asiakirjan bittikartoiksi artikkelissa PDF-sivujen muuntaminen JPEG-kuviksi kuvatulla tavalla ja vertaile niitä täysillä zoomauksilla
Adaptiivinen uudelleenotanta on yksi niistä ominaisuuksista, jotka ovat näkymättömiä toimiessaan ja tuottavat tukipyyntöjä toimiessaan väärin, minkä vuoksi mittaus, alfan käsittely ja muistibudjetti piti saada valmiiksi yhdessä eikä kolmena erillisenä viimeistelynä. Jos arvioit tätä Delphi-, C++Builder- tai Lazarus-tuotteeseen, koko API-pinta ja lisensointitiedot ovat PDFiumPas Delphi PDFium -komponentin sivulla