PDFlibPas pataisė dvi nepriklausomas klaidas savo natyviame JBIG2 halftone srities dekoduotojuje: v3.539.37 HSKIP praleidimo kaukė indeksuojama kaip HSKIP[ng, mg], kaip apibrėžia ITU-T T.88 §6.6.5.1, o v3.539.38 tinkleliai, pasiekiantys neigiamas koordinates – per neigiamą HGX ar HGY arba per pasukimą – išdėstomi tikru floor poslinkiu. Iki tų leidimų paveiktos halftone sritys išeidavo susimaišiusios arba pasislinkusios, nekeliant jokios klaidos. Abi klaidos slėpėsi už testinių duomenų, kurie atsitiktinai buvo simetriški ar neneigiami, o antroji užsidega nuo Delphi ir Free Pascal savybės, kandžiojančios gerokai už JBIG2 ribų: shr su ženklintu sveikuoju yra loginis poslinkis, o ne aritmetinis >>, kurį standartas suponuoja
Halftone sritys – rečiausias JBIG2 sričių tipas, tad dekoduotojas gali apdoroti tūkstančius skenuotų dokumentų, kol sutinka kaip tokią užkoduotą rastruotą nuotrauką. Kai sutinka, gedimas bjaurus: failas išanalizuotas, segmentų ilgiai susideda, puslapis teisingo dydžio, o sritis – šlamštas
Ką JBIG2 halftone sritis iš tiesų dekoduoja?
JBIG2 halftone sritis yra mažų bitmapų tinklelis, paimtų iš rašto žodyno, o tikrasis dekoduotojo darbas – apskaičiuoti indeksą kiekvienam tinklelio langeliui ir pikselio poziciją, kurioje tas langelis atsiduria. Rašto žodynas laiko HNUMPATS raštų, po HPW × HPH pikselių. Halftone srities segmentas tada aprašo HGW stulpelių į HGH eilučių tinklelį ir tokio pat dydžio pilkio skalės atvaizdą, užkoduotą kaip Gray koduoti bitplokščiai. Kiekvienas bitplokštis dekoduojamas bendrosios srities procedūra ant HGW × HGH bitmapo, reikšmingiausias plokštis pirmiausia, o plokščiai kartu kiekvienam langeliui duoda jo rašto indeksą
Langelio išdėstymas naudoja fiksuoto kablelio aritmetiką su 8 bitų trupmena. Tinklelio pradžia HGX, HGY yra 32 bitų reikšmių pora, o tinklelio vektorius HRX, HRY aprašo žingsnį tarp gretimų langelių, kas leidžia pasuktą tinklelį. Tinklelio eilutei mg ir tinklelio stulpeliui ng T.88 §6.6.5 pikselio poziciją skaičiuoja taip:
x = (HGX + mg × HRY + ng × HRX) >> 8y = (HGY + mg × HRX − ng × HRY) >> 8
Praleidimo kaukė įeina per pasirinktinę HENABLESKIP vėliavą. Kai vėliava numatyta, §6.6.5.1 sukuria HGW × HGH bitmapą HSKIP ir nustato HSKIP[ng, mg] į 1 kiekvienam langeliui, kurio raštas srito visai už srities ribų: x + HPW <= 0, x >= HBW, y + HPH <= 0 arba y >= HBH. Pilkio skalės bitplokščiai tada dekoduojami su ta kauke kaip bendrosios srities praleidimo bitmapas, tad aritmetinis dekuduotojas praleistam langeliui nei skaito, nei atnaujina konteksto. Dekoduotojas ir koduotojas privalo sutapti kiekvienam HSKIP bitui, kitaip du aritmetiniai koduotojai išsiderina
Kodėl permesta HSKIP kaukė lūždavo tik nelygiakraščiuose tinkleliuose?
Praleidimo kaukė buvo rašoma su sukeistomis koordinatėmis, ir atskirdavo ją tik nelygiakraštis tinklelis, nes lygiakraštis tinklelis kiekvieną sukeistą koordinatę laiko kaukės viduje. PDFlibPas bitmapus saugo su (column, row) pikselių prieiga, o kaukę kūręs kodas perdavinėjo (mg, ng) – eilute pirmiausia. Pilkio skalės bitplokščių dekoduotojas kaukę skaito teisingai kaip (ng, mg). Raštų išdėstymo ciklas ją skaitydavo atgal kūrėjo sukeista tvarka, tad abu sutapdavo, ir vien išdėstymo logikos peržiūra ją praleistų. Pavadinimų spąstai dar pablogino: išdėstymo cikle kintamasis, vadinamas col, apeina tinklelio eilutes, o Row – tinklelio stulpelius
Imkime 5 × 3 tinklelį iš 4 × 4 raštų ant 16 × 8 srities, kurį v3.539.37 naudoja kaip regresijos atvejį. Su HRX = 1024 ir HRY = 0 tinklelio stulpelis 4 atkrenta ties x = 16, o tinklelio eilutė 2 – ties y = 8, abu už srities. Teisinga kaukė pažymi septynis langelius: visą 4 stulpelį ir visą 2 eilutę. Sukeisti rašymai bandė nustatyti pikselius 3 ir 4 eilučių indeksuose kaukėje, vos trijų eilučių aukščio, o bitmapo nustatytojas tuos už ribų einančius rašymus tylioje ignoravo. Išgyveno 2 stulpelis, 0–2 eilutės. Dekoduotojas todėl praleido du langelius, kuriuos koduotojas buvo užkodavęs, ir dekodavo šešis, kuriuos koduotojas buvo praleidęs
Aritmetinis dekoduotojas tuo metu nesugriūva. Jis dekoduoja papildomus pikselius iš bitų, priklausančių vėlesniems langeliams, jo kontekstai skaito netinkamus kaimynus, o kiekvienas rašto indeksas po pirmojo nesutapimo yra triukšmas – todėl simptomas buvo supainiota sritis, o ne keli pasislinkę langeliai. Lygiakraščiame tinklelyje ta pati klaida dažnai nematoma: joks sukeistas koordinatas nepalieka kaukės, o kai už srities esantys langeliai simetriški įstrižainei – pavyzdžiui, tinklelis, dešinę ir apačios kraštines viršijantis vienodu langelių skaičiumi – permesta kaukė bitas po bito yra teisingoji. HENABLESKIP dar ir pasirinktinė, privalo būti 0, kai pilkio skalės atvaizdas koduotas MMR, ir koduotojų retai numatoma, tad klaida turėjo labai nedaug būdų iškilti. Nuo v3.539.37 kūrėjas rašo HSKIP[ng, mg], o išdėstymo ciklas skaito tą pačią tvarką
Kodėl neigiami halftone tinklelio poslinkiai krinta trimis sluoksniais?
Halftone tinklelis, prasidedantis kairiau ar aukščiau savos srities, PDFlibPas lūždavo trijose atskirose vietose, ir kiekviena klaida slėpdavo sekančią. T.88 tokią geometriją leidžia sąmoningai. Koduotojas, kuris savą rastrą sulygiuoja su puslapiu, o ne su sritimi, arba naudoja pasuktą tinklelį, natūraliai pagamina neigiamus langelių kampus, kuriuos srita apkarpo. v3.539.38 pataisė visus tris sluoksnius kartu, nes bet kurio vieno taisymas keistų tik simptomą
1 sluoksnis: ženklintas laukas skaitomas kaip neženklintas
T.88 §7.4.5.1.2 apibrėžia HGX ir HGY kaip ženklintas 32 bitų reikšmes, bet dekoduotojas jas skaitė ta pačia 32 bitų pagalbine funkcija, kuria naudodavosi neženklintiems laukams, o ta funkcija kiekvieną neigiamą rezultatą nukirpdavo iki 0. Tinklelis, turėjęs prasidėti ties HGX = -900, tylioje buvo perkeliamas ant srities pradžios. v3.539.38 regresijos atveju visas atvaizdas išeidavo dviem eilutėmis per žemai. Apkirpimas dar ir paaiškina, kodėl kitos dvi klaidos išgyveno taip ilgai: kilmei priverstinai esant neneigiamai, neigiama koordinatė galėjo atsirasti tik per pasuktą tinklelį su HRY > 0, kur y = HGY + mg × HRX − ng × HRY vėlesniems tinklelio stulpeliams nukrinta žemiau nulio
2 sluoksnis: shr nėra >> 8
T.88 rašo >> 8 ir turi galvoje aritmetinį poslinkį, kuris apvalina link minus begalybės. Dekoduotojas jį išvertė kaip shr 8. Delphi ir Free Pascal shr su ženklintu sveikuoju yra loginis poslinkis: ženklo bitas įstumiamas kaip nulis. Integer, laikančiam -512, shr 8 duoda 16777214 vietoj -2. Raštas, kuris turėjo būti nupieštas ties y = -2 ir apkirptas iki apatinės pusės, buvo nusiųstas 16 milijonų eilučių žemyn ir numestas kaip už srities. Niekas nesugriuvo; viršutinė halftone eilutė tiesiog dingo
3 sluoksnis: lyginama fiksuotas kablelis vietoj pikselių
Praleidimo testas lygino fiksuoto kablelio reikšmes, o ne pikselių pozicijas, ir šios nėra lygiavertės, kai tik trupmena nenulinė. Pradinis kodas loginio poslinkio išvengdavo tikrindamas xx + HPW × 256 <= 0 ant nepastumtos reikšmės – tariamai T.88 testo atitikmuo. Su HGX = -900 ir 4 pikselių raštu tai duoda -900 + 1024 = 124 – teigiama, tad langelis neskleidžiamas. Standartas spaudžia pirmiausia: floor(-900 / 256) = -4, o -4 + 4 = 0 tenkina x + HPW <= 0, tad langelis slypi visai už ribų ir privalo būti praleistas. Koduotojas jį praleido, dekoduotojas dekodavo, ir pilkio skalės atvaizdas pasislinkdavo lygiai kaip permestos kaukės atveju
v3.539.38 regresijos atvejis naudoja 4 × 3 tinklelį iš 4 × 4 raštų ties HGX = -900, HGY = -512, HRX = 1024 ant 12 × 10 srities. Tinklelio stulpeliai atkrenta ties x = -4, 0, 4 ir 8, tad 0 stulpelis visai už ribų ir priklauso HSKIP; tinklelio eilutės atkrenta ties y = -2, 2 ir 6, tad 0 eilutę reikia apkirpti iki jos apatinių dviejų pikselių eilučių, o ne numesti. Sluoksnius taisant po vieną atkuriamas toks laiptas:
| Pataisytos klaidos | Dekoduota sritis |
|---|---|
| Nė viena (iki v3.539.38) | Tinklelis pritemptas prie pradžios, visas atvaizdas dviem eilutėmis per žemai |
| Tik ženklintas HGX / HGY skaitymas | Pirmosios tinklelio eilutės nėra, kita maišosi dėl praleidimo testo dreifo |
| Ženklintas skaitymas, floor poslinkis ir pikselių erdvės praleidimo testas | Identiška, pikselis po pikselio, puslapiui, apskaičiuotam iš T.88 §6.6.5, ir dviem nepriklausomiems atskaitiniams dekoduotojams |
Taisymas – viena pagalbinė funkcija, HalftoneGridPixel, bendra praleidimo kaukės kūrėjui ir išdėstymo ciklui. Ji koordinatę kaupia Int64, tad didelė mg × HRX sandauga negali perpilti, dalija iš 256 apvalindama link minus begalybės ir kirpsta iki ±MaxInt div 2, kad sugadintas tinklelis negalėtų perpilti vėlesnės bitmapo aritmetikos. Praleidimo testas dabar tas pikselių reikšmes lygina su HPW, HPH, HBW ir HBH – lygiai kaip teigia §6.6.5.1
Kaip Delphi parašyti aritmetinį poslinkį dešinėn?
Delphi neturi aritmetinio poslinkio operatoriaus, tad teisingas ženklintas poslinkis dešinėn turi būti parašytas kaip floor dalyba, o paprastas div ta dalyba nėra. div nukerpa link nulio. Neneigiamoms reikšmėms nukarpymas ir floor sutampa, ir jie sutampa neigiamoms reikšmėms, kurios yra tikslūs daliklio kartotiniai – todėl -512 div 256 = -2 greitame teste atrodo gerai. Kitur jie išsiskiria: -900 div 256 yra -3, o floor – -4, -1 div 256 yra 0, o floor – -1. JBIG2 koordinatė su nenuline trupmena – būtent tas atvejis, kai div duoda netinkamą pikselį
Delphi Win32 ir Win64 kompiliatoriuose Integer kintamasis, laikantis -512, pastumtas dešinėn per 8, duoda 16777214, o Int64 su -512 duoda 72057594037927934. Free Pascal shr taip pat apibrėžia kaip loginį poslinkį, o savo System unitas prie jo prideda SarLongint ir SarInt64 aritmetinei versijai, bet tų funkcijų Delphi nėra, tad kodas, bendras abiem kompiliatoriams, reikalauja savos pagalbinės funkcijos:
// Floor dalyba: apvalina link minus begalybės bet kokiam A ir B ženklui.
// B negali būti 0, o FloorDiv(Low(Integer), -1) perpildo taip pat kaip div
function FloorDiv(A, B: Integer): Integer;
begin
Result := A div B;
if (A mod B <> 0) and ((A < 0) <> (B < 0)) then
Dec(Result);
end;
// Aritmetinis poslinkis dešinėn (C ir T.88 ">>" su ženklintomis reikšmėmis).
// Neigiamam Value not Value = -Value - 1 yra neneigiamas, tad ten
// loginis shr saugus, o išorinis not rezultatą sugrąžina atgal
function SarInt32(Value: Integer; Shift: Integer): Integer; // Shift 0..31
begin
if Value >= 0 then
Result := Value shr Shift
else
Result := not ((not Value) shr Shift);
end;
function SarInt64(Value: Int64; Shift: Integer): Int64; // Shift 0..63
begin
if Value >= 0 then
Result := Value shr Shift
else
Result := not ((not Value) shr Shift);
end;
not gudrybė niekada nepastumia neigiamo skaičiaus, todėl nepriklauso nuo to, kaip kompiliatorius elgiasi su ženklo bitu, ir niekada nepripildo, įskaitant Low(Integer). Abu pagalbininkai sutapo su Int64 floor atskaita per kelis milijonus reikšmių, kiekvieną poslinkį nuo 0 iki 31 bei Low(Integer) ir High(Integer) kraštus Delphi Win32, Delphi Win64 ir Free Pascal x86_64. Sąžiningumo patikra, kurios verta laikytis bet kokiame vienetų teste, liečiančiame koordinates:
var
V: Integer;
begin
V := -900;
Writeln(V shr 8); // 16777212 loginis poslinkis, senoji klaida
Writeln(V div 256); // -3 nukarpymas link nulio
Writeln(FloorDiv(V, 256)); // -4 ką T.88 reiškia >> 8
Writeln(SarInt32(V, 8)); // -4
end;
Math.Floor(V / 256) irgi grąžina -4, bet jos užanka per Double praranda tikslumą Int64 reikšmėms, viršijančioms 253, tad sveikojo skaičiaus geometrija turėtų likti sveikuosiuose
Kurie PDFlibPas kvietimai paleidžia halftone dekoduotoją?
JBIG2 halftone dekoduotojas paleidžiamas, kai PDFlibPas atvaizduoja puslapį su vidiniu atvaizduokliu, nes atvaizdavimui reikia pikselių. RenderPageToFile ir RenderPageToStream abu pas jį ateina per puslapio JBIG2Decode paveikslėlių srautus, tad halftone puslapio pakartotinis atvaizdavimas – tiesiausias būdas patvirtinti, kad v3.539.38 keičia jūsų išvestį. Tas pats dekoduotojas aptarnauja ir kitus JBIG2 sričių tipus, aprašytus JBIG2 pasirinktinėse Huffman lentelėse gryname Pascal dekoduotojuje ir atsitiktinės prieigos JBIG2 failų dekodavime Delphi, o atvaizduotas bitmapas maitina konversijas, kaip PDF puslapių atvaizdavimą į 1 bitų monochromą
uses
SysUtils, PDFlibrary;
var
Lib: TPDFlib;
Page: Integer;
begin
Lib := TPDFlib.Create;
try
if Lib.LoadFromFile('scanned-halftone.pdf', '') <> 1 then
raise Exception.CreateFmt('Load failed, error %d', [Lib.LastErrorCode]);
for Page := 1 to Lib.PageCount do
// Atvaizdavimas dekoduoja kiekvieną JBIG2 sritį, įskaitant halftone
if Lib.RenderPageToFile(150, Page, PDF_RENDER_PNG,
Format('page-%.3d.png', [Page])) <> 1 then
Writeln('Page ', Page, ' was not rendered');
finally
Lib.Free;
end;
end.
Paveikslėlių ištraukimas paprastai eina kitu keliu. GetPageImageList grąžina JBIG2 paveikslėlius natyvia forma, o SaveImageListItemDataToFile arba GetImageListItemDataToString atiduoda atskirą JBIG2 failą, sudėtą iš srauto baitų: failo antraštės, JBIG2Globals duomenų ir end-of-file segmento aplink puslapio duomenis. 400 savybė iš GetImageListItemIntProperty tokiam elementui praneša 6. Šis kelias nieko nedekoduoja, tad ištrauktas .jb2, kuris kitoje peržiūroje atrodo teisingai, o atvaizduotas puslapis rodo triukšmą, buvo būdingas šių dviejų halftone klaidų ženklas:
var
ListID, I: Integer;
begin
Lib.SelectPage(1);
ListID := Lib.GetPageImageList(0);
if ListID = 0 then
Exit;
try
for I := 1 to Lib.GetImageListCount(ListID) do
if Lib.GetImageListItemIntProperty(ListID, I, 400) = 6 then // atskirasis JBIG2
Lib.SaveImageListItemDataToFile(ListID, I, 0,
Format('page1-image%d.jb2', [I]));
finally
Lib.ReleaseImageList(ListID);
end;
end;
Kai kaukės ar spalvų konversija priverčia atvaizduotą atsarginį kelią, elementas grįžta kaip dekoduotas bitmapas, ir halftone dekoduotojas tada tikrai paleidžiamas. Daugiau apie paveikslėlių sąrašus – Delphi PDF teksto, paveikslėlių ir šriftų ištraukime
Trumpa atmintinė: JBIG2 halftone tinklelio taisyklės
- Indeksuokite praleidimo kaukę kaip
HSKIP[ng, mg]– tinklelio stulpelis pirmiausia – ir skaitykite ta pačia tvarka visur, kur išdėstomi langeliai (T.88 §6.6.5.1; pataisyta PDFlibPas v3.539.37) - Bet kokį halftone ar tinklelio kodą tikrinkite su nelygiakraščiu tinkleliu ir nesimetriška už srities esančių langelių aibe, nes lygiakraštis tinklelis gali visiškai paslėpti permestą indeksą
HGXirHGYskaitykite kaip ženklintas 32 bitų reikšmes (T.88 §7.4.5.1.2), niekada per neženklintą pagalbinę funkciją, kirpstančią neigiamuosius- Standarto
>> 8verskite kaip floor dalybą iš 256, ne kaipshr 8ir ne kaipdiv 256 - Praleidimo testą paleiskite ant pastumtų pikselių pozicijų; fiksuoto kablelio forma skiriasi, kai tik trupmena nenulinė, kaip rodo
HGX = -900su 4 pikselių raštu - Tinklelio koordinates kaupkite
Int64ir kirpkite, prieš atiduodami bitmapo kodui, kad sugadintas tinklelis negalėtų perpilti - Atnaujinkite iki v3.539.38 ar vėlesnės, jeigu jūsų dokumentuose yra halftone sritys su
HENABLESKIP, neigiamomis tinklelio kilmėmis ar pasuktais tinkleliais
PDFlibPas atvaizduoja, ištraukia ir redaguoja PDF dokumentus iš Delphi ir C++Builder su natyviu Pascal JBIG2 dekoduotoju, kuris dabar pagal T.88 susitvarko su halftone praleidimo kaukėmis, neigiamomis tinklelio kilmėmis ir pasuktais tinkleliais. Funkcijoms, leidimams ir bandomajam parsisiuntimui žiūrėkite PDFlibPas Delphi PDF biblioteką