Technical Article

Sumažinto regėjimo PDF spalvų filtrai Delphi su PDFium

Silpnaregis skaitytojas negali įskaityti juodo teksto baltame puslapyje esant numatytajam kontrastui, todėl prašo tamsiojo režimo. Paprasčiausias sprendimas – invertuoti kiekvieną sugeneruoto puslapio tašką. Tai įdiegiama per savaitę, o sugenda jau kitą dieną: nuskenuotos nuotraukos atrodo kaip fotojuostos negatyvai, skaitytojo geltonos paryškinimo žymės virsta neįskaitoma mėlyna dėme, o kas nors paklausia, kodėl spaudinys išėjo visiškai juodas. Šią funkciją tikrai verta kurti, ir ją labai lengva atlikti pusiau teisingai. Atotrūkį tarp šių dviejų rezultatų lemia viena idėja: kiekvienas sprendimas dėl spalvos turi būti priimamas tam tikrame atvaizdavimo konvejerio taške, o inversija yra netinkamas įrankis, taikomas netinkamame etape. Čia pateiktame kode naudojamas „PDFium Component“ – PDFium pagrįsta Delphi, C++Builder ir Lazarus peržiūros programa, kurios atvaizdavimo API šiuos etapus pateikia atskirai.

Filtrai yra pateikimo būsena, o ne dokumento būsena

Viena taisyklė padeda išvengti blogiausios šios srities klaidų kategorijos: skaitymo režimas keičia tik tai, kaip taškinis paveikslėlis (bitmap) yra sukuriamas arba apdorojamas vėliau, ir nieko daugiau. PDF failo baitai lieka nepaliesti, kiekvienas režimas yra grįžtamas atliekant pakartotinį atvaizdavimą, o išsaugojimas („save“) niekada neįrašo filtruotos išvaizdos atgal į failą. Tai skamba akivaizdžiai, kol teisininkas neišspausdina sutarties su įjungtu filtru ir neįtraukia į bylą invertuotos versijos. Tuo momentu klausimas „ar spausdinant naudojama paties dokumento, ar ekrano išvaizda“ reikalauja aiškaus atsakymo jūsų specifikacijoje, o ne atsitiktinio kodo veikimo. Išsaugokite filtro nustatymą peržiūros programos būsenoje, taikykite jį atvaizdavimo metu ir reikalaukite, kad kiekvienas eksporto kelias deklaruotų, kurią išvaizdą naudoja.

Ši taisyklė pasiteisina dvigubai. Grįžtamumas yra nemokamas, nes režimų perjungimas iš naujo atvaizduoja vaizdą iš nepakeisto šaltinio: nereikia palaikyti veiksmų atšaukimo (undo) dėklo, o režimų keitimas negali sugadinti puslapio kokybės. Kelių langų scenarijai išlieka nuoseklūs dėl tos pačios priežasties. Du to paties dokumento rodiniai gali naudoti skirtingus režimus, nes kiekvienas rodinys valdo savo pateikimo būseną, o dokumento objektas lieka bendras.

Pirmiausia atvaizdavimas, tada transformacija

Rekomenduojamas modelis yra taškinio paveikslėlio apdorojimas po atvaizdavimo: RenderPage sukuria puslapio rastrą, o transformacijos etapas jį pakoreguoja. Komponentas teikia tris transformacijas kaip vietines taškinių paveikslėlių operacijas: InvertPdfBitmap, DuotonePdfBitmap ir GrayscalePdfBitmap. Tai paverčia režimo keitimą aiškia dviejų etapų funkcija:

function TViewerForm.RenderWithMode(W, H: Integer): TBitmap;
begin
  Result := Pdf.RenderPage(0, 0, W, H, ro0, [reAnnotations]);
  case FReadingMode of
    rmInverted:     InvertPdfBitmap(Result);
    rmHighContrast: DuotonePdfBitmap(Result, clBlack, $0000C8FF);  // dark bg, amber text
    rmGrayscale:    GrayscalePdfBitmap(Result);
  end;
  // rmNormal falls through: the document keeps its own colors
end;

Iš tokio projekto išplaukia du dalykai. Pirma, transformacijos kaina yra proporcinga taškinio paveikslėlio dydžiui, soo šis darbas priklauso ten, kur yra saugomi jūsų atvaizdavimo rezultatai: filtruokite talpykloje esantį taškinį paveikslėlį vieną kartą, o ne per kiekvieną piešimo ciklą. Antra, kadangi transformacija atliekama su baigtu rastru, ji vienodai veikia tekstą, vektorinę grafiką, paveikslėlius ir anotacijų išvaizdą. Šis vienodumas yra būtent tai, ką paprasta inversija atlieka neteisingai nuotraukoms. Todėl dviejų tonų (duotone) transformacija yra geresnis pasirinkimas tekstiniams dokumentams, nes ji susieja skaisčio reikšmes su pasirinktu spalvų perėjimu nuo tamsios iki šviesios, užuot tiesiog paneigusi atspalvius; inversija lieka kaip aiškus pasirinkimas skaitytojams, kurie to nori. Ryškesni šrifto kraštai yra atskiras svertas. Atvaizdavimo parinktis reNoSmoothText išjungia teksto glotninimą (anti-aliasing) atvaizdavimo metu ir gerai dera su didelio kontrasto režimu esant dideliam masteliui.

Dvi nespalvotos skalės, kurios nesutampa

Atvaizdavimo parinktys apima reGrayscale, kuri atrodo kaip trumpesnis kelias, leidžiantis išvengti vėlesnio apdorojimo etapo. Tačiau tai nėra ta pati operacija:

// Engine-level: grayscale applied during rasterization
GrayA := Pdf.RenderPage(0, 0, W, H, ro0, [reGrayscale]);

// Post-process: render in color, convert the finished bitmap
GrayB := Pdf.RenderPage(0, 0, W, H);
GrayscalePdfBitmap(GrayB);

Variklio lygio parinktis taikoma vaizdo turinio rastro išvesčiai, tačiau nepasiekia vektorinių užpildų ar teksto spalvų, nes puslapis su spalvotomis antraštėmis gali būti grąžintas su pilkomis nuotraukomis ir užsispyrusiai mėlynomis antraštėmis. GrayscalePdfBitmap atlieka konvertavimą visam baigtam taškiniam paveikslėliui be išimčių. Atvaizdavimo parinktis vis tiek išlaiko savo vertę, kai norite pašalinti spalvas iš nuotraukų, bet palikti teksto spalvą kaip signalą – tai ypač tinka kai kuriems silpnaregiams skaitytojams. Tačiau jei reikalavime nurodyta „nespalvotas puslapis“, būtent vėlesnis apdorojimas yra tas variantas, kuris jį patenkina. Kad ir kurį kelią pasirinktumėte, turėkite omenyje abu RenderPage perkrovos stilius. Funkcinė forma grąžina taškinį paveikslėlį, kurį iškvietėjas valdo ir privalo atlaisvinti, o tai tampa svarbu, kai filtrai padidina vienu metu naudojamų atvaizduotų paveikslėlių skaičių.

Fonai, žymėjimai ir PageColor spąstai

Baltos puslapio spalvos pakeitimas šiltu tonu dažnai yra pakankamas sprendimas skaitytojams, jautriems akinimui, ir tam yra skirta speciali savybė. Ši savybė turi taikymo srities taisyklę, su kuria žmonės dažnai susiduria:

// Affects the on-screen view only
PdfView.PageColor := $00D9EDF2;  // warm paper tone behind page content

// RenderPage output ignores PageColor; pass the color explicitly
Bmp := Pdf.RenderPage(0, 0, W, H, ro0, [], $00D9EDF2);

PageColor pakeičia tai, ką rodo TPdfView, tačiau taškiniai paveikslėliai, sukurti per RenderPage, išlaiko numatytąją baltą spalvą, nebent parametras Color nurodo kitaip. Simptomas yra dėsningas: ekrane rodomas nuspalvintas puslapis, tačiau vartotojui eksportavus ar spausdinant, išvestis vėl tampa balta. Priskirkite tai tam pačiam sprendimui dėl eksporto politikos iš pirmojo skyriaus.

Likusios spalvų savybės apibrėžia perdangos žymes: HighlightColor paieškos rezultatams, SelectionColor vartotojo pasirenkamam tekstui, ReadingWordColor skaitomo žodžio žymekliui. Kiekvieną iš jų reikia patikrinti iš naujo su visais jūsų siūlomais filtrais. Gintarinis skaitymo žymeklis, kuris gerai matomas ant balto fono, po inversijos išnyksta; šviesiai mėlynas pasirinkimas pradingsta didelio kontrasto fone. Palaikykite kiekvienam režimui būdingas perdangos paletes, o ne vieną bendrą rinkinį, ir tikslingai išbandykite šiuos derinius. Filtrai ir teksto pavertimas kalba yra įprasta konfigūracija skaitytojams, kuriems skirta ši funkcija, o ne išskirtinis atvejis. Pati perdangos sistema aprašyta straipsnyje apie prieinamą skaitytuvą.

Skaičiai, patikrinimas ir spausdinimo klausimas

WCAG 2.1 paverčia šią funkciją išmatuojamu dalyku. Sėkmės kriterijus 1.4.3 reikalauja 4.5:1 kontrasto santykio pagrindiniam tekstui, o 1.4.6 padidina jį iki 7:1 padidintam kontrastui. Patikrinkite savo didelio kontrasto režimą pagal šiuos santykius naudodami kontrasto analizatorių su tikra sugeneruota išvestimi. Tekstas ant paveikslėlių ir tekstas formos laukuose yra tos vietos, kuriose šie santykiai tyliai sugenda, net jei pagrindinis tekstas praeina patikrą.

Spausdinimas reikalauja atskiro sprendimo, ir patikimiausia numatytoji reikšmė yra paties dokumento išvaizda, o parinktis „spausdinti taip, kaip rodoma“ turėtų būti siūloma kaip aiškus vartotojo pasirinkimas. Spausdintas puslapis yra įrodymas daugiau darbo eigų, nei tikisi peržiūros programų autoriai, o invertuotas sutarties spaudinys yra palaikymo incidentas su teisiniu atspalviu. Našumui svarbus dar vienas derinys: filtruotas atvaizdavimas padvigubina darbą su taškiniais paveikslėliais kiekvieną kartą pakeitus režimą, todėl netaikykite transformacijos per kiekvieną piešimo pranešimą. Saugokite filtruotą taškinį paveikslėlį talpykloje ir iš naujo atlikite transformaciją tik tada, kai pasikeičia puslapis, mastelis arba režimas. Talpyklos naudojimo strategija, kuri daro tai efektyvų, aprašyta straipsnyje apie atvaizdavimo talpyklą ir mastelio keitimo našumą.

Vieną dalyką verta išspręsti vartotojo sąsajoje, o ne kode: kuris režimas yra tinkamas kaip numatytasis. Vieno atsakymo nėra, todėl pasiūlykite visą rinkinį ir leiskite skaitytojui pasirinkti. Didelis kontrastas tinka daugumai tekstinio skaitymo atvejų, inversija tinka skaitytojams, kurie nori šviesaus teksto tamsiame fone, nespalvota skalė sumažina spalvų triukšmą, o fono atspalvis padeda esant jautrumui akinimui. Išsaugokite šį pasirinkimą kiekvienam vartotojui, atkurkite jį paleidžiant programą ir numatykite greitą sugrįžimo prie įprasto režimo kelią vienu klavišo paspaudimu, nes skaitytojui, patekusiam į neįskaitomą režimą, reikia greito būdo iš jo išeiti.

Čia naudojamos atvaizdavimo parinktys, taškinių paveikslėlių transformacijos ir rodinio spalvų savybės yra pateikiamos su „PDFium Component“, skirtu Delphi, C++Builder ir Lazarus/FPC, kartu su pilnu šaltinio kodu, kad transformacijų realizacijas būtų galima audituoti arba išplėsti.