Technisch artikel

HotPDF image downsampling kernels en print-dithering

HotPDF stelt drie image downsampling-kernels beschikbaar via de property ImageDownsampleKernel en een aparte Floyd-Steinberg-pas via RenderOutputDither. De eerste bepaalt hoe foto's eruitzien nadat u ze verkleint om binnen een size budget te blijven, de tweede bepaalt hoe ze eruitzien nadat een pagina tot zwart-wit is gereduceerd. Geen van beide staat standaard aan, en beide zijn opt-in om dezelfde reden: ze kosten echte tijd

De druk die mensen hierheen brengt, is bekend. Een gescand contract van 60 MB moet de deur uit via een e-mailgateway die alles boven 10 MB weigert, of een batch afschriften moet belanden op een faxachtig monochroom apparaat dat elke grijze pixel rendert als papier of toner. Beide problemen zijn resamplingproblemen, en beide hebben een snel antwoord dat er slecht uitziet en een traag antwoord dat er goed uitziet

Waar de drie kernels werkelijk in verschillen

THPDFResampleKernel heeft drie waarden, en ze liggen op werkelijk verschillende punten van de snelheid-kwaliteit-curve. rkHalftone delegeert aan het historische GDI StretchBlt-pad met de HALFTONE-modus, dat ondanks de naam bilinear-klasse filtering is: snel, toereikend voor lijntekeningen en screenshots, en gevoelig voor de korrelige randen die u op verkleinde foto's meteen herkent. rkBicubic draait een separabele Catmull-Rom-kernel, en rkLanczos3 draait een separabele windowed sinc met three-lobe support

Beide separabele kernels draaien als twee passen, eerst horizontaal dan verticaal, met 6 tot 12 taps per doelpixel in puur Pascal. Dat is grofweg een orde van grootte trager dan het GDI-pad, en dat is precies waarom rkHalftone de default blijft. Bij een nachtelijke batch van duizenden pagina's is het verschil een planningsbeslissing, geen voorkeur. Bij één document waar een gebruiker op wacht, is Lanczos3 vrijwel gratis en zichtbaar beter

Gewichtscurves van de drie HotPDF downsampling-kernels: rkHalftone delegeert aan het bilinear-klasse GDI HALFTONE-pad met support van één, rkBicubic draait een separabele Catmull-Rom-kubiek met support van twee, en rkLanczos3 draait een windowed sinc met support van drie, ruwweg een orde van grootte snelheid inleverend voor zichtbaar betere foto's
De drie kernelwaarden liggen op werkelijk verschillende punten van de snelheid-kwaliteit-curve: een bilinear-klasse GDI-pad, een Catmull-Rom-kubiek en een three-lobe windowed sinc, en de separabele kernels normaliseren gewichten zodat niets voorbij zwart of wit ringt

Twee implementatie-eigenschappen zijn het weten waard omdat ze bepalen wat de output wel en niet kan. Randen worden afgeklemd door edge replication in plaats van wrapping of uitfaden, en de gewichten worden per doelpixel genormaliseerd. Samen betekenen die twee dat het resultaat nooit onder zwart of boven wit ringt, dus de klassieke Lanczos-overshoot-halo rond een hardlopende rand verschijnt niet als afgeknipte artifacts in de gecodeerde afbeelding

var
  Pdf: THotPDF;
  Info: THPDFLoadedResourceOptimizationInfo;
  Changed: Integer;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.LoadFromFile('scanned-contract.pdf');
    Pdf.ImageDownsampleKernel := rkLanczos3;   // vóór de aanroep zetten
    Changed := Pdf.DownsampleLoadedImages(150, 82, 4096, Info);
    if Changed > 0 then
    begin
      Writeln('resampled images: ', Info.DownsampledImageCount);
      Writeln('kept calibrated : ', Info.PreservedCalibratedImageCount);
      Pdf.SaveToFile('scanned-contract-150dpi.pdf');
    end;
  finally
    Pdf.Free;
  end;
end;

Het argument MinimumSavingsBytes, hierboven 4096, is de guard die de operatie eerlijk houdt. Een afbeelding die al efficiënt gecomprimeerd was opnieuw encoderen kan een grotere stream opleveren dan het origineel, en een downsampler die blindelings elke afbeelding vervangt, vergroot af en toe de file die hij moest verkleinen. De drempel zegt: commit de vervanging alleen als hij ten minste zoveel bytes bespaart. PreservedCalibratedImageCount rapporteert de andere conservatieve beslissing: afbeeldingen die onaangeroerd blijven omdat ze een gekalibreerde color space dragen die resampling zou aantasten

Waarom is een verkeerde polynomiale coëfficiënt zo moeilijk te spotten?

Omdat een kapotte interpolatiekernel niet crasht of een exceptie gooit, hij produceert gewoon een afbeelding die subtiel verkeerd oogt op een manier die niemand kan herleiden. De Catmull-Rom-kernel is piecewise kubiek, en zijn buitenste tak in geneste Horner-vorm is ((-0.5t + 2.5)t - 4)t + 2. Schrijf die middelste coëfficiënt als -5 in plaats van -4, en de functie evalueert nog steeds, geeft nog steeds getallen in een plausibel bereik en produceert nog steeds een afbeelding

De schade toont zich doordat W(1) evalueert tot -1 waar het 0 moet zijn. Negatieve gewichten stapelen op, de som klemt op nul, en het zichtbare symptoom is een gradient waarvan het linkeruiteinde zwart wordt en een sprongrand die zijn tussentinten verliest. Niets in de failure wijst naar een polynoom. De check die hem in seconden vangt, is rekenkundig in plaats van visueel: een interpolerende kernel moet voldoen aan W(0) = 1 en W(±1) = W(±2) = 0, en elke kernel die die drie punten mist, heeft een coëfficiëntfout, punt. Assert in een unit test op die drie waarden en de hele categorie typefout-defecten verdwijnt

Plot van de buitenste tak van de HotPDF bicubic Catmull-Rom die laat zien waarom een verkeerde coëfficiënt zich verstopt: de typefout die -5 schrijft in plaats van -4 in de geneste Horner-vorm evalueert nog steeds en laat W(1) op -1 en W(2) op -2 waar nul vereist is, dus asserten dat W(0) gelijk is aan 1 plus de twee nulbeperkingen vangt het in seconden
Een kapotte kernel crasht nooit, hij geeft gewoon getallen terug die plausibel lijken, en daarom ziet het oog een coëfficiënt-typefout niet over. W(0) = 1 met nullen op plus en min één en twee is een unit test van drie regels

Floyd-Steinberg dithering, en waar die in de pipeline thuishoort

De dither-pas is een ander probleem dan resampling en huist op een ander punt in de pipeline. RenderOutputDither past Floyd-Steinberg error diffusion toe na page composition, en dat is de enige plek die zin heeft voor een monochrome print preview of een faxachtige export: de operatie gaat over het reduceren van een afgewerkt raster naar één bit per pixel, niet over hoe individuele afbeeldingen onderweg geschaald werden

Het algoritme zelf is kort. De luminantie krijgt een drempel van 50 procent, en de kwantisatiefout wordt gediffundeerd naar vier buren met de klassieke gewichten 7/16, 3/16, 5/16 en 1/16, richting rechts, linksonder, onder en rechtsonder. De outputpixel is 0 of 255 in elk kanaal. Wat het naïeve alternatief u in plaats daarvan geeft, een harde drempel zonder diffusie, maakt van een foto een silhouet en verliest elke midtone die de inhoud droeg

Plaatsing in de renderpipeline van HotPDF Floyd-Steinberg dithering: RenderOutputDither draait na page composition op het afgewerkte 24-bit raster, legt een drempel van 50 procent op de luminantie en diffundeert elke kwantisatiefout rechts en onder met gewichten 7/16, 3/16, 5/16 en 1/16 door een rijbuffer die moet accumuleren, met één-bit monochrome output als resultaat
Dithering hoort na de composition omdat het een afgewerkt raster tot één bit reduceert, niet vanwege hoe afbeeldingen geschaald werden. De diffusiegewichten tellen op tot één, en de rijbuffer moet accumuleren in plaats van overschrijven
// Dithering op rendertijd voor een monochroom preview-apparaat
Pdf.RenderOutputDither := True;

// Of pas dezelfde toe op een bitmap die u al heeft. De bitmap moet
// pf24bit zijn; de functie geeft False terug in plaats van te gokken
if not HPDFFloydSteinbergDitherBitmap(Preview) then
  raise Exception.Create('dither expects a 24-bit bitmap');

// Directe kerneltoegang als u buiten de documentpipeline resampled
Small := HPDFResizeBitmapKernel(Large, 640, 480, rkLanczos3);
try
  Small.SaveToFile('thumb.bmp');
finally
  Small.Free;
end;

Er is één implementatiedetail in error diffusion dat iedereen een keer bijt. De foutbuffer van rij naar rij moet accumuleren. Elke pixel in de volgende rij ontvangt bijdragen van drie verschillende pixels in de huidige rij, de 3/16-, 5/16- en 1/16-taps, en als de code toewijst in plaats van optelt, gooit elke schrijfactie de vorige bijdrage weg en overleeft alleen de laatste tap. De afbeelding ziet er nog steeds geditherd uit, en dat is wat het moeilijk te zien maakt, maar de textuur is verkeerd en de tonale reproductie drijft af. De test die het vangt, is kwantitatief: dither een egalig middengrijs veld en eis dat de binnendekking tussen 40 en 60 procent uitkomt

Welke combinatie hoort in een size-reductie-pipeline?

Koppel de kernel aan wat de afbeeldingen werkelijk zijn, en behandel dithering als een apparaatkwestie in plaats van een compressiekwestie. Voor fotografische scans die een size budget moeten overleven, houdt rkLanczos3 op 150 of 200 DPI het detail vast dat mensen opmerken terwijl het pixeraantal met een factor vier of meer keldert. Voor screenshots, diagrammen en lijntekeningen is rkHalftone echt prima en veel sneller, want die afbeeldingen hebben weinig tonale gradients te behouden. Voor een gemengde batch waarin u niet elke afbeelding kunt inspecteren, is rkBicubic het redelijke midden: beter dan bilinear, ruwweg de helft van het tapaantal van Lanczos3

Downsampling is één van de hendels, en niet altijd de grootste. Bilevel-scans reageren doorgaans veel beter op de encoder uit native JBIG2 bilevel compressie in Delphi, waar de winst uit symbol dictionaries komt in plaats van uit pixeraantallen. Voordat u besluit, helpt het te weten wat er werkelijk in de file zit, en daarvoor is afbeeldingen en hun decode filters extraheren bedoeld: een inventaris van image-objecten en hun bestaande compressie vertelt u of resampling iets te halen heeft

Bouwt u het preview-oppervlak dat het resultaat toont, dan is het renderpad gedocumenteerd in een PDF-pagina naar een bitmap renderen de plek waar RenderOutputDither doorwerkt, zodat de geditherde preview en de geditherde output uit één coderoute komen in plaats van uit twee implementaties die uit elkaar drijven

Het brede principe achter beide features is dat kwaliteitsinstellingen expliciet en omkeerbaar moeten zijn. HotPDF laat het historische gedrag de default blijven, zodat een bestaande applicatie upgradet zonder verrassende verandering in output of timing, en zet de mooiere, tragere paden één property-toewijzing verderop. Beide maken deel uit van de HotPDF Delphi PDF component, naast de resource-optimalisatie en rendering-machinery waarop ze voortbouwen