Tehnički članak

Prikaz PDF stranica u 1-bitni monokrom u Delphiju

Fax gateway ne želi vaš 24-bitni render stranice. Ne želi ga ni arhivski pipeline koji sprema milijun skeniranih računa, niti OCR front-end koji sve pragira na crno i bijelo prije nego što uopće pogleda znak. Svima je ista stvar potrebna: čist 1-bitni bitmap, jedan bit po pikselu, gdje je svaka točka ili tinta ili papir. Dajte im punobojni BMP i oni će ionako baciti 23 bita po pikselu, obično s gorim dithering prolazom nego što biste ga sami mogli napraviti. Zanimljivo pitanje je gdje ta down-conversion treba dogoditi, a odgovor u PDFlibPasu pokazuje nešto korisno o tome kako proširiti renderer koji ne biste radije prepisivali

PDF Library for Delphi je nativna Object Pascal PDF biblioteka za Delphi i C++Builder. Njegov rendering core rasterizira stranicu u bitmapu i može isporučiti BMP, PNG, JPEG, WMF i nekoliko drugih formata. Ono što do nedavno nije radio bilo je vraćanje prave monochrome bitmapke ili prikaz samo dijela stranice. Oba su stigla u v3.83.0, i oba su izgrađena kao tanke convenience layeri na vrhu postojećeg renderera, a ne kao promjene samog rasterizera. To je cijela priča

Zašto down-convert nakon renderiranja, a ne unutar renderera

Očigledan način za proizvodnju 1-bitne slike jest reći rasterizeru da crta u 1-bitu. To je također način da se razbije sve ostalo. Interna bitmapa renderera stvara se s hardkodiranim PixelFormat := pf24bit u PDFlibRenderer konstruktoru, i ta 24-bitna površina dijeli se sa svakim render putem: PNG export, device-context preview, JPEG output, sve. Prebacite je na pf1bit na izvoru i niste dodali monochrome feature, nego ste degradirali color fidelity za svakog pozivatelja u biblioteci i prijavili se za debugiranje desetak downstream regresija

Dakle, RenderPageToMonochromeFile ide suprotnim putem. Stranicu renderira normalno, u privremeni 24-bitni BMP, i tek je zatim kolabira u 1-bit kao post-processing korak. Renderer je netaknut. Monochrome ponašanje živi isključivo u convenience metodi, što znači da ne može utjecati na ikoga tko je ne pozove. To je vrsta trade-offa koju vrijedi izričito imenovati: post-process plaća jednu dodatnu bitmap alokaciju i temp datoteku, a zauzvrat potpuno izvan opsega drži nosivi core. Za značajku koja postoji da služi fax i arhivskim rubnim slučajevima, to je prava strana računa

PDF Library for Delphi tijek koji prikazuje PDF stranicu renderiranu u privremeni 24-bitni BMP, GDI HALFTONE blit pretvorbom sabijen u 1-bitnu monokromnu bitmapu i iskorišten u fax, arhivskim i OCR procesima
Nove metode prvo prikažu stranicu u punim bojama, a zatim pretvore gotovi raster izvan renderera. Fax pristupnici, arhivska spremišta i OCR front-endsi primaju pravu pf1bit bitmapu
var
  Pdf: TPDFlib;
begin
  Pdf := TPDFlib.Create;
  try
    Pdf.LoadFromFile('invoice.pdf');
    // 200 DPI je klasična rezolucija Group 4 faksa; indeks stranice je 1-indeksiran
    Pdf.RenderPageToMonochromeFile(200, 1, 'invoice-page1.bmp');
  finally
    Pdf.Free;
  end;
end;

Kako se 1-bitni collapse zapravo događa

Down-conversion se oslanja na GDI, a ne na ručno pisan threshold loop, i taj izbor utječe na kvalitetu izlaza. Unutar metode 24-bitni temp bitmap učitava se u TBitmap, druga TBitmap se stvara s PixelFormat := pf1bit na istim dimenzijama, a pikseli prelaze s jednim blitom:

PDF Library for Delphi: detalj GDI sažimanja koji uspoređuje HALFTONE stretch blit koji sive rastavlja u točkaste uzorke s blokovskim zadanim pragom BLACKONWHITE
Unutar RenderPageToMonochromeFile jedan StretchBlt prenosi svaki piksel na pf1bit površinu identične veličine. S aktivnim HALFTONE-om sivi tonovi postaju ditherirani uzorci točaka tinte umjesto blokovskih oblika koje proizvodi zadani prag
// unutar RenderPageToMonochromeFile, nakon učitavanja 24-bitnog ColorBmp
MonoBmp.PixelFormat := pf1bit;
MonoBmp.Width  := ColorBmp.Width;
MonoBmp.Height := ColorBmp.Height;
// HALFTONE govori GDI-ju da dithera 24-bitni izvor na 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. Iako su izvor i odredište iste veličine, pa nema skaliranja, stretch mode i dalje određuje kako GDI mapira boje u 1-bitnu paletu. HALFTONE ga tjeraju da primijeni halftone dithering, pretvarajući sive zone i antialiased rubove teksta u uzorke crnih i bijelih točkica umjesto u tvrdi clip na najbližu od dvije boje. Uklonite poziv na mode ili upotrijebite zadani BLACKONWHITE, i grayscale sadržaj posterizira u blokaste threshold oblike. Za izlaz namijenjen skeniranju dokumenata i OCR predobradi, dithered rezultat gotovo je uvijek ono što želite

Jedan detalj je neupitan i lako ga je pogriješiti: privremeni render mora biti BMP. RenderPageToMonochromeFile poziva generalni renderer s options kodom od 0, što je BMP. Options argument na RenderPageToFile je mali integer enum, a vrijednosti za tu svrhu nisu zamjenjive: 0 je BMP, 1 JPEG, 2 WMF, 3 EMF, 5 PNG, i tako dalje. Down-converter zatim radi TBitmap.LoadFromStream na temp datoteci. Dajte mu WMF, tako da proslijedite 2, i to učitavanje baca "Bitmap image is not valid", jer je Windows Metafile stream vektorskih zapisa, a ne DIB. Monochrome down-conversion je raster operacija od početka do kraja, pa međukorak mora biti raster format

Prikaz samo podregije stranice

Druga metoda, RenderPageRegionToFile, prikazuje samo pravokutnik stranice umjesto cijele stvari. Upotrebe su poznate čim ste izgradili bilo koji document viewer: izrezivanje bloka potpisa iz ugovora, generiranje pločice za zumiranu kartu velikog crteža ili izdvajanje jednog žigosanog područja za sličicu bez plaćanja rasterizacije cijele stranice na visokom DPI-ju. Potpis je jednostavan:

PDF Library for Delphi: renderiranje isjeka područja u PDF točkama: pravokutnik 72,72,180,72 na PDF stranici renderiranoj punom rezolucijom postaje bitmapa od 375 sa 150 piksela pri 150 DPI jer isjek izrezuje umjesto da skalira
RenderPageRegionToFile izreže prozor mjeren u PDF točkama iz prikaza pune razlučivosti. Izlazna bitmapa dimenzionira se iz širine i visine puta DPI kroz 72, nikad iz smanjene cijele stranice
// Clip je "Left,Top,Width,Height" u PDF točkama (72 pt = 1 inč)
// Ovdje: okvir 2,5in x 1in, jedan inč od gornjeg lijevog kuta stranice
Pdf.RenderPageRegionToFile(150, 1, '72,72,180,72', 'sig-block.bmp');

Clip string je četiri decimalna broja odvojena zarezima u PDF točkama, ručno parsiran unutar metode kako bi se zaobišle locale i DelimitedText quirks. Iz širine i visine metoda izračuna veličinu izlazne bitmapke kao Round(Width * DPI / 72) by Round(Height * DPI / 72), alocira in-memory pf24bit bitmap točno te veličine i renderira u njezin device context kroz RenderPageToDCClip. Izlazna datoteka sadrži samo izrezani pravokutnik, veličinom prilagođen području, a ne cijeloj stranici

Clip parametar koji nije radio ništa

Ovdje je posao bio oštriji nego što izgleda. RenderPageToDCClip je dugo nosio Clip parametar, a to je bila laž. Poziv je prihvaćao argument, prosljeđivao ga TPDFPageTree.RenderPageToDC, a ta je implementacija potpuno ignorirala, nikad ga nije predavala rendereru. Mogli ste poslati bilo koji pravokutnik i dobiti cijelu stranicu natrag. Svaki tko je povezao RenderPageToDCClip očekujući crop dobivao je render cijele stranice i, ovisno o layoutu, možda to nije ni primijetio

v3.83.0 je spojio žicu. RenderPageToDC sada parsira isti "Left,Top,Width,Height" point pravokutnik i primjenjuje ga kao pravi GDI clip region na ciljnom device contextu prije nego renderer nacrta. Pretvorba iz točaka u device piksele je uobičajeni DPI / 72 scale faktor, primijenjen na sva četiri ruba. Sekvenca oko rendera standardni je save/clip/restore ples:

// unutar TPDFPageTree.RenderPageToDC, kad Clip nije prazan
ScaleFactor := DPI / 72;
SaveDC(TargetDC);
IntersectClipRect(TargetDC,
  Round(ClipLeft * ScaleFactor),
  Round(ClipTop * ScaleFactor),
  Round((ClipLeft + ClipWidth) * ScaleFactor),
  Round((ClipTop + ClipHeight) * ScaleFactor));
// ... renderer ovdje crta stranicu ...
// u finally bloku:
RestoreDC(TargetDC, -1);

SaveDC / RestoreDC(-1) par je ono što ovo čini sigurnim za višekratni poziv: clip region se gura na DC state stack, stranica se nacrta i izvorni clip se vraća bez obzira na to kako render završi. RestoreDC(TargetDC, -1) vraća najnovije spremljeno stanje, što je standardni idiom za balanced save/restore. Preskočite restore i pozivatelj koji isti DC koristi za sljedeći full-page render zatekao bi ga misteriozno odrezanog na zadnju regiju. Popravak mrtvog parametra usput je popravio i RenderPageRegionToFile za slobodno, jer ta nova metoda prolazi upravo ovim putem

Jedna ponašajna točka koju treba usvojiti: clip crops, it does not scale. Stranica se i dalje rasterizira na DPI koji ste tražili, u normalnom položaju, a clip region jednostavno odbacuje sve izvan pravokutnika. Ne zumirate regiju da ispuni izlaz; režete prozor iz full-resolution rendera. Ako želite uvećanu regiju, povećajte DPI. Koordinate pravokutnika tumače se u device spaceu nakon points-to-pixels skaliranja, mjereno od gornjeg lijevog kuta renderirane površine, pa planirajte svoje Left i Top od vrha stranice prema dolje. Za dublji obilazak kako PDF Library for Delphi upravlja device contextom za on-screen izlaz, prateći članak o print preview and device-context output prolazi kroz isti DC plumbing s display strane

Poštena granica: 1-bitni BMP, ne G4 TIFF

Bilo bi lako ovo prenapuhati kao "fax-ready output", pa evo granice izrečene jasno. RenderPageToMonochromeFile proizvodi pf1bit BMP. Ne proizvodi CCITT Group 4 TIFF, koji je format koji stvarni fax workflow ili TIFF arhiva obično očekuje. Razlog je konkretan, a ne previd: PDFlibPasov CCITT unit trenutno dekodira G4 streamove, ali nema G4 encoder. Bez encoder-a nema gdje zapisati komprimirane monochrome runove, pa monochrome put staje na nekomprimiranom 1-bitnom DIB-u

U praksi je to i dalje korisno. 1-bitni BMP je ispravan pixel format, dithered i spreman, a većina fax, arhivskih ili OCR toolchainova ga rado ingestira ili sama pretvori u G4 u jednom downstream koraku. Ali ako vam je zahtjev doslovno Group 4 TIFF ravno iz biblioteke, to još nije to, i trebali biste planirati vlastiti compression stage. Znati gdje značajka staje vrijedno je koliko i znati što radi

Obje metode su namjerno male, i to je design lekcija koju vrijedi ponijeti s ove stranice: convenience API koji sjedi na vrhu renderera može dodati stvarnu sposobnost, monochrome izlaz, region cropping, bez diranja rasterizera i destabilizacije svakog drugog pozivatelja. Kad trebate birati između render enginea za underlying rasterization, pregled multi-engine PDF rendering in Delphi pokriva trade-offe u dubinu. Da biste vidjeli punu rendering surface i ostatak API-ja, PDF Library for Delphi Delphi PDF Library stranica proizvoda ima potpunu sliku