Praėjus savaitei po glaudinimo funkcijos išleidimo atkeliauja du skundai: nuskaityta sutartis įgijo laiptuotas, plaukuotas raides, o viršelio puslapio skaidrus logotipas sėdi blyškios aureolės viduje. PDFiumPas į abu atsako vienoje vietoje. TPdf.OptimizeImages prieš mažindamas kiekvieną vaizdą jį išmatuoja, tada renkasi persamplavimo branduolį, o spalvas kaupia atsižvelgdamas į alfa kanalą
Taip ne visada buvo. Iki v3.100.0 tas pats metodas kiekvieną ne dvejetainį vaizdą mažino fiksuotu artimiausio kaimyno žingsniu — būtent tas algoritmas sukuria abu skundus: jis kiekvienam išvesties pikseliui paima po vieną šaltinio pikselį, o po visiškai permatomu pikseliu esantį RGB traktuoja taip, lyg skaitytojas jį kada nors pamatytų. Perrašymas v3.100.0 tą vienintelį kelią pakeičia penkiais branduoliais, išmatuota atrankos taisykle ir aiškiu darbo atminties biudžetu
Kodėl mažinant raišką nuskaitytas tekstas tampa laiptuotas?
Todėl, kad taškinis ėmimas atsako į netinkamą klausimą. Kai 300 DPI nuskaitymas perkeliamas į 150 DPI, kiekvienas paskirties pikselis atstovauja dviejų per du šaltinio pikselių blokui, o artimiausias kaimynas palieka vieną iš keturių ir atmeta likusius. Kuris išgyvena, sprendžia apvalinimas, tad šrate sklandžiai nugludintas brūkšnio kraštas kiekvienam pikseliui tampa monetos metimu. Rezultatas — klasikinis laiptuotas aliasingas palei glifų kraštus, o pusatonio rastruose dar ir muaras, ten, kur atmesti ėminiai atsitiktinai nešiojo raštą. PDF atveju tai svarbiau nei ekrane, nes žala yra nuolatinė. Vaizdo XObject savo ėminių duomenis neša kartu su /Width, /Height ir /BitsPerComponent (ISO 32000-1 §8.9.5), o persamplavimas perrašo visus tris pačiame faile. Blogas priartinimas žiūryklėje yra kadras, kurį galite perpiešti, ir PDFiumPas tam turi atskirą mechanizmą, aprašytą renderio talpyklos ir priartinimo našumo straipsnyje. Blogas sumažinimas yra naujas dokumentas, kurį įteikiate klientui
Kaip PDFiumPas išmatuoja detales ir renkasi branduolį
PDFiumPas sprendžia kiekvienam vaizdui atskirai, o ne visam dokumentui. Prieš renkantis branduolį apskaičiuojamas normalizuotas šviesio detalių įvertis iš apriboto ėmimo tinklelio: horizontalus ir vertikalus žingsniai yra (Width + 63) div 64 ir (Height + 63) div 64, tad 12000 pikselių nuskaitymas ir 300 pikselių miniatiūra kainuoja maždaug tą patį 64 į 64 apėjimą. Kiekvienoje įmintoje vietoje sumuojamas absoliutus skirtumas iki kaimyno dešinėje ir kaimyno žemiau, per ne daugiau kaip tris kanalus, tada viskas dalijama iš ėminių skaičiaus ir 255 sandaugos. Įvertis patenka į intervalą nuo 0 iki 1, kur plokščia verslo grafika sėdi prie nulio, o tanki fotografinė tekstūra lipa aukštyn
Tada atrankos kopėčios žengia fiksuota tvarka. Jei ResampleFilter nustatytas į ką nors kitą nei pirfAdaptive, naudojamas tas filtras, kaip yra. Kitu atveju: 1 bito turinys gauna pirfBilevel; ContentClass, lygus piccLineArt, gauna pirfBox; mastelio koeficientas 4 ar didesnis taip pat gauna pirfBox, nes tokiame sumažinime ploto vidurkis yra ir pigiausias, ir teisingiausias atsakymas; piccPhoto, detalės įvertis 0.08 ar aukštesnis arba PreferredQuality 0.9 ar aukštesnė gauna pirfLanczos su jo trijų skilčių branduoliu; mastelis 2 ar didesnis arba kokybė 0.7 ar aukštesnė gauna pirfBicubic su spinduliu 2; viskas, kas liko, gauna pirfBilinear. Kadangi TPdfImageOptimizeOptions.Default nustato PreferredQuality į 0.85, numatytas paleidimas niekada negrįžta iki bilinear, nebent sumažinimas švelnus, o turinys plokščias
uses
PDFium;
procedure ShrinkScannedPdf(const InputFile, OutputFile: string);
var
Pdf: TPdf;
Options: TPdfImageOptimizeOptions;
Report: TPdfImageOptimizeReport;
begin
Pdf := TPdf.Create(nil);
try
Pdf.FileName := InputFile;
// Numatytoji: TargetDpi 150, MinDpiRatio 1.5, PreserveBilevel True,
// MinDimension 8, pirfAdaptive, piccAuto, kokybė 0.85, 64 MiB biudžetas.
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;
Vaizdas liečiamas tik tada, kai didesnė iš jo horizontalios ir vertikalios išdėstymo DPI, padalinta iš TargetDpi, pasiekia MinDpiRatio. Ta apsauga yra tam, kad 160 DPI nuotrauka, taikoma į 150 DPI tikslą, nebūtų perkoduota dėl šešių procentų laimėjimo, kurio kaina — visas kokybės kartas. Vaizdai, mažesni už MinDimension bet kurioje ašyje — 8 pagal numatytuosius — praleidžiami kaip piktogramos ar linijos
Kodėl skaidrūs logotipai įgauna baltą pakraštį?
Nes spalva po visiškai permatomu pikseliu yra savavališka, o paprastas svertinis vidurkis leidžia jai balsuoti. Eksportuokite logotipą iš dizaino įrankio, ir nematoma paraštė dažnai būna balta, juoda arba tokia, kokia buvo drobė; alfa kanalas ją slepia, o tiesioginė suma per branduolio pėdsaką greitai ją vėl įmaišo į matomą kraštą. PDFiumPas to išvengia kaupdama BGRA ėminius iš anksto padauginta forma ir atšaukdama išankstinį dauginimą tik paskirties pikseliue
Konkrečiai: kiekvienas prisidedantis ėminis prideda channel * alpha * weight prie spalvos kaupiklio, alpha * weight prie alfos kaupiklio ir weight prie svorių sumos. Paskirties spalva tada dalijama iš alfos kaupiklio, o ne iš svorių sumos, ir būtent tas žingsnis lemia: dalijimas iš svorių sumos temptų spalvą link nematomų pikselių, o dalijimas iš sukauptos alfos atkuria spalvą, kuria matomi ėminiai iš tikrųjų sutarė. Paskirties alfa yra atskiras dydis, 255 * AlphaSum / WeightSum. Ne alfos formatai dalija iš svorių sumos kaip įprasta, FPDFBitmap_BGRx paskirties užpildymo baitas rašomas kaip pastovus 255, o kiekvienas kanalas prieš išsaugojimą suvaržomas į 0–255. Ta alfa paprastai kyla iš soft mask įrašo vaizdo žodyne (ISO 32000-1 §11.4) — PDFium jau yra sukomponavęs jį į BGRA buferį, kurį gauna persamplavimo kodas
// Vidinio kaupimo ciklo pavidalas, kiekvienam prisidedančiam šaltinio ėminiui
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;
// ... o paskirties pikseliue atšaukiamas išankstinis dauginimas pagal alfos 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;
1 bitų linijinė grafika lieka už pilkos zonos ribų
Bet kuris tęstinis branduolys, pritaikytas dvejetainiam nuskaitymui, duoda pilką, o pilkos faksinio stiliaus vaizdas turėti negali. PDFiumPas todėl pagal numatytuosius 1 bito vaizdų neliečia: PreserveBilevel yra True TPdfImageOptimizeOptions.Default nustatymuose, ir tokie vaizdai patenka į SkippedCount nelieti. Nustatykite False, ir pirfBilevel kelias perima vietoj glotninamojo branduolio. Jis apėja tikslų šaltinio stačiakampį, dengiantį kiekvieną paskirties pikselį, vidutina šviesį su 0.114, 0.587 ir 0.299 svoriais BGR atminties tvarka ir slenksčiu 127.5 suverčia rezultatą į plokščią 0 arba 255. Nieko tarpinio įrašyti negalima, todėl kraštai lieka aštrūs ir aplink plonus brūkšnius nesusidaro pilka aureolė; BGRA šaltinio alfa kanalas vidutinamas įprastai, o BGRx paskirtis gauna pastovų 255. Jei jums reikia pačių pikselių duomenų, o ne mažesnio dokumento, atskiras kelias yra vaizdų išgavimas iš PDF dokumentų
Kas nutinka, kai vaizdas viršija darbo atminties biudžetą?
Jis paliekamas lygiai toks, koks buvo, ir suskaičiuojamas. MaxWorkingBytes pagal numatytuosius lygus 64 MiB ir vykdomas dukart. Prieš kuriant paskirties bitmap PDFiumPas atmeta vaizdą, jei plotio, aukščio ir baitų per pikselį sandauga viršija biudžetą. Kai FPDFBitmap_CreateEx pasiseka, tikrinama dar kartą su tikru stride ir aukščiu, nes eilučių užpildymas gali pervesti alokaciją už ribos, kurios naivioji sandauga neprieis. Bet koks atmetimas sunaikina paskirtį ir negrąžina nieko. Aiškiai suvokite šios degradacijos reikšmę: vaizdas, einantis per biudžetą, nėra persampluojamas žemesne kokybe ir nėra skaidomas į plyteles. Originalas lieka dokumente, BudgetExceededCount ir SkippedCount abu išauga, ir paleidimas gali pranešti sėkmę, nors dokumentas optimizuotas tik iš dalies. Tai sąmoninga fail-safe elgsena, bet vadinasi, ataskaita nėra nebūtinas skaitymas. Egzistuoja ir atskiras gedimo režimas: vaizdai, kurių bitmap PDFium iš viso negali pagaminti — CMYK, JPX, JBIG2 arba su kaukėmis — didina FailedCount ir taip pat paliekami nelieti
procedure OptimizeBatch(const Files: array of string);
var
Pdf: TPdf;
Options: TPdfImageOptimizeOptions;
Report: TPdfImageOptimizeReport;
I: Integer;
begin
Options := TPdfImageOptimizeOptions.Default;
Options.PreserveBilevel := False; // naudoti dvejetainio vaizdo ploto balsavimą
Options.ContentClass := piccPhoto; // priversti Lanczos nuotraukų rinkiniams
Options.MaxWorkingBytes := 256 * 1024 * 1024; // atsarga dideliems nuskaitymams
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;
Ataskaitos skaitymas prieš išsiunčiant failą
TPdfImageOptimizeReport sukurtas diagnozuoti, o ne vien rašyti į žurnalą. Šalia OptimizedCount, SkippedCount ir FailedCount jis atskleidžia po skaitiklį kiekvienam branduoliui, tad BoxFilterCount, BilinearFilterCount, BicubicFilterCount, LanczosFilterCount ir BilevelFilterCount pasako, ką adaptuotoji taisyklė iš tiesų nusprendė apie jūsų rinkinį. Vien box rezultatas reiškia, kad sumažinimai buvo statūs arba turinys klasifikuotas kaip linijinė grafika; vis Lanczos rezultatas dokumente, kurį laikėte linijine grafika, yra ženklas, kad ContentClass reikėtų nustatyti aiškiai. AverageDetailScore yra skaičius, kurį lyginti su 0.08 Lanczos riba derinant PreferredQuality, o PeakWorkingBytes rodo, kiek iš MaxWorkingBytes paleidimas iš tiesų prireikė. Netinkami parametrai žlunga garsiai, o ne tyliai: neigiamas arba nulinis TargetDpi, MinDpiRatio žemiau 1, PreferredQuality už 0–1 ribų arba neigiamas arba nulinis MaxWorkingBytes kelia EPdfError prieš paliečiant bent vieną puslapį. O OptimizeImages redaguoja tik dokumentą atmintyje; kiekvienas pakeistas puslapis įrašomas su FPDFPage_GenerateContent, o SaveAs po to vis tiek kviečiate patys. Kad akimis pamatytumėte, kas pasikeitė, atvaizduokite prieš ir po esančius dokumentus į bitmap, kaip aprašyta straipsnyje PDF puslapių konvertavimas į JPEG vaizdus, ir palyginkite pilnu priartinimu
Adaptuotas persamplavimas yra viena iš tų funkcijų, kurios nematomos, kai veikia, ir kurios kuria palaikymo bilietus, kai neveikia — todėl matavimas, alfa apdorojimas ir atminties biudžetas turėjo atkeliauti kartu, o ne kaip trys atskiri patobulinimai. Jei vertinate tai Delphi, C++Builder arba Lazarus produktui, pilnas API paviršius ir licencijavimo detalės yra PDFiumPas Delphi PDFium komponento puslapyje