Teknisk artikel

JBIG2-kodarebackends och Free Pascal-länkaren

PDFlibPas kan koda bilevel-bilder som JBIG2 via två olika backends. Den ena är en inbyggd Object Pascal-MMR-kodare som alltid finns där. Den andra är en extern symbolordlistekodare som ger betydligt mindre utdata på skannad text, och den är valfri: ett projekt måste länka backendenheten för att den ska finnas alls. Den distinktionen är källan till den vanligaste överraskningen med den här funktionen, så den är värd att ange först: DefaultJBIG2EncodeOptions begär den externa kodaren som standard, och när backendenheten inte är länkad faller begäran tyst tillbaka till Pascal-MMR-spåret

På Delphi och C++Builder är den externa backenden en uppsättning förbyggda statiska objekt. På Free Pascal fick den bli en DLL, och vägen till det beslutet är en länkarberättelse som är användbar för alla som har försökt länka C++-objekt i ett Free Pascal-program

Registreringen är kontraktet

Backendenheten registrerar sig själv från sitt initialiseringsavsnitt genom att anropa RegisterJBIG2EncoderBackend. Anropare begär den antingen via alternativbiten PDF_JBIG2_OPTION_EXTERNAL_ENCODER, som har värdet 4, eller via parametern UseExternalEncoder hos de utökade bildingångspunkterna. Bibliotekets paraplyenhet drar medvetet inte in backendenheten, eftersom att bära på en stor objektuppsättning bör vara varje projekts beslut; i C++Builder-trädet inkluderas den till exempel uttryckligen av de projekt som vill ha den

Konsekvensen för anropare är att begära den externa kodaren är en preferens, inte en garanti, och ett bygge som glömmer enheten ger större filer i stället för ett fel. Om utdatastorleken betyder tillräckligt mycket för att be om den bättre kodaren betyder den tillräckligt mycket för att kontrollera att du fick den

Flöde för JBIG2-kodningsbegäran i PDFlibPas där preferensen för extern kodare tyst faller tillbaka till det inbyggda Pascal-MMR-spåret utan backendenheten
Att begära den externa symbolordlistekodaren är en preferens: länkad krymper utdata; olänkad kör Pascal-MMR-spåret tyst med större filer
uses
  PDFlibrary,
{$IFDEF FPC}
  PDFlibJBIG2EncDLL;    // dynamisk backend för Free Pascal
{$ELSE}
  PDFlibJBIG2EncC;      // statisk objektuppsättning för 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;

Att kompilera enheten var två rader. Symbolerna var arbetet

Att få backendenheten själv att kompilera under Free Pascal krävde exakt två ändringar: att ställa in assemblerdialekten och att ersätta en postbaserad konstruktor för formatinställningar med den globala standardvariabeln. Det är en rättvis bild av hur portabel okomplicerad Pascal är mellan de två kompilatorerna

Symbolsidan var det verkliga arbetet. Objektuppsättningen refererar 176 C-symboler. Av dem hade 128 redan Pascal-implementeringar inuti enheten och behövde bara exportnamn knutna till sig, eftersom Delphi använder funktionsnamnet som symbolnamn medan Free Pascal kräver en uttrycklig publik namndeklaration. Tjugosju delades med JPEG 2000-codecen och fick exporteras från exakt ett ställe, eftersom att definiera dem två gånger bryter alla program som länkar båda. De återstående 21 var plattforms- och C-runtime-poster, sexton Win32-filfunktioner plus ett antal standardbiblioteksanrop, och de gick in i en ny kompatibilitetsenhet

Inget av detta är konceptuellt svårt, och allt är nödvändigt innan länkaren ens försöker. Länkaren var där det stannade

Tre länkningsvägar, tre återvändsgränder

Den interna Free Pascal-länkaren kan inte läsa objektfilerna, eftersom de producerades av en kompilator som avger associerande COMDAT-sektioner och den interna länkaren rapporterar att den inte stöder dem. Det är ett blankt avslag, inte en varning

Att byta till en extern länkare såg ut som svaret. Binutils-länkaren som medföljer Free Pascal kraschar blankt när den tillämpar section garbage collection på detta arkiv, och den flaggan är en del av den fasta parameteruppsättning Free Pascal skickar för 64-bit Windows-målet, så den kan inte tas bort från kommandoraden; de dokumenterade omkopplarna för att undertrycka den ignoreras på den vägen. Att i stället tillhandahålla en mycket nyare binutils misslyckas annorlunda: den kan inte bearbeta Free Pascal-länkskriptet alls, vilket ger en tom utdata utan skriptet och en vägg av relocation-fel med det

En gräns som upptäcktes på vägen är värd att känna till även om du aldrig stöter på länkarproblemet. Den externa länkaren löser objektfilsvägar relativt den körbara filens utdatakatalog i stället för källträdet, så ett relativt include-object-direktiv fungerar bara när utdatakatalogen råkar vara densamma som arbetskatalogen vid kompileringen. Ett bibliotek kan inte anta det om en konsumentprojekt, vilket i sig är en anledning att föredra ett länkat bibliotek framför lösa objekt

Tre misslyckade länkningsvägar för C++-JBIG2-kodarobjekt under Free Pascal och DLL:en som exponerar två platta C-ingångspunkter och löste problemet
COMDAT-sektioner besegrar den interna länkaren och båda externa länkarna misslyckas, så C++-kodaren levereras som en DLL som backendenheten binder dynamiskt

Varför en annan C++-kompilator inte hjälper

Den uppenbara nästa idén är att bygga om C++-sidan med en kompilator vars objekt Free Pascal kan läsa. Det fungerar inte heller, och orsaken är grundläggande snarare än en fråga om omkopplare. En minimal C++-översättningsenhet som innehåller en mall, kompilerad med alla kodgenereringsfunktioner avstängda, avger fortfarande svaga externa symboler, eftersom mall- och inline-instansiering producerar dem genom konstruktion. Free Pascal avvisar den symbolklassen blankt. Motsatt riktning misslyckas också: en mainstream C++-länkare kan inte konsumera objekt från den andra kompilatorn på grund av samma COMDAT-sektionhantering

Så C++-koden kan inte levereras som objekt till Free Pascal via någon tillgänglig väg. Den kan levereras som en DLL, vilket är vad som hände: kodaren och dess bildbehandlingsberoende byggs in i ett bibliotek som exponerar två platta C-ingångspunkter, och Free Pascal-backendenheten binder dem dynamiskt och registrerar sig exakt som den statiska backenden gör. Delphi- och C++Builder-spåret rördes inte alls, vilket är det rätta resultatet; ett portabilitetsproblem på en verktygskedja ska inte störa verktygskedjan som redan fungerade

Polariteten är det enda som kommer bita dig

Mellan en Windows-bilevel-bitmapp och en JBIG2-kodare finns ett konventionsmissmöte som inget typsystem fångar. En scanline i en enhetsoberoende bitmapp med en bit per pixel behandlar en sättad bit som vit. Kodaren behandlar en sättad bit som svart. Lämna över scanlinjerna oförändrade och du får en helt giltig JBIG2-ström av det fotografiska negativet av din sida

Polaritetskonventioner för enbitars DIB och JBIG2 där en sättad bit är vit i scanlinen och svart i kodaren, fixat genom att invertera varje byte
Samma byte, motsatt betydelse: utan att invertera varje byte ger kodaren en giltig JBIG2-ström av det fotografiska negativet
// Enbitars DIB: sättad bit betyder vit. JBIG2-kodare: sättad bit
// betyder svart. Invertera varje byte på vägen in
for I := 0 to RowBytes - 1 do
  Row[I] := Row[I] xor $FF;

Verifieringsmetoden betyder lika mycket som fixen. Att jämföra komprimerade strömlängder säger ingenting, eftersom en negativ bild komprimerar till en liknande storlek. Att titta på sidan bevisar bara att den inte är uppenbart inverterad. Den tillförlitliga kontrollen är att rendera utdata från båda kodningsspåren, inbyggd Pascal och extern, till PNG och jämföra dem byte för byte: båda kodare är förlustfria på samma källbild, så allt annat än en exakt matchning är en bugg i en av dem. Den jämförelsen är nu ett permanent regressionstest, och det är den sorts assertion som är värd att bygga närhelst två implementeringar förväntas stämma exakt

Vilken backend man ska använda

För allmänt bilevel-innehåll, dithrade halvtoner, linjekonst, blandad grafik, är den inbyggda Pascal-MMR-kodaren tillräcklig och har ingen distributionskostnad. För skannad text, vilket är fallet JBIG2 designades för, är den externa symbolordlistekodaren där storleksminskningen finns, eftersom den faktoriserar upprepade glyfformer i en ordlista i stället för att koda om varje förekomst. Om du producerar arkiv av skannade dokument är den skillnaden stor nog att ändra lagringsplaneringen

Den överordnade frågan, hur bilevel-bilden produceras från början, betyder lika mycket för utdatastorleken; regionbaserad monokrom rendering tas upp i artikeln om monokrom regionsrendering, och dokumentomfattande storleksstrategi i PDF-filstorleksoptimering och font subsetting. För skanningsuppsättningar med upprepade sidor slår deduplicering ofta bättre komprimering, vilket är ämnet för perceptuell bilddeduplicering. Tillgänglighet per plattform för verktygskedjor och backends listas på produktsidan för losLab PDF Developer Library