PDFlibPas, PDF-biblioteket för Delphi, importerar TIFF-bilder vars JPEG-komprimering går förbi 8-bitars baseline: 12-bitars extended sequential (SOF1), Huffman lossless (SOF3), aritmetiskt kodad (SOF9 och SOF10) och old-style TIFF 6.0 JPEG med tabelloffset. Eftersom PDF DCTDecode inte kan bära de processerna avkodar PDFlibPas dem i ren Pascal och bäddar in samplesna förlustfritt med 8 eller 16 bitar per komponent
Hela uppsättningen kräver v3.724.0 eller senare, och den siktar rak på TIFF:er från skannrar, medicinsk och vetenskaplig insamlingsmjukvara och arkivsystem frusna i 1990-talet. Nedanför handlar det om varför de varianterna inte kan kopieras rakt in i PDF:en som de är, hur PDFlibPas landar dem, och Pascalfällorna på vägen
Varför kan inte en 12-bitars JPEG direkt in i en PDF?
En 12-bitars JPEG kan inte skickas vidare för PDF har inget filter som är skyldigt att avkoda den. ISO 32000-1 §7.4.8 definierar DCTDecode kring JPEG-baselineformatet, med progressive-tillägget tillkommet i PDF 1.3. Ingenting i den klausulen täcker 12-bitars sampleprecision, den förlustfria processen i ITU-T T.81 Annex H, eller den aritmetiska entropikodaren i Annex D. En läsare som möter en SOF1 12-bitars frame eller en SOF9-markör inuti en DCTDecode-ström har all rätt att rita ingenting, och de flesta viewers gör precis det
För de 8-bitars Huffman-baseline- och progressive-strips som utgör de flesta JPEG-i-TIFF-filer validerar PDFlibPas fortfarande varje scan och släpper de komprimerade bytena vidare som DCTDecode, vilket håller PDF:en liten. Allt annat avkodas. PDFlibPas läser frammarkören och sampleprecisionen för det första JPEG-blocket, och när den hittar SOF3, SOF9, SOF10 eller en precision annan än 8 går sidan genom sampleavkodaren i stället för pass-through-vägen. Att detektera aritmetisk kodning via frammarkören betyder mer än det låter: en kontroll som bara tittar på sampleprecision släpper en klassisk TIFF smyga in en 8-bitars SOF9-strip i DCTDecode-vägen och producera en PDF som läsare inte kan visa
Vad PDFlibPas skriver in i PDF:en i stället
PDFlibPas gör avkodade JPEG-block till en vanlig image XObject med BitsPerComponent 8 eller 16. Samplesna lagras okomprimerade som standard; anropa CompressImages(1) innan bilden läggs till så skriver PDFlibPas dem med FlateDecode. ISO 32000-1 §8.9.5.1 tillåter 16 bitar per komponent från PDF 1.5, så PDFlibPas höjer dokumentversionen till minst 1.5 närhelst en bild med 16-bitars samples läggs till. Det omfattar multi-strip- och tiled-bilder, vars rotobjekt är en Form XObject utan egen BitsPerComponent att inspektera
Mappningen är fast per JPEG-process:
- 12-bitars Huffman-JPEG (sequential SOF1, progressive SOF2) dekvantiseras, inverstransformeras och breddas till 16-bitars samples
- Aritmetiskt kodad JPEG (sequential SOF9, progressive SOF10) blir 8-bitars samples vid 8-bitars precision och 16-bitars samples vid 12-bitars precision
- Huffman lossless JPEG (SOF3) blir 8-bitars eller 16-bitars samples, exakt matchande sin deklarerade precision
Breddningen från 12 till 16 bitar använder bitreplikering, (V shl 4) or (V shr 8), så 4095 mappas till 65535 och vitt förblir vitt i stället för att toppa vid 65520. PDFlibPas dumpar aldrig de bilderna till 8 bitar bakom din rygg. När biblioteket rasteriserar en sådan sida till sin egen visningsyta använder den den höga byten av varje sample, men den inbäddade PDF-bilden behåller alla sexton
Priset är filstorlek: även med CompressImages(1) är Flate-komprimerade 16-bitars samples större än DCT-data de kom från, och utan den är de större ändå. För diagnostik- och arkiveringsarbetsflöden är den avvägningen oftast rätt. Om storlek väger tyngre för ett visst jobb visar guiden till att minska PDF-filstorlek i Delphi var bytena faktiskt tar vägen
Importera de här TIFF:erna med TPDFlib
Anropskoden ändras inte alls. TPDFlib.AddImageFromFile(Const FileName: WideString; Options: Integer): Integer tar TIFF-sidnumret, 1-baserat, i Options och returnerar ett bild-ID, eller 0 när sidan inte kan importeras. JPEG-varianter utan stöd avvisas innan bilden sätts in, så ett 0 betyder att inget halvavkodat nådde dokumentet
uses
PDFlibrary;
function TiffToPdf(const TiffName, PdfName: WideString): Boolean;
var
Lib: TPDFlib;
Page, PageCount, ImageID: Integer;
DPI, W, H: Double;
begin
Result := False;
Lib := TPDFlib.Create;
try
Lib.SetMeasurementUnits(0); // standard user space, 1/72 tum
Lib.SetOrigin(1); // origo uppe till vänster för DrawImage
Lib.CompressImages(1); // Flate för avkodade samples
PageCount := Lib.GetImagePageCount(TiffName);
if PageCount = 0 then
Exit;
for Page := 1 to PageCount do
begin
ImageID := Lib.AddImageFromFile(TiffName, Page); // Options = TIFF-sida
if ImageID = 0 then
Exit; // ej stödd eller skadad bilddata
Lib.SelectImage(ImageID);
case Lib.ImageResolutionUnits of
2: DPI := Lib.ImageHorizontalResolution; // dots per inch
3: DPI := Lib.ImageHorizontalResolution * 2.54; // dots per cm
else
DPI := 0;
end;
if DPI <= 0 then
DPI := 300;
W := Lib.ImageWidth * 72 / DPI;
H := Lib.ImageHeight * 72 / DPI;
if Page > 1 then
Lib.NewPage;
Lib.SetPageDimensions(W, H);
Lib.DrawImage(0, 0, W, H);
end;
Result := Lib.SaveToFile(PdfName) = 1;
finally
Lib.Free;
end;
end;
Ingen per-formatförgrening behövs, och AddImageFromStream och AddImageFromString följer samma regler för TIFF-data i minnet
Old-style JPEG: tabeller på offset, strips utan huvuden
Sedan v3.713.0 importerar PDFlibPas old-style JPEG-TIFF:er (Compression = 6) vars tabeller kommer från offset-taggarna JPEGQTables, JPEGDCTables och JPEGACTables eller från en tables-only JPEGInterchangeFormat-ström. TIFF 6.0 Section 22 definierade den layouten, och TIFF Technical Note 2 ersatte den senare med Compression = 7 och en gemensam JPEGTables-tagg, men skannrar skrev det gamla upplägget i många år
Containersidan, BigTIFF och tiled-layouter, tas upp i genomgången om härdningen av TIFF-avkodaren; här är fokus JPEG-payloaden. Varje tabelloffset-tagg pekar på en rå 64-byte kvantifieringstabell, eller på en Huffmantabell med 16 counts följda av sina värden, en post per komponent. PDFlibPas gör om dem till DQT- och DHT-segment i en syntetiserad JPEGTables-ström. Varje strip av ren entropikodad data får sedan ett eget huvud: SOI, en frammarkör som bär stripens radantal, DRI när JPEGRestartInterval är satt, SOS, entropidatan och EOI. Stripsna kan inte klistras ihop till en scan, för old-style JPEG startar om DC-prediktionen vid varje stripgräns
Två detaljer är lätta att få fel. De tre komponenterna i en YCbCr-bild pekar ofta sina tabelloffset mot samma byte, så PDFlibPas deduplikrar tabeller efter innehåll innan den tilldelar tabellnummer. Om mer än två Huffmantabeller av en klass återstår använder den syntetiserade framen SOF1 i stället för SOF0, eftersom en baseline-frame bara får referera två DC- och två AC-tabeller. När JPEGInterchangeFormat i stället pekar på en komplett JPEG-ström som innehåller scans behandlar PDFlibPas den strömmen som hela bilden och importerar den som en enda new-style-strip. Lossless, tiled och separate-plane old-style JPEG avvisas med ett explicit fel i stället för att gissas fram, så konvertera dem till new-style JPEG uppströms
Varför kommer en CMYK JPEG i en TIFF ut inverterad?
En CMYK JPEG i en TIFF kommer ut inverterad när importören tillämpar Adobe APP14-konventionen som hör hemma i fristående JPEG-filer. Photoshop skriver inverterad CMYK i fristående JPEG:er, så PDF-producenter lägger traditionellt till /Decode [1 0 1 0 1 0 1 0] för att kompensera. TIFF PhotometricInterpretation 5 definierar samples som bläckmängder där 0 betyder ingen bläck, och etablerade TIFF-läsare tar de avkodade JPEG-samplesna till ytan utan att titta på APP14
PDFlibPas bäddar in JPEG-komprimerade CMYK TIFF-strips utan den inverterade Decode-arrayen, oavsett om en APP14-markör finns eller inte. APP14-transformflaggan avgör fortfarande om YCCK konverteras till CMYK, och DCTDecode sköter det steget själv. Fristående JPEG-filer med fyra komponenter behåller den inverterade Adobe-konventionen, så identiska JPEG-byte behandlas olika beroende på container, precis som de två formaten kräver
Varför går lossless JPEG-avkodning sönder på ett skift i Pascal?
Lossless JPEG-avkodning går sönder i Pascal för prediktorerna 5 och 6 behöver ett aritmetiskt högerskift och Pascals shr är ett logiskt. SOF3 predicerar varje sample från sin vänstra (Ra), övre (Rb) och övre vänstra (Rc) granne och kodar bara differensen. Av de sju prediktorerna i T.81 Table H.1 är prediktor 5 Ra + ((Rb - Rc) >> 1) och prediktor 6 Rb + ((Ra - Rc) >> 1), och differensen i parentes blir negativ varje gång den övre vänstra grannen är den ljusare. Mata ett negativt Integer till shr och teckenbiten blir en storleksbit, så bilden löser upp sig i brus
Att byta skiftet mot div 2 fixar det inte heller. div trunkerar mot noll medan ett aritmetiskt skift avrundar mot negativ oändlighet, och de två är oeniga om vartannat udda negativt värde. Free Pascal har SarLongint i sin System-unit; Delphi har ingen motsvarighet, så PDFlibPas använder en hjälpfunktion som kompilerar identiskt på båda:
// Aritmetiskt skift åt höger ett steg: floor(Value / 2)
// shr är ett logiskt skift, och div 2 trunkerar mot noll
function HalfDown(Value: Integer): Integer;
begin
if Value >= 0 then
Result := Value shr 1
else
Result := -((1 - Value) shr 1);
end;
// Value = -3: -3 shr 1 = 2147483646, -3 div 2 = -1, HalfDown(-3) = -2
För typbreddssidan av samma problemslag, se Longint- och pekarefällorna på Delphi Linux64
Resten av lossless-avkodaren har gränsregler av egen art. Första raden, och första raden efter varje restart-markör, predicerar bara från den vänstra grannen och börjar på 2^(P−Pt−1) för precision P och punkttransformation Pt; varje senare rad börjar från samplen ovanför. Rekonstruktionen wrapas modulo 2^16, och punkttransformationen skalar bara utdata, aldrig prediktionskedjan. Huffman DC-kategori 16, en differens på 32768 utan extra bitar, är laglig bara i lossless-läge, så tabellparsern måste acceptera den innan den vet vilken process framen kommer deklarera
PDFlibPas stöder alla sju prediktorer, punkttransformationer och restart-intervall som täcker hela rader. JPEG-precisionen måste vara lika med BitsPerSample och vara 8 eller 16, och varje komponent måste samplas 1x1. YCbCr-losslessdata och 12-bitars lossless JPEG avvisas med ett explicit fel
12-bitars DCT och aritmetisk kodning i ren Pascal
12-bitars- och aritmetikavkodarna i PDFlibPas delar en invers DCT och skiljer sig bara i entropiavkodning. I 12-bitars Huffman-JPEG når DC-kategorierna 15 och AC-kategorierna 14, kvantifieringstabeller kan bära 16-bitars poster, och DC-prediktionen nollställs vid varje scan och varje restart. Koefficienter stannar i zigzag-ordning tills dekvantiseringen, som måste använda kvantifieringsvärdet vid samma zigzag-index, och först därefter flyttas de till naturlig ordning för den separerbara tvåpass-IDCT. Avkodade samples landar inom ±1 av en oberoende flyttalsreferensavkodare
Aritmetisk kodning ersätter Huffman med QM-kodaren i T.81 Annex D: en sannolikhetstillståndstabell med 114 poster, ett DC-statistikområde med 64 bin och ett AC-område med 256 bin, styrd av conditioning-parametrar från DAC-segmentet (standard L = 0, U = 1, Kx = 5). Statistik, DC-kontext och prediktion nollställs vid varje restart-intervall och i början av varje scan. Det är därför PDFlibPas behåller DAC-segment när den normaliserar gemensamma JPEGTables för aritmetiska bilder; släng dem och varje strip avkodas med fel statistikmodell
v3.723.0 lade till subsamplade komponenter och YCbCr-till-RGB-konvertering med JFIF-ekvationerna till de avkodade vägarna, och v3.724.0 lade till progressive scans för både 12-bitars Huffman (SOF2) och aritmetisk (SOF10) data. Subsamplad kroma replikeras till full upplösning i stället för att jämnas ut, vilket är vad en referensavkodare producerar med chroma smoothing avstängt. Hierarchical JPEG och aritmetiskt kodad lossless JPEG (SOF11) avvisas med ett explicit fel
Testa binära JPEG-fixtures i Delphi
Handbyggda JPEG-fixtures i Delphi-unittests kan falla på ett sätt som ser ut precis som en avkodningsbugg. En strängkonstant byggd av #$xx-tecken som löper förbi 255 tecken typas som UnicodeString, och att tilldela den till en AnsiString konverterar den genom den aktiva kodsidan. Byte $80 till $9F, rutin i entropikodad data, skrivs om på vägen in. Att bygga markörhuvuden i runtime har samma fälla: i Delphi ger AnsiChar + AnsiChar upphov till en UnicodeString, så appenda enskilda byte till en AnsiString eller skriv dem med SetLength och indexerad tilldelning. För fixtures, lagra texten i hex och avkoda i runtime:
uses
SysUtils, PDFlibrary;
function HexToBytes(const Hex: AnsiString): AnsiString;
var
I: Integer;
begin
SetLength(Result, Length(Hex) div 2);
for I := 1 to Length(Result) do
Result[I] := AnsiChar(StrToInt('$' + string(Copy(Hex, I * 2 - 1, 2))));
end;
// Fixturen förblir ren ASCII, så ingen kodsida kan röra den
ImageID := Lib.AddImageFromString(HexToBytes(TiffHex), 1);
När du genererar testbilder, kom ihåg att jpegtran -arithmetic skriver sequential SOF9; SOF10 kräver -progressive också. Och ett bild-ID skilt från noll bevisar bara att importen accepterades. Rendera sidan och jämför pixlar, för ett pass-through-misstag producerar en välformad PDF som ingen viewer kan rita
Ta med äldre skanningsarkiv in i PDF
De här JPEG-varianterna tenderar att dyka upp alla samtidigt vid en arkivmigrering. PDFlibPas läser 12-bitars, lossless, aritmetiska och old-style JPEG-TIFF:er genom samma AddImageFromFile-anrop som allt annat, behåller full precision i PDF:en, och avvisar de sällsynta processerna utanför den uppsättningen med ett explicit fel. För licensiering, hela funktionslistan och testnedladdningar, se produktsidan för PDFlibPas PDF Library for Delphi