Elle yazılmış bir JBIG2 okuyucusunun alışılmadık ilk dosyada yaptığı hata şudur: dosya başlığını ayrıştırır, ilk segment başlığını ayrıştırır, dört baytlık veri uzunluğunu okur ve o kadar baytı segment gövdesi sayar. Çoğu .jb2 dosyasında bu tamamen doğrudur. Bayrak baytının 0. biti temizlenmiş bir dosyada ise o "gövde" baytları aslında ikinci, üçüncü ve dördüncü segment başlıklarıdır ve aritmetik çözücü onları gürültü dolu bir bit eşlemeye çevirir ya da tamponun sonuna atlar. Dosya bozuk değildir. ITU-T T.88 Annex D.2'deki random-access düzenini kullanır: her segment başlığı önce gelir, bütün segment verileri sonra, aynı sırayla
Random-access dosyalar nadirdir. Arşiv ve belge görüntüleme sistemlerinden çıkarlar; okuyucu sıkıştırılmış bir bayta dokunmadan önce her sayfayı ve sözlük bağımlılığını görsün isterler. Bir dönüştürme işi böyle bir kaynaktan gelen dosyaları sıradan taramalarla karıştırırsa, bir random-access dosya öbür yönüyle tamamen sıralı bir grubun ortasına düşebilir. Onları okuyan çözücü, JBIG2 özel Huffman tabloları ve kanonik önek kodları işini gören aynı yerel Pascal çözücüdür; kodlama tarafı değişmez, değişen yalnızca segmentlerin bulunma sırasıdır
Tek bir bayrak baytının ardındaki üç dosya düzeni
PDF Library for Delphi, bağımsız JBIG2 dosyalarını PDFlibJBIG2 birimindeki TPLJBIG2Decoder üzerinden okur ve çözücü düzeni dosya başlığından kendisi çıkarır. T.88 Annex D aynı segmentlerin üç türlü dizilişine izin verir: sequential (D.1) her başlığı doğrudan verisinin önüne koyar, random-access (D.2) bütün başlıkları öne, bütün verileri ardına alır, embedded (D.3) başka kapsayıcıların içinde kullanılan başlıksız biçimdir. Bağımsız bir dosya sekiz baytlık 97 4A 42 32 0D 0A 1A 0A tanıtıcısıyla başlar, ardından bir bayrak baytı, sonra — bayraklar sayının bilinmediğini söylemedikçe — dört baytlık sayfa sayısı gelir
Bayrak baytının 0. biti düzeni seçer: 1 sequential, 0 random-access demektir. 1. bit kuruluysa sayfa sayısı bilinmiyordur ve dört baytlık sayı yoktur; ilk segment başlığı bayt 13 yerine bayt 9'da başlar. 2'den 7'ye bitler rezervdir; çözücü, kurulu rezerv bitlerin segmentlerin kendisinin okunabilirliği hakkında hiçbir şey söylemediğini düşünerek onları kurmuş dosyaları reddetmek yerine kabul eder
PDF'in kendisi random-access düzenini hiç taşımaz. ISO 32000-1 §7.4.7'de anlatılan bir JBIG2Decode görüntü akışı yalnızca sayfa segmentlerini embedded biçimde tutar; paylaşılan sembol sözlükleri ayrı bir JBIG2Globals akışına taşınır ve dosya başlığı, end-of-page ya da end-of-file segmenti yoktur. Çözücü sekiz baytlık tanıtıcıyı bulamadığında girdiyi tam olarak o türden bir akış sayar: sequential, tek sayfa. Ters yönü de öyledir: PDF Library for Delphi bir JBIG2Decode görüntüsünü yerli bir .jb2 dosyasına aktardığında bayrak baytı $03 yazar (sequential, sayfa sayısı bilinmiyor) ve bir end-of-file segment başlığı ekler. Yani random access yalnızca dışarıdan gelir; TPLJBIG2Decoder'a doğrudan verilen, genellikle PDFlibPas'taki JBIG2 kodlayıcı arka uçlarıyla PDF'e dönüştürülmek ya da yeniden sıkıştırılmak üzere gelen bağımsız bir dosya olarak
Bir random-access dosyayı dosya sırasında neden okuyamazsınız?
Çünkü bayt akışında başlık bloğunun nerede bitip veri bloğunun nerede başladığını işaretleyen tek şey, end-of-file segment başlığının (tür 51) kendisidir. JBIG2 segment başlıklarının sabit boyutu yoktur. Referans verilen segment sayısı ya üç bitlik kısa biçimdir ya da bir saklama bit eşlemesinin izlediği uzun biçim; referans verilen her segment numarası, o anki segmentin kendi numarasının büyüklüğüne göre bir, iki ya da dört bayt tutar; sayfa ilişkilendirme alanı bir ya da dört bayttır. Bir okuyucu, sonraki başlığın nerede başladığını öğrenmek için bir başlığın her alanını ayrıştırmak zorundadır
Dosyayı sırayla gezen bir sequential okuyucu ilk başlığı doğru alır, ardından gelen her şeyi yanlış alır. Daha kötüsü, bir süre fark etmeyebilir: genel bir bölge çözücüsüne verilen başlık baytları, çok sonra bir uzunluk ya da referans denetimi sonunda tetiklenene dek hata değil piksel üretir. Bizim görüşümüz, başlıkları önce indekslemeyen bir çözücünün random-access dosyaları düpedüz reddetmesi. Net bir red, gruptaki tek dosyaya mal olur; makul görünen ama yanlış bir bit eşleme bir arşive girip orada kalabilir
İlk gövdeden önce her başlığı indekslemek
Bayrak baytı random-access dediğinde PDF Library for Delphi, hiçbir şey çözmeden önce başlık bloğu üzerinde tek bir geçiş yapar. Her başlık eksiksiz ayrıştırılır ve sonra atılır; saklanan tek şey dosyadaki bayt uzaklığıdır. Sıkıştırılmış veri hiçbir zaman yeniden sıralanmış ikinci bir tampona kopyalanmaz; büyük arşiv taramalarında veri bloğu dosyanın çoğunu tutabildiği için bu önemlidir. Geçiş ilk end-of-file başlığında durur; o anda okuma konumu ilk segmentin verisinin ilk baytının üzerinde durur
Random-access dosyanın sıralı olandan daha az artığı vardır; bu yüzden ön tarama katıdır. Şu durumlarda dosyayı reddeder:
- bir başlık tamponun sonunda kesilmişse;
JBIG2 truncated random-access header at byte Nolarak, o başlığın başladığı uzaklıkla birlikte bildirilir - segment numaraları kesin artmıyorsa (işaretsiz değerler olarak karşılaştırılır); çünkü aynı numarayı isteyen iki başlık, sonraki bir bölgenin referans listesini belirsiz kılar
- bir veri uzunluğunun en üst biti kuruluysa;
0xFFFFFFFF"bilinmeyen uzunluk" işareti de buna dâhildir. Bu dizilimde sonraki gövdenin nerede başladığını bulmanın başka yolu yoktur; çözücü bir uç işareti için ileri taramak yerine hemen reddeder - end-of-file başlığı veri bildiriyorsa ya da hiç görünmüyorsa (
JBIG2 random-access file is missing its end-of-file header) - bildirilen uzunlukların toplamı kalan baytlardan fazlaysa ya da azsa
İnsanların takıldığı kural sondur. Son gövdeden sonraki tek bir fazladan bayt, JBIG2 trailing random-access data ile başarısız olur. Sequential yola kıyasla sert görünür; orada çözüm end-of-file segmentinde durur ve sonrasındaki her şey yok sayılır. Asimetrinin bir nedeni vardır. Sequential dizilimde her başlık kendi verisinin hemen yanında durur; sondaki çöp hiçbir şeyi kaydıramaz. Random-access dizilimde başlıklar veri bloğunun tek haritasıdır ve uzunluklar tutmuyorsa çözücü, zararsız dolguyu çok daha önce kaybolmuş ya da eklenmiş ve ardındaki her gövdeyi kaydırmış bir bayttan ayırt edemez
Sondaki end-of-page segmenti neden kayboldu?
Bayt kaldıkça koşan bir döngü random-access dosyada erken biter; çünkü veri, başlık indeksinden önce tükenir. End-of-page (tür 49) ve end-of-file segmentleri sıfır bayt veri taşır ve normalde sonda gelir. Son bölge gövdesi tüketildiğinde okuma konumu tam olarak tamponun sonundadır; bayt sayan döngü oradan çıkar ve sıfır uzunluklu end-of-page segmenti hiç dağıtılmaz, sayfa yarım kalır
Bu yüzden random-access dosyalarda çözücü bayt değil başlık sayar. Her adım bir sonraki indeksli başlığa geri atlar, bayt sınırına hizalanır (aritmetik kodlu bir gövde baytın ortasında bitebilir), başlığı yeniden ayrıştırır, sonra sonraki gövdenin başladığı yere atlar ve bildirilen uzunluk kadar onu geçer. Çözücüsü erken biten gövde bildirilen sonuna atlanır; onu aşan okunan gövde reddedilir. Oradan itibaren segment, sequential bir dosyadakiyle aynı işleyicilere ve referans denetimlerine girer. Gövde çözümü sırasında fırlayan hatalar başlığın özgün konumunu taşır: JBIG2 segment N (type T) at byte B: ... biçiminde; böylece LastError'daki uzaklık hex düzenleyicide bulabileceğiniz bir yeri gösterir
Delphi'de bir random-access .jb2 dosyası yüklemek
Uygulama kodu düzenle birlikte değişmez. TPLJBIG2Decoder.LoadFromByteArray dosya başlığını ve düzeni saptar, reddedilen her girdide False döndürür ve nedeni LastError'a yazar; çözülen sayfayı, siyah için 1 yazan, piksel başına bir bayt dolduran Width, Height ve GetScanline üzerinden açığa eder
uses
SysUtils, Classes, PDFlibJBIG2;
function ReadJb2(const FileName: string): TJBIG2ByteArray;
var
FS: TFileStream;
begin
FS := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite);
try
SetLength(Result, FS.Size);
if Length(Result) > 0 then
FS.ReadBuffer(Result[0], Length(Result));
finally
FS.Free;
end;
end;
function CountBlackPixels(const FileName: string): Integer;
var
Decoder: TPLJBIG2Decoder;
Row: TJBIG2ByteArray;
X, Y: Integer;
begin
Result := 0;
Decoder := TPLJBIG2Decoder.Create;
try
// Sıralı ve random-access bağımsız dosyalar aynı çağrıyı alır
if not Decoder.LoadFromByteArray(ReadJb2(FileName)) then
raise Exception.Create('JBIG2 rejected: ' + Decoder.LastError);
for Y := 0 to Decoder.Height - 1 do
if Decoder.GetScanline(Y, Row) then
for X := 0 to Decoder.Width - 1 do
if Row[X] = 1 then
Inc(Result);
finally
Decoder.Free;
end;
end;
Bir dosyanın hangi düzeni kullandığını bilmek isterseniz — günlük tutmak ya da bir dönüştürme koşusu öncesi bir korpusu ayıklamak için — bayrak baytını kendiniz okuyun. Kütüphane saptadığı düzeni açığa etmez ve dokuz bayt yeter. Aşağıdaki yardımcı kendi kodunuza ekleyeceğiniz türdendir; yalnızca T.88 başlık kurallarını yeniden söyler:
type
TJb2Layout = (jlNoFileHeader, jlSequential, jlRandomAccess);
// Kendi yardımcınız, PDFlibJBIG2'nin parçası değil: bir tamponu T.88 dosya başlığına göre sınıflandırır
function DetectJb2Layout(const Data: TJBIG2ByteArray;
out PageCountKnown: Boolean): TJb2Layout;
const
Signature: array[0..7] of Byte = ($97, $4A, $42, $32, $0D, $0A, $1A, $0A);
var
I: Integer;
begin
Result := jlNoFileHeader; // örneğin bir PDF'ten çıkarılmış gömülü akış
PageCountKnown := False;
if Length(Data) < 9 then
Exit;
for I := 0 to 7 do
if Data[I] <> Signature[I] then
Exit;
PageCountKnown := (Data[8] and 2) = 0; // bit 1 kurulu: dört baytlık sayfa sayısı yok
if (Data[8] and 1) = 0 then
Result := jlRandomAccess
else
Result := jlSequential;
end;
Toplu işler: dolgulu tamponlar ve reddedilen dosyalar
Sonda-veri kuralı, toplu iş kodunun ters gitmeye en yatkın olduğu yerdir; başka yerde açılan bir dosya bu yüzden reddedilirse dosyadan önce tampona bakın. Veritabanı BLOB sütunundan çekilen bir .jb2, sabit boyutlu bir ağ okuması ya da hizalama dolgusu taşıyan bir kapsayıcı biçimi, gerçek sonun ardından birkaç sıfır baytla gelebilir. Sequential dosya bunu silker. Aynı dolguyu taşıyan random-access dosya JBIG2 trailing random-access data ile başarısız olur; birkaç bayt kısa kesilmiş tampon da kesilmiş olarak başarısız olur. Tam olarak dosyanın baytlarını geçin, boyutu kaynağın gerçek uzunluğundan alın; asla parça boyutundan ya da kapasiteden değil
Reddedilen dosya geride hiçbir şey de bırakmaz. LoadFromByteArray, çözmeye başlamadan önce Width, Height ve scanline'ları sıfırlar; başarısız çağrı önceki dosyanın sayfasını değil sıfır boyutları bildirir ve aynı çözücü örneği doğrudan sıradaki dosyaya geçebilir:
procedure DecodeBatch(const Files, Log, Quarantine: TStrings);
var
Decoder: TPLJBIG2Decoder;
I: Integer;
begin
Decoder := TPLJBIG2Decoder.Create;
try
for I := 0 to Files.Count - 1 do
if Decoder.LoadFromByteArray(ReadJb2(Files[I])) then
Log.Add(Format('%s: %d x %d', [Files[I], Decoder.Width, Decoder.Height]))
else
begin
// Burada Width ve Height 0'dır; önceki dosyadan hiçbir şey kalmaz
Log.Add(Files[I] + ': ' + Decoder.LastError);
if Pos('trailing random-access data', Decoder.LastError) > 0 then
Quarantine.Add(Files[I]); // dosyayı suçlamadan önce baytların nasıl saklandığını denetleyin
end;
finally
Decoder.Free;
end;
end;
Bunu kendi hattınızda denetlemek nadir örnek dosyalar gerektirmez. Sequential bir .jb2'yi random-access düzene örgütleyin; her başlığı ve her sıkıştırılmış baytı koruyarak, end-of-page ve end-of-file başlıklarını başlık bloğunun sonuna taşıyın ve çözülen pikseller sequential özgünle birebir eşleşmeli — sayfa sayısı alanıyla da onsuz da. Sonra ona her kesilmiş öneki, çiftlenmiş bir segment numarası, tek bir fazladan sondaki baytı ve 0xFFFFFFFF uzunluğunu verin; her biri False döndürmeli, Width ile Height sıfırda kalmalı
Çözücünün döndürdüğü ve beklediği
Buradaki random access, dosya düzeninin özelliğidir. TPLJBIG2Decoder, dosya hangi düzeni kullanırsa kullansın ilk sayfanın bit eşlemesini döndürür ve Width ile Height o sayfayı anlatır. Her segment veri uzunluğunu bildirmek zorundadır; T.88, hemen bir generic region'ın 0xFFFFFFFF işaretini kullanmasına izin verir ve çözücü o işareti her iki düzende de unknown or oversized segment length is not supported ile reddeder. Özel Huffman önek uzunlukları ile tablo girdi sayıları, sequential dosyalardakiyle aynı biçimde sınırlandırılır
JBIG2 çözmek, PDF görüntü ayıklamayı, yeniden sıkıştırmayı ve JBIG2 kodlamayı da kapsayan daha uzun görüntü yolunun bir aşamasıdır; PDF Library for Delphi ürün sayfası hattın geri kalanını ve desteklenen görüntü biçimlerini anlatır