HotPDF wystawia trzy jądra downsamplingu obrazów przez właściwość ImageDownsampleKernel i osobny przebieg Floyd-Steinberg przez RenderOutputDither. Pierwsze kontroluje, jak wyglądają fotografie po tym, jak zmniejszysz je, by zmieścić się w budżecie rozmiaru, drugie — jak wyglądają po zredukowaniu strony do czerni i bieli. Żadne nie jest domyślnie włączone i oba są opt-in z tego samego powodu: kosztują realny czas
Nacisk, który sprowadza tu ludzi, jest znajomy. Zeskanowana umowa o rozmiarze 60 MB musi wyjść przez bramkę poczty odrzucającą cokolwiek ponad 10 MB, albo partia wyciągów ma wylądować na monochromatycznym urządzeniu w stylu faksu, które renderuje każdy szary piksel jako papier albo toner. Oba problemy to problemy resamplingu i oba mają szybką odpowiedź, która wygląda źle, oraz powolną, która wygląda dobrze
Czym faktycznie różnią się trzy jądra
THPDFResampleKernel ma trzy wartości i siedzą one w istotnie różnych punktach krzywej szybkość–jakość. rkHalftone deleguje do historycznej ścieżki GDI StretchBlt z trybem HALFTONE, która mimo nazwy jest filtrowaniem klasy bilinear: szybkie, wystarczające dla line artu i screenshotów, podatne na chrupiące krawędzie, które od razu poznajesz na zeskalowanych w dół fotografiach. rkBicubic odpala separowalne jądro Catmull-Rom, a rkLanczos3 separowalne okienkowane sinc o trzech płatkach wsparcia
Oba jądra separowalne chodzą jako dwa przebiegi, poziomy potem pionowy, z 6 do 12 tapami na piksel docelowy w czystym Pascalu. To z grubsza rząd wielkości wolniej niż ścieżka GDI, i dokładnie dlatego rkHalftone zostaje domyślne. Przy nocnej paczce tysięcy stron różnica to decyzja szeregowania, nie preferencja. Przy pojedynczym dokumencie, na który użytkownik czeka, Lanczos3 jest prawie darmowy i widocznie lepszy
Dwie własności implementacyjne warto znać, bo determinują, co wynik może, a czego nie może. Brzegi są clampowane przez replikację krawędzi, a nie zawijanie albo wygaszanie, a wagi są normalizowane per piksel docelowy. Raz oznaczają, że wynik nigdy nie dzwoni poniżej czerni ani powyżej bieli, więc klasyczna halo overshootu Lanczosa wokół twardej krawędzie nie pojawia się jako przycięte artefakty w zakodowanym obrazie
var
Pdf: THotPDF;
Info: THPDFLoadedResourceOptimizationInfo;
Changed: Integer;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.LoadFromFile('scanned-contract.pdf');
Pdf.ImageDownsampleKernel := rkLanczos3; // ustaw przed wywołaniem
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;
Argument MinimumSavingsBytes, wyżej 4096, to straż trzymająca operację w ryzach uczciwości. Rekodowanie obrazu już sprawnie skompresowanego może dać strumień większy niż oryginał, a downsampler ślepo zastępujący każdy obraz od czasu do czasu powiększy plik, który miał zmniejszyć. Próg mówi: zatwierdź zastąpienie tylko, gdy oszczędza przynajmniej tyle bajtów. PreservedCalibratedImageCount raportuje drugą konserwatywną decyzję, obrazy pozostawione nietknięte, bo niosą skalibrowaną przestrzeń kolorów, którą resampling by skompromitował
Dlaczego zły współczynnik wielomianu tak trudno wychwycić?
Bo zepsute jądro interpolacji nie wywala się i nie rzuca wyjątkiem, po prostu produkuje obraz, który wygląda subtelnie źle w sposób, którego nikt nie umie przypisać. Jądro Catmull-Rom jest sześcienne odcinkami, a jego zewnętrzna gałąź w zagnieżdżonej postaci Hornera to ((-0.5t + 2.5)t - 4)t + 2. Zapisz ten środkowy współczynnik jako -5 zamiast -4, a funkcja nadal się liczy, nadal zwraca liczby w wiarygodnym zakresie i nadal produkuje obraz
Szkoda wychodzi jako W(1) liczące się do -1 tam, gdzie musi być 0. Ujemne wagi się kumulują, suma clipuje się do zera, a widoczny objaw to gradient czerniejący lewym końcem i krawędź skokowa tracąca tony pośrednie. Nic w tej awarii nie wskazuje na wielomian. Kontrola, która łapie to w sekundy, jest arytmetyczna, nie wizualna: jądro interpolujące musi spełniać W(0) = 1 oraz W(±1) = W(±2) = 0, a każde jądro, które mija te trzy punkty, ma błąd współczynnika i kropka. Zasertuj te trzy wartości w teście jednostkowym, a cała klasa defektów od literówek znika
Dithering Floyd-Steinberg i jego miejsce w potoku
Przebieg ditheringu to inny problem niż resampling i mieszka w innym punkcie potoku. RenderOutputDither nakłada dyfuzję błędu Floyd-Steinberg po kompozycji strony, co jest jedynym sensownym miejscem dla monochromatycznego podglądu wydruku albo eksportu w stylu faksu: ta operacja dotyczy redukcji skończonego rastra do jednego bitu na piksel, a nie tego, jak pojedyncze obrazy były skalowane na wejściu
Sam algorytm jest krótki. Luminancja jest progowana na 50 procentach, a błąd kwantyzacji jest dyfundowany do czterech sąsiadów z klasycznymi wagami 7/16, 3/16, 5/16 i 1/16, w prawo, w lewo w dół, w dół i w prawo w dół. Piksel wyjściowy to 0 albo 255 w każdym kanale. Naiwna alternatywa daje zamiast tego twardy próg bez dyfuzji, co zamienia fotografię w sylwetkę i gubi każdy ton średni, który niósł zawartość
// Dithering w czasie renderowania dla monochromatycznego urządzenia podglądu
Pdf.RenderOutputDither := True;
// Albo nałóż ten sam przebieg na bitmapę, którą już masz. Bitmapa musi
// być pf24bit; funkcja zwraca False, zamiast zgadywać
if not HPDFFloydSteinbergDitherBitmap(Preview) then
raise Exception.Create('dither expects a 24-bit bitmap');
// Bezpośredni dostęp do jądra, gdy resamplujesz poza potokiem dokumentu
Small := HPDFResizeBitmapKernel(Large, 640, 480, rkLanczos3);
try
Small.SaveToFile('thumb.bmp');
finally
Small.Free;
end;
Jeden detal implementacyjny dyfuzji błędu kąsa każdego raz. Bufor błędu między wierszami musi akumulować. Każdy piksel w następnym wierszu dostaje wkłady od trzech różnych pikseli bieżącego wiersza, tapy 3/16, 5/16 i 1/16, i jeśli kod przypisuje zamiast dodawać, każdy zapis wyrzuca poprzedni wkład i przeżywa tylko ostatni tap. Obraz nadal wygląda na ditherowany, co utrudnia zauważenie, ale tekstura jest zła i reprodukcja tonalna dryfuje. Test, który to łapie, jest ilościowy: zditheruj jednolite pole średniej szarości i wymagaj, by pokrycie wnętrza wylądowało między 40 a 60 procentami
Której kombinacji powinien użyć potok redukcji rozmiaru?
Dopasuj jądro do tego, czym obrazy faktycznie są, a dithering traktuj jako sprawę urządzenia, nie kompresji. Dla skanów fotograficznych, które muszą zmieścić się w budżecie rozmiaru, rkLanczos3 przy 150 albo 200 DPI trzyma detal, który ludzie zauważają, ścinając liczbę pikseli czterokrotnie i bardziej. Dla screenshotów, diagramów i line artu rkHalftone jest naprawdę w porządku i znacznie szybszy, bo te obrazy mają niewiele gradientów tonalnych do zachowania. Dla mieszanej paczki, w której nie da się przejrzeć każdego obrazu, rkBicubic to rozsądny środek: lepszy niż bilinear, z grubsza połowa liczby tapów Lanczos3
Downsampling to jedna dźwignia z kilku i nie zawsze największa. Skany bilevel zwykle reagują znacznie lepiej na enkoder opisany w artykule natywna kompresja JBIG2 bilevel w Delphi, gdzie zysk bierze się ze słowników symboli, a nie z liczby pikseli. Zanim zdecydujesz, pomaga wiedzieć, co faktycznie jest w pliku, do tego służy wydobywanie obrazów i ich filtrów decode: inwentaryzacja obiektów obrazów i ich istniejącej kompresji mówi ci, czy resampling ma czym zyskać
Jeśli budujesz powierzchnię podglądu pokazującą wynik, ta sama ścieżka renderowania udokumentowana w artykule renderowanie strony PDF do bitmapy to miejsce, gdzie działa RenderOutputDither, więc zditherowany podgląd i zditherowane wyjście pochodzą z jednej ścieżki kodu, a nie z dwóch implementacji, które się rozjeżdżają
Szeroka zasada za obiema funkcjami: ustawienia jakości powinny być jawne i odwracalne. HotPDF trzyma historyczne zachowanie jako domyślne, więc istniejąca aplikacja upgraduje się bez zaskakującej zmiany w wyjściu albo czasie, a ładniejsze, wolniejsze ścieżki stawia jedno przypisanie właściwości dalej. Obie są częścią komponentu PDF HotPDF dla Delphi, razem z maszynerią optymalizacji zasobów i renderowania, na której budują