To klager ankommer ugen efter, at en komprimeringsfunktion udrulles: den scannede kontrakt har nu trappeformede, lodne bogstavformer, og det transparente logo på forsiden sidder inde i en bleg halo. PDFiumPas besvarer begge ét sted. TPdf.OptimizeImages måler hvert billede, før det formindsker det, vælger derefter en resamplingskerne og akkumulerer farve i alpha-bevidst form
Det var ikke altid sådan. Før v3.100.0 nedskalerede samme metode alle ikke-bilevel-billeder med et fast nærmeste-nabo-trin, hvilket er præcis den algoritme, der frembringer begge klager: den punktsampler én kildepixel pr. outputpixel, og den behandler RGB under en fuldt transparent pixel, som om en læser nogensinde skulle se den. Omskrivningen i v3.100.0 erstatter den eneste sti med fem kerner, en målt udvælgelsesregel og et eksplicit arbejdshukommelsesbudget
Hvorfor får nedskalering scannet tekst til at se takket ud?
Fordi punktsampling besvarer det forkerte spørgsmål. Når en 300 DPI-scan rettes mod 150 DPI, repræsenterer hver destinationspixel en to-gange-to-blok af kildepixels, og nærmeste nabo beholder én af de fire og kasserer resten. Hvilken én, der overlever, afhænger af afrunding, så en stregkant, der var glat antialiased i kilden, bliver et møntkast pr. pixel. Resultatet er den klassiske aliaserede trappeform langs glyfkanter plus moiré på rasterområder, hvor de kasserede prøver tilfældigt bar mønsteret. Det betyder mere i en PDF end på skærmen, fordi skaden er permanent. Et image XObject bærer sine prøvedata sammen med /Width, /Height og /BitsPerComponent (ISO 32000-1 §8.9.5), og resampling omskriver alle tre inde i filen. Et dårligt zoom i en fremviser er en ramme, du kan tegne om, og PDFiumPas har separat maskineri til det i render cache og zoom-ydelse. En dårlig nedskalering er et nyt dokument, du rækker kunden
Hvordan PDFiumPas måler detaljer og vælger en kerne
PDFiumPas beslutter pr. billede, ikke pr. dokument. Før den vælger en kerne, beregner den en normaliseret luminansdetaljescore fra et afgrænset samplinggitter: de vandrette og lodrette trin er (Width + 63) div 64 og (Height + 63) div 64, så en 12000-pixel-scan og en 300-pixel-thumbnail begge koster omkring samme 64-gange-64-svab. Ved hver sampled position summerer den den absolutte forskel til naboen mod højre og naboen nedenunder, på op til tre kanaler, og dividerer derefter med prøveantallet gange 255. Scoren lander mellem 0 og 1, hvor flad forretningsgrafik ligger nær nul, og tæt fotografisk tekstur klatrer
Udvælgelsesstigen kører derefter i fast rækkefølge. Hvis ResampleFilter er noget andet end pirfAdaptive, bruges det filter ordret. Ellers: 1-bit-indhold får pirfBilevel; en ContentClass af piccLineArt får pirfBox; en skaleringsfaktor på 4 eller mere får også pirfBox, for ved den formindskelse er et arealgennemsnit både det billigste og det mest korrekte svar; piccPhoto, en detaljescore på 0,08 eller højere eller en PreferredQuality på 0,9 eller højere får pirfLanczos med sin tre-lobe-kerne; en skala på 2 eller mere eller en kvalitet på 0,7 eller højere får pirfBicubic med radius 2; alt tilbage får pirfBilinear. Da TPdfImageOptimizeOptions.Default sætter PreferredQuality til 0,85, falder en standardkørsel aldrig tilbage til bilinear, medmindre formindskelsen er mild, og indholdet er fladt
uses
PDFium;
procedure ShrinkScannedPdf(const InputFile, OutputFile: string);
var
Pdf: TPdf;
Options: TPdfImageOptimizeOptions;
Report: TPdfImageOptimizeReport;
begin
Pdf := TPdf.Create(nil);
try
Pdf.FileName := InputFile;
// Standarder: TargetDpi 150, MinDpiRatio 1.5, PreserveBilevel True,
// MinDimension 8, pirfAdaptive, piccAuto, kvalitet 0.85, budget på 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;
Et billede berøres kun, når den største af dets vandrette og lodrette placerings-DPI divideret med TargetDpi når MinDpiRatio. Den vagt findes, så et 160 DPI-foto rettet mod et 150 DPI-mål ikke re-kodes for en gevinst på seks procent, der koster en generation af kvalitet. Billeder under MinDimension på en af akserne, 8 som standard, springes over som ikoner eller linjer
Hvorfor får transparente logoer en hvid kantglød?
Fordi farven under en fuldt transparent pixel er vilkårlig, og et simpelt vægtet gennemsnit lader den stemme. Eksportér et logo fra et designværktøj, og den usynlige margen er ofte hvid, eller sort, eller hvad lærredet nu var; alfakanalen skjuler den, og en lige sum over kerneaftrykket blander den straks tilbage i den synlige kant. PDFiumPas undgår det ved at akkumulere BGRA-prøver i præmultiplieret form og kun fortryde præmultiplikationen ved destinationspixel
Konkret tilføjer hver bidragende prøve channel * alpha * weight til farveakkumulatoren, alpha * weight til en alpha-akkumulator og weight til vægtsummen. Destinationsfarven divideres derefter med alpha-akkumulatoren frem for med vægtsummen, og det er trinnet, der tæller: at dividere med vægtsummen ville trække farven mod de usynlige pixels, mens at dividere med den akkumulerede alpha rekonstruerer farven, som de synlige prøver reelt var enige om. Destinationens alpha er en separat størrelse, 255 * AlphaSum / WeightSum. Ikke-alpha-formater dividerer med vægtsummen som sædvanlig, paddingbyten af en FPDFBitmap_BGRx-destination skrives som konstanten 255, og hver kanal klemmes til mellem 0 og 255, før den gemmes. Den alpha stammer normalt fra en soft mask-post i billedordbogen (ISO 32000-1 §11.4), som PDFium allerede har kompositeret ind i den BGRA-buffer, resampleren modtager
// Formen af den indre akkumuleringsløkke, pr. bidragende kildeprøve
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;
// ... og ved destinationspixel, af-præmultiplikér mod alphasummen
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;
Holde 1-bit line art uden for gråzonen
Enhver kontinuert kerne anvendt på en bilevel-scan frembringer grå, og grå er præcis, hvad et fax-stil-billede ikke må indeholde. PDFiumPas lader derfor 1-bit-billeder i fred som standard: PreserveBilevel er True i TPdfImageOptimizeOptions.Default, og sådanne billeder lander urørte i SkippedCount. Sæt den til False, og pirfBilevel-stien overtager i stedet for en udjævningskerne. Den går det præcise kilderektangel igennem, der dækker hver destinationspixel, gennemsnitser luminans med vægtene 0,114, 0,587 og 0,299 i BGR-hukommelsesrækkefølge og tærskler resultatet ved 127,5 til en flad 0 eller 255. Intet mellemliggende kan skrives, så kanter forbliver skarpe, og ingen grå halo dannes omkring tynde streger; alfakanalen af en BGRA-kilde gennemsnitssættes normalt, og en BGRx-destination får konstanten 255. Hvis du har brug for de underliggende pixels frem for et mindre dokument, er udtrækning af billeder fra PDF-dokumenter den separate sti
Hvad sker der, når et billede overskrider arbejdshukommelsesbudgettet?
Det efterlades præcis, som det var, og det tælles. MaxWorkingBytes er 64 MiB som standard og håndhæves to gange. Før destinationsbitmap oprettes, afviser PDFiumPas billedet, hvis bredde gange højde gange bytes pr. pixel overskrider budgettet. Efter FPDFBitmap_CreateEx lykkes tjekker den igen med den reelle stride gange højde, fordi række-padding kan skubbe en allokering forbi en grænse, som det naive produkt clearede. Enhver afvisning ødelægger destinationen og returnerer ingenting. Vær klar over den forværring det indebærer: et billede over budget resamples ikke ved lavere kvalitet, og det opdeles ikke i fliser. Originalen bliver i dokumentet, BudgetExceededCount og SkippedCount stiger begge, og en kørsel kan derfor rapportere succes, mens et dokument kun er delvist optimeret. Det er bevidst fail-safe-adfærd, men det betyder, at rapporten ikke er valgfri læsning. En særskilt fejlmåde findes også: billeder, hvis bitmap PDFium slet ikke kan frembringe, som CMYK, JPX, JBIG2 eller maskerede kilder, øger i stedet FailedCount og efterlades ligeledes urørte
procedure OptimizeBatch(const Files: array of string);
var
Pdf: TPdf;
Options: TPdfImageOptimizeOptions;
Report: TPdfImageOptimizeReport;
I: Integer;
begin
Options := TPdfImageOptimizeOptions.Default;
Options.PreserveBilevel := False; // brug bilevel-arealafstemningen
Options.ContentClass := piccPhoto; // tving Lanczos til fotosæt
Options.MaxWorkingBytes := 256 * 1024 * 1024; // spillerum til store scanninger
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;
Læs rapporten, før du afskiber filen
TPdfImageOptimizeReport er bygget til at blive diagnosticeret, ikke blot logget. Ved siden af OptimizedCount, SkippedCount og FailedCount eksponerer den én tæller pr. kerne, så BoxFilterCount, BilinearFilterCount, BicubicFilterCount, LanczosFilterCount og BilevelFilterCount fortæller dig, hvad den adaptive regel reelt konkluderede om dit korpus. Et alt-box-resultat betyder, at formindskelserne var stejle, eller at indholdet blev klassificeret som line art; et alt-Lanczos-resultat på et dokument, du troede var line art, er et tegn på, at ContentClass burde sættes eksplicit. AverageDetailScore er tallet, der sammenlignes med Lanczos-tærsklen på 0,08 ved justering af PreferredQuality, og PeakWorkingBytes viser, hvor stor en del af MaxWorkingBytes kørslen reelt havde brug for. Ugyldige indstillinger fejler højlydt frem for stille: en ikke-positiv TargetDpi, en MinDpiRatio under 1, en PreferredQuality uden for 0-1 eller en ikke-positiv MaxWorkingBytes rejser EPdfError, før nogen side berøres. Og OptimizeImages redigerer kun dokumentet i hukommelsen; hver ændret side committes med FPDFPage_GenerateContent, hvorefter du stadig selv kalder SaveAs. For at se med egne øjne, hvad der ændredes, render før- og efterdokumenterne til bitmaps som beskrevet i konvertering af PDF-sider til JPEG-billeder og sammenlign dem ved fuldt zoom
Adaptiv resampling er en af de funktioner, der er usynlig, når den virker, og genererer supportbilletter, når den ikke gør, og det er derfor, målingen, alpha-håndteringen og hukommelsesbudgettet måtte lande sammen frem for som tre separate forfinelser. Hvis du evaluerer dette til et Delphi-, C++Builder- eller Lazarus-produkt, er den fulde API-overflade og licensdetaljerne på PDFiumPas Delphi PDFium-komponentsiden