Technický článek

Jádra downsamplingu obrázků a tiskový dithering v HotPDF

HotPDF vystavuje tři jádra downsamplingu obrázků přes vlastnost ImageDownsampleKernel a samostatný průchod Floyd-Steinberg přes RenderOutputDither. První řídí, jak fotografie vypadají poté, co je zmenšíte, aby se vešly na rozpočet velikosti, druhý řídí, jak vypadají poté, co se stránka redukuje na černobílou. Ani jedno není zapnuté defaultně a obě jsou opt-in ze stejného důvodu: stojí reálný čas

Tlak, který sem lidi přivádí, je známý. 60 MB naskenovaná smlouva musí ven přes e-mailovou bránu odmítající cokoliv nad 10 MB, nebo dávka výpisů musí dopadnout na faxově pojaté monochromatické zařízení, které každý šedý pixel vykreslí jako papír nebo toner. Oba problémy jsou problémy resamplingu a oba mají rychlou odpověď, která vypadá špatně, a pomalou odpověď, která vypadá správně

V čem se ta tři jádra doopravdy liší

THPDFResampleKernel má tři hodnoty a sedí na skutečně různých místech křivky rychlost a kvalita. rkHalftone deleguje na historickou GDI cestu StretchBlt s módem HALFTONE, který přes svůj název je filtr bilineární třídy: rychlý, dostačující pro line art a screenshoty a náchylný k těm drsným hranám, které na zmenšených fotografiích poznáte okamžitě. rkBicubic běží se separabilním jádrem Catmull-Rom a rkLanczos3 běží se separabilním okénkovaným sincem s podporou tří laloků

Obě separabilní jádra běží jako dva průchody, vodorovný pak svislý, se 6 až 12 taps na cílový pixel v čistém Pascalu. To je zhruba řád velikosti pomalejší než GDI cesta, což je přesně důvod, proč rkHalftone zůstává defaultem. Na noční dávce tisíců stránek je ten rozdíl schedulingové rozhodnutí, ne preference. U jediného dokumentu, na kterého uživatel čeká, je Lanczos3 téměř zadarmo a viditelně lepší

Váhové křivky tří jader downsamplingu HotPDF: rkHalftone deleguje na GDI cestu HALFTONE bilineární třídy s podporou jedna, rkBicubic běží s separabilní kubikou Catmull-Rom s podporou dva a rkLanczos3 běží s okénkovaným sincem s podporou tři, směňujíce zhruba řád velikosti v rychlosti za viditelně lepší fotografie
Tři hodnoty jader sedí na skutečně různých místech křivky rychlost a kvalita: GDI cesta bilineární třídy, kubika Catmull-Rom a okénkovaný sinc se třemi laloky a separabilní jádra normalizují váhy, takže nic nezvoní za černou ani bílou

Dvě implementační vlastnosti stojí za znalost, protože určují, co výstup dokáže a nedokáže. Okraje se upnou replikací hrany místo obalení nebo vyprchání a váhy se normalizují per cílový pixel. Spolu to znamená, že výsledek nikdy nezvoní pod černou ani nad bílou, takže klasická přesstřelová halo Lanczosu kolem tvrdé hrany se v zakódovaném obrázku neobjeví jako ořezané artefakty

var
  Pdf: THotPDF;
  Info: THPDFLoadedResourceOptimizationInfo;
  Changed: Integer;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.LoadFromFile('scanned-contract.pdf');
    Pdf.ImageDownsampleKernel := rkLanczos3;   // nastavte před voláním
    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;

Argument MinimumSavingsBytes, výše 4096, je hlídka držící operaci při zemi. Znovu zakódování obrázku, který už byl efektivně komprimovaný, může produkovat větší stream než originál a downsampler slepě nahrazující každý obrázek občas zvětší soubor, který měl zmenšit. Práh říká: potvrď nahrazení jen tehdy, když ušetří aspoň tolik bajtů. PreservedCalibratedImageCount hlásí druhé konzervativní rozhodnutí, obrázky ponechané nedotčené, protože nesou kalibrovaný barevný prostor, který by resampling kompromitoval

Proč je špatný polynomiální koeficient tak těžké odhalit?

Protože rozbité interpolační jádro nepadá a nehodí výjimku, prostě produkuje obrázek, který vypadá subtilně špatně způsobem, jenž nikdo nedokáže přiřadit. Jádro Catmull-Rom je po částech kubické a jeho vnější větev v zanořeném Hornerově tvaru je ((-0.5t + 2.5)t - 4)t + 2. Napíšete-li ten prostřední koeficient jako -5 místo -4, funkce pořád vyhodnocuje, pořád vrací čísla v plausibilním rozsahu a pořád produkuje obrázek

Škoda se projeví jako W(1) vyhodnocující se na -1, kde musí být 0. Záporné váhy se kumulují, součet se klipne na nule a viditelným symptomem je gradient, jehož levý konec zčerná, a skoková hrana, která ztratí své mezilehlé tóny. Nic v tom selhání neukazuje na polynom. Kontrola, která to chytne za sekundy, je aritmetická, ne vizuální: interpolační jádro musí splňovat W(0) = 1 a W(±1) = W(±2) = 0 a jakékoli jádro, které u těch tří bodů selže, má chybu koeficientu, tečka. Asertujte ty tři hodnoty v unit testu a celá třída překlepových defektů zmizí

Graf vnější větve bicubic Catmull-Rom v HotPDF ukazující, proč se špatný koeficient schovává: překlep píšící -5 místo -4 v zanořeném Hornerově tvaru pořád vyhodnocuje a nechává W(1) na -1 a W(2) na -2, kde je požadována nula, takže aserování W(0) rovná se 1 plus obě nulová omezení to chytne za sekundy
Rozbité jádro nikdy nepadá, jen vrací čísla vypadající plausibilně, a proto oko nechytne překlep v koeficientu. W(0) = 1 s nulami v plus a minus jedna a dva je tři řádky unit testu

Dithering Floyd-Steinberg a kam v pipeline patří

Dither průchod je jiný problém než resampling a bydlí na jiném místě pipeline. RenderOutputDither aplikuje error diffusion Floyd-Steinberg po skládání stránky, což je jediné umístění dávající smysl pro monochromatický tiskový náhled nebo faxový export: operace je o redukci hotového rastru na jeden bit na pixel, ne o tom, jak byly jednotlivé obrázky cestou dovnitř škálovány

Samotný algoritmus je krátký. Luminance se prahuje na 50 procentech a kvantizační chyba se difunduje na čtyři sousedy s klasickými váhami 7/16, 3/16, 5/16 a 1/16, doprava, dole-vlevo, dole a dole-vpravo. Výstupní pixel je 0 nebo 255 v každém kanálu. Co vám místo toho dá naivní alternativa, tvrdý práh bez difuse, promění fotografii v siluetu a ztratí každý mezitón, který nesl obsah

Umístění v render pipeline ditheringu Floyd-Steinberg HotPDF: RenderOutputDither běží po skládání stránky nad hotovým 24bitovým rastrem, prahuje luminanci na 50 procentech a difunduje každou kvantizační chybu doprava a dolů s váhami 7/16, 3/16, 5/16 a 1/16 přes řádkový buffer, který musí akumulovat, a produkuje jednobitový monochromatický výstup
Dithering patří za skládání, protože redukuje hotový raster na jeden bit, ne kvůli tomu, jak byly obrázky škálovány. Difusní váhy se sečtou na jednu a řádkový buffer musí akumulovat, ne přepisovat
// Dithering při vykreslování pro monochromatické náhledové zařízení
Pdf.RenderOutputDither := True;

// Nebo aplikujte týž průchod nad bitmapou, kterou už vlastníte. Bitmapa
// musí být pf24bit; funkce vrátí False místo hádání
if not HPDFFloydSteinbergDitherBitmap(Preview) then
  raise Exception.Create('dither expects a 24-bit bitmap');

// Přímý přístup k jádru, když resamplujete mimo dokumentovou pipeline
Small := HPDFResizeBitmapKernel(Large, 640, 480, rkLanczos3);
try
  Small.SaveToFile('thumb.bmp');
finally
  Small.Free;
end;

V error diffusion je jeden implementační detail, který kousne každého jednou. Chybový buffer mezi řádky musí akumulovat. Každý pixel dalšího řádku dostává příspěvky od tří různých pixelů aktuálního řádku, taps 3/16, 5/16 a 1/16, a jestliže kód přiřazuje místo sčítání, každý zápis zahodí předchozí příspěvek a přežije jen poslední tap. Obrázek pořád vypadá ditherovaně, což ztěžuje všimnutí, ale textura je špatná a tónová reprodukce driftuje. Test, který to chytne, je kvantitativní: ditherněte rovnoměrné pole střední šedi a vyžadujte, aby vnitřní pokrytí dopadlo mezi 40 a 60 procenty

Jakou kombinaci má zvolit pipeline na zmenšování velikosti?

Přiřaďte jádro k tomu, čím obrázky doopravdy jsou, a berte dithering jako starost zařízení, ne komprese. Pro fotografické skeny, které musí přežít rozpočet velikosti, drží rkLanczos3 na 150 nebo 200 DPI detail, který si lidé všímají, a přitom sekne počet pixelů na čtvrtinu nebo víc. Pro screenshoty, diagramy a line art je rkHalftone upřímně v pořádku a mnohem rychlejší, protože ty obrázky mají málo tónových gradientů k uchování. Pro smíšenou dávku, kterou nemůžete prohlížet obrázek po obrázku, je rkBicubic rozumný střed: lepší než bilineární, zhruba polovina tap countu Lanczosu3

Downsampling je jedna páka z několika a není vždy ta největší. Bilevel skeny obvykle reagují daleko lépe na encoder pokrytý v článku nativní JBIG2 bilevel komprese v Delphi, kde zisk pochází ze symbolových slovníků, ne z počtů pixelů. Než se rozhodnete, pomáhá vědět, co je v souboru doopravdy, k čemu slouží článek extrakce obrázků a jejich decode filtrů: inventura obrazových objektů a jejich stávající komprese vám řekne, zda má resampling vůbec co získat

Stavíte-li náhledovou plochu, která výsledek ukazuje, tatáž renderovací cesta zdokumentovaná v článku vykreslení PDF stránky do bitmapy je ta, kde RenderOutputDither naskočí, takže ditherovaný náhled i ditherovaný výstup pocházejí z jediné kódové cesty, ne ze dvou implementací rozbíhajících se od sebe

Široký princip za oběma featurami je, že kvalitativní nastavení má být explicitní a reverzibilní. HotPDF nechává historické chování jako default, takže stávající aplikace upgraduje bez překvapivé změny výstupu nebo času, a lepší a pomalejší cesty dává jediné přiřazení vlastnosti daleko. Obojí je částí HotPDF Delphi PDF komponenty, spolu s optimalizací resources a renderovací mechanikou, na nichž stojí