Tehnični članak

Prilagodljivo prevzorčanje slik PDF v Delphiju s PDFiumPas

Dve pritožbi prispetata v tednu, ko funkcija stiskanja pride ven: optično prebrana pogodba ima zdaj stopničaste, kosmate črkovne oblike, prosojni logotip na naslovni strani pa sedi znotraj bledega hala. PDFiumPas odgovori na obe na enem mestu. TPdf.OptimizeImages izmeri vsako sliko, preden jo pomanjša, nato izbere jedro prevzorčanja in nabira barvo v obliki, zavedajoči se alfe

To ni bilo vedno res. Pred v3.100.0 je ista metoda zmanjšala vsako sliko, ki ni bila dvolična, s fiksnim korakom najbližjega soseda, kar je točno algoritem, ki da obe pritožbi: točkovno vzorči en izvorni piksel na izhodni piksel, RGB pod popolnoma prosojnim pikselom pa obravnava, kot bi ga bralec kdaj videl. Prepisava v v3.100.0 zamenja to eno samo pot s petimi jedri, izmerjenim pravilom izbire in izrecnim proračunom delovnega pomnilnika

Zakaj zmanjšanje vzorčenja naredi prebrano besedilo raztrgano?

Ker točkovno vzorčenje odgovori na napačno vprašanje. Ko se optični prebris 300 DPI preusmeri na 150 DPI, vsak izhodni piksel pomeni blok dva-krat-dva izvornih pikslov, najbližji sosed pa ohrani enega od štirih in ostale zavrže. Kateri preživi, je odvisno od zaokroževanja, zato roba poteze, ki je bila v viru gladko protizobčena, postane met kovanca na piksel. Rezultat je klasično stopničenje z zubčki vzdolž robov glifov, plus moire na poltonskih območjih, kjer so zavrženi vzorci slučajno nosili vzorec. To je pomembnejše v PDF kot na zaslonu, ker je škoda trajna. Slikovni XObject nosi svoje podatke vzorcev poleg /Width, /Height in /BitsPerComponent (ISO 32000-1 §8.9.5), prevzorčanje pa prepise vse tri znotraj datoteke. Slaba povečava v pregledovalniku je sličica, ki jo lahko znova narišete, PDFiumPas pa ima za to ločeno mehaniko v predpomnilniku izrisovanja in zmogljivosti povečave. Slabo zmanjšanje vzorčenja je nov dokument, ki ga podate stranki

Zakaj zmanjšanje vzorčenja z najbližjim sosedom pokvari prebrano besedilo v PDFiumPas za Delphi: vsak izhodni piksel ohrani enega od štirih izvornih pikslov in zavrže ostale, kar da robove glifov z zubčki in moire, kar pet jeder prevzorčanja zamenja
Točkovno vzorčenje ohrani en izvorni piksel na izhodni piksel in vrže ostale tri stran, zato PDFiumPas zdaj ponuja pet jeder namesto enega

Kako PDFiumPas meri podrobnost in izbere jedro

PDFiumPas se odloča na sliko, ne na dokument. Pred izbiro jedra izračuna normalizirano oceno podrobnosti svetlosti iz omejene mreže vzorčenja: vodoravni in navpični koraka sta (Width + 63) div 64 in (Height + 63) div 64, tako da optični prebris s 12000 piksli in sličica s 300 piksli stanejo približno isto 64-krat-64 prebiranje. Na vsakem vzorčenem položaju sešteje absolutno razliko do soseda na desno in soseda spodaj, čez do tri kanale, nato deli s številom vzorcev krat 255. Ocena pristane med 0 in 1, kjer ravna poslovna grafika sedi blizu ničle in gosta fotografska tekstura pleza

Izborna lestvica nato teče v fiksnem vrstnem redu. Če je ResampleFilter karkoli drugega kot pirfAdaptive, se ta filter uporabi dobesedno. Sicer: 1-bitna vsebina dobi pirfBilevel; ContentClass piccLineArt dobi pirfBox; faktor merila 4 ali več dobi tudi pirfBox, ker je pri takem zmanjšanju povprečje po območju tako najcenejši kot najbolj pravilen odgovor; piccPhoto, ocena podrobnosti 0,08 ali več ali PreferredQuality 0,9 ali več dobi pirfLanczos s svojim jedrom s tremi režnji; merilo 2 ali več ali kakovost 0,7 ali več dobi pirfBicubic pri polmeru 2; vse, kar ostane, dobi pirfBilinear. Ker TPdfImageOptimizeOptions.Default nastavi PreferredQuality na 0,85, privzet zagon nikoli ne pade nazaj na dvolinearno, razen če je zmanjšanje blago in vsebina ravna

Kako PDFiumPas izbere jedro prevzorčanja v Delphiju: omejeno prebiranje 64-krat-64 da normalizirano oceno podrobnosti, nato fiksna lestvica pogojev usmeri vsako sliko na filter dvolično, box, Lanczos, bikubično ali dvolinearno
Ocena podrobnosti stane enako na prebrisu s 12000 piksli kot na sličici, lestvica pod njo pa se ustavi pri prvem pogoju, ki se ujame
uses
  PDFium;

procedure ShrinkScannedPdf(const InputFile, OutputFile: string);
var
  Pdf: TPdf;
  Options: TPdfImageOptimizeOptions;
  Report: TPdfImageOptimizeReport;
begin
  Pdf := TPdf.Create(nil);
  try
    Pdf.FileName := InputFile;
    // Privzetke: TargetDpi 150, MinDpiRatio 1.5, PreserveBilevel True,
    // MinDimension 8, pirfAdaptive, piccAuto, kakovost 0.85, proračun 64 MiB.
    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;

Slika se dotakne šele, ko večji od njenih vodoravne in navpične umestitvene DPI, deljen z TargetDpi, doseže MinDpiRatio. Ta čuvaj obstaja, zato foto 160 DPI, namenjen cilju 150 DPI, ni ponovno kodiran za šestodstotni dobiček, ki stane eno generacijo kakovosti. Slike pod MinDimension na kateri koli osi, privzeto 8, se preskočijo kot ikone ali pravila

Zakaj prosojni logotipi poberejo belo obrobo?

Ker je barva pod popolnoma prosojnim pikselom poljubna, in preprosto uteženo povprečje mu da glas. Izvozite logotip iz oblikovalskega orodja in nevidni rob je pogosto bel, ali črn, ali kar kanvas je bil; kanal alfa ga skrije, ravna vsota po odtisu jedra pa ga takoj zmeša nazaj v vidni rob. PDFiumPas se temu izogne z nabiranjem vzorcev BGRA v predpomnoženi obliki in razveljavitvijo predpomnoženja šele pri izhodnem pikslu

Konkretno vsak prispevajoč vzorec doda channel * alpha * weight akumulatorju barve, alpha * weight akumulatorju alfe in weight vsoti uteži. Ciljno barvo nato deli akumulator alfe in ne vsota uteži, in to je korak, ki šteje: deljenje z vsoto uteži bi vleklo barvo proti nevidnim pikslom, deljenje z nabrano alfo pa obnovi barvo, na kateri so se vidni vzorci dejansko strinjali. Ciljna alfa je ločena količina, 255 * AlphaSum / WeightSum. Formati brez alfe delijo z vsoto uteži kot običajno, polnilni bajt cilja FPDFBitmap_BGRx se zapiše kot konstanta 255, vsak kanal pa se pripne na 0 do 255, preden se shrani. Ta alfa običajno izhaja iz vnosa mehke maske v slovarju slike (ISO 32000-1 §11.4), ki jo je PDFium že sestavil v medpomnilnik BGRA, ki ga prejemnik prevzorčanja dobi

Kako PDFiumPas odstrani beli halo s prosojnih slik PDF v Delphiju: vzorci se nabirajo v predpomnoženi obliki, ciljno barvo pa deli nabrana alfa namesto vsote uteži, tako da nevidni piksli ne morejo glasovati
Deljenje predpomnožene barve z nabrano alfo obnovi, na čem so se vidni vzorci strinjali, deljenje z vsoto uteži pa vleče rob proti nevidnim pikslom
// Oblika notranje zanke nabiranja, na prispevajoč izvorni vzorec
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;

// ... in pri izhodnem pikslu, razpredpomnoži proti vsoti alfe
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;

Ohranjanje 1-bitne linijske grafike izven sive cone

Vsako zvezno jedro, uporabljeno na dvoličnem prebrisu, da sivo, sivo pa je točno tisto, kar faks-sliki ni dovoljeno vsebovati. PDFiumPas zato pusti 1-bitne slike pri miru privzeto: PreserveBilevel je True v TPdfImageOptimizeOptions.Default, take slike pa pristanejo v SkippedCount nedotaknjene. Nastavite na False in pot pirfBilevel prevzame namesto gladilnega jedra. Ta prehodi točen izvorni pravokotnik, ki pokriva vsak izhodni piksel, povprečuje svetlost z utežmi 0,114, 0,587 in 0,299 v pomnilniškem vrstnem redu BGR in praguje rezultat pri 127,5 v raven 0 ali 255. Nič vmesnega se ne sme zapisati, zato robovi ostanejo ostri in okoli tankih potez ne nastane siv hala; kanal alfe vira BGRA se povprečuje običajno, cilj BGRx pa dobi konstanto 255. Če potrebujete podlegle piksle in ne manjšega dokumenta, je izvlečenje slik iz dokumentov PDF ločena pot

Kaj se zgodi, ko slika preseže proračun delovnega pomnilnika?

Pusti se točno takšna, kot je bila, in se prešteje. MaxWorkingBytes je privzeto 64 MiB in se uveljavi dvakrat. Preden se ustvari ciljna slika (bitmap), PDFiumPas zavrne sliko, če širina krat višina krat bajti na piksel preseže proračun. Potem ko FPDFBitmap_CreateEx uspe, preveri znova z realnim korakom vrste krat višino, ker lahko polnjenje vrst potisne dodelitev čez mejo, ki jo je naiven produkt opravil. Vsaka zavrnitev uniči cilj in ne vrne ničesar. Bodite jasni glede degradacije, ki jo to pomeni: slika čez proračun se ne prevzorči z nižjo kakovostjo in se ne razdeli na ploščice. Izvirnik ostane v dokumentu, BudgetExceededCount in SkippedCount oba narasteta, zato lahko zagon poroča uspeh, medtem ko je dokument le delno optimiziran. To je namerno varno odpovedoče vedenje, a pomeni, da poročilo ni izbirno branje. Ločen način spodletelosti prav tako obstaja: slike, katerih slike (bitmap) PDFium sploh ne more dati, kot so CMYK, JPX, JBIG2 ali maskirani viri, povečajo FailedCount in so prav tako puščene nedotaknjene

procedure OptimizeBatch(const Files: array of string);
var
  Pdf: TPdf;
  Options: TPdfImageOptimizeOptions;
  Report: TPdfImageOptimizeReport;
  I: Integer;
begin
  Options := TPdfImageOptimizeOptions.Default;
  Options.PreserveBilevel := False;              // uporabi glasovanje po območju dvolično
  Options.ContentClass := piccPhoto;             // vsili Lanczos za fotografske nabore
  Options.MaxWorkingBytes := 256 * 1024 * 1024;  // rezerva za velike prebrise
  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;

Branje poročila, preden odprete datoteko

TPdfImageOptimizeReport je zgrajen, da se diagnosticira, ne da bi se le zabeležil. Poleg OptimizedCount, SkippedCount in FailedCount izpostavi en števec na jedro, tako da BoxFilterCount, BilinearFilterCount, BicubicFilterCount, LanczosFilterCount in BilevelFilterCount povedo, česa se je pravilo prilagajanja dejansko sklenilo o vašem korpusu. Rezultat vse-box pomeni, da so bila zmanjšanja strma ali da je bila vsebina razvrščena kot linijska grafika; rezultat vse-Lanczos na dokumentu, za katerega ste verovali, da je linijska grafika, je znak, da ContentClass nastavite izrecno. AverageDetailScore je številka za primerjavo s pragom Lanczos 0,08 pri uglaševanju PreferredQuality, PeakWorkingBytes pa pokaže, koliko MaxWorkingBytes je zagon res potreboval. Neveljavne možnosti odpovedo glasno in ne tiho: nepozitiven TargetDpi, MinDpiRatio pod 1, PreferredQuality izven 0 do 1 ali nepozitiven MaxWorkingBytes sproži EPdfError, preden se katera koli stran dotakne. In OptimizeImages ureja le dokument v pomnilniku; vsaka spremenjena stran se uveljavi z FPDFPage_GenerateContent, po čemer še vedno sami pokličete SaveAs. Da si z očmi ogledate, kaj se je spremenilo, izrišite dokumente pred in po na slike (bitmape) kot opisano v pretvorbi strani PDF v slike JPEG in jih primerjajte pri polni povečavi

Prilagodljivo prevzorčanje je ena od tistih funkcij, ki so nevidne, ko deluje, in ustvarjajo prijave podpore, ko ne deluje, zato je morala meritev, obravnava alfe in proračun pomnilnika pristati skupaj in ne kot tri ločene izboljšave. Če to ocenjujete za izdelek Delphi, C++Builder ali Lazarus, sta celotna površina API in podrobnosti licenciranja na strani komponente PDFiumPas Delphi PDFium