HotPDF expune trei kernel-uri de downsampling a imaginilor prin proprietatea ImageDownsampleKernel și o trecere separată Floyd-Steinberg prin RenderOutputDither. Prima controlează cum arată fotografiile după ce le micșorați ca să încapă într-un buget de mărime, a doua controlează cum arată ele după ce o pagină e redusă la alb-negru. Niciuna nu e activă din start, iar ambele sunt opt-in din același motiv: costă timp real
Presiunea care aduce oamenii aici e cunoscută. Un contract scanat de 60 MB trebuie să iasă printr-un gateway de email care respinge orice peste 10 MB, sau un lot de extrase de cont trebuie să ajungă pe un dispozitiv monocrom în stil fax, care randează fiecare pixel gri fie ca hârtie, fie ca toner. Ambele probleme sunt probleme de resampling, iar ambele au un răspuns rapid, care arată rău, și un răspuns lent, care arată bine
În ce diferă de fapt cele trei kernel-uri
THPDFResampleKernel are trei valori, iar ele stau în puncte cu adevărat diferite pe curba viteză-calitate. rkHalftone delegă către calea istorică GDI StretchBlt cu modul HALFTONE, care, în ciuda numelui, e filtrare de clasă bilineară: rapidă, suficientă pentru line art și capturi de ecran și predispusă la muchiile tăioase pe care le recunoașteți instantaneu pe fotografiile micșorate. rkBicubic rulează un kernel separabil Catmull-Rom, iar rkLanczos3 rulează un sinc cu fereastră, separabil, cu un suport de trei lobi
Ambele kernel-uri separabile rulează în două treceri, orizontală apoi verticală, cu 6 până la 12 tap-uri per pixel destinație în Pascal pur. E cu un ordin de mărime mai lent aproximativ decât calea GDI, exact motivul pentru care rkHalftone rămâne implicit. Pe un lot nocturn de mii de pagini, diferența e o decizie de planificare, nu o preferință. Pe un document unic pe care un utilizator așteaptă, Lanczos3 e aproape gratis și vizibil mai bun
Două proprietăți de implementare merită cunoscute pentru că determină ce poate și ce nu poate face rezultatul. Bordurile fac clamp prin replicarea muchiei, nu prin înfășurare sau estompare, iar greutățile sunt normalizate per pixel destinație. Împreună, cele două înseamnă că rezultatul nu sună niciodată sub negru sau peste alb, deci halo-ul clasic de overshoot Lanczos din jurul unei muchii dure nu apare ca artefacte tăiate în imaginea encodată
var
Pdf: THotPDF;
Info: THPDFLoadedResourceOptimizationInfo;
Changed: Integer;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.LoadFromFile('scanned-contract.pdf');
Pdf.ImageDownsampleKernel := rkLanczos3; // se setează înaintea apelului
Changed := Pdf.DownsampleLoadedImages(150, 82, 4096, Info);
if Changed > 0 then
begin
Writeln('resampled images: ', Info.DownsampledImageCount);
Writeln('kept calibrated : ', Info.PreservedCalibratedImageCount);
Pdf.SaveToFile('scanned-contract-150dpi.pdf');
end;
finally
Pdf.Free;
end;
end;
Argumentul MinimumSavingsBytes, 4096 mai sus, e garda care ține operația cinstită. Reencodarea unei imagini deja comprimate eficient poate produce un stream mai mare decât originalul, iar un downsampler care înlocuiește orbește fiecare imagine va crește, o dată în eon, fișierul pe care i s-a cerut să îl micșoreze. Pragul spune: comiteți înlocuirea doar când economisește cel puțin atâția octeți. PreservedCalibratedImageCount raportează cealaltă decizie conservatoare, imaginile lăsate neatinse pentru că poartă un spațiu de culoare calibrat pe care resampling-ul l-ar compromite
De ce e atât de greu de observat un coeficient polinomial greșit?
Pentru că un kernel de interpolare stricat nu crapă și nu aruncă excepție, ci doar produce o imagine care arată subtil greșit într-un fel pe care nimeni nu îl poate atribui. Kernel-ul Catmull-Rom e cubic pe porțiuni, iar ramura lui exterioară în formă Horner imbricată e ((-0.5t + 2.5)t - 4)t + 2. Scrieți acel coeficient din mijloc ca -5 în loc de -4, iar funcția se evaluează în continuare, întoarce în continuare numere într-un interval plauzibil și produce în continuare o imagine
Defectul apare ca W(1) evaluând la -1 acolo unde trebuie să fie 0. Greutățile negative se acumulează, suma taie la zero, iar simptomul vizibil e un gradient al cărui capăt stâng devine negru și o muchie de treaptă care își pierde tonurile intermediare. Nimic din eșec nu arată spre un polinom. Verificarea care îl prinde în secunde e aritmetică, nu vizuală: un kernel de interpolare trebuie să satisfacă W(0) = 1 și W(±1) = W(±2) = 0, iar orice kernel care ratează cele trei puncte are o eroare de coeficient, punct. Afirmați asupra celor trei valori într-un test unitar și întreaga clasă de defecte de tip typo dispare
Dithering Floyd-Steinberg și locul lui în pipeline
Trecerea de dithering e o problemă diferită de resampling și stă într-un alt punct al pipeline-ului. RenderOutputDither aplică difuzia de eroare Floyd-Steinberg după compoziția paginii, singura plasare care are sens pentru o previzualizare de tipărire monocromă sau un export în stil fax: operația ține de reducerea unui raster finit la un bit per pixel, nu de modul în care au fost scalate imaginile individuale la intrare
Algoritmul în sine e scurt. Luminanța e praguită la 50 la sută, iar eroarea de cuantizare e difuzată spre patru vecini cu greutățile clasice 7/16, 3/16, 5/16 și 1/16, spre dreapta, stânga-jos, jos și dreapta-jos. Pixelul de ieșire e 0 sau 255 în fiecare canal. Varianta naivă, un prag dur fără difuzie, transformă o fotografie într-o siluetă și pierde fiecare ton intermediar care purta conținutul
// Dithering la randare pentru un dispozitiv de previzualizare monocrom
Pdf.RenderOutputDither := True;
// Sau aplicați aceeași trecere unui bitmap pe care îl dețineți deja. Bitmap-ul
// trebuie să fie pf24bit; funcția returnează False în loc să ghicească
if not HPDFFloydSteinbergDitherBitmap(Preview) then
raise Exception.Create('dither expects a 24-bit bitmap');
// Acces direct la kernel când resample-ați în afara pipeline-ului documentului
Small := HPDFResizeBitmapKernel(Large, 640, 480, rkLanczos3);
try
Small.SaveToFile('thumb.bmp');
finally
Small.Free;
end;
Există un detaliu de implementare în difuzia de eroare care mușcă pe toată lumea o dată. Buffer-ul de eroare de la rând la rând trebuie să acumuleze. Fiecare pixel din rândul următor primește contribuții de la trei pixeli diferiți din rândul curent, tap-urile 3/16, 5/16 și 1/16, iar dacă codul atribuie în loc să adune, fiecare scriere aruncă contribuția anterioară și doar ultimul tap supraviețuiește. Imaginea încă pare dithered, iar asta o face greu de observat, dar textura e greșită și reproducția tonală derivează. Testul care îl prinde e cantitativ: aplicați dithering pe un câmp uniform de gri mediu și cereți acoperirii interioare să cadă între 40 și 60 la sută
Ce combinație ar trebui să folosească un pipeline de reducere de mărime?
Potriviți kernel-ul cu ce sunt efectiv imaginile și tratați dithering-ul ca o preocupare de dispozitiv, nu de compresie. Pentru scanări fotografice care trebuie să treacă printr-un buget de mărime, rkLanczos3 la 150 sau 200 DPI păstrează detaliul pe care oamenii îl observă, reducând numărul de pixeli de patru ori sau mai mult. Pentru capturi de ecran, diagrame și line art, rkHalftone e cu adevărat suficient și cu mult mai rapid, pentru că acele imagini au puține gradienți tonali de păstrat. Pentru un lot mixt în care nu puteți inspecta fiecare imagine, rkBicubic e mijlocul rezonabil: mai bun decât bilinear, cu cam jumătate din numărul de tap-uri al lui Lanczos3
Downsampling-ul e o pârghie dintre mai multe și nu e întotdeauna cea mai mare. Scanările bilevel răspund de regulă mult mai bine encoder-ului acoperit în compresia JBIG2 bilevel nativă în Delphi, unde câștigul vine din dicționarele de simboluri, nu din numărul de pixeli. Înainte să decideți, ajută să știți ce e efectiv în fișier, iar pentru asta e extragerea imaginilor și a filtrelor lor de decodare: un inventar al obiectelor imagine și al compresiei lor existente vă spune dacă resampling-ul are ceva de câștigat
Dacă construiți suprafața de previzualizare care arată rezultatul, aceeași cale de randare documentată în randarea unei pagini PDF într-un bitmap e locul unde RenderOutputDither își face efectul, deci previzualizarea dithered și ieșirea dithered vin dintr-o singură cale de cod, nu din două implementări care derivă una de la alta
Principiul larg din spatele ambelor trăsături e că setările de calitate ar trebui să fie explicite și reversibile. HotPDF păstrează comportamentul istoric ca implicit, astfel încât o aplicație existentă face upgrade fără o schimbare de surpriză în ieșire sau în timpi, și pune căile mai frumoase, dar mai lente, la o atribuire de proprietate distanță. Ambele fac parte din HotPDF Delphi PDF component, alături de mașinăria de optimizare a resurselor și de randare pe care se construiesc