Dvije pritužbe stignu tjedan nakon što kompresijska značajka krene u prodaju: skenirani ugovor sada ima stubasto, dlakavo oblikovana slova, a transparentni logotip na naslovnici sjedi unutar blijedog oreola. PDFiumPas odgovara na oboje na jednom mjestu. TPdf.OptimizeImages izmjeri svaku sliku prije nego što je smanji, a zatim bira jezgru resamplinga i akumulira boju u obliku osviještenom na alfu
To nije uvijek bilo tako. Prije v3.100.0 ista je metoda smanjivala svaku nebilevelnu sliku fiksiranim korakom najbližeg susjeda, što je točno algoritam koji proizvodi obje pritužbe: točkastim uzorkovanjem uzima jedan izvorni piksel po izlaznom pikselu, a RGB ispod potpuno transparentnog piksela tretira kao da bi ga čitatelj ikad vidio. Prepravka u v3.100.0 taj jedini put zamjenjuje s pet jezgri, izmjerenim pravilom odabira i izričitim proračunom radne memorije
Zašto smanjivanje čini skenirani tekst nazobčenim?
Jer točkast uzorkovanje odgovara na pogrešno pitanje. Kad se 300 DPI sken preusmjeri na 150 DPI, svaki odredišni piksel predstavlja blok izvornih piksela dva puta dva, a najbliži susjed zadrži jedan od četiri i odbaci ostatak. Preživjeli ovisi o zaokruživanju, pa rub poteza koji je u izvoru bio glatko zaglađen postaje bacanje novčića po pikselu. Rezultat je klasična nazubljena stepenica uz rubove znakova, plus moare na rasterskim područjima gdje su odbačeni uzorci slučajno nosili obrazac. U PDF-u je to važnije nego na ekranu jer je šteta trajna. Slika XObject nosi svoje podatke uzoraka uz /Width, /Height i /BitsPerComponent (ISO 32000-1 §8.9.5), a resampling prepisuje sva tri unutar datoteke. Loš zoom u pregledniku je kadar koji možete ponovno iscrtati, a PDFiumPas za to ima zasebnu mehaniku u render kešu i performansama zooma. Loš downsample je novi dokument koji predajete kupcu
Kako PDFiumPas mjeri detalje i bira jezgru
PDFiumPas odlučuje po slici, ne po dokumentu. Prije odabira jezgre računa normalizirani rezultat detalja luminancije iz ograničene mreže uzorkovanja: vodoravni i okomiti koraci su (Width + 63) div 64 i (Height + 63) div 64, pa 12000-pikselni sken i 300-pikselna minijatura koštaju otprilike isti predlaz 64 puta 64. Na svakom uzorkovanom položaju zbraja apsolutnu razliku prema susjedu desno i susjedu ispod, kroz najviše tri kanala, a zatim dijeli brojem uzoraka puta 255. Rezultat pada u raspon 0 do 1, gdje ravna poslovna grafika sjedi blizu nule, a gusta fotografska tekstura raste
Ljestvica odabira zatim prolazi u fiksnom redoslijedu. Ako je ResampleFilter išta drugo nego pirfAdaptive, taj se filter koristi doslovno. Inače: 1-bitni sadržaj uzima pirfBilevel; ContentClass od piccLineArt uzima pirfBox; faktor mjerila 4 ili više također uzima pirfBox, jer je pri tom smanjenju prosjek površine i najjeftiniji i najtočniji odgovor; piccPhoto, rezultat detalja 0.08 ili viši ili PreferredQuality 0.9 ili viši uzimaju pirfLanczos s njegovom trokrilom jezgrom; mjerilo 2 ili više ili kvaliteta 0.7 ili viša uzima pirfBicubic radijusa 2; sve što ostane uzima pirfBilinear. Budući da TPdfImageOptimizeOptions.Default postavlja PreferredQuality na 0.85, zadano pokretanje nikad ne pada na bilinearno osim ako je smanjenje blago a sadržaj ravan
uses
PDFium;
procedure ShrinkScannedPdf(const InputFile, OutputFile: string);
var
Pdf: TPdf;
Options: TPdfImageOptimizeOptions;
Report: TPdfImageOptimizeReport;
begin
Pdf := TPdf.Create(nil);
try
Pdf.FileName := InputFile;
// Defaults: TargetDpi 150, MinDpiRatio 1.5, PreserveBilevel True,
// MinDimension 8, pirfAdaptive, piccAuto, quality 0.85, 64 MiB budget.
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;
Slika se dira samo kad veća od njezina vodoravnog i okomitog DPI smještaja, podijeljena s TargetDpi, dosegne MinDpiRatio. Taj osigurač postoji da 160 DPI fotografija namijenjena cilju od 150 DPI ne bude ponovno kodirana za dobit od šest posto koja košta jednu generaciju kvalitete. Slike ispod MinDimension po bilo kojoj osi, 8 po zadanom, preskaču se kao ikone ili crtice
Zašto transparentni logotipi dobivaju bijeli obrub?
Jer je boja ispod potpuno transparentnog piksela proizvoljna, a obična težinska sredina dopusti joj da glasa. Izvezite logotip iz dizajnerskog alata i nevidljiva margina je često bijela, ili crna, ili bilo što što je platno bilo; alfa kanal je sakriva, a ravna suma preko otiska jezgre odmah je natapa u vidljivi rub. PDFiumPas to izbjegava tako da BGRA uzorke akumulira u premnoženoj formi, a premnoženje poništava tek na odredišnom pikselu
Konkretno, svaki doprinoseći uzorak dodaje channel * alpha * weight akumulatoru boje, alpha * weight alfa akumulatoru i weight sumi težina. Odredišna se boja zatim dijeli alfa akumulatorom, a ne sumom težina, i to je korak koji glavu vuče: dijeljenje sumom težina vuklo bi boju prema nevidljivim pikselima, dok dijeljenje akumuliranom alfom rekonstruira boju na kojoj se vidljivi uzorci stvarno složili. Odredišna alfa je zasebna veličina, 255 * AlphaSum / WeightSum. Formati bez alfe dijele sumom težina kao i obično, bajt popune FPDFBitmap_BGRx odredišta upisuje se kao konstantna 255, a svaki se kanal steže u raspon 0 do 255 prije spremanja. Ta alfa obično potječe iz unosa meke maske u rječniku slike (ISO 32000-1 §11.4), koju je PDFium već kompozitirao u BGRA međuspremnik koji resampler prima
// Shape of the inner accumulation loop, per contributing source sample
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;
// ... and at the destination pixel, unpremultiply against the alpha sum
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;
Držanje 1-bitne linijske grafike izvan sive zone
Bilo koja kontinuirana jezgra primijenjena na bilevelni sken proizvodi sivu, a siva je upravo ono što fax stilu slike nije dopušteno sadržavati. PDFiumPas zato po zadanom ostavlja 1-bitne slike na miru: PreserveBilevel je True u TPdfImageOptimizeOptions.Default, i takve slike završavaju u SkippedCount nedirnute. Postavite ga na False i put pirfBilevel preuzme umjesto jezgre zaglađivanja. Ona obilazi točan izvorni pravokut koji pokriva svaki odredišni piksel, prosječi luminanciju s težinama 0.114, 0.587 i 0.299 u BGR redoslijedu memorije, a rezultat svodi pragom 127.5 na ravnu 0 ili 255. Ništa posredno ne može se upisati, pa rubovi ostaju oštri i oko tankih poteza ne nastaje sivi oreol; alfa kanal BGRA izvora prosječi se normalno, a BGRx odredište dobiva konstantnu 255. Ako trebate piksele u pozadini, a ne manji dokument, izdvajanje slika iz PDF dokumenata je zaseban put
Što se događa kad slika premaši proračun radne memorije?
Ostavlja se točno kakva je bila, i ubraja se. MaxWorkingBytes je po zadanom 64 MiB i provodi se dvaput. Prije stvaranja odredišne bitmape PDFiumPas odbije sliku ako širina puta visina puta bajtovi po pikselu premašuju proračun. Nakon što uspije FPDFBitmap_CreateEx provjerava opet koristeći stvarni korak reda puta visinu, jer ispunjavanje redova može gurnuti alokaciju preko granice koju je naivni umnožak očistio. Bilo koje odbijanje uništi odredište i ne vraća ništa. Budite jasni oko degradacije koju to povlači: slika preko proračuna ne resamplinga se nižom kvalitetom, i ne dijeli se na pločice. Izvornik ostaje u dokumentu, BudgetExceededCount i SkippedCount oboje rastu, pa pokretanje može izvještati uspjeh dok je dokument samo djelomično optimiran. To je namjerno fail-safe ponašanje, ali znači da izvještaj nije obavezno štivo. Postoji i zaseban oblik neuspjeha: slike čiju bitmapu PDFium uopće ne može proizvesti, poput CMYK, JPX, JBIG2 ili maskiranih izvora, umjesto toga povećavaju FailedCount i jednako ostaju nedirnute
procedure OptimizeBatch(const Files: array of string);
var
Pdf: TPdf;
Options: TPdfImageOptimizeOptions;
Report: TPdfImageOptimizeReport;
I: Integer;
begin
Options := TPdfImageOptimizeOptions.Default;
Options.PreserveBilevel := False; // use the bilevel area vote
Options.ContentClass := piccPhoto; // force Lanczos for photo sets
Options.MaxWorkingBytes := 256 * 1024 * 1024; // headroom for large scans
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;
Čitanje izvještaja prije otpreme datoteke
TPdfImageOptimizeReport je građen za dijagnozu, ne samo za zapisivanje. Uz OptimizedCount, SkippedCount i FailedCount izlaže jedan brojač po jezgri, pa vam BoxFilterCount, BilinearFilterCount, BicubicFilterCount, LanczosFilterCount i BilevelFilterCount kažu što je prilagodljivo pravilo stvarno zaključilo o vašem korpusu. Rezultat samo box znači da su smanjenja bila strma ili da je sadržaj razvrstan kao linijska grafika; rezultat samo Lanczos na dokumentu za koji ste vjerovali da je linijska grafika znak je da treba izričito postaviti ContentClass. AverageDetailScore je broj koji uspoređujete s pragom 0.08 za Lanczos kad štimate PreferredQuality, a PeakWorkingBytes pokazuje koliko je MaxWorkingBytes pokretanje stvarno trebalo. Neispravne opcije propadaju glasno, ne tiho: nepozitivan TargetDpi, MinDpiRatio ispod 1, PreferredQuality izvan 0 do 1 ili nepozitivan MaxWorkingBytes podiže EPdfError prije nego bilo koja stranica bude dirnuta. A OptimizeImages uređuje samo dokument u memoriji; svaka izmijenjena stranica predaje se s FPDFPage_GenerateContent, nakon čega i dalje sami zovete SaveAs. Da pogledom usporedite što se promijenilo, iscrtajte dokumente prije i poslije u bitmape kao što opisuje pretvaranje PDF stranica u JPEG slike i usporedite ih na punom zoomu
Prilagodljivi resampling je jedna od onih značajki koja je nevidljiva kad radi i stvara zahtjeve za podršku kad ne radi, zato su mjerenje, rukovanje alfom i proračun memorije morale stići zajedno, a ne kao tri zasebna usavršenja. Ako ovo procjenjujete za proizvod u Delphi, C++Builder ili Lazarusu, potpuna API površina i detalji licenciranja su na stranici PDFiumPas Delphi PDFium komponente