En faxgateway vil ikke have din 24-bit side-rendering. Det vil arkivpipen, der gemmer en million scannede fakturaer, heller ikke, og heller ikke OCR-frontenden, der tærskler alt til sort-hvid, før den overhovedet leder efter et tegn. Alle tre vil have det samme: et rent 1-bit bitmap, én bit pr. pixel, hvor hver prik enten er blæk eller papir. Giv dem et fuldfarve-BMP, og de smider alligevel 23 bit pr. pixel væk, som regel med en ringere dithering end du selv kunne have lavet. Det interessante spørgsmål er, hvor den nedkonvertering bør ske, og svaret i PDF Library for Delphi viser sig at sige noget nyttigt om, hvordan man udvider en renderer, man helst ikke vil omskrive
PDF Library for Delphi er et native Object Pascal PDF-bibliotek til Delphi og C++Builder. Dets rendering-kærne rasteriserer en side til et bitmap og kan udgive BMP, PNG, JPEG, WMF og en håndfuld andre formater. Det, den ikke gjorde før for nylig, var at give et ægte monokromt bitmap tilbage eller rendre kun en del af en side. Begge dele landede i v3.83.0, og begge blev bygget som tynde bekvemmelighedslag oven på den eksisterende renderer i stedet for som ændringer i selve rasterizeren. Den begrænsning er hele historien
Hvorfor nedkonvertere efter rendering og ikke inde i renderer'en
Den oplagte måde at producere et 1-bit billede på er at bede rasterizeren om at tegne i 1-bit. Det er også den måde, der ødelægger alt andet. Rendererens interne bitmap oprettes med en hardcodet PixelFormat := pf24bit i konstruktøren PDFlibRenderer, og den 24-bit overflade deles af hver eneste render-sti: PNG-eksport, device-context-previewet, JPEG-output, det hele. Vend den om til pf1bit ved kilden, og du har ikke tilføjet en monokrom funktion, du har forringet farvetroskaben for enhver kalder i biblioteket og meldt dig til at fejlfinde et dusin nedstrøms-regressioner
Så RenderPageToMonochromeFile tager den modsatte rute. Den rendrer siden normalt, til et midlertidigt 24-bit BMP, og først derefter kollapser den det til 1-bit som et efterbehandlingstrin. Rendereren er urørt. Den monokrome adfærd bor udelukkende i bekvemmelighedsmetoden, hvilket betyder, at den ikke kan påvirke nogen, der ikke kalder den. Det er den slags afvejning, det er værd at navngive eksplicit: en efterbehandling betaler én ekstra bitmap-allokering og en temp-fil, og til gengæld holder den en bærende kerne fuldstændig uden for scope. For en funktion, der findes for at betjene fax- og arkivrand-tilfælde, er det den rigtige side af regnskabet
var
Pdf: TPDFlib;
begin
Pdf := TPDFlib.Create;
try
Pdf.LoadFromFile('invoice.pdf');
// 200 DPI er den klassiske Group 4-faxopløsning; sideindeks er 1-baseret
Pdf.RenderPageToMonochromeFile(200, 1, 'invoice-page1.bmp');
finally
Pdf.Free;
end;
end;
Sådan sker 1-bit-sammenbruddet faktisk
Nedkonverteringen læner sig op ad GDI frem for en håndrullet tærskelløkke, og valget har betydning for outputkvaliteten. Inde i metoden indlæses det midlertidige 24-bit bitmap i en TBitmap, en anden TBitmap oprettes med PixelFormat := pf1bit i samme dimensioner, og pixelsene flyttes over med én enkelt blit:
// inde i RenderPageToMonochromeFile, efter indlæsning af det 24-bit ColorBmp
MonoBmp.PixelFormat := pf1bit;
MonoBmp.Width := ColorBmp.Width;
MonoBmp.Height := ColorBmp.Height;
// HALFTONE fortæller GDI at dithere den 24-bit kilde ned til 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');
Tricket er SetStretchBltMode med HALFTONE. Selvom kilde og destination har samme størrelse, så der ikke sker nogen skalering, styrer stretch-tilstanden stadig, hvordan GDI mapper farver ind i 1-bit-paletten. HALFTONE får den til at anvende halvtone-dithering, som omdanner grå områder og antialiasede tekstkanter til mønstre af sorte og hvide prikker frem for en hård afskæring til den nærmeste af to farver. Drop kaldet til tilstanden, eller brug standarden BLACKONWHITE, og gråtoneindhold posteriseres til blokagtige tærskelformer. For scannet-dokument- og OCR-forbehandlingsoutput er det ditherede resultat næsten altid, hvad du vil have
Én detalje er ikke til forhandling og let at få galt: den midlertidige render skal være et BMP. RenderPageToMonochromeFile kalder den generelle renderer med en options-kode på 0, som er BMP. Options-argumentet på RenderPageToFile er en lille heltalsenum, og værdierne kan ikke ombyttes til dette formål: 0 er BMP, 1 JPEG, 2 WMF, 3 EMF, 5 PNG, og så videre. Nedkonverteren gør derefter TBitmap.LoadFromStream på temp-filen. Fod den med et WMF ved at give 2, og den indlæsning kaster "Bitmap image is not valid", fordi en Windows Metafile er en vektor-record-stream, ikke en DIB. Monokrom nedkonvertering er en rasteroperation fra ende til anden, så mellemformatet skal være et rasterformat
Render kun et delområde af en side
Den anden metode, RenderPageRegionToFile, rendrer kun et rektangel af siden i stedet for det hele. Brugstilfældene er velkendte, når du først har bygget en dokumentviewer: at beskære en underskriftsblok ud af en kontrakt, generere et tile til et zoomet kort over en stor tegning, eller trække ét stemplet område ud til en miniature uden at betale for at rasterisere hele siden i høj DPI. Signaturen er ligetil:
// Clip er "Left,Top,Width,Height" i PDF-punkter (72 pt = 1 tomme)
// Her: en 2,5x1 tommer boks, én tomme inde fra sidens øverste venstre hjørne
Pdf.RenderPageRegionToFile(150, 1, '72,72,180,72', 'sig-block.bmp');
Clip-strengen er fire kommaseparerede doubles i PDF-punkter, parset manuelt inde i metoden for at omgå locale- og DelimitedText-finurligheder. Ud fra bredden og højden beregner metoden output-bitmap-størrelsen som Round(Width * DPI / 72) gange Round(Height * DPI / 72), allokerer et in-memory pf24bit-bitmap i nøjagtig den størrelse og rendrer ind i dets device context gennem RenderPageToDCClip. Resultatfilen indeholder kun det beskårne rektangel, i regionens størrelse frem for hele sidens
Clip-parameteren, der ikke gjorde noget
Her er, hvor arbejdet var skarpere, end det ser ud til. RenderPageToDCClip havde båret en Clip-parameter i lang tid, og den var en løgn. Kaldet accepterede argumentet, sendte det ned til TPDFPageTree.RenderPageToDC, og den implementering ignorerede det fuldstændigt og gav det aldrig videre til rendereren. Du kunne give et hvilket som helst rektangel og få hele siden tilbage. Enhver, der havde koblet RenderPageToDCClip op i forventning om en beskæring, fik en render af hele siden og havde afhængigt af sit layout måske ikke bemærket det
v3.83.0 koblede ledningen sammen. RenderPageToDC parser nu det samme "Left,Top,Width,Height"-punktrektangel og anvender det som en reel GDI-klipperegion på target device context, før rendereren tegner. Konverteringen fra punkter til enhedspixels er den sædvanlige DPI / 72-skaleringsfaktor, anvendt på alle fire kanter. Sekvensen omkring renderingen er den standard save/clip/restore-dans:
// inde i TPDFPageTree.RenderPageToDC, når Clip ikke er tom
ScaleFactor := DPI / 72;
SaveDC(TargetDC);
IntersectClipRect(TargetDC,
Round(ClipLeft * ScaleFactor),
Round(ClipTop * ScaleFactor),
Round((ClipLeft + ClipWidth) * ScaleFactor),
Round((ClipTop + ClipHeight) * ScaleFactor));
// ... rendereren tegner siden her ...
// i finally-blokken:
RestoreDC(TargetDC, -1);
Parret SaveDC / RestoreDC(-1) er, hvad der holder dette sikkert at kalde gentagne gange: klipperegionen skubbes op på DC-tilstandsstakken, siden tegnes, og den oprindelige klipning hentes tilbage, uanset hvordan renderingen afsluttes. RestoreDC(TargetDC, -1) gendanner den senest gemte tilstand, hvilket er standardidiomet for balanceret save/restore. Springer du gendannelsen over, ville en kalder, der genbruger den samme DC til en efterfølgende render af hele siden, opdage, at den mystisk nok var klippet til den sidste region. At rette den døde parameter fikserede også RenderPageRegionToFile gratis, eftersom den nye metode ruter gennem præcis denne sti
Ét adfærdspunkt at indoptage: klipningen beskærer, den skalerer ikke. Siden rasteriseres stadig ved den DPI, du bad om, i sin normale position, og klipperegionen kasserer simpelthen alt uden for rektanglet. Du zoomer ikke regionen op for at udfylde outputtet; du skærer et vindue ud af full-resolution-renderingen. Vil du have en region forstørret, så hæv DPI'en. Rektanglets koordinater fortolkes i device space efter punkt-til-pixel-skaleringen, målt fra den renderede overflades øverste venstre hjørne, så planlæg din Left og Top fra toppen af siden og ned. For en dybere gennemgang af, hvordan PDF Library for Delphi driver en device context til skærmoutput, gennemgår sidestykket om printvisning og device-context-output det samme DC-rørsystem fra visningssiden
Den ærlige grænse: 1-bit BMP, ikke G4 TIFF
Det ville være let at oversælge dette som "fax-klart output", så her er grænsen sagt ligeud. RenderPageToMonochromeFile producerer et pf1bit BMP. Den producerer ikke en CCITT Group 4 TIFF, hvilket er det format, en reel fax-workflow eller et TIFF-arkiv normalt forventer. Grunden er konkret snarere end en forglemmelse: PDF Library for Delphi's CCITT-enhed afkoder i øjeblikket G4-streams, men har ingen G4-encoder. Uden en encoder er der ingen steder at skrive komprimerede monokrome forløb, så den monokrome sti stopper ved et ukomprimeret 1-bit DIB
I praksis er det stadig nyttigt. Et 1-bit BMP er det korrekte pixelformat, ditheret og klart, og de fleste fax-, arkiv- eller OCR-toolchains vil gerne æde det eller selv konvertere det til G4 med ét nedstrøms trin. Men er dit krav bogstaveligt talt en Group 4 TIFF direkte ud af biblioteket, er det ikke det endnu, og du bør planlægge et komprimeringstrin selv. At vide, hvor en funktion stopper, er lige så meget værd som at vide, hvad den gør
Begge metoder er bevidst små, og det er designlektien værd at tage med fra denne side: en bekvemmeligheds-API, der sidder oven på en renderer, kan tilføje reel funktionalitet, monokromt output, regionsbeskæring, uden at række ind i rasterizeren og destabilisere enhver anden kalder. Skal du vælge mellem render-motorer til den underliggende rasterisering, dækker oversigten over multi-engine PDF-rendering i Delphi afvejningerne i dybden. For at se hele render-overfladen og resten af API'en har produktsiden for PDF Library for Delphi Delphi PDF Library det fulde billede