Prehod za faks ne želi vašega 24-bitnega izrisa strani. Tudi arhivski cevovod, ki shrani milijon računov, videti kot skeni, ga ne želi, prav tako ne vhod OCR, ki vse najprej zreducira na črno in belo, preden sploh poišče kak znak. Vsi trije želijo isto: čisto 1-bitno bitno sliko, en bit na piksel, kjer je vsaka pika bodisi črnilo bodisi papir. Če jim ponudite polnobarvni BMP, bodo tako ali tako zavrgli 23 bitov na piksel, pogosto s slabšim prehodom ditheringa, kot bi ga lahko naredili sami. Zanimivo vprašanje je, kje naj se ta pretvorba navzdol zgodi, odgovor v PDFlibPas pa pove nekaj uporabnega o tem, kako razširiti izrisovalnik, ki ga raje ne bi prepisovali
PDFlibPas je izvorna knjižnica PDF v Object Pascal za Delphi in C++Builder. Njeno jedro za izris rasterizira stran v bitno sliko in zna izpisati BMP, PNG, JPEG, WMF in še nekaj drugih formatov. Do nedavnega pa ni znalo vrniti resnično enobarvne bitne slike niti izrisati samo dela strani. Oboje je prispelo v v3.83.0 in oboje je bilo zgrajeno kot tanka plast priročnih metod nad obstoječim izrisovalnikom, ne pa kot sprememba rasterizatorja samega. Prav ta omejitev je celotna zgodba
Zakaj pretvoriti navzdol po izrisu, ne znotraj izrisovalnika
Očiten način za izdelavo 1-bitne slike je, da rasterizatorju poveste, naj riše v 1 bitu. To je tudi način, ki pokvari vse ostalo. Interna bitna slika izrisovalnika je ustvarjena s trdo zakodiranim PixelFormat := pf24bit v konstruktorju PDFlibRenderer, ta 24-bitna površina pa je skupna vsem potem izrisa: izvozu v PNG, predogledu v device context, izhodu v JPEG, vsemu. Če jo pri izvoru preklopite na pf1bit, niste dodali enobarvne funkcije, temveč ste poslabšali barvno zvestobo za vsakega klicatelja v knjižnici in se prijavili na odpravljanje ducata kasnejših regresij
Zato RenderPageToMonochromeFile ubere nasprotno pot. Stran najprej izriše normalno, v začasni 24-bitni BMP, in jo šele nato kot naknadni korak zloži na 1 bit. Izrisovalnik ostane nedotaknjen. Enobarvno vedenje živi v celoti znotraj priročne metode, kar pomeni, da ne more vplivati na nikogar, ki je ne pokliče. To je vrsta kompromisa, ki ga je vredno izrecno poimenovati: naknadna obdelava plača eno dodatno alokacijo bitne slike in začasno datoteko, v zameno pa obdrži nosilno jedro popolnoma zunaj dosega. Za funkcijo, ki obstaja za robne primere faksa in arhiviranja, je to prava stran knjige
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;
Kako se 1-bitno zlaganje dejansko zgodi
Pretvorba navzdol se opira na GDI, ne na ročno napisano zanko za prag, in izbira je pomembna za kakovost izhoda. Znotraj metode se začasna 24-bitna bitna slika naloži v TBitmap, druga TBitmap pa se ustvari z PixelFormat := pf1bit v istih dimenzijah, nato pa se piksli prestavijo čez z enim samim blitom:
// 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 z HALFTONE. Čeprav sta izvor in cilj enake velikosti, torej do skaliranja ne pride, način stretch še vedno določa, kako GDI preslika barve v 1-bitno paleto. HALFTONE povzroči poltonski dithering, ki siva področja in robove z glajenjem spremeni v vzorce črnih in belih pik, namesto v trdo obrezovanje na najbližjo od dveh barv. Če klic načina izpustite ali uporabite privzeti BLACKONWHITE, se sivinsko gradivo posterizira v grobe pragovno odrezane oblike. Za izhod skeniranih dokumentov in predobdelavo OCR je rezultat z ditheringom skoraj vedno tisto, kar želite
Ena podrobnost je neizpogajljiva in jo je lahko zgrešiti: začasni izris mora biti BMP. RenderPageToMonochromeFile pokliče splošni izrisovalnik s kodo možnosti 0, kar pomeni BMP. Argument možnosti pri RenderPageToFile je majhna celoštevilska enumeracija in vrednosti za ta namen niso zamenljive: 0 pomeni BMP, 1 JPEG, 2 WMF, 3 EMF, 5 PNG in tako naprej. Pretvornik navzdol nato nad začasno datoteko izvede TBitmap.LoadFromStream. Če mu s podajanjem 2 podtaknete WMF, bo nalaganje vrglo "Bitmap image is not valid", ker je Windows Metafile tok vektorskih zapisov, ne DIB. Enobarvna pretvorba navzdol je rastarska operacija od začetka do konca, zato mora biti tudi vmesni format rastarski
Izris samo podregije strani
Druga metoda, RenderPageRegionToFile, izriše samo pravokotnik strani namesto cele strani. Primeri uporabe so znani vsakomur, ki je kdaj zgradil pregledovalnik dokumentov: izrez bloka s podpisom iz pogodbe, ustvarjanje ploščice za povečani zemljevid velike risbe ali vlečenje ene same žigosane regije za sličico, ne da bi morali celotno stran pri visokem DPI rasterizirati do konca. Podpis je preprost:
// 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');
Niz za clip so štiri z vejicami ločene vrednosti double v točkah PDF, ki jih metoda ročno razčleni, da se izogne lokalnim posebnostim in kapricam DelimitedText. Iz širine in višine metoda izračuna velikost izhodne bitne slike kot Round(Width * DPI / 72) krat Round(Height * DPI / 72), alocira bitno sliko pf24bit točno te velikosti v pomnilniku in izriše v njen device context prek RenderPageToDCClip. Rezultatna datoteka vsebuje samo izrezani pravokotnik in je dimenzionirana na regijo, ne na celotno stran
Parameter clip, ki ni naredil ničesar
Tu je bilo delo ostrejše, kot je videti. RenderPageToDCClip je dolgo časa nosil parameter Clip, in to je bila laž. Klic je argument sprejel, ga podal v TPDFPageTree.RenderPageToDC, ta implementacija pa ga je popolnoma ignorirala in ga nikoli ni predala izrisovalniku. Podali ste lahko kakršenkoli pravokotnik in vedno dobili celo stran nazaj. Vsakdo, ki je povezal RenderPageToDCClip in pričakoval izrez, je dobival izris cele strani in, odvisno od postavitve, tega morda sploh ni opazil
v3.83.0 je žico priklopil. RenderPageToDC zdaj razčleni isti pravokotnik "Left,Top,Width,Height" v točkah in ga pred risanjem uporabi kot pravo regijo clip GDI na ciljnem device context. Pretvorba iz točk v piksle naprave je običajni faktor skale DPI / 72, uporabljen na vseh štirih robovih. Zaporedje okoli izrisa je standardni ples shrani/clip/obnovi:
// 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);
Par SaveDC / RestoreDC(-1) poskrbi, da je to varno pri večkratnem klicanju: regija clip se potisne na sklad stanja DC, stran se nariše, prvotni clip pa se vrne ne glede na to, kako se izris konča. RestoreDC(TargetDC, -1) obnovi nazadnje shranjeno stanje, kar je standardni idiom za uravnoteženo shrani/obnovi. Če obnovo preskočite, bo klicatelj, ki isti DC ponovno uporabi za naslednji izris cele strani, skrivnostno odrezan na zadnjo regijo. Popravek mrtvega parametra je mimogrede brezplačno popravil tudi RenderPageRegionToFile, saj gre nova metoda natanko skozi to pot
Ena vedenjska točka, ki jo je treba ponotranjiti: clip obreže, ne pa poveča. Stran je še vedno rasterizirana pri DPI, ki ste ga zahtevali, v svojem običajnem položaju, regija clip pa preprosto zavrže vse zunaj pravokotnika. Regije ne povečujete, da bi zapolnila izhod; iz polnega izrisa visoke ločljivosti izrežete okno. Če želite povečano regijo, dvignite DPI. Koordinate pravokotnika se po skaliranju iz točk v piksle razlagajo v prostoru naprave, merjeno od zgornjega levega kota izrisane površine, zato načrtujte svoja Left in Top od vrha strani navzdol. Za globlji ogled tega, kako PDFlibPas žene device context za izhod na zaslon, spremljevalni članek o predogledu tiskanja in izhodu v device context razloži enake cevi DC z zaslonske strani
Poštena meja: 1-bitni BMP, ne G4 TIFF
To bi bilo lahko prelahko prodati kot "izhod, pripravljen za faks", zato je tu meja povedana naravnost. RenderPageToMonochromeFile izdela pf1bit BMP. Ne izdela TIFF v formatu CCITT Group 4, kar je format, ki ga dejanski delovni tok faksa ali arhiv TIFF običajno pričakuje. Razlog je konkreten, ne spregled: enota CCITT v PDFlibPas trenutno dekodira tokove G4, nima pa kodirnika G4 encoder. Brez kodirnika ni mesta, kamor bi zapisali stisnjene enobarvne nize, zato se enobarvna pot ustavi pri nestisnjenem 1-bitnem DIB
V praksi je to še vedno uporabno. 1-bitni BMP je pravilen format pikslov, z ditheringom in pripravljen, večina cevovodov za faks, arhiviranje ali OCR pa ga bo z veseljem sprejela ali pa ga v enem naslednjem koraku sama pretvorila v G4. Če pa je vaša zahteva dobesedno TIFF Group 4 neposredno iz knjižnice, to še ni to in načrtovati bi morali lastno stopnjo stiskanja. Vedeti, kje se funkcija konča, je vredno toliko kot vedeti, kaj počne
Obe metodi sta namenoma majhni in prav to je oblikovalska lekcija, ki jo je vredno odnesti s te strani: priročni API, ki sedi nad izrisovalnikom, lahko doda resnično zmožnost - enobarvni izhod, izrez regije - ne da bi segel v rasterizator in destabiliziral vsakega drugega klicatelja. Ko pa morate izbirati med pogoni za izris v ozadju, pregled večmotornega izrisa PDF v Delphi poglobljeno pokrije kompromise. Če želite videti celotno površino izrisa in preostali API, vam stran izdelka PDFlibPas Delphi PDF Library pokaže popolno sliko