Techninis straipsnis

PDF Predictor ir LZWDecode dekodavimas Delphi su HotPDF

Srautas, pažymėtas /Predictor 12, nereiškia, kad kiekviena eilutė naudoja PNG filtrą 2. HotPDF, natyvus VCL PDF komponentas Delphi ir C++Builder, traktuoja predictor reikšmes nuo 10 iki 15 kaip vieną šeimą: tikra filtro žyma, nuo 0 iki 4, yra pirmasis kiekvienos užkoduotos eilutės baitas, o HPDFDecodePredictor skaito ir tikrina tą žymą eilutė po eilutės. Šis skirtumas yra beveik kiekvienos šio PDF kampelio klaidos forma, nes niekas nekelia klaidos, kai suklystate. Filtrų grandinė vykdoma, raster'as yra to dydžio, kokio tikėjotės, o vaizdas išeina kaip įstrižainės triukšmas arba gradientas, kuris kiekviena skenavimo eilute vis labiau nuklysta. Penki skaičiai /DecodeParms (ISO 32000-1 §7.4.4) daugiausiai keičia baitų reikšmę, o ne jų ilgį, todėl neteisingas vienas iš jų sukuria tikėtinas šiukšles vietoj klaidos

Kodėl /Predictor 12 nereiškia PNG filtro 2 kiekvienoje eilutėje?

Todėl, kad predictor numeris tik sako "naudojama PNG prognozė", o ne kuris filtras. PNG koduotuvai renkasi filtrą kiekvienai skenavimo eilutei, o PDF filtras paveldi tai, todėl predictor reikšmės 10 (None), 11 (Sub), 12 (Up), 13 (Average), 14 (Paeth) ir 15 (Optimum) dekoduojamos identiškai: dekoderis turi paklusti kiekvienos eilutės pirmaujančiam žymos baitui. Išdėstymo pasekmė svarbi lygiai taip pat, kaip ir semantika. Kiekviena užkoduota eilutė yra 1 + RowBytes baitų ilgio, todėl įvestis viršija išvestį būtent eilučių skaičiumi, o srautas, kurio ilgis nėra RowBytes + 1 sveikas kartotinis, yra sutrumpintas pagal apibrėžimą. HotPDF tikrina tą ribą prieš paliesdamas baitą, atmeta bet kokią žymą virš 4 su Invalid PNG predictor row tag ir skaito ankstesnę eilutę tiesiai iš vieno išvesties buferio, vietoj dvimatės eilučių masyvo sudarymo. Filtrai 1 ir 3 siekia atgal BytesPerPixel dabartinėje eilutėje, filtras 2 skaito tiesiai aukštyn, filtras 4 vykdo Paeth pasirinkimą per kairę, viršų ir viršų-kairę — ir visi keturi veikia jau atkurtą išvestį, todėl viršutinė eilutė turi būti dekoduota eilutė, o ne filtruota įvestis

uses
  HPDFPredictor;

var
  Filtered, Raster: AnsiString;
  ErrorText: string;
begin
  // /DecodeParms << /Predictor 12 /Colors 3 /BitsPerComponent 8 /Columns 1024 >>
  if HPDFTryDecodePredictor(Filtered, 12, 3, 8, 1024,
       Int64(1024) * 3 * 8192, Raster, ErrorText) then
    ConsumeRaster(Raster)
  else
    LogStreamDefect('predictor', ErrorText);   // no exception, no partial raster
end;

MaxOutputBytes argumentas nėra dekoracija. Predictor etapas yra dekompresijos etapas persirengęs, o priešiška ar tiesiog sugadinta /Columns reikšmė paverčia kelis kilobaitus įvesties į daugiagigabaitinį paskirstymo prašymą. HotPDF apskaičiuoja bitus eilutei, baitus eilutei ir bendrą raster'o dydį Int64 pirmiausiai, atmeta geometriją, kuri perpildo, ir paiso iškvietėjo pateiktos ribos. Perduokite realią ribą, kildinamą iš vaizdo žodyno, ir nesėkmės režimas tampa registruotu pranešimu, o ne atminties trūkumo dialogu kliento mašinoje

Kodėl TIFF Predictor 2 sugadina 4 bitų vaizdus?

Todėl, kad Predictor 2 yra horizontalus skirtumo skaičiavimas pagal pavyzdį, ne baitą, o 1, 2 ar 4 bitų komponentui keli pavyzdžiai dalijasi baitą. Įprasta realizacija prideda baitą N-Colors prie baito N, kas atsitinka teisinga 8 bitų komponente ir tyliai neteisinga visur kitur. 8 bitų RGB skenavimas dekoduojamas puikiai, tada tas pats kodas sugadina 4 bitų indeksuotą vaizdą pirmą kartą, kai koks nors pasirodo produkcijoje

Teisinga aritmetika veikia bitų lauko viduje. HotPDF eina per pavyzdžius nuo indekso Colors iki Colors * Columns - 1, ištraukia pavyzdį ir jo to paties komponento kairįjį kaimyną su kauke (1 shl BitsPerComponent) - 1 atitinkamame poslinkyje, sudeda juos modulo tos kaukės ir įrašo rezultatą atgal netrikdydamas kitų pavyzdžių, supakuotų tame pačiame baite. Uodega svarbi lygiai taip pat: eilutė užpildoma iki baito ribos, todėl užpildymo bitai po paskutinio pavyzdžio turi išgyventi nepaliesti, o ne būti sulankstyti į aritmetiką. Su 16 bitų komponente kiekvienas pavyzdys yra didžiojo endian baitų pora, o sudėtis apsuka prie $FFFF per porą, ne pernaša tarp baitų nepriklausomai; su 8 bitais paprastas baitų rekurentas yra teisingas, žingsniuojant per Colors, kad raudona kauptųsi prieš raudoną, o alfa prieš alfa. Kiekviename variante pirmasis eilutės pikselis yra literalas, niekada skirtumas, o rekurentas iš naujo pradedamas kiekvienoje eilutės riboje — TIFF prognozė niekada neskaito viršutinės eilutės, kas yra visas skirtumas tarp jos ir PNG šeimos

Ką iš tikrųjų kontroliuoja EarlyChange LZWDecode?

Jis kontroliuoja, kada skaitytuvas praplečia savo kodo dydį vienu bitu, o nukrypimas vienu kodu sugadina viską, kas seka. HotPDF išreiškia taisyklę kaip vieną invariantą: pridėjus žodyno įrašą, kitas skaitymas praplečiamas, kai NextCode pasiekia (1 shl CodeSize) - Ord(EarlyChange). Su /EarlyChange 1, ISO 32000-1 §7.4.4 numatytoji reikšmė, perjungimas vyksta vienu kodu anksčiau; su /EarlyChange 0 jis vyksta lygiai ties riba. Abu pasitaiko realiuose failuose, ir niekas bitų sraute nepasako, kurį naudojo koduotuvas. Likusi būsenų mašina turi judėti sinchroniai: valymo kodas kartu atstato kodo dydį, bitų kaukę, kitą laisvą kodą ir frazių saugyklą, o pabaigos-informacijos kodas skaitomas su tuo pločiu, koks tuo metu galioja, ne pradiniais 9 bitais. HotPDF pradeda nuo InitialCodeSize 9, riboja kodo dydį iki 12, o žodyną iki 4096 įrašų, ir pagal numatymą FillOrder nustato foTop, nes PDF pakuoja kodus aukštesnio bito pirmiausia — foBottom egzistuoja TIFF stiliaus srautams, kurie taip nedaro

uses
  HPDFLZW;

var
  Decoder: TPDFLZWDecompressor;
  Parms: TPDFLZWParms;
  Plain: AnsiString;
begin
  Decoder := TPDFLZWDecompressor.Create;
  try
    Decoder.EarlyChange := True;      // /EarlyChange 1 is the PDF default
    Decoder.FillOrder := foTop;       // high-order bit first
    Decoder.MaxOutputBytes := 256 * 1024 * 1024;
    Decoder.RequireInitialClear := False;
    Decoder.RequireEndOfInformation := False;

    Parms.Predictor := 12;
    Parms.Colors := 3;
    Parms.BitsPerComponent := 8;
    Parms.Columns := 1024;
    Parms.ExpandedTo8Bit := False;
    Parms.ColorSpace := 'DeviceRGB';

    if Decoder.TryDecompress(RawStreamBytes, Parms, Plain) then
      LogDecodeStats(Decoder.PeakCodeSize, Decoder.DictionaryAdds,
        Decoder.KwKwKExpansions, Decoder.OutputBytes)
    else
      LogStreamDefect('lzw', Decoder.LastError);
  finally
    Decoder.Free;
  end;
end;

Statistika egzistuoja triažui, ne pagyroms. Kai failas dekoduojamas teisingu ilgiu, bet neteisingais pikseliais, PeakCodeSize ir DictionaryAdds nedelsiant pasako, ar skaitytuvas kada nors išsiplėtė ten, kur išsiplėtė koduotuvas. Apverskite EarlyChange, dekoduokite dar kartą, palyginkite abu: jei skaičiai pasikeičia, turite atsakymą per vieną paleidimą, vietoj žingsniavimo per bitų skaitytuvą

KwKwK atšaka ir kada srautas tiesiog turėtų nepavykti

Vienintelis teisėtas atvejis, atrodantis neteisėtai, yra Code = NextCode, ir HotPDF jį tvarko sukurdamas įrašą prieš jį išleisdamas. Koduotuvas gali išleisti kodą frazei, kurią jis apibrėžia tame pačiame žingsnyje, kas nutinka bet kada, kai įvestis turi šabloną formos K w K w K; dekoderis negali to kodo ieškoti, nes jis dar neegzistuoja, todėl jis turi sukurti Previous + First(Previous), pridėti kaip naują įrašą ir išleisti įrašą, kurį ką tik sukūrė. HotPDF juos skaičiuoja KwKwKExpansions ir sutikrina, kad kodas, kurį jis pridėjo, yra kodas, kurio buvo prašyta. Viskas virš NextCode yra sugadinimas, ir ten dekoderis turėtų sustoti, o ne improvizuoti: HotPDF kelia klaidą dėl būsimo kodo, dėl žodyno prefikso, rodančio už frazių arenos ribų, dėl pilno žodyno ir dėl pirmojo kodo, kuris nėra literalas. Du griežtumo jungikliai sąmoningai išjungti pagal numatymą, RequireInitialClear ir RequireEndOfInformation, nes daugybė produkcinių PDF failų praleidžia pradinį valymo kodą arba baigiasi duomenimis be terminatoriaus. Įjunkite juos tikrindami savo pačių išvestį, palikite išjungtus vartodami failus iš laukinės gamtos

Kur /DecodeParms iš tikrųjų skaitomas įkelto dokumento pusėje

HotPDF išsprendžia /DecodeParms arba jos sutrumpinimą /DP vaizdo srauto žodyne, priima arba žodyną, arba masyvą ir paima paskutinį elementą, kai jis yra masyvas, tada perneša Predictor, Colors, BitsPerComponent, Columns ir EarlyChange į raster kelią. Masyvo atvejis yra tas, kurį žmonės pamiršta: srautas, filtruotas [/ASCII85Decode /FlateDecode], neša lygiagretų parametrų masyvą, o predictor nustatymai priklauso paskutiniam filtrui, ne pirmam

var
  Pdf: THotPDF;
  Info: THPDFLoadedImageInfo;
  Bmp: TBitmap;
  I: Integer;
begin
  Pdf := THotPDF.Create(nil);
  try
    if Pdf.LoadFromFile('scanned.pdf', '') > 0 then
      for I := 0 to Pdf.GetLoadedImageCount - 1 do
        if Pdf.GetLoadedImageInfo(I, Info) then
        begin
          Bmp := Pdf.ExtractLoadedImage(I);   // nil when the raster is unusable
          if Bmp <> nil then
          try
            Bmp.SaveToFile(Format('image-%d.bmp', [I]));
          finally
            Bmp.Free;
          end;
        end;
  finally
    Pdf.Free;
  end;
end;

Vienas istorinis defektas tame kelyje vertas paminėti, nes klaidų klasė kartojasi. Senoji Flate-su-parametrais rutina sukurdavo dekompresijos srautą, o tada kopijuodavo iš originalios suspaustos įvesties, todėl predictor etapas gaudavo suspaustus baitus ir sąžiningai juos "atprognozavo": visada neteisingai, niekada neiškeliant klaidos. Dabartinis kodas skaito tik iš dekoderio prieš perduodamas rezultatą bendram predictor'iui, ir atmeta raster'ą, trumpesnį nei apskaičiuotas dydis, o ne grįžta prie vis dar suspaustų baitų — atsarginis variantas, kuris anksčiau paversdavo dekodavimo klaidą sugadintu bitmap'u. Tas pats predictor realizacija dabar aptarnauja ir kryžminės nuorodos srautus, kas yra naudingas nuoseklumas, jei taip pat dirbate su objektų srautais ir laipsniškais atnaujinimais, o aplinkinė ištraukimo mechanika aprašyta gretimame straipsnyje apie įkeltų vaizdų ir jų dekodavimo filtrų ištraukimą. Vaizdai, atkeliaujantys kaip DCTDecode ar JPXDecode, niekada nepasiekia predictor'io; jie neša savo pačių suspaustą pikselių modelį

Pralaidumas: vientisa frazių arena prieš atskirus įrašus

Atskiro įrašo eilučių žodyno pakeitimas vientisa frazių arena pamatuotas maždaug 1,61 karto greitesnis su patologine įvestimi: 1558 MiB/s prieš 969 MiB/s, testuojant su testu, kurio viena ilgiausia frazė pasiekia 7 370 880 baitų. Tos įvesties forma paaiškina atotrūkį, nes klasikinės realizacijos pasirenka vieną iš dviejų blogų mainų. AnsiString reikšmių žodynas paskirsto ir kopijuoja šviežią eilutę kiekvienam iš iki 4096 įrašų, kiekvienam naujam įrašui kopijuojant visą savo tėvą; prefikso/sufikso dėklas to atminties visiškai išvengia, tačiau atkuria kiekvieną frazę vaikščiodamas grandine atgal baitas po baito ir ją apversdamas, kas gerai įprastam tekstui ir skausminga, kai viena frazė nusidriekia iki megabaitų. HotPDF prideda kiekvieną frazę vientisai prie geometriškai augančios arenos, indeksuoja įrašus pagal poslinkį ir ilgį, ir išleidžia frazę vienu Move į išvesties buferį. Sąžininga kaina yra atmintis: arena, laikanti kiekvieną frazę pilnai, ribojama visų frazių ilgių suma, o ne įrašų skaičiumi, ir būtent todėl MaxOutputBytes egzistuoja tiek dekompresoriuje, tiek predictor'yje. Išveskite tą ribą iš to, ką vaizdo žodynas tvirtina raster'ui turint būti, ir meluojantis srautas žlunga greitai

LZW dekompresorius, bendras predictor'is ir įkelto vaizdo ištraukimo kelias, parodyti čia, pristatomi standartiniame HotPDF Component, skirtame Delphi ir C++Builder, su pilna filtrų ir DecodeParms nuoroda produkto puslapyje