losLab PDF Library for Delphi comprimeert een gescande pagina met Mixed Raster Content via TPDFlib.CompressPageAsMRC. De methode rendert de pagina, maakt met een drempelwaarde een 1-bit voorgrondmask op volledige resolutie, downsamplet de rest naar een JPEG-achtergrond en bouwt de pagina opnieuw op als twee images: een /DCTDecode-achtergrond en een /CCITTFaxDecode-stencil mask geschilderd in de huidige vulkleur
De pagina waarvoor dit bestaat is degene waar elk documentarchief vol van zit: een kleurenscan van 300 DPI van een factuur of een contract, opgeslagen als één grote JPEG. Encodeert u hem met een kwaliteit die de tekst leesbaar houdt, dan wordt het bestand groot; zakt u in kwaliteit, dan komen er halo's om de letters en schreven die uitsmeren, want het slechtste wat JPEG kan is een scherpe zwarte rand op wit papier. Mixed Raster Content vraagt niet langer één codec om twee klussen te doen
Waarom maakt één JPEG gescande tekst zowel wazig als groot?
Eén JPEG behandelt gescande tekst slecht, omdat de DCT-codec zijn bits uitgeeft aan gladde gradiënten en harde randen benadert met hoogfrequente coëfficiënten die quantisatie als eerste weggooit. Tekst op een scan bestaat bijna uit niets anders dan harde randen. Die randen schoon houden dwingt tot een hoge kwaliteitsinstelling voor de hele pagina, inclusief de papierstructuur, het logo en de koffievlek, die er geen van allen iets aan hadden
Het MRC-idee splitst de pagina op wat elke regio werkelijk is. Inkt wordt een bilevel-image, waarin een faxcodec zoals CCITT reeksen zwarte en witte pixels lossless en zeer compact vastlegt. Al de rest, de papiertint, foto's, gekleurde achtergronden, wordt een continu-tone image die op een fractie van de resolutie kan worden opgeslagen, want niemand leest een achtergrond op 300 DPI. De bilevel-laag houdt de tekst strak op volledige resolutie; de achtergrondlaag houdt de kleur tegen lage kosten
Hoe splitst CompressPageAsMRC een pagina in twee lagen?
TPDFlib.CompressPageAsMRC doorloopt per pagina één vaste pipeline. De methode rendert de pagina op de gevraagde DPI naar een 24-bit bitmap, classificeert elke pixel met een luminantie gewogen volgens Rec. 601 (299/587/114 promille) tegen Threshold en schrijft het resultaat weg in een 1-bit mask op dezelfde resolutie. Daarnaast verkleint hij dezelfde render met BGScale via een boxgemiddelde, dus een BGScale van 4 maakt van elk blok van 4×4 pixels één achtergrondpixel
Daarna wordt de pagina-inhoud vervangen. De originele operators verdwijnen, de gedownsamplde achtergrond wordt ingebed als /DCTDecode-image in /DeviceRGB (ISO 32000-1 §7.4.8), het mask wordt met de CCITT-encoder van de library gecodeerd als /CCITTFaxDecode-image (§7.4.6) en gemarkeerd met /ImageMask true, en beide worden over de volledige page box getekend met het mask erbovenop. De signatuur is kort:
function CompressPageAsMRC(Page: Integer; DPI: Double;
BGScale, JPEGQuality, Threshold, Options: Integer): Integer;
Pageis 1-based en moet binnenPageCountliggenDPIis de renderresolutie van de bronraster, 50 tot 1200BGScaleis de downsamplefactor van de achtergrond, 1 tot 8JPEGQualityis de JPEG-kwaliteit van de achtergrond, 1 tot 100Thresholdis de luminantiegrens, 1 tot 255; pixels op of boven de grens zijn papier, donkerdere pixels zijn inktOptionsis gereserveerd en moet 0 zijn
Een typische batch over een gescand bestand ziet er zo uit. Roep SetFillColor aan vlak vóór elke pagina, om een reden die de volgende sectie uitlegt
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); // inktkleur van de stencil-laag
// 300 DPI mask, 75 DPI achtergrond, JPEG-kwaliteit 60, grens bij 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;
Waarom wordt de voorgrond geschilderd in de huidige vulkleur?
De voorgrondlaag is een stencil mask, en een stencil mask heeft geen eigen kleur. ISO 32000-1 §8.9.6.2 definieert een image met /ImageMask true als een 1-bit vorm: samplewaarden markeren waar de verf heen gaat, en de verf is welke nonstroking kleur er op dat moment in de graphics state staat. Precies dat maakt de laag goedkoop, en het is ook de reden dat CompressPageAsMRC geen kleurparameter heeft
TPDFlib.CompressPageAsMRC schildert het mask met de kleur die SetFillColor heeft gezet, dus de aanroeprangschikking telt mee. De schilder geeft een vulkleur lui af: SetFillColor noteert de kleur en markeert hem als gewijzigd, en de stenciltekenopdracht schrijft de rg-operator pas binnen zijn eigen q/Q-blok zolang die wijziging nog openstaat. Tekent u er een gevulde vorm tussendoor, dan wordt de openstaande wijziging door die vorm opgebruikt. De kleur vlak vóór elke pagina zetten, zoals in de batch hierboven, haalt het giswerk weg. Zwart is de normale keuze; donker marineblauw werkt voor formulieren in blauwe inkt waarbij u wilt dat de output minder op een kopie lijkt
// Formulier in blauwe balpen: render de inktlaag in donker marineblauw
Lib.SetFillColor(0.08, 0.12, 0.35);
Replaced := Lib.CompressPageAsMRC(PageNo, 300, 4, 55, 170, 0);
De plaatsing volgt de coördinaatoorsprong van de library. CompressPageAsMRC leest GetOrigin en tekent beide images vanaf de bovenrand van de page box: topcoördinaat 0 als de oorsprong linksboven ligt (SetOrigin(1)), topcoördinaat PageHeight bij de standaardoorsprong linksonder. U hoeft niets aan te passen, maar het verklaart waarom de lagen correct uitkomen met welke oorsprong uw pipeline al gebruikte
Eén consequentie van de stencil-semantiek is het helder vermelden waard: elke pixel die als inkt is geclassificeerd krijgt dezelfde kleur. Een rode stempel, een blauwe handtekening en zwarte lopende tekst komen alle terecht in één vulkleur op de voorgrondlaag. Alleen de achtergrond-JPEG bewaart eronder een flauw spoor op lage resolutie van de originele kleuren
DPI, BGScale, Threshold en JPEG-kwaliteit kiezen
De waarde DPI bepaalt de scherpte van de tekst, en 300 is het verstandige standaardgetal voor kantoorscans. Het mask wordt op volledige renderresolutie gehouden, dus DPI bepaalt hoeveel strokedetail de drempel overleeft. Verder gaan dan de resolutie van de originele scan levert niets op, want de renderer zou alleen de ingebedde scan-image upsamplen. Geheugen is de andere grens: een US Letter-pagina op 300 DPI rendert naar 2550×3300 pixels, 25.245.000 bytes aan 24-bit pixeldata voordat mask en achtergrond zijn afgeleid, en dezelfde pagina op het plafond van 1200 DPI is 10200×13200 pixels, zo'n 404 MB
BGScale wisselt achtergronddetail in tegen grootte. Op 300 DPI geeft factor 4 een achtergrond van 75 DPI, 638×825 pixels voor Letter, wat ruim voldoende is voor papiertint en egale kleurvlakken. Pagina's met fotografie of rastrerillustraties die een lezer echt gaat bekijken verdienen een 2; platte tekst op wit papier kan 6 of 8 aan, want de achtergrond is dan nauwelijks meer dan een tint
Threshold is de parameter die verandert hoe de pagina eruitziet. Verhogen classificeert meer midgrijze pixels als inkt, wat dunne strepen en potloodlijnen dikker maakt maar ook scannerruis en papierschaduw als spikkels binnentrekt. Verlagen geeft schoner papier en dunnere, soms gebroken letters. Waarden rond 128 tot 170 zijn een redelijk startbereik voor zwarte tekst op wit; de afweging is dezelfde als in PDF-pagina's renderen naar 1-bit monochrome in Delphi, waar dezelfde luminantiedrempel RenderPageToMonochromeFileEx aandrijft. Anders dan die methode heeft CompressPageAsMRC geen despeckle-pas, dus ruisige scans zijn beter af met een iets lagere drempel
JPEGQuality telt minder zwaar dan bij een scan van één image, want de achtergrond draagt geen tekstrand meer. Kwaliteit 50 tot 70 is op een achtergrond van 75 DPI achter een scherp mask doorgaans onzichtbaar
Wat zegt de retourwaarde, en wat niet?
TPDFlib.CompressPageAsMRC geeft de grootte in bytes terug van de pagina-inhoud die hij verving, en 0 wanneer iets faalde. Het is een succesindicator, geen maat voor de winst. Op een typische gescande pagina is de originele content stream maar een paar tientallen bytes, net genoeg operators om één image te plaatsen, dus een klein positief getal is het normale resultaat, ook wanneer de pagina met megabytes krimpt. Een pagina waarvan de originele content stream leeg was, meldt 0 ook al is hij herbouwd
Het faalgedrag is niet atomair, en dat is het deel waar u uw ontwerp op moet inrichten. Argumentcontroles, de render en de JPEG-encoding draaien voordat de pagina wordt aangeraakt, dus een BGScale buiten bereik, een JPEGQuality van 101 of een mislukte render laat de pagina precies zoals hij was. Zodra de originele inhoud echter is gewist, geeft het falen om een van de lagen in te bedden of te tekenen ook 0 terug, en op dat punt kan de pagina al gedeeltelijk herbouwd zijn. Behandel elke 0 als reden om het in-memory document weg te gooien in plaats van het op te slaan, en schrijf het resultaat nooit over het bronbestand
Om erachter te komen wat de conversie werkelijk opleverde, meet u het document. De lengte van SaveToString voor en na vergelijken is de snelste controle; voor een antwoord per categorie laat de PDF-bestandsgrootte-audit met AuditDocumentSpace zien of het gewicht zoals verwacht uit de images is verdwenen
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;
Waar deze tweelaagse MRC tekortschiet ten opzichte van ITU-T T.44
CompressPageAsMRC implementeert een met opzet vereenvoudigde MRC. Het volledige model in ITU-T T.44 gebruikt drie lagen per stripe: een achtergrondimage, een voorgrondimage die de inktkleuren draagt en een bilevel mask die er pixelgewijs tussen kiest, met ruimte voor meerdere paren van mask en voorgrond op één pagina. In PDF-termen zou de derde laag een kleurvoorgrondimage op lage resolutie zijn met een expliciet mask (§8.9.6.3) in plaats van een stencil in één kleur. PDF Library for Delphi slaat de voorgrondkleurlaag over, wat het resultaat zo klein maakt en ook de reden waarom gekleurde inkt in de vulkleur vervalt
- De methode past bij zwarte of single-color tekst op gescand papier; documenten met kleurcoderingen, gemarkeerde tekst en gekleurde stempels verliezen hun inktkleur
- De herbouwde pagina is puur raster: alle echte tekst, vectorwerk of selecteerbare tekens op de originele pagina worden pixels, dus zet hem in op scans, niet op born-digital pagina's, waar font subsetting en image-downsampling de juiste gereedschappen zijn
- Het mask gebruikt de CCITT-encoder van de library in T.4 tweedimensionale modus, weggeschreven als
/CCITTFaxDecodemet/K 1; de encoder heeft ook een Group 4-modus viaAddImageCCITTFromBitmapStream, maarCompressPageAsMRCstelt die niet bloot, en JBIG2 wordt niet gebruikt - Annotaties en formuliervelden worden niet afgevlakt; ze blijven als objecten boven de herbouwde inhoud bestaan
- De achtergrond is
/DeviceRGB, dus de output is niet geschikt voor PDF/X-1a-workflows, enSetFillColorzelf wordt in PDF/X-1a-modus geweigerd - Een scan die toch al OCR nodig heeft, moet vóór de MRC worden herkend, terwijl de originele pixels nog op volledige kleurdiepte bestaan
FAQ
Houdt CompressPageAsMRC de tekst doorzoekbaar?
Nee. CompressPageAsMRC vervangt de pagina-inhoud door twee images, dus alle tekstoperators op de originele pagina, inclusief een onzichtbare OCR-tekstlaag, verdwijnen mee. Voer OCR daarna uit op de gecomprimeerde pagina, of voeg de tekstlaag na compressie weer toe
Kan ik alleen enkele pagina's van een document comprimeren?
Ja. CompressPageAsMRC werkt per aanroep op één pagina met 1-based nummering, dus u kiest de pagina's en kunt zelfs BGScale en Threshold per pagina variëren, bijvoorbeeld factor 2 voor een foto-invoegsel en 6 voor pagina's met platte tekst
Wat moet de parameter Options zijn?
Geef 0 mee. Options is gereserveerd voor toekomstig gebruik in de huidige release en er zijn geen bits gedefinieerd, dus een waarde ongelijk nul heeft vandaag geen betekenis
CompressPageAsMRC verscheen in losLab PDF Library for Delphi v3.561.0, samen met de render-, JPEG-, CCITT- en stencil mask-bouwstenen waaruit hij is opgebouwd, allemaal beschikbaar als publieke methoden als u een variant van de pipeline nodig heeft. De library, de C++Builder-ondersteuning en de volledige API-referentie staan op de productpagina van PDF Library for Delphi