Technický článek

Mixed Raster Content v Delphi: skenované PDF na JPEG + CCITT

losLab PDF Library for Delphi komprimuje naskenovanou stránku metodou Mixed Raster Content přes TPDFlib.CompressPageAsMRC. Metoda stránku vyrenderuje, z plného rozlišení vytvoří prahováním 1bitovou masku popředí, zbytek downsampluje do JPEG pozadí a postaví stránku znovu ze dvou obrázků: pozadí /DCTDecode a stencil masky /CCITTFaxDecode malované aktuální barvou výplně

Stránka, pro kterou tohle existuje, je ta, jichž je každý dokumentový archiv plný: barevný sken faktury nebo smlouvy v 300 DPI uložený jako jeden velký JPEG. Zakódujte ho v kvalitě, která udrží text čitelný, a soubor je velký; pusťte kvalitu dolů a kolem písmen se objeví halo a rozmazané patky, protože to, v čem je JPEG nejhorší, je ostrá černá hrana na bílém papíru. Mixed Raster Content přestane požadovat po jednom kodeku dvě práce

Proč jediný JPEG dělá naskenovaný text rozmazaný i velký?

Jediný JPEG si naskenovaný text poradí špatně, protože DCT kodek utrácí bity za hladké přechody a tvrdé hrany aproximuje vysokofrekvenčními koeficienty, které kvantizace zahazuje jako první. Text na skenu není skoro nic jiného než tvrdé hrany. Udržet ty hrany čisté vynutí vysokou kvalitu pro celou stránku, včetně textury papíru, loga a kávové skvrny, což nepotřebovalo žádné z toho

Myšlenka MRC rozdělí stránku podle toho, co každá oblast skutečně je. Inkoust se stane bilevel obrázkem, kde faxový kodek jako CCITT zaznamenává běhy černých a bílých pixelů bezztrátově a velmi kompaktně. Všechno ostatní — tón papíru, fotky, barevná pozadí — se stane spojitotónovým obrázkem, který se dá uložit ve zlomku rozlišení, protože nikdo nečte pozadí v 300 DPI. Bilevel vrstva udrží text ostrý v plném rozlišení; vrstva pozadí udrží barvu za nízkou cenu

Jediný JPEG sken versus vrstvové rozdělení Mixed Raster Content vytvořené PDFlibPas CompressPageAsMRC v Delphi: DCT kodek rozmazává tvrdé hrany textu a nutí vysokou kvalitu pro celou stránku, zatímco inkoust se stane bezztrátovým 1bitovým CCITT stencilem v 300 DPI a tón papíru se stane 75 DPI DeviceRGB JPEG pozadím
Mixed Raster Content přestane žádat jeden kodek o dvě práce: bilevel vrstva drží písmena ostrá v plném rozlišení a pozadí drží barvu za zlomek nákladů, takže kvalita 50 až 70 bývá neviditelná

Jak CompressPageAsMRC rozdělí stránku na dvě vrstvy?

TPDFlib.CompressPageAsMRC jede jednu pevnou pipeline na stránku. Stránku vyrenderuje v požadovaném DPI do 24bitové bitmapy, každý pixel klasifikuje podle váženého jasu Rec. 601 (299/587/114 z tisíce) proti Threshold a výsledek zapíše do 1bitové masky ve stejném rozlišení. Paralelně tentýž render zmenší přes BGScale boxovým průměrem, takže BGScale 4 promění každý blok 4×4 pixelů na jeden pixel pozadí

Datový tok CompressPageAsMRC v PDFlibPas pro Delphi: jeden 300 DPI 24bitový render se prahuje jasem Rec. 601 na full-resolution CCITTFaxDecode K 1 ImageMask stencil, boxovým průměrem přes BGScale 4 na DCTDecode DeviceRGB pozadí, a oba obrázky nahradí původní operátory stránky s maskou navrchu
Obě vrstvy vycházejí z jediného renderu: DPI rozhoduje, jak tenký tah přežije prahování, BGScale sází detail pozadí od 2 u fotografií po 8 u čistého textu a znovu postavená stránka kreslí stencil aktuální barvou výplně

Obsah stránky se pak vymění. Původní operátory se odstraní, zmenšené pozadí se embedduje jako obrázek /DCTDecode v /DeviceRGB (ISO 32000-1 §7.4.8), maska se zakóduje knihovním CCITT enkodérem jako obrázek /CCITTFaxDecode (§7.4.6) s příznakem /ImageMask true a obojí se nakreslí přes celý page box s maskou navrchu. Signatura je krátká:

function CompressPageAsMRC(Page: Integer; DPI: Double;
  BGScale, JPEGQuality, Threshold, Options: Integer): Integer;
  • Page se počítá od 1 a musí ležet v PageCount
  • DPI je renderovací rozlišení zdrojového rastru, 50 až 1200
  • BGScale je faktor zmenšení pozadí, 1 až 8
  • JPEGQuality je JPEG kvalita pozadí, 1 až 100
  • Threshold je řez jasem, 1 až 255; pixely na hodnotě a světlejší jsou papír, tmavší pixely jsou inkoust
  • Options je rezervované a musí být 0

Typická dávka nad naskenovaným souborem vypadá takhle. SetFillColor volejte těsně před každou stránkou, z důvodu, který vysvětluje další oddíl

uses
  SysUtils, PDFlibrary;

procedure CompressScannedFile(const SrcFile, DstFile: string);
var
  Lib: TPDFlib;
  P, Replaced: Integer;
begin
  Lib := TPDFlib.Create;
  try
    if Lib.LoadFromFile(SrcFile, '') <> 1 then
      raise Exception.CreateFmt('Cannot open %s', [SrcFile]);
    for P := 1 to Lib.PageCount do
    begin
      Lib.SetFillColor(0, 0, 0);  // barva inkoustu stencil vrstvy
      // maska 300 DPI, pozadí 75 DPI, JPEG kvalita 60, řez při luma 160
      Replaced := Lib.CompressPageAsMRC(P, 300, 4, 60, 160, 0);
      if Replaced = 0 then
        raise Exception.CreateFmt('MRC failed on page %d', [P]);
    end;
    if Lib.SaveToFile(DstFile) <> 1 then
      raise Exception.CreateFmt('Cannot save %s', [DstFile]);
  finally
    Lib.Free;
  end;
end;

Proč se popředí maluje aktuální barvou výplně?

Vrstva popředí je stencil maska a stencil maska nemá žádnou vlastní barvu. ISO 32000-1 §8.9.6.2 definuje obrázek s /ImageMask true jako 1bitový tvar: vzorové hodnoty značí, kam barva patří, a barva je aktuální nonstroking barva v grafickém stavu. Přesně to dělá vrstvu levnou a je to i důvod, proč CompressPageAsMRC nebere žádný barevný parametr

TPDFlib.CompressPageAsMRC maluje masku barvou nastavenou přes SetFillColor, takže pořadí volání záleží. Painter vysílá barvu výplně líně: SetFillColor si barvu zapamatuje a označí ji jako změněnou a stencil kreslení zapíše operátor rg uvnitř vlastního bloku q/Q jen tehdy, dokud je ta změna stále nevyřízená. Nakreslíte mezi tím vyplněný tvar a nevyřízenou změnu spotřebuje místo toho ten tvar. Nastavení barvy těsně před každou stránkou, jako v dávce výše, odstraní luštění. Černá je normální volba; tmavě modrá (navy) funguje u formulářů psaných modrým inkoustem, kde chcete, aby výstup vypadal méně jako fotokopie

Sekvence líného vysílání barvy výplně pro stencil vrstvu PDFlibPas MRC v Delphi: SetFillColor si zapamatuje barvu jako nevyřízenou změnu, kreslení ImageMask stencilu zapíše operátor rg uvnitř vlastního bloku q a Q, dokud je změna stále nevyřízená, a vsunutý vyplněný tvar spotřebuje nevyřízenou změnu místo ní
Stencil maska žádnou barvu nevlastní, takže každý pixel inkoustu dostane jednu barvu výplně: rudé razítko, modrý podpis i černý text se slijou dohromady, a proto se musí SetFillColor volat těsně před každou stránkou
// Formulář psaný modrou kuličkovou perem: vykreslete vrstvu inkoustu v tmavé modré
Lib.SetFillColor(0.08, 0.12, 0.35);
Replaced := Lib.CompressPageAsMRC(PageNo, 300, 4, 55, 170, 0);

Umístění se řídí souřadnicovým počátkem knihovny. CompressPageAsMRC čte GetOrigin a kreslí oba obrázky od horní hrany page boxu: horní souřadnice 0, když je počátek nahoře vlevo (SetOrigin(1)), horní souřadnice PageHeight při výchozím počátku dole vlevo. Nic nemusíte upravovat, ale vysvětluje to, proč vrstvy dopadnou správně, ať už vaše pipeline používala jakýkoli počátek

Jeden důsledek stencil sémantiky stojí za výslovné řízení: každý pixel klasifikovaný jako inkoust dostane tutéž barvu. Rudé razítko, modrý podpis i černý text článku skončí v jedné barvě výplně na vrstvě popředí. Jen pozadí JPEG uchová slabou, nízkorozlišenou stopu původních barev pod tím

Volba DPI, BGScale, Threshold a JPEG kvality

Hodnota DPI rozhoduje o ostrosti textu a 300 je rozumný výchozí bod pro kancelářské skeny. Maska se drží v plném renderovacím rozlišení, takže DPI určuje, jak tenký tah přežije prahování. Jít výš než původní rozlišení skenu nekupuje nic, protože by renderer jen upsamplvoval embedded skenový obrázek. Paměť je druhý limit: stránka US Letter v 300 DPI se vyrenderuje na 2550×3300 pixelů, 25 245 000 bajtů 24bitových pixelových dat, než se odvodí maska a pozadí, a ta samá stránka u stropu 1200 DPI je 10200×13200 pixelů, zhruba 404 MB

BGScale vyměňuje detail pozadí za velikost. Na 300 DPI dává faktor 4 pozadí 75 DPI, 638×825 pixelů pro Letter, což pro tón papíru a ploché barevné plochy bohatě stačí. Stránky s fotografiemi nebo rastrovými ilustracemi, které si čtenář skutečně prohlédne, si zaslouží 2; čistý text na bílém papíru zvládne 6 nebo 8, protože pozadí je sotva víc než nádech

Threshold je parametr, který mění to, jak stránka vypadá. Zvýšení klasifikuje víc polosedých pixelů jako inkoust, což ztlustí tenké tahy a tužkové čáry, ale natáhne to i šum skeneru a stín papíru jako tečky. Snížení dá čistší papír a tenčí, někdy přerušená písmena. Hodnoty kolem 128 až 170 jsou rozumný start pro černý text na bílém; kompromis mezi oběma je tentýž, o kterém pojednává vykreslování stránek PDF do 1bitové monochromie v Delphi, kde stejný jasový práh řídí RenderPageToMonochromeFileEx. Na rozdíl od té metody nemá CompressPageAsMRC žádný despeckle průchod, takže šumivé skeny poslouží lépe mírně nižší práh

JPEGQuality záleží méně než u skenu jako jediného obrázku, protože pozadí už nenese žádné hrany textu. Kvalita 50 až 70 bývá neviditelná na pozadí 75 DPI za ostrou maskou

Co říká návratová hodnota a co ne?

TPDFlib.CompressPageAsMRC vrací velikost v bajtech obsahu stránky, který nahradila, a 0, když něco selhalo. Je to indikátor úspěchu, ne míra úspory. Na typické naskenované stránce je původní content stream pár desítek bajtů, jen dost operátorů na umístění jednoho obrázku, takže malé kladné číslo je normální výsledek, i když stránka zeštíhle o megabajty. Stránka, jejíž původní content stream byl prázdný, hlásí 0, i když byla postavena znovu

Chování při selhání není atomické a právě na to je potřeba navrhovat. Kontroly argumentů, render i JPEG kódování běží dřív, než se na stránku sáhne, takže BGScale mimo rozsah, JPEGQuality 101 nebo selhání renderu nechají stránku přesně tak, jak byla. Jakmile se ale původní obsah vymaže, selhání embeddu jedné z vrstev nebo jejího vykreslení také vrátí 0, a v tom okamžiku už může být stránka částečně postavena znovu. Jakoukoli nulu berte jako důvod zahodit dokument v paměti místo jeho uložení, a výsledek nikdy nepište přes zdrojový soubor

Abyste zjistili, co konverze skutečně ušetřila, změřte dokument. Srovnání délky SaveToString před a po je nejrychlejší kontrola; odpověď po kategoriích dá audit velikosti PDF s AuditDocumentSpace, který ukáže, jestli se váha přesunula z obrázků podle očekávání

function MRCSavings(Lib: TPDFlib): Int64;
var
  Before: Int64;
  P: Integer;
begin
  Before := Length(Lib.SaveToString);
  for P := 1 to Lib.PageCount do
  begin
    Lib.SetFillColor(0, 0, 0);
    if Lib.CompressPageAsMRC(P, 300, 4, 60, 160, 0) = 0 then
      raise Exception.CreateFmt('Page %d failed; discard this document', [P]);
  end;
  Result := Before - Length(Lib.SaveToString);
end;

Kde tohle dvouvrstvé MRC za ITU-T T.44 zaostává

CompressPageAsMRC implementuje záměrně zjednodušené MRC. Plný model v ITU-T T.44 používá tři vrstvy na stripe: obrázek pozadí, obrázek popředí, který nese barvy inkoustu, a bilevel masku, která mezi nimi vybírá pixel po pixelu, s místem pro několik párů maska a popředí na jedné stránce. V PDF řeči by třetí vrstva byla nízkorozlišený barevný obrázek popředí s explicitní maskou (§8.9.6.3) místo stencilu malovaného jednou barvou. PDF Library for Delphi vynechá barevnou vrstvu popředí, což výsledek dělá tak malým a je to i důvod, proč barevný inkoust sklouzne do barvy výplně

  • Metoda sedí na černý nebo jednobarevný text na naskenovaném papíru; dokumenty kódované barvami, zvýrazněný text a barevná razítka přijdou o barvu inkoustu
  • Znovu postavená stránka je čistý raster: skutečný text, vektorová grafika i vybíratelné znaky na původní stránce se stanou pixely, takže to pouštějte na skeny, ne na born-digital stránky, kde jsou správnými nástroji font subsetting a downsampling obrázků
  • Maska používá knihovní CCITT enkodér ve dvourozměrném režimu T.4, zapsaný jako /CCITTFaxDecode s /K 1; enkodér má i režim Group 4 přes AddImageCCITTFromBitmapStream, ale CompressPageAsMRC ho nevystavuje a JBIG2 nepoužívá
  • Anotace a formulářová pole se neflattují; zůstávají jako objekty nad znovu postaveným obsahem
  • Pozadí je /DeviceRGB, takže výstup nesedí na workflow PDF/X-1a a samotné SetFillColor je v režimu PDF/X-1a odmítáno
  • Sken, který bude potřebovat OCR, se má rozpoznat před MRC, dokud původní pixely ještě existují v plné barevné hloubce

FAQ

Udržuje CompressPageAsMRC text vyhledavatelný?

Ne. CompressPageAsMRC vymění obsah stránky za dva obrázky, takže všechny textové operátory na původní stránce, včetně neviditelné OCR textové vrstvy, zmizí s nimi. OCR pusťte na komprimované stránce poté, nebo textovou vrstvu po komprimaci přidejte zpět

Dá se komprimovat jen část stránek dokumentu?

Ano. CompressPageAsMRC pracuje na jednu stránku (od 1) na volání, takže stránky vybíráte vy a můžete i měnit BGScale a Threshold po stránkách, třeba faktor 2 pro vloženou fotku a 6 pro stránky s čistým textem

Jaký má být parametr Options?

Předávejte 0. Options je v aktuální verzi rezervované pro budoucí použití a žádné bity nejsou definované, takže nenulová hodnota dnes neznamená nic

CompressPageAsMRC přibyl v losLab PDF Library for Delphi v3.561.0 spolu se stavebními bloky render, JPEG, CCITT a stencil maska, ze kterých je složen, všechny dostupné jako veřejné metody, kdybyste potřebovali variantu pipeline. Knihovna, její podpora C++Builderu a kompletní API reference jsou na stránce produktu PDF Library for Delphi