losLab PDF Library for Delphi komprimerar en inskannad sida med Mixed Raster Content via TPDFlib.CompressPageAsMRC. Metoden renderar sidan, trösklar fram en 1-bitars förgrundsmask i full upplösning, nedskalar resten till en JPEG-bakgrund, och bygger om sidan som två bilder: en /DCTDecode-bakgrund och en /CCITTFaxDecode-stencilmask målad i aktuell fyllnadsfärg
Sidan det här finns för är den som varje dokumentarkiv är fullt av: en 300 DPI-färgskanning av en faktura eller ett avtal, lagrad som en enda stor JPEG. Koda den med en kvalitet som håller texten läsbar och filen blir stor; sänk kvaliteten och bokstäverna får ringande halos och utsmetade seriffer, för det JPEG är sämst på är en skarp svart kant på vitt papper. Mixed Raster Content slutar be en enda codec göra två jobb
Varför gör en enda JPEG inskannad text både suddig och stor?
En enda JPEG hanterar inskannad text dåligt eftersom DCT-codecen lägger sina bitar på mjuka gradienter och approximerar hårda kanter med högfrekventa koefficienter som kvantiseringen kastar bort först. Text på en skanning är i princip inget annat än hårda kanter. Att hålla de kanterna rena tvingar fram en hög kvalitetsinställning över hela sidan, inklusive papperstexturen, logotypen och kaffefläcken, varav inget behövde den
MRC-idén delar sidan efter vad varje region faktiskt är. Bläcket blir en bilevel-bild, där en faxcodec som CCITT sparar runor av svarta och vita pixlar förlustfritt och mycket kompakt. Allt annat, papperstonen, fotografierna, de färgade bakgrundarna, blir en kontinuerlig tonbild som kan lagras på en bråkdel av upplösningen, för ingen läser en bakgrund i 300 DPI. Bilevel-lagret håller texten knivskarp i full upplösning; bakgrundslagret håller färgen till låg kostnad
Hur delar CompressPageAsMRC en sida i två lager?
TPDFlib.CompressPageAsMRC kör en fast pipeline per sida. Den renderar sidan vid begärd DPI till en 24-bitars bitmap, klassificerar varje pixel med en Rec. 601-viktad luminans (299/587/114 promille) mot Threshold, och skriver resultatet i en 1-bitars mask i samma upplösning. Parallellt krymper den samma rendering med BGScale och ett boxmedelvärde, så en BGScale på 4 gör varje 4×4-block av pixlar till en bakgrundspixel
Sidinnehållet ersätts sedan. De ursprungliga operatorerna tas bort, den nedskalade bakgrunden bäddas in som en /DCTDecode-bild i /DeviceRGB (ISO 32000-1 §7.4.8), masken kodas med bibliotekets CCITT-kodare som en /CCITTFaxDecode-bild (§7.4.6) och flaggas /ImageMask true, och båda ritas över hela sidrutan med masken överst. Signaturen är kort:
function CompressPageAsMRC(Page: Integer; DPI: Double;
BGScale, JPEGQuality, Threshold, Options: Integer): Integer;
Pageär 1-baserad och måste ligga inomPageCountDPIär renderupplösningen för källrastern, 50 till 1200BGScaleär bakgrundens nedskalningsfaktor, 1 till 8JPEGQualityär bakgrundens JPEG-kvalitet, 1 till 100Thresholdär luminansgränsen, 1 till 255; pixlar på eller över den är papper, mörkare pixlar är bläckOptionsär reserverad och måste vara 0
En typisk batch över en inskannad fil ser ut så här. Anropa SetFillColor precis före varje sida, av den anledning nästa avsnitt förklarar
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); // stencillagrets bläckfärg
// 300 DPI-mask, 75 DPI-bakgrund, JPEG-kvalitet 60, gräns vid 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;
Varför ritas förgrunden i aktuell fyllnadsfärg?
Förgrundslagret är en stencilmask, och en stencilmask har ingen egen färg. ISO 32000-1 §8.9.6.2 definierar en bild med /ImageMask true som en 1-bitars form: samplevärdena markerar var färgen ska hamna, och färgen är den icke-strijkande färg som är aktuell i grafiktillståndet. Det är exakt det som gör lagret billigt, och det är också därför CompressPageAsMRC inte tar någon färgparameter
TPDFlib.CompressPageAsMRC målar masken med färgen som satts av SetFillColor, så anropsordningen spelar roll. Målaren sänder fyllnadsfärgen lat: SetFillColor registrerar färgen och markerar den som ändrad, och stencilritningen skriver operatorn rg inuti sitt eget q/Q-block bara medan ändringen fortfarande är väntande. Rita en fylld form emellan och den väntande ändringen konsumeras av den formen i stället. Att sätta färgen direkt före varje sida, som i batchen ovan, tar bort gissningsleken. Svart är det normala valet; en mörk marinblå fungerar för blanketter med blått bläck där du vill att resultatet ska se mindre ut som en fotokopia
// Blå kulspetsblankett: rita bläcklagret i mörk marinblå
Lib.SetFillColor(0.08, 0.12, 0.35);
Replaced := Lib.CompressPageAsMRC(PageNo, 300, 4, 55, 170, 0);
Placeringen följer bibliotekets koordinatursprung. CompressPageAsMRC läser GetOrigin och ritar båda bilderna från sidrutans överkant: toppkoordinat 0 när ursprunget är uppe till vänster (SetOrigin(1)), toppkoordinat PageHeight med standardursprunget nere till vänster. Du behöver inte justera något, men det förklarar varför lagren hamnar rätt oavsett vilket ursprung din pipeline redan använde
En konsekvens av stencilsemantiken är värd att säga rakt ut: varje pixel klassad som bläck får samma färg. Ett rött stämpel, en blå signatur och svart löptext hamnar alla i en fyllnadsfärg på förgrundslagret. Bara bakgrunds-JPEG:en bevarar ett svagt, lågupplöst spår av originalfärgerna under
Att välja DPI, BGScale, Threshold och JPEG-kvalitet
Värdet DPI avgör textens skärpa, och 300 är det förnuftiga standardvalet för kontorsskanningar. Masken hålls i full renderupplösning, så DPI styr hur fint ett streck som överlever trösklingen. Att gå högre än skanningsupplösningen köper ingenting, eftersom renderaren bara skulle skala upp den inbäddade skannade bilden. Minnet är den andra gränsen: en US Letter-sida i 300 DPI renderas till 2550×3300 pixlar, 25 245 000 byte 24-bitars pixeldata innan mask och bakgrund härletts, och samma sida vid 1200 DPI-taket är 10200×13200 pixlar, omkring 404 MB
BGScale byter bakgrundsdetalj mot storlek. Vid 300 DPI ger en faktor 4 en 75 DPI-bakgrund, 638×825 pixlar för Letter, vilket räcker gott för papperston och platta färgytor. Sidor med fotografier eller rastrerade illustrationer som en läsare faktiskt tittar på förtjänar 2; ren text på vitt papper kan ta 6 eller 8, eftersom bakgrunden knappt är mer än en ton
Threshold är parametern som ändrar hur sidan ser ut. Att höja den klassificerar fler mellangrå pixlar som bläck, vilket tjocknar tunna streck och pennstreck men också drar in skannerbrus och pappersskugga som prickar. Att sänka den ger renare papper och tunnare, ibland brutna, bokstäver. Värden runt 128 till 170 är ett rimligt startintervall för svart text på vitt; avvägningen mellan de två är samma som i att rendera PDF-sidor till 1-bitars monokromt i Delphi, där samma luminanströskel driver RenderPageToMonochromeFileEx. Till skillnad från den metoden har CompressPageAsMRC inget despeckle-steg, så brusiga skanningar mår bättre av en något lägre tröskel
JPEGQuality spelar mindre roll än för en enfärgad bildskanning, eftersom bakgrunden inte längre bär några textkanter. Kvalitet 50 till 70 är oftast osynligt på en 75 DPI-bakgrund bakom en skarp mask
Vad säger returvärdet dig, och vad säger det inte?
TPDFlib.CompressPageAsMRC returnerar storleken i byte av det sidinnehåll den ersatte, och 0 när något misslyckades. Det är en framgångsindikator, inte ett mått på besparingar. På en typisk inskannad sida är originalinnehållsströmmen några dussin byte, bara tillräckligt med operatorer för att placera en bild, så ett litet positivt tal är det normala resultatet även när sidan krymper med megabyte. En sida vars ursprungliga innehållsström var tom rapporterar 0 fast den byggdes om
Felbeteendet är inte atomärt, och det är den del som ska designas kring. Argumentkontroller, renderingen och JPEG-kodningen körs innan sidan röras, så en BGScale utanför intervallet, en JPEGQuality på 101, eller ett renderingsfel lämnar sidan exakt som den var. När originalinnehållet väl rensats returnerar dock även ett misslyckande att bädda in ett av lagren eller rita det 0, och då kan sidan redan vara delvis ombyggd. Behandla varje 0 som en anledning att kasta det minnesbundna dokumentet snarare än att spara det, och skriv aldrig resultatet över källfilen
För att ta reda på vad konverteringen faktiskt sparade, mät dokumentet. Att jämföra längden på SaveToString före och efter är den snabbaste kontrollen; för ett svar per kategori visar PDF-filstorleksgranskningen med AuditDocumentSpace om vikten flyttade ut ur bilderna som förväntat
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;
Vad tvålager-MRC:n saknar jämfört med ITU-T T.44
CompressPageAsMRC implementerar en medvetet förenklad MRC. Den fullständiga modellen i ITU-T T.44 använder tre lager per stripe: en bakgrundsbild, en förgrundsbild som bär bläckfärgerna, och en bilevel-mask som väljer mellan dem pixel för pixel, med utrymme för flera mask- och förgrundspar på en sida. I PDF-termer skulle tredje lagret vara en lågupplöst färgad förgrundsbild med en explicit mask (§8.9.6.3) i stället för en stencil målad i en färg. PDF Library for Delphi hoppar över förgrundsfärglagret, vilket är det som gör resultatet så litet och också varför färgat bläck kollapsar i fyllnadsfärgen
- Metoden passar svart eller enfärgad text på inskannat papper; färgkodade dokument, textmarkeringar och färgade stämplar tappar sin bläckfärg
- Den ombyggda sidan är ren raster: verklig text, vektorgrafik eller valbara tecken på originalsidan blir pixlar, så kör den på skanningar, inte på födda-digitala sidor, där typsnittssubsetting och bildnedskalning är de rätta verktygen
- Masken använder bibliotekets CCITT-kodare i T.4 tvådimensionellt läge, skriven som
/CCITTFaxDecodemed/K 1; kodaren har också ett Group 4-läge viaAddImageCCITTFromBitmapStream, menCompressPageAsMRCexponerar det inte, och den använder inte JBIG2 - Annoteringar och formulärfält plattas inte ut; de blir kvar som objekt ovanför det ombyggda innehållet
- Bakgrunden är
/DeviceRGB, så resultatet passar inte PDF/X-1a-arbetsflöden, ochSetFillColorsjälv avvisas i PDF/X-1a-läge - En skanning som ändå behöver OCR bör kännas igen före MRC, medan originalpixlarna fortfarande finns i fullt färgdjup
FAQ
Behåller CompressPageAsMRC texten sökbar?
Nej. CompressPageAsMRC ersätter sidinnehållet med två bilder, så alla textoperatorer på originalsidan, inklusive ett osynligt OCR-textlager, tas bort med den. Kör OCR på den komprimerade sidan efteråt, eller lägg tillbaka textlagret efter komprimeringen
Kan jag komprimera bara vissa sidor i ett dokument?
Ja. CompressPageAsMRC arbetar på en 1-baserad sida per anrop, så du väljer sidorna och kan till och med variera BGScale och Threshold per sida, till exempel faktor 2 för ett fotoinstick och 6 för rena textsidor
Vad ska parametern Options vara?
Skicka 0. Options är reserverad för framtida användning i aktuell release och inga bitar är definierade, så ett värde skilt från noll bär ingen betydelse i dag
CompressPageAsMRC kom i losLab PDF Library for Delphi v3.561.0 tillsammans med renderings-, JPEG-, CCITT- och stencilmaskbyggstenarna den är sammansatt av, alla tillgängliga som publika metoder om du behöver en variant av pipelinen. Biblioteket, dess C++Builder-stöd och den fullständiga API-referensen finns på produktsidan för PDF Library for Delphi