momente şi schiţe de informatică şi matematică
To attain knowledge, write. To attain wisdom, rewrite.

Tabele-gradient, explorând PostScript

HTML | LaTex | PostScript
2019 may

Următoarele două tabele sunt extrase din fişierul postscript/image.html, asociat secţiunii "Raster Graphics" din tutorialul – prezentat ca site HTML – A First Guide to PostScript (Peter Weingartner, 2006):

(a)
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

(b)
Gradient: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
Red: 00000000 00000000
Green: 15141312111098 76543210
Blue: 01234567 89101112131415

Se poate inspecta şi aici, codul HTML care le defineşte (după selectarea lor cu mouse-ul, click-dreapta oferă "View Selection Source" în Firefox, sau "Inspect" în Chrome); pe celulele <td> s-a aplicat style="background: #xxxxxx" cu x = 0..F pentru (a) şi style="background: #00xxyy" cu x = 0..F şi y + x = F pentru primul rând <tr> al tabelului (b) – unde 'F' reprezintă numărul 15, iar x şi y sunt cifre hexazecimale. Culorile sunt gradate de la negru spre alb în (a) şi de la verde (0-15-0) spre albastru (0-0-15) în (b).

În [1] am "comasat" pachetul de fişiere HTML care constituie site-ul menţionat, într-un fişier HTML5 pe care apoi l-am convertit în LaTeX - obţinând în final fgps.pdf; totuşi, tabelele notate mai sus cu (a) şi (b) au fost "convertite" defectuos, scăpându-mi aspectul colorării celulelor (vezi pag. 20-21 din fgps.pdf).

Acum facem o mică explorare a limbajului PostScript, urmărind să obţinem "tabele-gradient" precum (a) şi (b) şi să le integrăm ca atare, într-un mic document PDF (desigur, am plecat de la constatarea evidenţiată deja mai sus, că pe site-ul menţionat ele au fost generate folosind elemente HTML <table>, deşi contextul explicativ respectiv viza obţinerea lor prin operatori PS).

Reperele folosirii limbajului PostScript

Pentru început să definim într-un fişier "cell.ps", o celulă (una circulară):

%!PS
/Cell {
   .5 .5 .5 0 360 arc  % centru (.5, .5), rază .5 (în unitatea de măsură curentă)
   setgray fill  % culoarea (număr între 0 şi 1) va fi preluată din stiva operanzilor
} def

72 750 translate  % noua origine a sistemului de coordonate este (72, 750)
36 36 scale  % unitatea de măsură este acum de 36 "bp" = 36*1/72 = 0.5 inch
.5 Cell  % celulă colorată cu o proporţie de negru şi alb (câte 0.5)

1 0 translate  % translatează spre dreapta cu 1 (=0.5 inch)
.7 Cell  % (celulă vecină) .3 negru şi .7 alb ("gri deschis")

De obicei, prima linie dintr-un fişier care conţine instrucţiuni PS este "comentariul" %!PS (sau o variantă precum %!PS-Adobe-2.0 EPSF-2.0). Un "Document Viewer" (de exemplu, evince) înţelege deja după extensia specifică ".ps" că are de-a face cu un program PS (prima linie %!PS poate lipsi) şi va pasa instrucţiunile respective unui interpretor de PostScript (de exemplu, Ghostscript), redând rezultatele pe ecran. Însă în cazul imprimantelor dotate cu interpretor propriu de PS, activarea acestuia se face numai dacă prima linie din fişierul indicat este "%!PS" (altfel, se va tipări textul-sursă al programului PS transmis şi nu rezultatul execuţiei acestuia).

În PostScript, pagina de hârtie este mapată peste primul cadran al sistemului de axe obişnuit (originea este în colţul stânga-jos, punctele paginii fiind accesate de la stânga spre dreapta şi de jos în sus), având ca unitate de măsură 1bp=1/72 inch. Folosind operatorii translate şi respectiv scale, sistemul de coordonate poate fi translatat şi apoi scalat cum ne convine (încât figura creată în sistemul de coordonate iniţial poate fi mărită şi adusă în orice loc din pagina reală).

Parametrii necesari unui operator sunt transmişi prin "stiva operanzilor"; de exemplu, linia de program 36 36 72 750 translate scale s-ar interpreta (mai popular) astfel: se stivuiesc primele patru numere, ca "operanzi"; primul operator întâlnit translate, consumă primii doi operanzi din vârful stivei (efectul fiind că acum originea este la 72 unităţi de marginea stângă şi 750 unităţi de marginea de jos a paginii); în stivă rămâne 36 36 scale şi executând "scale", unitatea de măsură devine de 36bp (pe ambele axe) şi stiva devine vidă (iar o translaţie ulterioară va ţine seama de noua unitate de măsură - de exemplu, dacă ar urma linia 1 0 translate (ca în programul de mai sus), atunci efectul va consta în deplasarea originii spre dreapta cu 36bp).

Un nou operator (cum este Cell, în programul formulat mai sus) se poate introduce folosind sintaxa /identificator {...} def şi apoi se poate invoca folosind identificator (fără prefixul "/"). Astfel, .7 Cell adaugă în vârful stivei 0.7 şi începe să execute instrucţiunile din corpul definiţiei /Cell, iar când se ajunge la linia setgray fill, operatorul setgray va prelua operandul care îi este necesar din vârful stivei şi va avea ca efect stabilirea în cadrul modului grafic curent a valorii 0.7 pentru nivelul de culoare alb-negru a cernelii (0 corespunde nivelului maxim "negru", iar 1 corespunde cu "alb", sau "absenţa" cernelii propriu-zise; valoarea 0.7 corespunde unei proporţii de 3 părţi negru şi şapte părţi "alb", deci cu "gri deschis"); fill va umple celula folosind culoarea tocmai setată drept "culoarea curentă".

Rezultatul execuţiei programului de mai sus se poate tipări pe hârtie prin lpr cell.ps, sau se poate vedea pe ecran fie folosind un "Document Viewer", fie invocând direct interpretorul Ghostscript (preinstalat pe distribuţiile de Linux) prin gs cell.ps:

Dar ce este "rezultatul execuţiei"? Este o pagină, de un anumit format care este setat în prealabil (de exemplu "a4", cu dimensiunile 595bp şi 842bp). Desigur, ne-ar conveni să fie nu o pagină independentă, ci doar acea zonă dreptunghiulară care încadrează suficient de bine figura construită de programul respectiv (în scopul încorporării acesteia într-o pagină existentă); înlocuind "%!PS" prin "%!PS-Adobe-2.0 EPSF-2.0", putem afla coordonatele acestei zone folosind gs:

vb@Home:~/4apr$ gs -q -o /dev/null -sDEVICE=bbox  cell.ps
%%BoundingBox: 71 749 145 787
%%HiResBoundingBox: 71.991068 749.987977 144.008996 786.005976

Copiind de aici linia "%%BoundingBox: ..." imediat după linia %!PS-Adobe-2.0 EPSF-2.0 şi salvând programul modificat astfel ca "cell.eps", vom putea încorpora figura ca atare, în contextul curent al unei pagini - cel puţin în cazul în care folosim LaTeX: din valorile citite din BoundingBox, compilatorul va deduce dimensiunile boxei pe care trebuie să o rezerve figurii în cadrul paginii, iar în procesul finalizării documentului PDF, va invoca automat un anumit program (epstopdf) care, executând codul PS respectiv, va trasa efectiv figura, în boxa care i-a fost rezervată.

Generarea iterativă a unui tabel-gradient

Concatenăm un anumit număr de celule folosind operatorul for – construit sintactic prin From Step To {Proc} for şi executat cam aşa: se depune pe stivă valoarea iniţială From (mai mică decât valoarea finală To) şi se execută secvenţa de instrucţiuni Proc; apoi valoarea din vârful stivei este majorată cu Step şi dacă astfel nu se depăşeşte To, se repetă executarea secvenţei Proc; ş.a.m.d. Pe parcurs, în vârful stivei găsim indexul iteraţiei curente; pentru a-l folosi, îl adăugăm încă o dată în stivă (prin dup) şi apoi îl preluăm din noul vârf al stivei (prin 0 index).

În 'Proc' translatăm spre dreapta cu lăţimea unei celule, construim o nouă celulă (prin operatorul Cell) setându-i culoarea în funcţie de rangul ei (care este preluat de pe stivă), iar dedesubtul celulei notăm rangul respectiv (cu fontul care a fost fixat în prealabil şi folosind culoarea curentă):

%!PS-Adobe-2.0 EPSF-2.0
%%BoundingBox: 20 480 384 521

/Cell {  
   newpath  % omite segmentul dintre ultimul punct curent şi începutul arcului
   .5 .5 .5 0 360 arc
   setgray fill  % culoarea curentă este preluată din stivă
} def

[20 0 0 20 .5 500] concat  % 20 20 .5 500 translate scale  (unitatea = 20bp)
/Courier-Bold findfont .6 scalefont setfont  % font-size = .6*20 = 12bp

0 1 15 {  % indexul pasului curent 0..15 este plasat în vârful stivei
    1 0 translate  % la fiecare pas, mută spre dreapta cu 20bp
    dup   % adaugă în stivă (încă o dată) indexul pasului curent
    16 div Cell   % 'setgray' capătă valoarea index_curent/16 (culoarea curentă)
    .4 -.75 moveto  % mută punctul curent dedesubtul celulei
    0 index 16 1 string cvrs show  % scrie indexul 0..15 în hexazecimal
} for

.5 setgray
(  \(\/16\)) show  % culorile celulelor nu sunt 0..15, ci (0..15)/16

newpath cu care începe construcţia celulei curente, "anulează" punctul curent existent în momentul re-invocării operatorului Cell (prin scrierea finală a rangului, dedesubtul celulei, "punctul curent" rămâne în mod implicit în dreapta caracterului tocmai scris); fără aceasta, operatorul arc ar fi adăugat suplimentar segmentul care uneşte "punctul curent" existent cu punctul iniţial al arcului propriu-zis.

Mai precizăm că 0 index 16 1 string cvrs produce un "string" de lungime 1 conţinând reprezentarea în baza 16 a valorii extrase prin '0 index' din vârful stivei (încât show obţine de scris rangul celulei în hexazecimal).

Gradientul tabelat mai sus are pasul 1/16, de la negru (0) la "aproape-alb" (15/16); factorul "(/16)" a fost afişat prin ultima instrucţiune, ca şir de caractere (semnalat în PS prin ambalarea în paranteze rotunde) - având grijă să prefixăm caracterele "speciale" (ca "(", ")" şi "/") cu "backslash".

Formularea directă a unor tabele-gradient

Metoda standard pentru obţinerea unui gradient de culoare (dar cu "celule" dreptunghiulare, nu circulare), constă în folosirea unuia dintre operatorii image şi colorimage. Pentru a şi eticheta celulele respective (reflectând culoarea fiecăreia), avem de setat (ca şi mai sus) un "font curent" şi eventual, o anumită distanţare (după lăţimea unei celule) între etichetele respective.

Programul "gradient1.eps" produce un tabel-gradient de la negru spre alb:

%!PS-Adobe-2.0 EPSF-2.0
%%BoundingBox: 71 199 360 234  % obţinut prin: gs -o /dev/null -sDEVICE=bbox

% "tabel-gradient" cu 16 celule (de la negru spre alb), cu lăţimea 288=16*18, 
% înălţimea 18 şi originea în punctul (72, 216)

gsave
    [288 0 0 18 72 216] concat
    16 1 4 [16 0 0 1 0 0] <0123456789ABCDEF> image
grestore

/Courier findfont 12 scalefont setfont  % Pentru etichetarea celulelor.
/gaps {
    [16 {18} repeat]  % Tablou de 16 distanţe egale cu 18, folosit pentru
} def                 % a distanţa între ele etichetele celulelor (cu 'xshow').

0.3 78 200 moveto setgray
(0123456789ABCDEF) gaps xshow  % etichetează celulele (cu distanţare)

Subliniem şi noi obiceiul de a salva (prin gsave) şi restaura (prin grestore) contextul grafic curent, încât transformările de coordonate, font, culoare, etc. din interior să nu afecteze restul programului; dar să observăm că în cazul de faţă, dacă mutăm blocul respectiv la sfârşitul programului (dar numai aşa), atunci nu va mai fi necesar să-l ambalăm cu gsave - grestore.

image interpretează şirul de cifre hexazecimale primit '012...EF' ca secvenţă binară '000000010010...11101111', pe care o porţionează şi o scalează conform valorilor celorlaţi patru parametri (vezi linia boldată din textul programului de mai sus): o desparte în 16 subsecvenţe consecutive de câte 4 biţi, '0000', '0001', ..., '1110', '1111' şi consideră că fiecare dintre acestea reprezintă - cu o anumită nuanţă de "negru" - câte un pixel al unei imagini produse iniţial în pătratul unitar (cu latura de 1bp); transformarea [16 0 0 1 0 0] care îi este indicată, determină scalarea prin care latura orizontală devine de 16bp, iar cea verticală rămâne de 1bp.

Astfel, imaginea finală produsă ar consta în 16 pixeli alăturaţi pe orizontală, marcaţi prin câte o anumită nuanţă de "negru"; dar cealaltă transformare implicată în program, [288 0 0 18 72 216], măreşte laturile iniţiale de 18 ori (încât "pixelul" vizat iniţial de image devine pe hârtie un pătrat cu latura de 18bp) şi mută originea imaginii mărite astfel în punctul (72, 216)bp.

Programul "gradient2.eps" produce un tabel-gradient de la verde spre albastru:

%!PS-Adobe-2.0 EPSF-2.0
%%BoundingBox: 71 137 360 193

/gaps { [16 {18} repeat] } def  % interspaţiere, pentru 'xshow'
/Courier findfont 12 scalefont setfont  % font pentru etichete

78 162 1 0 0 setrgbcolor moveto
(0000000000000000) gaps xshow  % notează valorile R ("red"), sub fiecare celulă
78 150 0 1 0 setrgbcolor moveto
(FEDCBA9876543210) gaps xshow  % notează valorile G ("green")
78 138 0 0 1 setrgbcolor moveto
(0123456789ABCDEF) gaps xshow  % notează valorile B ("blue")

% gradient Green..Blue cu 16 celule, cu lăţimea 288=16*18, 
% înălţimea 18 şi cu originea în punctul (72, 174).
[288 0 0 18 72 174] concat 
16 1 4 [16 0 0 1 0 0] 
    <0000000000000000> % canalul valorilor "Red"
    <FEDCBA9876543210> % canalul valorilor "Green"
    <0123456789ABCDEF> % canalul valorilor "Blue"
    true  3  % 3 canale de culoare (RGB), separate
colorimage

Blocul care conţine colorimage este executat o singură dată şi poziţionându-l la sfârşitul programului, nu mai este necesar să-l ambalăm între gsave şi grestore.

Folosind pachetul graphicx putem "include" fişiere PostScript (în format "EPS", adică în principal, conţinând şi linia %%BoundingBox ...) într-un document LaTeX, încât documentul PDF rezultat prin compilare cu xelatex să încorporeze rezultatul execuţiei programelor respective:

Putem compila şi cu pdflatex (în loc de xelatex), dar atunci trebuie utilizat şi pachetul epstopdf (şi mai trebuie luate măsuri pentru recunoaşterea literelor "cu accent", precum cele româneşti).

Bineînţeles că acum putem interveni în fgps.tex, pentru a înlocui tabelele-text (a) şi (b) cu tabelele-gradient produse prin programele PostScript de mai sus.
De altfel, s-ar cuveni să completăm fgps.tex (şi implicit, documentul PDF asociat), inserând alături de exemplele de programare în PS preluate din tutorialul menţionat şi imaginile rezultate prin execuţia acestora – ceea ce este mai simplu de făcut în LaTeX, decât pe un site HTML: trebuie doar să indicăm compilatorului codul PS de executat (de fapt, fişierul EPS care conţine codul respectiv), acolo unde vrem să apară imaginea rezultată, iar aceasta va fi încorporată apoi (definitiv) în documentul PDF.

vezi Cărţile mele (de programare)

docerpro | Prev | Next