Teknisk artikkel

JBIG2-encoder-backends og Free Pascal-linkeren

PDFlibPas kan kode bilevel-bilder som JBIG2 gjennom to forskjellige backends. Én er en nativ Object Pascal MMR-encoder som alltid er til stede. Den andre er en ekstern symbolordbok-encoder som produserer vesentlig mindre output på skannet tekst, og den er valgfri: et prosjekt må linke backendenheten for at den i det hele tatt skal finnes. Det skillet er kilden til den vanligste overraskelsen med denne funksjonen, så det er verdt å slå fast først: DefaultJBIG2EncodeOptions ber om den eksterne encoderen som standard, og når backendenheten ikke er linket, faller forespørselen lydløst tilbake til Pascal MMR-veien

På Delphi og C++Builder er den eksterne backenden et sett med forhåndsbygde statiske objekter. På Free Pascal måtte den bli en DLL, og veien til den konklusjonen er en linkerhistorie som er nyttig for alle som har prøvd å linke C++-objekter inn i et Free Pascal-program

Registrering er kontrakten

Backendenheten registrerer seg fra sin initialiseringsseksjon ved å kalle RegisterJBIG2EncoderBackend. Kallere ber om den enten gjennom opsjonsbiten PDF_JBIG2_OPTION_EXTERNAL_ENCODER, som har verdien 4, eller gjennom UseExternalEncoder-parameteren til de utvidete bildeinngangspunktene. Bibliotekets paraplyenhet trekker bevisst ikke backendenheten inn, fordi å bære på et stort objektsett bør være hvert prosjekts avgjørelse; i C++Builder-treet er den for eksempel inkludert eksplisitt av prosjektene som vil ha den

Konsekvensen for kallere er at å be om den eksterne encoderen er en preferanse, ikke en garanti, og et bygg som glemmer enheten produserer større filer snarere enn en feil. Hvis outputstørrelsen betyr nok til å be om den bedre encoderen, betyr den nok til å sjekke at du fikk den

PDFlibPas JBIG2-kodingsforespørselsflyt der preferansen for ekstern encoder lydløst faller tilbake til den native Pascal MMR-veien uten backendenheten
Å be om den eksterne symbolordbok-encoderen er en preferanse: linket krymper output; ulinket kjører Pascal MMR-veien lydløst med større filer
uses
  PDFlibrary,
{$IFDEF FPC}
  PDFlibJBIG2EncDLL;    // dynamisk backend for Free Pascal
{$ELSE}
  PDFlibJBIG2EncC;      // statisk objektsett for 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;

Å kompilere enheten var to linjer. Symbolene var arbeidet

Å få backendenheten selv til å kompilere under Free Pascal tok nøyaktig to endringer: å sette assembler-dialekten, og å erstatte en recordbasert formatinnstillingskonstruktør med den globale standardvariabelen. Det er en rettferdig refleksjon av hvor portabel rettfram Pascal er mellom de to kompilatorene

Symbolsiden var det virkelige arbeidet. Objektsettet refererer 176 C-symboler. Av disse hadde 128 allerede Pascal-implementeringer inne i enheten og trengte bare eksportnavn knyttet til seg, fordi Delphi bruker funksjonsnavnet som symbolnavn mens Free Pascal krever en eksplisitt offentlig navnedeklarasjon. Tjuesyv ble delt med JPEG 2000-codec-en og måtte eksporteres fra nøyaktig ett sted, siden å definere dem to ganger ødelegger ethvert program som linker begge. De gjenværende 21 var plattform- og C-runtime-oppføringer, seksten Win32-filfunksjoner pluss en håndfull standardbibliotekkall, og de gikk inn i en ny kompatibilitetsenhet

Ingen av dette er konseptuelt vanskelig, og alt av det er nødvendig før linkeren i det hele tatt prøver. Linkeren er der det stoppet

Tre linkeringsruter, tre blindveier

Den interne Free Pascal-linkeren kan ikke lese objektfilene, fordi de ble produsert av en kompilator som sender ut associative COMDAT-seksjoner, og den interne linkeren rapporterer at den ikke støtter dem. Det er et blankt avslag, ikke en advarsel

Å bytte til en ekstern linker så ut som svaret. Binutils-linkeren som følger med Free Pascal krasjer fullstendig mens den anvender seksjons garbage collection på dette arkivet, og det flagget er en del av det faste parametersettet Free Pascal sender med for 64-bits Windows-målet, så det kan ikke fjernes fra kommandolinjen; de dokumenterte bryterne for å undertrykke det ignoreres på denne veien. Å levere en mye nyere binutils i stedet feiler annerledes: den kan ikke prosessere Free Pascal link-skriptet i det hele tatt, noe som gir en tom output uten skriptet og en vegg av relokeringsfeil med det

En grense oppdaget underveis er verdt å kjenne til selv om du aldri treffer linkerproblemet. Den eksterne linkeren løser objektfilstier relativt til den kjørbare filens output-katalog snarere enn kildetreet, så et relativt include-objekt-direktiv fungerer bare når output-katalogen tilfeldigvis er lik arbeidskatalogen ved kompileringstid. Et bibliotek kan ikke anta det om et forbrukerprosjekt, noe som i seg selv er en grunn til å foretrekke et linket bibliotek fremfor løse objekter

Tre feilede linkeringsruter for C++ JBIG2-encoder-objekter under Free Pascal og DLL-en som eksponerte to flate C-inngangspunkter som løste dem
COMDAT-seksjoner beseirer den interne linkeren og begge eksterne linkere feiler, så C++-encoderen sendes som én DLL bundet dynamisk av backendenheten

Hvorfor en annen C++-kompilator ikke hjelper

Den opplagte neste ideen er å bygge C++-siden på nytt med en kompilator hvis objekter Free Pascal kan lese. Det fungerer heller ikke, og grunnen er fundamental snarere enn et spørsmål om brytere. En minimal C++-oversettelsesenhet som inneholder en mal, kompilert med alle kodegenereringsfunksjoner slått av, sender fortsatt ut svake eksterne symboler, fordi mal- og inline-instansiering produserer dem av konstruksjon. Free Pascal avviser den symbolklassen blankt. Den motsatte retningen feiler også: en vanlig C++-linker kan ikke konsumere objekter fra den andre kompilatoren på grunn av den samme COMDAT-seksjonshåndteringen

Så C++-koden kan ikke leveres som objekter til Free Pascal via noen tilgjengelig rute. Den kan leveres som en DLL, noe som er det som skjedde: encoderen og dens bildebehandlingsavhengighet er bygget inn i ett bibliotek som eksponerer to flate C-inngangspunkter, og Free Pascal backendenheten binder disse dynamisk og registrerer seg nøyaktig som den statiske backenden gjør. Delphi- og C++Builder-veien ble ikke rørt i det hele tatt, noe som er det riktige utfallet; et portabilitetsproblem på én verktøykjede bør ikke forstyrre verktøykjeden som allerede fungerte

Polariteten er det eneste som biter deg

Mellom en Windows bilevel-bitmap og en JBIG2-encoder finnes et konvensjonsavvik som intet typesystem vil fange. En skannlinje i en én-bit-per-piksel enhetsuavhengig bitmap behandler en satt bit som hvit. Encoderen behandler en satt bit som svart. Lever skannlinjene over uendret, og du får en helt gyldig JBIG2-strøm av det fotografiske negativet av siden din

Én-bits DIB- og JBIG2-polaritetskonvensjoner der en satt bit er hvit i skannlinjen og svart i encoderen, fikset ved å invertere hver byte
Samme byte, motsatt betydning: uten å invertere hver byte produserer encoderen en gyldig JBIG2-strøm av det fotografiske negativet
// Én-bits DIB: satt bit betyr hvit. JBIG2-encoder: satt bit betyr
// svart. Inverter hver byte på veien inn
for I := 0 to RowBytes - 1 do
  Row[I] := Row[I] xor $FF;

Verifiseringsmetoden betyr like mye som fiksen. Å sammenligne komprimerte strømlengder forteller deg ingenting, fordi et negativt bilde komprimerer til en lignende størrelse. Å se på siden beviser bare at den ikke er åpenbart invertert. Den pålitelige sjekken er å rendre output fra begge kodingsveiene, nativ Pascal og ekstern, til PNG og sammenligne dem byte for byte: begge encoderne er tapsfrie på det samme kildebildet, så alt annet enn et eksakt treff er en feil i en av dem. Den sammenligningen er nå en permanent regresjonstest, og det er den typen påstand som er verdt å bygge når som helst to implementeringer skal være helt enige

Hvilken backend du bør bruke

For generelt bilevel-innhold, ditherede halvtoner, strektegninger, blandet grafikk, er den native Pascal MMR-encoderen tilstrekkelig og har ingen utrullingskostnad. For skannet tekst, som er tilfellet JBIG2 ble designet for, er den eksterne symbolordbok-encoderen der størrelsesreduksjonen bor, fordi den faktorerer gjentatte glyfformer inn i en ordbok i stedet for å rekode hver forekomst. Hvis du produserer arkiver av skannede dokumenter, er den forskjellen stor nok til å endre lagringsplanleggingen

Oppstrømsspørsmålet om hvordan bilevel-bildet i det hele tatt produseres, betyr like mye for outputstørrelsen; regionbasert monokrom rendering er dekket i artikkelen om monokrom regionrendering, og dokumentvid størrelsesstrategi i PDF-filstørrelseoptimalisering og font-subsetting. For skannesett med gjentatte sider slår deduplisering ofte bedre komprimering, noe som er emnet for perseptuell bildededuplisering. Verktøykjede- og backend-tilgjengelighet per plattform er oppført på produktsiden for losLab PDF Developer Library