PDFlibPas potrafi kodować obrazy dwupoziomowe jako JBIG2 dwoma różnymi backendami. Jednym jest natywny koder MMR w Object Pascalu, obecny zawsze. Drugim jest zewnętrzny koder słowników symboli, dający na skanowanym tekście wyraźnie mniejsze wyniki, i jest opcjonalny: projekt musi zlinkować jednostkę backendu, żeby w ogóle zaistniał. To rozróżnienie jest źródłem najczęstszej niespodzianki związanej z tą funkcją, więc warto powiedzieć o nim najpierw: DefaultJBIG2EncodeOptions domyślnie żąda kodera zewnętrznego, a gdy jednostka backendu nie jest zlinkowana, żądanie po cichu cofa się do ścieżki MMR w Pascalu
Na Delphi i C++Builder zewnętrzny backend to zbiór wcześniej zbudowanych statycznych plików obiektowych. Na Free Pascalu musiał stać się biblioteką DLL, a droga do tego wniosku to historia konsolidatora użyteczna dla każdego, kto próbował linkować obiekty C++ do programu w Free Pascalu
Rejestracja jest kontraktem
Jednostka backendu rejestruje się z sekcji inicjalizacyjnej, wywołując RegisterJBIG2EncoderBackend. Wywołujący żądają go albo przez bit opcji PDF_JBIG2_OPTION_EXTERNAL_ENCODER, mający wartość 4, albo przez parametr UseExternalEncoder rozszerzonych punktów wejścia dla obrazów. Parasol biblioteki celowo nie wciąga jednostki backendu, bo noszenie dużego zbioru obiektów powinno być decyzją każdego projektu; w drzewie C++Builder jest on na przykład włączany jawnie przez projekty, które go chcą
Konsekwencją dla wywołujących jest to, że żądanie kodera zewnętrznego to preferencja, nie gwarancja, a build, który zapomni jednostki, produkuje większe pliki zamiast błędu. Jeśli rozmiar wyjścia na tyle Cię obchodzi, że prosisz o lepszy koder, to na tyle obchodzi, żeby sprawdzić, czy rzeczywiście go masz
uses
PDFlibrary,
{$IFDEF FPC}
PDFlibJBIG2EncDLL; // backend dynamiczny dla Free Pascala
{$ELSE}
PDFlibJBIG2EncC; // zbiór obiektów statycznych dla Delphi / C++Builder
{$ENDIF}
var
Pdf: TPDFlib;
ImageId: Integer;
begin
Pdf := TPDFlib.Create(nil);
try
Pdf.NewDocument;
Pdf.NewPage;
// Interpolate, SymbolExtract, UseExternalEncoder, SkipBlackDots,
// BlackDotSize, LossyLevel
ImageId := Pdf.AddImageJBIG2FromFileEx('scan-page-1.tif',
0, 1, 1, 0, 0, 0);
if ImageId = 0 then
raise Exception.Create('JBIG2 encoding failed');
Pdf.SaveToFile('archive.pdf');
finally
Pdf.Free;
end;
end;
Skompilowanie jednostki to dwie linie. Symbole były robotą
Skompilowanie samej jednostki backendu pod Free Pascalem kosztowało dokładnie dwie zmiany: ustawienie dialektu asemblera i zastąpienie konstruktora ustawień formatu opartego na rekordzie globalną zmienną domyślną. To uczciwe odzwierciedlenie tego, jak przenośny jest prosty Pascal między tymi dwoma kompilatorami
Strona symboli była prawdziwą robotą. Zbiór obiektów odwołuje się do 176 symboli C. Z nich 128 miało już implementacje w Pascalu wewnątrz jednostki i potrzebowało tylko doczepionych nazw eksportowych, bo Delphi używa nazwy funkcji jako nazwy symbolu, podczas gdy Free Pascal wymaga jawnej deklaracji nazwy publicznej. Dwadzieścia siedem było współdzielonych z kodekiem JPEG 2000 i trzeba było je wyeksportować z dokładnie jednego miejsca, bo podwójna definicja psuje każdy program linkujący oba. Pozostałe 21 to wpisy platformowe i czasu wykonania C: szesnaście funkcji plikowych Win32 plus garść wywołań biblioteki standardowej, i trafiły do nowej jednostki kompatybilności
Nic z tego nie jest pojęciowo trudne, a wszystko jest konieczne, zanim konsolidator w ogóle spróbuje. Na konsolidatorze to się zatrzymało
Trzy ścieżki linkowania, trzy ślepe zaułki
Wewnętrzny konsolidator Free Pascala nie potrafi czytać tych plików obiektowych, bo powstały one kompilatorem emitującym asocjacyjne sekcje COMDAT, a wewnętrzny konsolidator zgłasza, że ich nie obsługuje. To kategoryczna odmowa, nie ostrzeżenie
Przejście na zewnętrzny konsolidator wyglądało jak odpowiedź. Konsolidator binutils dołączany do Free Pascala pada bezpośrednio przy zastosowaniu garbage collection sekcji do tego archiwum, a ta flaga jest częścią stałego zestawu parametrów, które Free Pascal przekazuje dla celu 64-bit Windows, więc nie da się jej usunąć z linii poleceń; udokumentowane przełączniki jej tłumienia są na tej ścieżce ignorowane. Podanie znacznie nowszego binutils zawodzi inaczej: nie potrafi w ogóle przetworzyć skryptu linkowania Free Pascala, produkując puste wyjście bez skryptu i ścianę błędów relokacji ze skryptem
Granica odkryta po drodze jest warta znajomości, nawet jeśli nigdy nie trafisz na problem konsolidatora. Zewnętrzny konsolidator rozwiązuje ścieżki plików obiektowych względem katalogu wyjściowego pliku wykonywalnego, a nie drzewa źródeł, więc względna dyrektywa dołączania obiektu działa tylko wtedy, gdy katalog wyjściowy akurat równa się katalogowi roboczemu kompilacji. Biblioteka nie może tego zakładać o projekcie konsumenta, co samo w sobie jest powodem, by wolić linkowaną bibliotekę od luźnych obiektów
Dlaczego inny kompilator C++ nie pomaga
Następny oczywisty pomysł to przebudowanie strony C++ kompilatorem, którego obiekty Free Pascal potrafi czytać. To też nie działa, a powód jest fundamentalny, a nie kwestią przełączników. Minimalna jednostka translacji C++ zawierająca szablon, skompilowana z każdą funkcją generowania kodu wyłączoną, wciąż emituje słabe symbole zewnętrzne, bo instantiacja szablonów i inline tworzy je konsekwentnie z konstrukcji. Free Pascal odrzuca tę klasę symboli kategorycznie. Odwrotny kierunek też zawodzi: powszechnie używany konsolidator C++ nie potrafi skonsumować obiektów drugiego kompilatora z powodu tej samej obsługi sekcji COMDAT
Kod C++ nie może więc trafić do Free Pascala jako obiekty żadną dostępną drogą. Może trafić jako biblioteka DLL i tak się stało: koder i jego zależność przetwarzania obrazu są zbudowane w jednej bibliotece wystawiającej dwa płaskie punkty wejścia C, a jednostka backendu Free Pascala wiąże je dynamicznie i rejestruje się dokładnie tak, jak robi to backend statyczny. Ścieżka Delphi i C++Builder nie została tknięta wcale, co jest właściwym wynikiem; problem przenośności na jednym toolchainie nie powinien zaburzać toolchaina, który już działał
Polarność to jedyna rzecz, która cię ugryzie
Między bitmapą dwupoziomową Windows a koderem JBIG2 czeka niezgodność konwencji, której żaden system typów nie wyłapie. Linia skanowania urządzeniowo niezależnej bitmapy o jednym bicie na piksel traktuje ustawiony bit jako biały. Koder traktuje ustawiony bit jako czarny. Przekaż linie bez zmian i dostaniesz w pełni poprawny strumień JBIG2 fotograficznego negatywu twojej strony
// Jednobitowa DIB: ustawiony bit znaczy biały. Koder JBIG2:
// ustawiony bit znaczy czarny. Odwróć każdy bajt na wejściu
for I := 0 to RowBytes - 1 do
Row[I] := Row[I] xor $FF;
Metoda weryfikacji znaczy tyle, co sama poprawka. Porównywanie długości skompresowanych strumieni nie mówi nic, bo obraz negatywu kompresuje się do podobnego rozmiaru. Spojrzenie na stronę dowodzi tylko, że nie jest jawnie odwrócona. Pewnym sprawdzeniem jest wyrenderowanie wyjścia obu ścieżek kodowania, natywnej w Pascalu i zewnętrznej, do PNG i porównanie ich bajt w bajt: oba kodery są bezstratne na tym samym obrazie źródłowym, więc cokolwiek poza dokładnym dopasowaniem jest błędem jednego z nich. To porównanie jest teraz stałym testem regresji i to ten rodzaj asercji, który warto budować zawsze, gdy dwie implementacje mają zgadzać się dokładnie
Którego backendu używać
Dla ogólnej treści dwupoziomowej, drukowanych rasterów, grafiki liniowej, mieszanej grafiki, natywny koder MMR w Pascalu jest wystarczający i nie ma kosztu wdrożeniowego. Dla skanowanego tekstu, czyli przypadku, dla którego JBIG2 zaprojektowano, zewnętrzny koder słowników symboli to miejsce, gdzie mieszka redukcja rozmiaru, bo wydziela powtarzające się kształty glifów do słownika zamiast rekodować każde wystąpienie. Jeśli produkujesz archiwa skanowanych dokumentów, ta różnica jest na tyle duża, że zmienia planowanie przestrzeni dyskowej
Pytanie leżące wyżej, jak w ogóle powstaje obraz dwupoziomowy, znaczy dla rozmiaru wyjścia równie dużo; renderowanie monochromatyczne oparte na regionach omawia artykuł o renderowaniu regionów monochromatycznych, a strategia rozmiaru całego dokumentu — optymalizacja rozmiaru pliku PDF i wydzielanie podzbiorów fontów. Dla zestawów skanów z powtarzającymi się stronami deduplikacja często wygrywa z lepszą kompresją, czego tematem jest perceptualna deduplikacja obrazów. Dostępność toolchainów i backendów per platforma jest wypisana na stronie produktu losLab PDF Developer Library