PDFlibPas, native JBIG2 halftone bölge decoder'ındaki iki bağımsız hatayı düzeltti: v3.539.37'de HSKIP skip maskesi, ITU-T T.88 §6.6.5.1'in tanımladığı gibi HSKIP[ng, mg] olarak indeksleniyor; v3.539.38'de ise negatif HGX ya da HGY ya da rotasyon yoluyla negatif koordinatlara ulaşan grid'ler gerçek bir floor kaydırmasıyla yerleştiriliyor. O sürümlerden önce etkilenen halftone bölgeleri hiçbir hata fırlatmadan bozuk ya da yerinden oynmuş çıkıyordu. İki hata da tesadüfen simetrik ya da negatif olmayan test verisinin arkasına saklanmıştı ve ikincisi, JBIG2'nin çok ötesini ısıran bir Delphi ve Free Pascal özelliğine dayanır: işaretli bir integer üzerinde shr mantıksal kaydırmadır, standardın varsaydığı aritmetik >> değil
Halftone bölgeleri en nadir JBIG2 bölge tipidir; dolayısıyla bir decoder, biri olarak kodlanmış benekli bir fotoğrafa rastlamadan önce binlerce taranmış belge işleyebilir. Rastladığında başarısızlık çirkindir: dosya ayrışır, segment uzunlukları toplanır, sayfa doğru boyuttadır ve bölge çöptür
Bir JBIG2 halftone bölgesi aslında neyi çözer?
Bir JBIG2 halftone bölgesi, bir pattern dictionary'den seçilen küçük bitmap'lerden oluşan bir grid'dir ve decoder'ın asıl işi her grid hücresi için bir indeks ile o hücrenin düştüğü pixel konumunu hesaplamaktır. Pattern dictionary HPW × HPH pixel boyutunda HNUMPATS desen tutar. Halftone bölge segmenti sonra HGW sütun × HGH satırlık bir grid'i ve aynı boyutta, Gray-kodlu bitplane'ler olarak kodlanmış gri tonlamalı bir image'i tanımlar. Her bitplane, en anlamlı plane önce olacak biçimde HGW × HGH bitmap üzerinde generic region prosedürüyle çözülür ve plane'ler birlikte her hücreye pattern indeksini verir
Hücre yerleşimi 8 bitlik kesirli sabit noktalı aritmetik kullanır. Grid kökeni HGX, HGY 32 bitlik bir değer çiftidir ve grid vektörü HRX, HRY komşu hücreler arasındaki adımı tanımlar; bu da döndürülmüş bir grid'e izin verir. Grid satırı mg ve grid sütunu ng için T.88 §6.6.5 pixel konumunu şöyle hesaplar:
x = (HGX + mg × HRY + ng × HRX) >> 8y = (HGY + mg × HRX − ng × HRY) >> 8
Skip maskesi isteğe bağlı HENABLESKIP bayrağıyla girer. Bayrak setliyken §6.6.5.1, HGW × HGH boyutlu bir HSKIP bitmap'i kurar ve deseni bölgenin tamamen dışında duran her hücre için HSKIP[ng, mg]'i 1 set eder: x + HPW <= 0, x >= HBW, y + HPH <= 0 ya da y >= HBH. Gri tonlamalı bitplane'ler sonra o maskeyle, generic region skip bitmap'i olarak çözülür; böylece aritmetik decoder atlanan bir hücre için ne context okur ne günceller. Decoder ile encoder HSKIP'in her bitinde hemfikir olmalıdır, yoksa iki aritmetik coder uyumdan düşer
Yer değiştirmiş bir HSKIP maskesi neden yalnızca kare olmayan grid'leri bozdu?
Skip maskesi koordinatları yer değiştirmiş yazılıyordu ve yalnızca kare olmayan bir grid onu ortaya çıkardı; kare bir grid her değişmiş koordinatı maske içinde tutuyor. PDFlibPas bitmap'leri bir (column, row) pixel erişicisiyle saklar ve maskesi kuran kod (mg, ng) geçiyordu, önce satır. Gri tonlamalı bitplane decoder'ı maskeyi (ng, mg) olarak doğru okur. Pattern yerleştirme döngüsü ise onu kurucunun değişmiş sırasında geri okuyordu; yani ikisi hemfikirdi ve yerleştirme mantığının tek başına bir gözden geçirmesi onu geçerdi. Bir adlandırma tuzağı işi daha da kötüleştirdi: yerleştirme döngüsünde col adlı değişken grid satırlarını, Row ise grid sütunlarını geziniyor
v3.539.37'nin regresyon olgusu olarak kullandığı 16 × 8 bölge üzerindeki 4 × 4 desenli 5 × 3 grid'i alın. HRX = 1024 ile HRY = 0 iken grid sütunu 4 x = 16'ya, grid satırı 2 y = 8'e düşer; ikisi de bölge dışıdır. Doğru maske yedi hücre işaretler: 4. sütunun tamamı ve 2. satırın tamamı. Değişmiş yazımlar, yalnızca üç satır yüksekliğindeki bir maskede 3 ve 4 satır indekslerine pixel set etmeye çalışıyordu ve bitmap setter'ı o menzil dışı yazımları sessizce yok sayıyordu. Sağ kalan 2. sütun, 0-2. satırlardı. Decoder bu yüzden encoder'ın kodladığı iki hücreyi atlıyor, encoder'ın attığı altı hücreyi çözüyordu
Aritmetik decoder bu olduğunda başarısız olmaz. Sonraki hücrelere ait bitlerden fazla pixel çözüyor, context'leri yanlış komşular okuyor ve ilk uyuşmazlıktan sonraki her pattern indeks gürültüdür; belirti birkaç yanlış yerleştirilmiş hücre değil bozuk bir bölgeydi, çünkü. Kare bir grid'de aynı hata çoğu zaman görünmezdir: hiçbir değişmiş koordinat maskeden çıkmaz ve bölge dışı hücreler köşegene göre simetrikken — örneğin sağ ve alt kenardan aynı sayıda hücre taşan bir grid — değişmiş maske birebir doğru maskenin kendisidir. HENABLESKIP ayrıca isteğe bağlıdır, gri tonlamalı image MMR-kodlu iken 0 olmalıdır ve encoder'lar tarafından nadiren set edilir; hatanın yüzeye çıkma yolu çok azdı. v3.539.37'den beri kurucu HSKIP[ng, mg] yazıyor ve yerleştirme döngüsü aynı sırayı okuyor
Negatif halftone grid ofsetleri neden üç katmanda başarısız olur?
Bölgesinin solundan ya da üstünden başlayan bir halftone grid'i PDFlibPas'ı üç ayrı yerde kırdı ve her hata bir sonrakini sakladı. T.88 bu geometriye bilerek izin verir. Ekranını bölgeye değil sayfaya hizalayan ya da döndürülmüş grid kullanan bir encoder, bölgenin kırptığı negatif hücre köşelerini doğal olarak üretir. v3.539.38 üç katmanı birlikte düzeltti; çünkü herhangi birini tek başına düzeltmek yalnızca belirtiyi değiştiriyordu
Katman 1: işaretsiz okunan işaretli alan
T.88 §7.4.5.1.2, HGX ile HGY'yi işaretli 32 bitlik değerler olarak tanımlar ama decoder onları işaretsiz alanlar için kullandığı aynı 32 bitlik yardımcıyla okuyordu ve o yardımcı her negatif sonucu 0'a sabitliyordu. HGX = -900'de başlaması gereken bir grid sessizce bölge kökenine çekiliyordu. v3.539.38 regresyon olgusunda bütün resim iki satır fazla aşağı çıkıyordu. Sabitleme, diğer iki hatanın neden bu kadar uzun yaşadığını da açıklar: köken negatif olmaya zorlanınca negatif bir koordinat yalnızca HRY > 0 ile döndürülmüş bir grid üzerinden görünebilirdi; orada y = HGY + mg × HRX − ng × HRY sonraki grid sütunlarında sıfırın altına düşer
Katman 2: shr, >> 8 değildir
T.88 >> 8 yazar ve eksi sonsuza yuvarlayan bir aritmetik kaydırma kasteder. Decoder bunu shr 8'e çevirdi. Delphi ve Free Pascal'da işaretli bir integer üzerinde shr mantıksal kaydırmadır: işaret biti sıfır olarak içeri kaydırılır. -512 tutan bir Integer için shr 8, -2 yerine 16777214 verir. y = -2'de çizilip alt yarısına kırpması gereken bir desen 16 milyon satır aşağı gönderiliyor ve bölge dışı diye atılıyordu. Hiçbir şey çökmedi; halftone'un üst satırı sade bir şekilde kayboldu
Katman 3: pixel yerine sabit noktalı karşılaştırma
Skip testi sabit noktalı değerleri karşılaştırıyordu, pixel konumlarını değil; kesir sıfırdan farklı olduğu anda ikisi denk değildir. Özgün kod mantıksal kaydırmayı, kaydırılmamış değer üzerinde xx + HPW × 256 <= 0 sınamakla aşıyordu — T.88 testinin sözde eşdeğeri. HGX = -900 ve 4 pixellik bir desenle bu -900 + 1024 = 124 verir; pozitiftir, dolayısıyla hücre atlanmıyor. Standart önce kaydırır: floor(-900 / 256) = -4 ve -4 + 4 = 0, x + HPW <= 0'ı karşılar; hücre tamamen dışarıdadır ve atlanmalıdır. Encoder atlıyor, decoder çözüyor ve gri tonlamalı image, değişmiş maske olgusundaki gibi tam olarak sürükleniyordu
v3.539.38'in regresyon olgusu, 12 × 10 bölge üzerinde HGX = -900, HGY = -512, HRX = 1024 konumlarında 4 × 4 desenli 4 × 3 bir grid kullanır. Grid sütunları x = -4, 0, 4 ve 8'e düşer; 0. sütun tamamen dışarıdadır ve HSKIP'e aittir. Grid satırları y = -2, 2 ve 6'ya düşer; 0. satır atılmak yerine alt iki pixel satırına kırpmalıdır. Katmanları tek tek düzeltmek yığını yeniden üretir:
| Düzeltilen hatalar | Çözülen bölge |
|---|---|
| Hiçbiri (v3.539.38 öncesi) | Grid kökene çekildi, bütün resim iki satır fazla aşağıda |
| Yalnızca işaretli HGX / HGY okuması | İlk grid satırı eksik, gerisi skip testi sürüklenmesiyle bozuk |
| İşaretli okuma, floor kaydırması ve pixel uzayı skip testi | T.88 §6.6.5'ten hesaplanan sayfayla ve iki bağımsız referans decoder'ıyla pixel pixel aynı |
Düzeltme tek bir yardımcıdır: skip-maskesi kurucusu ile yerleştirme döngüsünün paylaştığı HalftoneGridPixel. Koordinatı Int64'te biriktirir; böylece büyük bir mg × HRX çarpımı taşamaz, 256'ya eksi sonsuza yuvarlayarak böler ve ±MaxInt div 2'ye sabitler; böylece bozuk bir grid sonraki bitmap aritmetiğini taşıramaz. Skip testi artık o pixel değerlerini HPW, HPH, HBW ve HBH ile karşılaştırır, tam olarak §6.6.5.1'in söylediği gibi
Delphi'de aritmetik sağa kaydırma nasıl yazılır?
Delphi'nin aritmetik kaydırma operatörü yoktur; dolayısıyla doğru bir işaretli sağa kaydırma floor bölmesi olarak yazılmalıdır ve sade div o bölme değildir. div sıfıra doğru keser. Negatif olmayan değerler için kesme ile floor hemfikirdir ve bölenin tam katı olan negatif değerler için de hemfikirdir; -512 div 256 = -2'nin hızlı bir testte iyi görünmesi bu yüzden. Başka her yerde anlaşamazlar: -900 div 256 -3'tür, floor ise -4'tür; -1 div 256 0'dır, floor ise -1'dir. Sıfırdan farklı kesirli bir JBIG2 koordinatı, div'in yanlış pixeli verdiği tam olarak şu olgudur
Delphi Win32 ve Win64 derleyicilerinde -512 tutan bir Integer değişkeni 8 sağa kaydırıldığında 16777214, -512 tutan bir Int64 ise 72057594037927934 verir. Free Pascal da shr'yi mantıksal kaydırma olarak tanımlar ve aritmetik sürüm için System unitinde SarLongint ile SarInt64'ü kargolar; ama o fonksiyonlar Delphi'de yoktur, dolayısıyla iki derleyici arasında paylaşılan kodun kendi yardımcısına ihtiyacı vardır:
// Floor division: A ve B'nin her işareti için eksi sonsuza yuvarlar.
// B 0 olmamalı; FloorDiv(Low(Integer), -1) tıpkı div gibi taşar
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;
// Aritmetik sağa kaydırma (C ve T.88'in işaretli değerlerdeki ">>"i).
// Negatif Value için not Value = -Value - 1 negatif değildir; mantıksal
// shr orada güvenlidir ve dıştaki not sonucu geri eşler
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 hilesi asla negatif bir sayıyı kaydırmaz; derleyicinin işaret bitine nasıl davrandığına bağlı değildir ve Low(Integer) dahil hiç taşmaz. İki yardımcı da birkaç milyon değer üzerinde bir Int64 floor referansıyla, 0'dan 31'e her kaydırma ile ve Low(Integer) ile High(Integer) kenarlarında Delphi Win32, Delphi Win64 ve Free Pascal x86_64 üzerinde eşleşti. Koordinatlara dokunan her unit testte tutmaya değer bir sağlamlık sınaması:
var
V: Integer;
begin
V := -900;
Writeln(V shr 8); // 16777212 mantıksal kaydırma, eski hata
Writeln(V div 256); // -3 sıfıra doğru kesme
Writeln(FloorDiv(V, 256)); // -4 T.88'in >> 8 ile kastettiği
Writeln(SarInt32(V, 8)); // -4
end;
Math.Floor(V / 256) da -4 döndürür ama Double üzerinden aldığı dolan yolu, 253 üzerindeki Int64 değerlerinde hassasiyet kaybeder; integer geometri integerlarda kalmalıdır
Hangi PDFlibPas çağrıları halftone decoder'ını çalıştırır?
JBIG2 halftone decoder'ı, PDFlibPas bir sayfayı gömülü renderer ile render ettiğinde çalışır; çünkü render pixel ister. RenderPageToFile ile RenderPageToStream ikisi de sayfanın JBIG2Decode image stream'leri üzerinden ona ulaşır; dolayısıyla bir halftone sayfasını yeniden render etmek, v3.539.38'in çıktınızı değiştirdiğini doğrulamanın doğrudan yoludur. Aynı decoder diğer JBIG2 bölge tiplerini de işler — saf Pascal decoder'ında JBIG2 custom Huffman tabloları ile Delphi'de random-access JBIG2 dosyalarının çözümünde kapsanır — ve render edilen bitmap, PDF sayfalarını 1-bit monochrome'a render etme gibi dönüşümleri besler
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
// Rendering her JBIG2 bölgesini çözer, halftone'lar dahil
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.
Image extraction normalde başka bir yol izler. GetPageImageList JBIG2 image'lerini native biçimde döndürür; SaveImageListItemDataToFile ya da GetImageListItemDataToString ise stream baytlarından kurulmuş bağımsız bir JBIG2 dosyası verir: dosya başlığı, JBIG2Globals verisi ve sayfa verisinin etrafında bir end-of-file segmenti. GetImageListItemIntProperty'nin 400 propertysi böyle bir girdi için 6 bildirir. O yolda hiçbir şey çözülmez; dolayısıyla render edilen sayfa gürültü gösterirken çıkarılan .jb2'nin başka bir görüntüleyicide doğru görünmesi bu iki halftone hatasının tipik işaretiydi:
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 // standalone JBIG2
Lib.SaveImageListItemDataToFile(ListID, I, 0,
Format('page1-image%d.jb2', [I]));
finally
Lib.ReleaseImageList(ListID);
end;
end;
Maskeler ya da renk dönüşümü render edilmiş bir yedek yol zorladığında girdi çözülmüş bir bitmap olarak döner ve halftone decoder'ı gerçekten çalışır. Image list'ler hakkında daha fazlası Delphi PDF text, image ve font extractiondadır
Hızlı başvuru: JBIG2 halftone grid kuralları
- Skip maskesini
HSKIP[ng, mg]olarak indeksleyin, önce grid sütunu, ve hücrelerin yerleştirildiği her yerde aynı sırayla geri okuyun (T.88 §6.6.5.1, PDFlibPas v3.539.37'de düzeltildi) - Her halftone ya da grid kodunu kare olmayan bir grid ile bölge dışı hücrelerin asimetrik bir kümesiyle test edin; kare bir grid, değişmiş bir indeksi bütünüyle saklayabilir
HGXileHGY'yi işaretli 32 bitlik değerler olarak okuyun (T.88 §7.4.5.1.2), negatifleri sabitleyen işaretsiz bir yardımcı üzerinden asla- Standardın
>> 8'ini 256'ya floor bölmesi olarak çevirin; neshr 8ne dediv 256olarak - Skip testini kaydırılmış pixel konumları üzerinde koşun; sabit noktalı biçim kesir sıfırdan farklı olduğunda farklıdır,
HGX = -900'in 4 pixellik desenle gösterdiği gibi - Grid koordinatlarını
Int64'te biriktirin ve bitmap koduna vermeden önce sabitleyin; bozuk bir grid taşıramasın - Belgeleriniz
HENABLESKIP'li, negatif grid kökenli ya da döndürülmüş grid'li halftone bölgeleri içeriyorsa v3.539.38 ve sonrasına yükseltin
PDFlibPas, halftone skip maskelerini, negatif grid kökenlerini ve döndürülmüş grid'leri artık T.88'in belirttiği gibi işleyen native Pascal bir JBIG2 decoder'ıyla Delphi ve C++Builder'dan PDF belgelerini render eder, çıkarır ve düzenler. Özellikler, sürümler ve deneme indirmesi için PDFlibPas Delphi PDF library'ye bakın