Műszaki cikk

Adaptív PDF-kép-átméretezés Delphiben PDFiumPasszal

Két panasz érkezik abban a héten, amikor egy tömörítési funkció megjelenik: a beszkennelt szerződés betűalakjai most lépcsősek és szálkásak, a címlap átlátszó logóját pedig halvány haló övezi. A PDFiumPas mindkettőre egyetlen helyen válaszol. A TPdf.OptimizeImages minden képet megmér, mielőtt zsugorítaná, majd átméretezési magot választ, és a színt alfa-tudatos formában halmozza fel

Ez nem mindig volt így. A v3.100.0 előtt ugyanez a módszer minden nem bilevel képet egy rögzített legközelebbi szomszéd lépéssel méretezett át, ami pontosan az az algoritmus, amely mindkét panaszt előidézi: kimeneti pixelenként egy forráspixelt tart meg, és a teljesen átlátszó pixel alatt álló RGB-vel úgy bánik, mintha egy olvasó valaha látná. A v3.100.0 újraírása ezt az egyetlen útvonalat öt magra, egy mért kiválasztási szabályra és egy explicit munkamemória-keretre cseréli

Miért lesz a beszkennelt szöveg recés a kicsinyítéstől?

Mert a pontminta a rossz kérdésre válaszol. Amikor egy 300 DPI-s szkennelést 150 DPI-re céloznak át, minden célpixel egy 2×2-es forráspixel-blokkot képvisel, és a legközelebbi szomszéd a négy közül egyet megtart, a többit eldobja. Az dönti el, melyik marad meg, hogy a kerekítés hogyan esik, így a forrásban simán élsimított vonalél pixelenként pénzérme-feldobássá válik. Az eredmény a klasszikus aliasos lépcső a glyph-éleken, plusz moaré a halftone területeken, ahol az eldobott minták épp a mintázatot hordozták. Ez PDF-ben fontosabb, mint a képernyőn, mert a kár végleges. Egy image XObject a mintaadatát a /Width, /Height és /BitsPerComponent mellett hordozza (ISO 32000-1 §8.9.5), és az átméretezés mindhármat újraírja a fájlban. A rossz nagyítás egy megjelenítőben csak újrarajzolható képkocka, és arra a PDFiumPasnak külön gépezete van a render cache és zoom teljesítmény cikkben. A rossz kicsinyítés viszont egy új dokumentum, amelyet az ügyfélnek ad át

Miért rontja el a legközelebbi szomszéd átméretezése a beszkennelt szöveget a PDFiumPasban Delphiben: minden kimeneti pixel a négy forráspixel közül egyet tart meg és a többit eldobja, ami aliasos glyph-éleket és moarét ad, amit az öt átméretezési mag vált fel
A pontminta kimeneti pixelenként egy forráspixelt tart meg és a másik hármat eldobja, ezért kínál most a PDFiumPas öt magot egy helyett

Hogyan méri a PDFiumPas a részletgazdagságot, és hogyan választ magot?

A PDFiumPas képenként dönt, nem dokumentumonként. Mielőtt magot választana, normalizált luminancia-részletpontszámot számol egy korlátos mintavételi rácsból: a vízszintes és függőleges lépések (Width + 63) div 64 és (Height + 63) div 64, így egy 12000 pixeles szkennelés és egy 300 pixeles indexkép egyaránt nagyjából ugyanannyiba kerülő 64×64-es bejárást jelent. Minden mintavett pozíción összegzi az abszolút különbséget a jobbra és az alatta lévő szomszédhoz, legfeljebb három csatornán, majd elosztja a mintaszám szorozva 255-tel. A pontszám 0 és 1 közé esik, ahol a lapos üzleti grafika a nulla közelében ül, a sűrű fotós textúra pedig felkúszik

A kiválasztási létra rögzített sorrendben fut. Ha a ResampleFilter bármi más, mint pirfAdaptive, az a szűrő szó szerint alkalmazásra kerül. Egyébként: az 1 bites tartalom pirfBilevel-t kap; a piccLineArt ContentClass pirfBox-ot kap; a 4 vagy afeletti skálafaktor szintén pirfBox-ot, mert akkora kicsinyítésnél a területi átlag egyszerre a legolcsóbb és a leghelyesebb válasz; a piccPhoto, a 0.08 vagy afeletti részletpontszám vagy a 0.9 vagy afeletti PreferredQuality pirfLanczos-t kap a háromlebenyes magjával; a 2 vagy afeletti skála vagy a 0.7 vagy afeletti minőség pirfBicubic-ot kap 2 sugárral; ami marad, az pirfBilinear-t kap. Mivel a TPdfImageOptimizeOptions.Default a PreferredQuality-t 0.85-re állítja, egy alapértelmezett futás csak akkor esik vissza bilineárisra, ha a kicsinyítés enyhe és a tartalom lapos

Hogyan választ átméretezési magot a PDFiumPas Delphiben: egy korlátos hatvannégy-szor-hatvannégyes bejárás normalizált részletpontszámot ad, majd egy rögzített feltétellétra minden képet a bilevel, box, Lanczos, bikubikus vagy bilineáris szűrőhöz irányít
A részletpontszám ugyanannyiba kerül egy 12000 pixeles szkennelésen, mint egy indexképen, és alatta a létra az első illeszkedő feltételnél megáll
uses
  PDFium;

procedure ShrinkScannedPdf(const InputFile, OutputFile: string);
var
  Pdf: TPdf;
  Options: TPdfImageOptimizeOptions;
  Report: TPdfImageOptimizeReport;
begin
  Pdf := TPdf.Create(nil);
  try
    Pdf.FileName := InputFile;
    // Alapértékek: TargetDpi 150, MinDpiRatio 1.5, PreserveBilevel True,
    // MinDimension 8, pirfAdaptive, piccAuto, minőség 0.85, 64 MiB keret.
    Options := TPdfImageOptimizeOptions.Default;
    Options.TargetDpi := 150;
    Options.MinDpiRatio := 1.5;
    Options.ContentClass := piccAuto;
    Options.PreferredQuality := 0.85;
    if Pdf.OptimizeImages(Options, Report) and (Report.OptimizedCount > 0) then
      Pdf.SaveAs(OutputFile);
  finally
    Pdf.Free;
  end;
end;

Egy kép csak akkor kerül módosításra, amikor a vízszintes és függőleges elhelyezési DPI közül a nagyobb, osztva a TargetDpi-vel, eléri a MinDpiRatio-t. Ez az őr azért létezik, hogy egy 150 DPI-s céléra szánt 160 DPI-s fotó ne kódolódjon újra egy hat százalékos nyereségért, amely egy minőségi generációt emészt fel. A MinDimension alatt, bármelyik tengelyen — alapértelmezésben 8 — lévő képek ikonként vagy vonalként kihagyásra kerülnek

Miért kapnak az átlátszó logók fehér szegélyt?

Mert a teljesen átlátszó pixel alatti szín tetszőleges, és egy sima súlyozott átlag engedi, hogy szavazzon. Ha egy logót tervezőeszközből exportál, a láthatatlan margó gyakran fehér, fekete, vagy bármilyen színű, amilyen a vászon volt; az alfa-csatorna elrejti, és egy egyenes összegzés a mag lábnyoma felett azonnal visszakeveri a látható élbe. A PDFiumPas úgy kerüli el ezt, hogy a BGRA mintákat premultiplikált formában halmozza, és a premultiplikációt csak a célpixelnél vonja vissza

Konkrétan minden hozzájáruló minta channel * alpha * weight-et ad a színakkumulátorhoz, alpha * weight-et egy alfaakkumulátorhoz és weight-et a súlyösszeghez. A célszínt ezután az alfaakkumulátorral, nem a súlyösszeggel osztják, és ez a számításban lényeges lépés: a súlyösszeggel osztva a szín a láthatatlan pixelek felé húzódna, míg a felhalmozott alfával osztva rekonstruálja azt a színt, amelyen a látható minták ténylegesen megegyeztek. A célalfa külön mennyiség: 255 * AlphaSum / WeightSum. A nem alfa formátumok a szokásos módon a súlyösszeggel osztanak, egy FPDFBitmap_BGRx cél kitöltőbájtja konstans 255-öt kap, és minden csatorna 0 és 255 közé szorul, mielőtt tárolásra kerülne. Ez az alfa normálisan a képszótár soft mask bejegyzéséből származik (ISO 32000-1 §11.4), amelyet a PDFium már a BGRA pufferbe kompozitált, amelyet az átméretező kap

Hogyan távolítja el a PDFiumPas a fehér halót az átlátszó PDF-képekről Delphiben: a minták premultiplikált formában halmozódnak, és a célszínt a felhalmozott alfával osztják a súlyösszeg helyett, így a láthatatlan pixelek nem szavazhatnak
A premultiplikált szín felhalmozott alfával osztva rekonstruálja, amin a látható minták megegyeztek, míg a súlyösszeggel osztva az élt a láthatatlan pixelek felé húzná
// A belső akkumulációs ciklus alakja, hozzájáruló forrásmintánként
if SrcFormat = FPDFBitmap_BGRA then
  Alpha := PByte(PAnsiChar(Pixel) + 3)^ / 255
else
  Alpha := 1;
for Channel := 0 to Min(BytesPerPixel, 3) - 1 do
  Accumulated[Channel] := Accumulated[Channel] +
    PByte(PAnsiChar(Pixel) + Channel)^ * Alpha * Weight;
AlphaSum := AlphaSum + Alpha * Weight;
WeightSum := WeightSum + Weight;

// ... és a célpixelnél a premultiplikáció visszavonása az alfaösszeg alapján
if SrcFormat = FPDFBitmap_BGRA then
begin
  if Abs(AlphaSum) > 1E-12 then
    ValueSum := Accumulated[Channel] / AlphaSum
  else
    ValueSum := 0;
end
else
  ValueSum := Accumulated[Channel] / WeightSum;

Az 1 bites vonalgrafika távol tartása a szürke zónától

Bármely folytonos mag, amelyet bilevel szkennelésre alkalmaznak, szürkét ad, és a szürke pontosan az, amit egy fax-stílusú kép nem tartalmazhat. A PDFiumPas ezért alapból békén hagyja az 1 bites képeket: a PreserveBilevel True a TPdfImageOptimizeOptions.Default-ban, és az ilyen képek érintetlenül a SkippedCount-ba kerülnek. Állítsa False-ra, és a pirfBilevel útvonal veszi át a feladatot simító mag helyett. Végigjárja a minden célpixelt lefedő pontos forrástéglalapot, a luminanciát a 0.114, 0.587 és 0.299 súlyokkal átlagolja BGR memóriasorrendben, és az eredményt 127.5-nél simán 0-ra vagy 255-re küszöböli. Semmi köztes nem írható, így az élek élesek maradnak, és vékony vonások körül nem keletkezik szürke haló; egy BGRA forrás alfa-csatornája a szokásos módon átlagol, egy BGRx cél a konstans 255-öt kapja. Ha magára a pixeladatra van szüksége egy kisebb dokumentum helyett, a képek kinyerése PDF-dokumentumokból a külön út

Mi történik, amikor egy kép átlépi a munkamemória-keretet?

Pontosan úgy marad, ahogy volt, és megszámolásra kerül. A MaxWorkingBytes alapértéke 64 MiB, és kétszer érvényesül. Mielőtt a célbitkép létrejönne, a PDFiumPas elutasítja a képet, ha a szélesség szorozva a magassággal szorozva a pixelenkénti bájtokkal meghaladja a keretet. Miután a FPDFBitmap_CreateEx sikerrel jár, a valós stride szorozva a magassággal alapján újra ellenőriz, mert a soreltolás (padding) egy olyan foglalást a határ fölé tolhat, amelyet a naiv szorzat még átengedett. Bármelyik elutasítás megsemmisíti a célt, és semmit nem ad vissza. Legyen világos a degradáció, amelyet ez jelent: a keretet túllépő képet nem méretezik át alacsonyabb minőséggel, és nem bontják csempékre. Az eredeti a dokumentumban marad, a BudgetExceededCount és a SkippedCount egyaránt nő, és egy futás ezért jelenthet sikert, miközben a dokumentum csak részben optimalizált. Ez szándékos fail-safe viselkedés, de azt jelenti, hogy a jelentés nem opcionális olvasmány. Külön hibamód is létezik: azok a képek, amelyeknek a bitképét a PDFium egyáltalán nem tudja előállítani, például CMYK, JPX, JBIG2 vagy maszkolt források, FailedCount-ot növelnek, és szintén érintetlenek maradnak

procedure OptimizeBatch(const Files: array of string);
var
  Pdf: TPdf;
  Options: TPdfImageOptimizeOptions;
  Report: TPdfImageOptimizeReport;
  I: Integer;
begin
  Options := TPdfImageOptimizeOptions.Default;
  Options.PreserveBilevel := False;              // a bilevel területi szavazás használata
  Options.ContentClass := piccPhoto;             // Lanczos kényszerítése fotóhalmazokhoz
  Options.MaxWorkingBytes := 256 * 1024 * 1024;  // tartalék hely nagy szkennelésekhez
  Pdf := TPdf.Create(nil);
  try
    for I := Low(Files) to High(Files) do
    begin
      Pdf.FileName := Files[I];
      if not Pdf.OptimizeImages(Options, Report) then
      begin
        WriteLn('optimize failed: ', Report.ErrorMessage);
        Continue;
      end;
      if Report.BudgetExceededCount > 0 then
        WriteLn(Files[I], ': ', Report.BudgetExceededCount,
          ' image(s) over budget and kept at full size');
      if Report.FailedCount > 0 then
        WriteLn(Files[I], ': ', Report.FailedCount,
          ' image(s) could not be decoded to a bitmap');
      if Report.OptimizedCount > 0 then
        Pdf.SaveAs(ChangeFileExt(Files[I], '.opt.pdf'));
    end;
  finally
    Pdf.Free;
  end;
end;

A jelentés elolvasása, mielőtt kiküldené a fájlt

A TPdfImageOptimizeReport diagnosztizálásra készült, nem pusztán naplózásra. A OptimizedCount, SkippedCount és FailedCount mellett magonként egy számlálót tár fel, így a BoxFilterCount, BilinearFilterCount, BicubicFilterCount, LanczosFilterCount és BilevelFilterCount megmondja, mit állapított meg valójában az adaptív szabály az Ön állományáról. Egy csupa-box eredmény azt jelenti, hogy a kicsinyítések meredekek voltak, vagy a tartalmat vonalgrafikának sorolták; egy csupa-Lanczos eredmény egy olyan dokumentumon, amelyről hitte, hogy vonalgrafika, annak jele, hogy a ContentClass-t explicit módon kell beállítani. Az AverageDetailScore az a szám, amelyet a 0.08-as Lanczos küszöbhöz kell hasonlítani a PreferredQuality hangolásakor, és a PeakWorkingBytes megmutatja, mennyi MaxWorkingBytes-ra volt valóban szüksége a futásnak. Az érvénytelen beállítások hangosan, nem csendben hibáznak: nem pozitív TargetDpi, 1 alatti MinDpiRatio, a 0 és 1 közé nem eső PreferredQuality vagy nem pozitív MaxWorkingBytes EPdfError-t dob, mielőtt bármelyik lapot érintené. És az OptimizeImages csak a memóriabeli dokumentumot szerkeszti; minden módosított lap a FPDFPage_GenerateContent hívással rögzítődik, ezt követően Önnek még magának kell a SaveAs-t meghívnia. Hogy szemrevételezze, mi változott, renderelje le az eredeti és az optimalizált dokumentumokat bitképekre a PDF-lapok JPEG képekké alakítása cikkben leírt módon, és hasonlítsa össze őket teljes nagyításban

Az adaptív átméretezés azon funkciók egyike, amely működés közben láthatatlan, és amikor nem működik, támogatási jegyeket generál; ezért kellett a mérésnek, az alfa-kezelésnek és a memóriakeretnek együtt megérkeznie, nem pedig három külön finomításként. Ha ezt Delphi, C++Builder vagy Lazarus termékhez vizsgálja, a teljes API-felület és a licenc részletei a PDFiumPas Delphi PDFium komponens oldalán találhatók