Техническа статия

HotPDF ядра за намаляване на изображения и Floyd-Steinberg

HotPDF излага три ядра за смъкване на изображения през свойството ImageDownsampleKernel и отделен Floyd-Steinberg пас през RenderOutputDither. Първото контролира как изглеждат фотографиите, след като ги смъкнете, за да уцрите размерен бюджет, второто – как изглеждат, след като страницата е сведена до черно и бяло. Нито едното не е включено по подразбиране, а и двете са opt-in по една и съща причина: струват реално време

Натискът, който завежда хората тук, е познат. 60 MB сканиран договор трябва да излезе през email gateway, отхвърлящ всичко над 10 MB, или партида изявления трябва да кацне на факсоподобно монохромно устройство, рендиращо всеки сив пиксел като хартия или тонер. И двата проблема са проблеми на презамплването, и и двата имат бърз отговор, който изглежда зле, и бавен отговор, който изглежда право

По какво реално се различават трите ядра

THPDFResampleKernel има три стойности, и те седят на истински различни точки по кривата скорост-качество. rkHalftone делегира към историческия GDI StretchBlt път с HALFTONE режим, който въпреки името е билинейно-класово филтриране: бързо, достатъчно за line art и скрийншоти, и податливо на трошливите ръбове, които разпознавате мигновено на смалени фотографии. rkBicubic върви с разделимо Catmull-Rom ядро, а rkLanczos3 – с разделимо windowed sinc с three-lobe обхват

И двете разделими ядра вървят като два паса, хоризонтален после вертикален, с 6 до 12 taps на целеви пиксел в чист Pascal. Това е грубо с порядък по-бавно от GDI пътя, което е точно причината rkHalftone да остава по подразбиране. На нощна партида от хиляди страници разликата е решение за планиране, не предпочитание. На един документ, който потребител чака, Lanczos3 е почти безплатен и видимо по-добър

Криви на теглата на трите HotPDF ядра за смъкване: rkHalftone делегира към билинейно-класовия GDI HALFTONE път с обхват едно, rkBicubic върви с разделим Catmull-Rom кубик с обхват две, а rkLanczos3 – с windowed sinc с обхват три, разменяйки грубо порядък в скоростта за видимо по-добри фотографии
Трите стойности на ядрото седят на истински различни точки по кривата скорост-качество: билинейно-класов GDI път, Catmull-Rom кубик и three-lobe windowed sinc, а разделимите ядра нормализират теглата, така че нищо не звъни отвъд черно или бяло

Две имплементационни свойства си заслужават познание, защото определят какво изходът може и какво не може. Границите clamp-ват чрез копиране на ръба, вместо обгръщане или избледняване, а теглата се нормализират на всеки целеви пиксел. Двете заедно значат, че резултатът никога не звъни под черно или над бяло, така че класическият Lanczos overshoot ореол около твърд ръб не се появява като подрязани артефакти в кодираното изображение

var
  Pdf: THotPDF;
  Info: THPDFLoadedResourceOptimizationInfo;
  Changed: Integer;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.LoadFromFile('scanned-contract.pdf');
    Pdf.ImageDownsampleKernel := rkLanczos3;   // задайте преди викането
    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;

Аргументът MinimumSavingsBytes – 4096 по-горе – е guard-ът, пазещ операцията честна. Прекодиране на изображение, вече ефективно компресирано, може да произведе по-голям stream от оригинала, а downsampler, който сляпо заменя всяко изображение, понякога надува файла, когото са го молили да смъкне. Прагът казва: приеми замяната само когато пести поне толкова байта. PreservedCalibratedImageCount докладва другото консервативно решение – изображения, оставени недокоснати, защото носят калибрирано цветово пространство, което презамплването би компрометирало

Защо грешен полиномиален коефициент е толкова труден за забелязване?

Защото счупено интерполационно ядро не гърми и не хвърля, а само произвежда изображение, изглеждащо фино грешно по начин, който никой не може да припише. Catmull-Rom ядрото е парче-кубично, а външният му клон във вложена Horner форма е ((-0.5t + 2.5)t - 4)t + 2. Напишете ли онзи среден коефициент -5 вместо -4, функцията пак се смята, пак връща числа в правдоподобен диапазон и пак произвежда изображение

Щетата се проявява като W(1), смятащ се на -1, където трябва да е 0. Отрицателните тегла се трупат, сумата се подрязва на нула, а видимият симптом е градиент, чиито ляв край почернява, и стъпален ръб, загубил междинните си тонове. Нищо в провала не сочи полином. Проверката, хващаща го за секунди, е аритметична, не визуална: интерполиращо ядро трябва да удовлетвори W(0) = 1 и W(±1) = W(±2) = 0, а всяко ядро, пропусне ли тези три точки, има коефициентна грешка, точка. Утвърдете тези три стойности в unit тест и целият клас типографски дефекти изчезва

Графика на външния клон на HotPDF bicubic Catmull-Rom, показваща защо се крие грешен коефициент: типото, пишещо -5 вместо -4 във вложената Horner форма, пак се смята и оставя W(1) на -1 и W(2) на -2, където нула се изисква, така че утвърждаване, че W(0) е равно на 1 плюс двете нулеви ограничения, го хваща за секунди
Счупено ядро никога не гърми, само връща числа, изглеждащи правдоподобно, затова окото не хваща коефициентна типо. W(0) = 1 с нули на плюс и минус едно и две е триредов unit тест

Floyd-Steinberg dithering и къде му е мястото в pipeline-а

Dither пасът е различен проблем от презамплването и живее на друго място в pipeline-а. RenderOutputDither прилага Floyd-Steinberg error diffusion след композицията на страницата, което е единственото място, имащо смисъл за монохромен print preview или факсоподобен export: операцията е за смъкването на готов растер до един бит на пиксел, не за това как отделните изображения са скалирани по пътя навътре

Алгоритъмът сам по себе си е кратък. Luminance-ът се подлага на праг от 50 процента, а квантизационната грешка се разпръсква към четири съседа с класическите тегла 7/16, 3/16, 5/16 и 1/16 – надясно, долу-ляво, долу и долу-дясно. Изходният пиксел е 0 или 255 във всеки канал. Наивната алтернатива вместо това – твърд праг без разпръскване – превръща фотография в силует и губи всеки междинен тон, носил съдържанието

Разположение в render pipeline-а на HotPDF Floyd-Steinberg dithering: RenderOutputDither върви след композицията на страницата върху готовия 24-битов растер, подлага luminance на праг от 50 процента и разпръсква всяка квантизационна грешка надясно и надолу с теглата 7/16, 3/16, 5/16 и 1/16 през редов буфер, който трябва да акумулира, произвеждайки еднобитов монохромен изход
Dithering-ът е след композицията, защото смъква готов растер до един бит, не заради това как изображенията са скалирани. Теглата на разпръскването се събират на едно, а редовият буфер трябва да акумулира, а не да презаписва
// Dithering по време на render за монохромно preview устройство
Pdf.RenderOutputDither := True;

// Или приложете същия пас върху bitmap, който вече притежавате. Bitmap-ът
// трябва да е pf24bit; функцията връща False, вместо да гадае
if not HPDFFloydSteinbergDitherBitmap(Preview) then
  raise Exception.Create('dither expects a 24-bit bitmap');

// Директен достъп до ядрото, когато презамплирате извън документния pipeline
Small := HPDFResizeBitmapKernel(Large, 640, 480, rkLanczos3);
try
  Small.SaveToFile('thumb.bmp');
finally
  Small.Free;
end;

Има един имплементационен детайл в error diffusion, хапнал всеки по веднъж. Буферът за грешка ред-до-ред трябва да акумулира. Всеки пиксел в следващия ред получава приноси от три различни пиксела в текущия ред – tap-овете 3/16, 5/16 и 1/16 – и ако кодът присвоява вместо да събира, всяко записване изхвърля предишния принос и оцелява само последният tap. Изображението пак изглежда dither-нато, което го прави трудно за забелязване, но текстурата е грешна и тоналната репродукция се отмества. Тестът, който го хваща, е количествен: dither-нете униформено средно сиво поле и изисквайте вътрешното покритие да падне между 40 и 60 процента

Коя комбинация трябва да ползва pipeline за смъкване на размер?

Напаснете ядрото с това какво реално са изображенията, а dithering-а третирайте като грижа за устройството, не за компресията. За фотографични сканирания, които трябва да оцелеят размерен бюджет, rkLanczos3 на 150 или 200 DPI пази детайла, който хората забелязват, докато реже броя пиксели четирикратно или повече. За скрийншоти, диаграми и line art rkHalftone е истински достатъчен и много по-бърз, защото тези изображения имат малко тонални градиенти за пазене. За смесена партида, в която не можете да огледате всяко изображение, rkBicubic е разумната среда: по-добър от билинейния, грубо половината tap брой на Lanczos3

Смъкването е една от няколко lever-и, и не винаги най-голямата. Двустепенните сканирания обикновено отговарят далеч по-добре на енкодера, обхванат в нативна JBIG2 bilevel компресия в Delphi, където печалбата идва от символни речници, а не от брой пиксели. Преди да решите, помага да знаете какво реално има във файла, за което служи извличането на изображения и техните decode филтри: опис на image обектите и съществуващата им компресия ви казва дали презамплването има какво да спечели

Ако строите preview повърхността, показваща резултата, същият render път, документиран в рендиране на PDF страница в bitmap, е мястото, където RenderOutputDither влиза в действие, така че dither-натият preview и dither-натият изход идват от един кодов път, а не от две имплементации, които се разминават

Широкият принцип зад двете възможности е, че настройките за качество трябва да са изрични и обратими. HotPDF пази историческото поведение по подразбиране, така че съществуващо приложение се ъпгрейдва без изненадваща промяна в изхода или времето, и слага по-красивите, по-бавни пътища на едно задаване на свойство разстояние. И двете са част от HotPDF Delphi PDF компонента, редом с machinery-то за ресурсна оптимизация и рендиране, върху което градят