Po odoslaní kompresnej funkcie príde v priebehu týždňa dve sťažnosti: skenovaná zmluva má teraz schodovité, chlpaté tvary písmen a transparentné logo na obálke sedí v bledom hále. PDFiumPas odpovedá na obe na jednom mieste. TPdf.OptimizeImages zmeria každý obrázok skôr, než ho zmenší, potom vyberie jadro prevzorkovania a akumuluje farbu v podobe ohľaduplnej k alfa
Tak to nebolo vždy. Pred verziou v3.100.0 metóda prevzorkovala každý nebilevel obrázok pevným krokom najbližšieho suseda, čo je presne algoritmus, ktorý vyrobí obe sťažnosti: bodovo vzorkuje jeden zdrojový pixel na výstupný pixel a s RGB skrytým pod úplne priehľadným pixelom zaobchádza, akoby sa naň čitateľ kedy pozrel. Prepis vo v3.100.0 nahrádza túto jedinú cestu piatimi jadrami, meraným pravidlom výberu a explicitným rozpočtom pracovnej pamäte
Prečo pôsobí prevzorkovaný skenovaný text rozstrapkane?
Pretože bodové vzorkovanie odpovedá na nesprávnu otázku. Keď sa sken 300 DPI prerieši na 150 DPI, každý cieľový pixel zastupuje blok zdrojových pixelov dva krát dva a najbližší sused ponechá jeden zo štyroch a ostatné zahodí. Ktorý prežije, závisí od zaokrúhlenia, takže hrana ťahu plynulo vyhladená antialiasingom v zdroji sa stane hodom mince na pixel. Výsledok je klasické aliasované schodisko pozdĺž okrajov písmen a navyše moaré na rastrových plochách, kde zahodené vzorky práve niesli vzor. V PDF je to dôležitejšie než na obrazovke, pretože škoda je trvalá. Image XObject nesie svoje vzorkové dáta popri /Width, /Height a /BitsPerComponent (ISO 32000-1 §8.9.5) a prevzorkovanie prepíše všetky tri vnútri súboru. Zlé priblíženie v prehliadači je rámec, ktorý môžete prekresliť, a PDFiumPas na to má samostatnú mechaniku v render cache a výkone priblíženia. Zlé prevzorkovanie je nový dokument, ktorý odovzdáte zákazníkovi
Ako PDFiumPas meria detail a vyberá jadro
PDFiumPas rozhoduje po obrázku, nie po dokumente. Skôr než vyberie jadro, vypočíta normalizované skóre detailu luminancie z ohraničenej vzorkovacej siete: horizontálne a vertikálne kroky sú (Width + 63) div 64 a (Height + 63) div 64, takže sken o 12000 pixeloch a miniatúra o 300 pixeloch stoja približne rovnaké prejdenie 64 krát 64. Na každej vzorkovanej pozícii sčíta absolútny rozdiel k susedovi vpravo a susedovi pod, cez až tri kanály, potom vydelí počtom vzoriek krát 255. Skóre sa usadí v rozsahu 0 až 1, kde plochá firemná grafika sedí blízko nuly a hustá fotografická textúra stúpa
Rebrík výberu potom beží v pevnom poradí. Ak je ResampleFilter čokoľvek iné než pirfAdaptive, používa sa tento filter doslova. Inak: 1-bitový obsah berie pirfBilevel; ContentClass piccLineArt berie pirfBox; faktor mierky 4 a viac berie tiež pirfBox, pretože pri takej redukcii je plošný priemer najlacnejšou aj najsprávnejšou odpoveďou; piccPhoto, skóre detailu 0.08 a viac alebo PreferredQuality 0.9 a viac berie pirfLanczos s jeho trojlobovým jadrom; mierka 2 a viac alebo kvalita 0.7 a viac berie pirfBicubic s polomerom 2; všetko ostatné berie pirfBilinear. Keďže TPdfImageOptimizeOptions.Default nastavuje PreferredQuality na 0.85, predvolený beh nikdy nespadne na bilineárny, pokiaľ redukcia nie je mierna a obsah plochý
uses
PDFium;
procedure ShrinkScannedPdf(const InputFile, OutputFile: string);
var
Pdf: TPdf;
Options: TPdfImageOptimizeOptions;
Report: TPdfImageOptimizeReport;
begin
Pdf := TPdf.Create(nil);
try
Pdf.FileName := InputFile;
// Predvolené: TargetDpi 150, MinDpiRatio 1.5, PreserveBilevel True,
// MinDimension 8, pirfAdaptive, piccAuto, kvalita 0.85, rozpočet 64 MiB.
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;
Obrázok sa dotkne len vtedy, keď väčšia z hodnôt DPI jeho horizontálneho a vertikálneho umiestnenia delená TargetDpi dosiahne MinDpiRatio. Táto stráž existuje preto, aby fotografia 160 DPI mierená na cieľ 150 DPI nebola znovu enkódovaná pre šesťpercentný zisk, ktorý stojí jednu generáciu kvality. Obrázky pod MinDimension na ktorejkoľvek osi, predvolene 8, sa preskočia ako ikony alebo čiary
Prečo dostávajú transparentné logá biely okraj?
Pretože farba pod úplne priehľadným pixelom je ľubovoľná a obyčajný vážený priemer jej dovolí hlasovať. Vyexportujete logo z dizajnérskeho nástroja a neviditeľný okraj je často biely, čierny alebo čokoľvek, čím bolo plátno; alfa kanál ho schová a priamy súčet cez stopu jadra ho rýchlo zamieša späť do viditeľného okraja. PDFiumPas sa tomu vyhýba tak, že akumuluje vzorky BGRA v premultiplikovanej podobe a premultiplikáciu odvolá až na cieľovom pixeli
Konkrétne: každá prispievajúca vzorka pridá channel * alpha * weight do akumulátora farby, alpha * weight do akumulátora alfa a weight do súčtu váh. Cieľová farba sa potom vydelí akumulátorom alfa, nie súčtom váh, a presne v tom je krok, ktorý rozhoduje: delenie súčtom váh by farbu ťahalo k neviditeľným pixelom, zatiaľ čo delenie akumulovanou alfou zrekonštruuje farbu, na ktorej sa viditeľné vzorky skutočne zhodli. Cieľová alfa je samostatná veličina, 255 * AlphaSum / WeightSum. Formáty bez alfy delia súčtom váh ako zvyčajne, výplňový bajt cieľa FPDFBitmap_BGRx sa zapíše ako konštantných 255 a každý kanál sa pred uložením pritlačí do rozsahu 0 až 255. Táto alfa zvykne pochádzať z položky soft mask v slovníku obrázku (ISO 32000-1 §11.4), ktorú PDFium už skompozitoval do vyrovnávacej pamäte BGRA, ktorú resampler dostáva
// Tvar vnútornej akumulačnej slučky, pre každú prispievajúcu zdrojovú vzorku
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;
// ... a v cieľovom pixeli, odpremultiplovať proti súčtu alfa
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;
Ako držať 1-bitovú linkovú grafiku mimo šedej zóny
Každé spojité jadro aplikované na bilevel sken vyrobí sivú a sivá je presne to, čo faxový štýl obrázku obsahovať nesmie. PDFiumPas preto 1-bitové obrázky v predvolenom stave necháva tak, ako sú: PreserveBilevel je True v TPdfImageOptimizeOptions.Default a také obrázky skončia v SkippedCount nedotknuté. Nastavte na False a cestu prevezme pirfBilevel namiesto vyhladzovacieho jadra. Tá prejde presný zdrojový obdĺžnik pokrývajúci každý cieľový pixel, spriemeruje luminanciu s váhami 0.114, 0.587 a 0.299 v poradí pamäte BGR a výsledok prahuje pri 127.5 na ploché 0 alebo 255. Nič medziľahlé sa zapísať nedá, takže okraje zostávajú ostré a okolo tenkých ťahov nevzniká sivý halo; alfa kanál BGRA zdroja sa spriemeruje normálne a cieľ BGRx dostane konštantných 255. Ak potrebujete podkladové pixeli a nie menší dokument, extrakcia obrázkov z PDF dokumentov je samostatná cesta
Čo sa stane, keď obrázok prekročí rozpočet pracovnej pamäte?
Nechá sa presne tak, ako bol, a započíta sa. MaxWorkingBytes má predvolených 64 MiB a vynucuje sa dvakrát. Pred vytvorením cieľovej bitmapy PDFiumPas obrázok odmietne, ak šírka krát výška krát bajtov na pixel prekročí rozpočet. Po úspešnom FPDFBitmap_CreateEx skontroluje znovu pomocou skutočného stride krát výšky, pretože výplň riadkov môže alokáciu zatlačiť za limit, ktorý naivný súčin pustil. Každé odmietnutie zničí cieľ a nevráti nič. Buďte si vedomí degradácie, ktorú to znamená: obrázok nad rozpočet sa neprevzorkuje v nižšej kvalite a nedelí sa na dlaždice. Originál zostáva v dokumente, BudgetExceededCount aj SkippedCount stúpnu a beh preto môže nahlásiť úspech, zatiaľ čo dokument je optimalizovaný len čiastočne. Toto je zámerné fail-safe správanie, ale znamená to, že report nie je nepovinné čítanie. Existuje aj osobitný režim zlyhania: obrázky, ktorých bitmapu PDFium nedokáže vyrobiť vôbec, ako CMYK, JPX, JBIG2 alebo maskované zdroje, zvýšia namiesto toho FailedCount a zostanú rovnako nedotknuté
procedure OptimizeBatch(const Files: array of string);
var
Pdf: TPdf;
Options: TPdfImageOptimizeOptions;
Report: TPdfImageOptimizeReport;
I: Integer;
begin
Options := TPdfImageOptimizeOptions.Default;
Options.PreserveBilevel := False; // použiť plošné hlasovanie bilevel
Options.ContentClass := piccPhoto; // vynútiť Lanczos pre fotografické sady
Options.MaxWorkingBytes := 256 * 1024 * 1024; // rezerva pre veľké skeny
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;
Čítanie reportu skôr, než súbor odošlete
TPdfImageOptimizeReport je stavaný na diagnostiku, nielen na zalogovanie. Popri OptimizedCount, SkippedCount a FailedCount vystavuje jedno počítadlo na jadro, takže BoxFilterCount, BilinearFilterCount, BicubicFilterCount, LanczosFilterCount a BilevelFilterCount vám povedia, k akému záveru o vašom korpuse adaptívne pravidlo skutočne dospelo. Výsledok všetko-box znamená, že redukcie boli prudké alebo obsah bol klasifikovaný ako linková grafika; výsledok všetko-Lanczos na dokumente, o ktorom ste verili, že je linková grafika, je známkou, že ContentClass by sa mal nastaviť explicitne. AverageDetailScore je číslo na porovnanie s prahom 0.08 pre Lanczos pri ladení PreferredQuality a PeakWorkingBytes ukazuje, koľko z MaxWorkingBytes beh skutočne potreboval. Neplatné voľby zlyhajú nahlas, nie potichu: nekladné TargetDpi, MinDpiRatio pod 1, PreferredQuality mimo rozsahu 0 až 1 alebo nekladné MaxWorkingBytes vyvolajú EPdfError skôr, než sa čokoľvek dotkne akejkoľvek stránky. A OptimizeImages upravuje len dokument v pamäti; každá upravená stránka sa zaviaže cez FPDFPage_GenerateContent, po čom SaveAs stále voláte sami. Ak chcete na vlastné oči vidieť, čo sa zmenilo, vyrenderujte dokumenty pred a po na bitmapy ako popisuje konverzia stránok PDF na JPEG obrázky a porovnajte ich pri plnom priblížení
Adaptívne prevzorkovanie patrí k funkciám, ktoré sú neviditeľné, keď fungujú, a generujú support tickety, keď nefungujú; preto meranie, správa alfy a rozpočet pamäte museli prísť spolu, nie ako tri oddelené vylepšenia. Ak to vyhodnocujete pre produkt v Delphi, C++Builder alebo Lazare, úplný povrch API a licenčné detaily nájdete na stránke PDFiumPas Delphi PDFium component