Faxová brána nechce váš 24bitový render stránky. Stejně tak ani archivační pipeline, která ukládá milion faktur vypadajících jako naskenované, ani OCR front-end, který všechno ještě před hledáním znaku prahuje na černou a bílou. Všechny tři chtějí totéž: čistou 1bitovou bitmapu, jeden bit na pixel, kde je každý bod buď inkoust, nebo papír. Dejte jim plnobarevný BMP a stejně zahodí 23 bitů na pixel, obvykle s horším ditheringem, než byste zvládli sami. Zajímavá otázka je, kde by ten převod dolů měl proběhnout, a odpověď v PDFlibPas nakonec říká něco užitečného o tom, jak rozšířit renderer, který byste raději nepřepisovali
PDFlibPas je nativní knihovna PDF v Object Pascalu pro Delphi a C++Builder. Její vykreslovací jádro rastrovuje stránku do bitmapy a umí vypsat BMP, PNG, JPEG, WMF a několik dalších formátů. Co až donedávna neumělo, bylo vrátit skutečnou monochromatickou bitmapu nebo vykreslit jen část stránky. Obojí přibylo ve verzi v3.83.0 a obojí bylo postaveno jako tenká pohodlná vrstva nad stávajícím rendererem, nikoli jako změny samotného rasterizéru. Právě to omezení je celý příběh
Proč převádět dolů až po vykreslení, a ne uvnitř rendereru
Zjevný způsob, jak vytvořit 1bitový obrázek, je říct rasterizéru, aby kreslil v 1 bitu. To je také způsob, jak rozbít všechno ostatní. Vnitřní bitmapa rendereru se vytváří s napevno zadanou PixelFormat := pf24bit v PDFlibRenderer konstruktoru a tato 24bitová plocha je sdílená všemi cestami vykreslení: exportem do PNG, náhledem přes device context, výstupem do JPEG, prostě vším. Přepněte ji na pf1bit na zdroji a nepřidali jste monochromatickou funkci, ale zhoršili barevnou věrnost pro každého volajícího v knihovně a přihlásili se k ladění desítek regresí downstream
Proto RenderPageToMonochromeFile volí opačnou cestu. Stránku vykreslí normálně do dočasného 24bitového BMP a teprve potom ji v postprocessingu zploští na 1 bit. Renderer zůstává nedotčený. Monochromatické chování žije výhradně v pohodlné metodě, takže nemůže ovlivnit nikoho, kdo ji nevolá. To je ten typ kompromisu, který stojí za to pojmenovat přímo: postproces přidá jednu bitmapovou alokaci navíc a dočasný soubor, a výměnou nechá nosné jádro úplně mimo záběr. U funkce, která existuje proto, aby obsloužila faxové a archivační okrajové případy, je to správná strana účetní bilance
var
Pdf: TPDFlib;
begin
Pdf := TPDFlib.Create(nil);
try
Pdf.LoadFromFile('invoice.pdf');
// 200 DPI is the classic Group 4 fax resolution; page index is 1-based
Pdf.RenderPageToMonochromeFile(200, 1, 'invoice-page1.bmp');
finally
Pdf.Free;
end;
end;
Jak přesně probíhá převod na 1 bit
Převod dolů spoléhá na GDI namísto ručně napsané prahovací smyčky a na kvalitě výstupu to záleží. Uvnitř metody se 24bitová dočasná bitmapa načte do TBitmap, druhé TBitmap se vytvoří s PixelFormat := pf1bit ve stejných rozměrech a pixely se přenesou jedním blit:
// inside RenderPageToMonochromeFile, after loading the 24-bit ColorBmp
MonoBmp.PixelFormat := pf1bit;
MonoBmp.Width := ColorBmp.Width;
MonoBmp.Height := ColorBmp.Height;
// HALFTONE tells GDI to dither the 24-bit source down to 1-bit
SetStretchBltMode(MonoBmp.Canvas.Handle, HALFTONE);
StretchBlt(MonoBmp.Canvas.Handle, 0, 0, MonoBmp.Width, MonoBmp.Height,
ColorBmp.Canvas.Handle, 0, 0, ColorBmp.Width, ColorBmp.Height, SRCCOPY);
MonoBmp.SaveToFile('out.bmp');
Trik je SetStretchBltMode s HALFTONE. I když mají zdroj a cíl stejnou velikost, takže k žádnému škálování nedochází, režim roztahování stále určuje, jak GDI mapuje barvy do 1bitové palety. HALFTONE z něj udělá polotónové ditheringové zpracování, takže šedé oblasti a hrany anti-aliased textu přemění na vzory černých a bílých teček místo tvrdého ořezu na nejbližší ze dvou barev. Když volání režimu vynecháte nebo použijete výchozí BLACKONWHITE, šedotónový obsah se posterizuje do hranatých prahovaných tvarů. Pro výstup pro naskenované dokumenty a OCR předzpracování je ditherovaný výsledek téměř vždy to, co chcete
Jeden detail je neoddiskutovatelný a snadno se pokazí: dočasný render musí být BMP. RenderPageToMonochromeFile volá obecný renderer s kódem možností 0, což je BMP. Argument options u RenderPageToFile je malé celočíselné enum a hodnoty se pro tento účel nedají zaměňovat: 0 je BMP, 1 JPEG, 2 WMF, 3 EMF, 5 PNG a tak dále. Převodník pak provede TBitmap.LoadFromStream na dočasném souboru. Když mu předáte WMF přes 2, tento load vyhodí "Bitmap image is not valid", protože Windows Metafile je vektorový proud záznamů, ne DIB. Převod na monochromatickou podobu je rastrová operace od začátku do konce, takže mezivýsledek musí být rastrový formát
Vykreslení pouze dílčí oblasti stránky
Druhá metoda, RenderPageRegionToFile, vykreslí jen obdélník stránky místo celé stránky. Případy použití jsou známé každému, kdo někdy vytvořil prohlížeč dokumentů: oříznutí podpisového bloku ze smlouvy, generování dlaždice pro přiblíženou mapu velkého výkresu nebo získání jednoho orazítkovaného regionu pro miniaturu, aniž byste museli rastrování celé stránky na vysokém DPI zaplatit. Signatura je jednoduchá:
// Clip is "Left,Top,Width,Height" in PDF points (72 pt = 1 inch)
// Here: a 2.5in x 1in box, one inch in from the top-left of the page
Pdf.RenderPageRegionToFile(150, 1, '72,72,180,72', 'sig-block.bmp');
Řetězec clip obsahuje čtyři desetinná čísla oddělená čárkami v bodech PDF, která metoda parsuje ručně, aby obešla národní prostředí a DelimitedTextzvláštnosti. Z šířky a výšky metoda vypočítá velikost výstupního bitmapu jako Round(Width * DPI / 72) na Round(Height * DPI / 72), přidělí bitmapu v paměti pf24bit o přesně této velikosti a vykreslí do jejího kontextu zařízení prostřednictvím RenderPageToDCClip. Výsledný soubor obsahuje jen oříznutý obdélník, jehož velikost odpovídá oblasti, ne celé stránce.
Parametr clip, který nedělal nic
Tady byla práce ostřejší, než se zdá. RenderPageToDCClip měl Clip parametr clip po dlouhou dobu a byla to lež. Volání ten argument přijalo, předalo ho do TPDFPageTree.RenderPageToDC a ta implementace ho zcela ignorovala a nikdy ho nepředala rendereru. Mohli jste zadat libovolný obdélník a dostat zpět celou stránku. Kdo zapojil RenderPageToDCClip a čekal oříznutí, dostával vykreslení celé stránky a podle svého rozvržení si toho možná ani nevšiml.
Verze v3.83.0 to propojila. RenderPageToDC nyní parsuje stejný "Left,Top,Width,Height" obdélník v bodech a před samotným vykreslením jej použije jako skutečnou ořezovou oblast GDI na cílovém kontextu zařízení. Převod z bodů na pixely zařízení je obvyklý DPI / 72škálovací faktor, použitý na všechny čtyři okraje. Postup kolem vykreslení je standardní sekvence save/clip/restore:
// inside TPDFPageTree.RenderPageToDC, when Clip is non-empty
ScaleFactor := DPI / 72;
SaveDC(TargetDC);
IntersectClipRect(TargetDC,
Round(ClipLeft * ScaleFactor),
Round(ClipTop * ScaleFactor),
Round((ClipLeft + ClipWidth) * ScaleFactor),
Round((ClipTop + ClipHeight) * ScaleFactor));
// ... renderer draws the page here ...
// in the finally block:
RestoreDC(TargetDC, -1);
Dvojice SaveDC / RestoreDC(-1) je to, co zajišťuje bezpečné opakované volání: ořezová oblast se nahraje na zásobník stavu DC, stránka se vykreslí a původní ořez se vrátí zpět bez ohledu na to, jak vykreslení skončí. RestoreDC(TargetDC, -1) vrací naposledy uložený stav, což je běžný idiom pro vyvážené ukládání a obnovu. Když obnovu přeskočíte, volající, který znovu použije stejný DC pro následné vykreslení celé stránky, by zjistil, že je záhadně oříznutý na poslední oblast. Oprava mrtvého parametru zároveň zdarma opravila RenderPageRegionToFile i to, protože nová metoda vede právě touto cestou.
Jedna důležitá věc k zapamatování: ořez ořezává, ne škáluje. Stránka se stále rasterizuje v DPI, které jste zadali, ve své běžné poloze a ořezová oblast jednoduše zahodí vše mimo obdélník. Oblast nepřibližujete tak, aby vyplnila výstup; vyřezáváte okno z vykreslení v plném rozlišení. Pokud chcete oblast zvětšit, zvyšte DPI. Souřadnice obdélníku se po převodu bodů na pixely vykládají v prostoru zařízení a měří se od levého horního rohu vykreslené plochy, proto si naplánujte Left a Topod horního okraje stránky směrem dolů. Pro hlubší pohled na to, jak PDFlibPas ovládá kontext zařízení pro výstup na obrazovku, navazující článek o náhledu tisku a výstupu přes kontext zařízení prochází stejným propojením DC z pohledu zobrazení.
Poctivá hranice: 1bitový BMP, ne G4 TIFF
Bylo by snadné to přehnat a vydávat to za „výstup připravený pro fax“, proto je tu omezení řečeno naplno. RenderPageToMonochromeFile vytváří pf1bit BMP. Nevytváří CCITT Group 4 TIFF, což je formát, který obvykle čeká skutečný faxový workflow nebo archiv TIFF. Důvod je konkrétní, ne opomenutí: jednotka CCITT v PDFlibPas v současnosti dekóduje proudy G4, ale nemá žádný G4 kodér. Bez kodéru není kam zapisovat komprimované běhy monochromatických dat, takže monochromatická cesta končí u nekomprimovaného 1bitového DIB.
V praxi je to pořád užitečné. 1bitový BMP je správný pixelový formát, je už rozptýlený a připravený, a většina faxových, archivačních nebo OCR nástrojů ho bez potíží přijme nebo si ho jedním navazujícím krokem sama převede na G4. Pokud ale doslova potřebujete TIFF Group 4 přímo z knihovny, tohle tím zatím není a měli byste si naplánovat vlastní kompresní krok. Vědět, kde funkce končí, má stejnou cenu jako vědět, co dělá.
Obě metody jsou záměrně malé a právě to je designová lekce, kterou si z této stránky stojí za to odnést: pohodlné API nad rendererem může přidat skutečné schopnosti, monochromatický výstup i ořez oblasti, aniž by sahalo do rastrovacího jádra a rozkolísalo všechny ostatní volající. Když už je potřeba vybírat mezi renderovacími enginy pro samotné rastrování, přehled víceenginového vykreslování PDF v Delphi rozebírá kompromisy do hloubky. Pro úplný pohled na vykreslovací plochu a zbytek API se PDFlibPas Delphi PDF LibraryStránka produktu ukazuje celý obraz