PDFlibPas kan kode bilevel-billeder som JBIG2 gennem to forskellige backends. Den ene er en indbygget Object Pascal MMR-encoder, der altid er til stede. Den anden er en ekstern symbolordbogs-encoder, der producerer markant mindre output på scannet tekst, og den er valgfri: et projekt skal linke backend-uniten, for at den overhovedet findes. Den skelnen er kilden til den mest almindelige overraskelse ved denne funktion, så den er det værd at slå fast først: DefaultJBIG2EncodeOptions anmoder som standard om den eksterne encoder, og når backend-uniten ikke er linket, falder anmodningen i stilhed tilbage til Pascal MMR-stien
På Delphi og C++Builder er den eksterne backend et sæt forbyggede statiske objekter. På Free Pascal måtte den blive en DLL, og vejen til den konklusion er en linker-historie, der er nyttig for alle, der har forsøgt at linke C++-objekter ind i et Free Pascal-program
Registrering er kontrakten
Backend-uniten registrerer sig selv fra sin initialiseringssektion ved at kalde RegisterJBIG2EncoderBackend. Kaldere beder om den enten gennem options-bit'en, PDF_JBIG2_OPTION_EXTERNAL_ENCODER, som har værdien 4, eller gennem parameteren UseExternalEncoder på de udvidede image entry points. Bibliotekets paraply-unit trækker med vilje ikke backend-uniten ind, for at bære et stort objektsæt bør være hvert projekts egen beslutning; i C++Builder-træet inkluderes den eksempelvis eksplicit af de projekter, der vil have den
Konsekvensen for kaldere er, at at bede om den eksterne encoder er en præference, ikke en garanti, og et build, der glemmer uniten, producerer større filer frem for en fejl. Hvis outputstørrelsen betyder nok til at bede om den bedre encoder, betyder det nok til at tjekke, at du fik den
uses
PDFlibrary,
{$IFDEF FPC}
PDFlibJBIG2EncDLL; // dynamisk backend til Free Pascal
{$ELSE}
PDFlibJBIG2EncC; // statisk objektsæt til 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;
At kompilere uniten var to linjer. Symbolerne var arbejdet
At få selve backend-uniten til at kompilere under Free Pascal tog præcis to ændringer: at sætte assembler-dialekten og at erstatte en record-baseret konstruktor til formatindstillinger med den globale standardvariabel. Det er et rimeligt billede på, hvor portabel ligetil Pascal er mellem de to compilere
Symbolsiden var det egentlige arbejde. Objektsættet refererer 176 C-symboler. Af dem havde 128 allerede Pascal-implementeringer inde i uniten og behøvede blot at få export-navne knyttet, for Delphi bruger funktionsnavnet som symbolnavn, mens Free Pascal kræver en eksplicit public name-deklaration. Sjuogtyve var delt med JPEG 2000-codec'en og skulle eksporteres fra præcis ét sted, da det at definere dem to gange ødelægger ethvert program, der linker begge. De resterende 21 var platform- og C runtime-indgange, seksten Win32-filfunktioner plus en håndfuld standardbibliotekskald, og de kom i en ny kompatibilitetsunit
Ingen af det er begrebsmæssigt svært, og det hele er nødvendigt, før linkeren overhovedet prøver. Linkeren er, hvor det stoppede
Tre linkeringsruter, tre blindgyder
Den interne Free Pascal-linker kan ikke læse objektfilerne, for de blev produceret af en compiler, der udsender associative COMDAT-sektioner, og den interne linker rapporterer, at den ikke understøtter dem. Det er et blankt afslag, ikke en advarsel
At skifte til en ekstern linker lignede svaret. Binutils-linkeren, der følger med Free Pascal, crasher fuldstændigt, mens den anvender section garbage collection på dette arkiv, og det flag er en del af det faste parametersæt, som Free Pascal sender med for 64-bit Windows-målet, så det kan ikke fjernes fra kommandolinjen; de dokumenterede switches til at undertrykke det ignoreres på denne vej. At levere en meget nyere binutils i stedet fejler på en anden måde: den kan slet ikke behandle Free Pascal link-scriptet, hvilket giver et tomt output uden scriptet og en væg af relocation-fejl med det
En begrænsning, der blev opdaget undervejs, er værd at kende, selv hvis du aldrig rammer linkerproblemet. Den eksterne linker opløser objektfil-stier relativt til den eksekverbare outputsmappe frem for kildetræet, så et relativt include-object-direktiv virker kun, når outputmappen tilfældigvis er lig arbejdsmappen på kompileringstidspunktet. Et bibliotek kan ikke antage det om en forbrugers projekt, hvilket i sig selv er en grund til at foretrække et linket bibliotek frem for løse objekter
Hvorfor en anden C++-compiler ikke hjælper
Den oplagte næste idé er at genbygge C++-siden med en compiler, hvis objekter Free Pascal kan læse. Det virker heller ikke, og årsagen er grundlæggende snarere end et spørgsmål om switches. En minimal C++ translation unit, der indeholder en template og kompileres med alle kodegenereringsfunktioner slået fra, udsender stadig svage eksterne symboler, for template- og inline-instansiering producerer dem per konstruktion. Free Pascal afviser den symbolklasse blankt. Den modsatte retning fejler også: en gængs C++-linker kan ikke indtage objekter fra den anden compiler på grund af den samme COMDAT-sektionshåndtering
Altså kan C++-koden ikke leveres som objekter til Free Pascal ad nogen tilgængelig rute. Den kan leveres som en DLL, og det var det, der skete: encoderen og dens billedbehandlingsafhængighed er bygget ind i ét bibliotek, der eksponerer to flade C-entry points, og Free Pascal-backend-uniten binder dem dynamisk og registrerer sig præcis som den statiske backend gør. Delphi- og C++Builder-vejen blev slet ikke rørt, hvilket er det rigtige udfald; et portabilitetsproblem på én toolchain bør ikke forstyrre den toolchain, der allerede virkede
Polariteten er det eneste, der bider dig
Mellem en Windows bilevel-bitmap og en JBIG2-encoder er der en konventionsuoverensstemmelse, som intet typesystem fanger. En scanline i et device-independent bitmap med én bit pr. pixel behandler en sat bit som hvid. Encoderen behandler en sat bit som sort. Giv scanlinerne videre uændret, og du får en fuldt gyldig JBIG2-strøm af dit sides fotografiske negativ
// One-bit DIB: sat bit betyder hvid. JBIG2-encoder: sat bit betyder
// sort. Invertér hver byte på vejen ind
for I := 0 to RowBytes - 1 do
Row[I] := Row[I] xor $FF;
Verifikationsmetoden betyder lige så meget som rettelsen. At sammenligne komprimerede strømlængder fortæller dig intet, for et negativt billede komprimerer til en lignende størrelse. At kigge på siden beviser kun, at den ikke åbenlyst er inverteret. Det pålidelige tjek er at rendere outputtet af begge kodningsveje, indbygget Pascal og ekstern, til PNG og sammenligne dem byte for byte: begge encodere er tabsfri på det samme kildebillede, så alt andet end et eksakt match er en fejl i en af dem. Den sammenligning er nu en permanent regressionstest, og det er den slags assertion, der er værd at bygge, hver gang to implementationer forventes at være helt ens
Hvilken backend man skal bruge
Til generelt bilevel-indhold, ditherede halvtone, linjekunst, blandet grafik, er den indbyggede Pascal MMR-encoder tilstrækkelig og har ingen udrulningsomkostning. Til scannet tekst, som er det tilfælde, JBIG2 blev designet til, er den eksterne symbolordbogs-encoder, hvor størrelsesreduktionen bor, for den opløser gentagne glyph-former i en ordbog i stedet for at omkode hver forekomst. Hvis du producerer arkiver over scannede dokumenter, er den forskel stor nok til at ændre lagerplanlægningen
Spørgsmålet længere oppe i kæden, hvordan bilevel-billedet overhovedet produceres, betyder mindst lige så meget for outputstørrelsen; regionbaseret monokrom rendering er dækket i artiklen om monokrom region-rendering, og dokumentomfattende størrelsesstrategi i PDF-filstørrelsesoptimering og font subsetting. Til scansæt med gentagne sider slår deduplikering ofte bedre komprimering, hvilket er emnet i perceptuel billededuplikering. Toolchain- og backend-tilgængelighed pr. platform er opført på produktsiden losLab PDF Developer Library