Una Chiave per il Desktop Publishing
Corso di PostScript
- Marco A. Calamari
Undicesima Puntata
Oggi parleremo del problema della retinatura nella stampa con periferiche PostScript; buona parte della puntata sarà dedicata quindi all'utilizzo dell'operatore setscreen.
Durante tutto il corso, ci siamo spesso serviti dell'operatore setgray
per variare il tono di grigio delle varie parti delle nostre
stampe; non ci siamo mai occupati però di che cosa accada esattamente
nell'interprete PostScript quando devono essere prodotti i toni
di grigio.
La stampa con retini
Come sappiamo, la maggior parte delle stampanti laser e delle unità di fotocomposizione sono capaci di riprodurre solo il bianco ed il nero; per produrre i toni di grigio è necessario adoperare il meccanismo della retinatura. Si tratta dello stesso procedimento che deve essere utilizzato per riprodurre le fotografie sui quotidiani; le macchine da stampa con cui essi sono realizzati possono infatti stampare solo il nero pieno. Nei tempi andati, quando la retinatura era eseguita con metodi fotografici, si riproduceva la fotografia da stampare attraverso un retino (di solito costituito da piccoli cerchi trasparenti su fondo nero); sviluppando poi l'immagine così ottenuta ad alto contrasto con due passaggi su pellicola fotomeccanica si otteneva un nuovo positivo.
Con questa tecnica, nelle zone più chiare dell'immagine
originale si formano dei puntini neri più piccoli di quelli del
retino, mentre nelle zone più scure i puntini adiacenti si
impastano tra loro, dando luogo ad un retino nero con losanghe
bianche, tanto più piccole quanto più scuro era l'originale. Questo
effetto può essere notato anche nel retino che la LaserWriter
utilizza per default. Si esamini ad esempio la scala dei toni di
grigio stampata dal Programma 1 della Parte VIII; è facilmente riconoscibile
l'effetto di transizione da pallini neri a losanghe bianche che
abbiamo appena descritto.
La retinatura in PostScript
Il PostScript ha la possibilità di variare sia il retino che la scala dei toni di grigio; per poter introdurre i relativi operatori, è necessario prima spendere due parole descrivendo il meccanismo che l'interprete PostScript utilizza per generare il retino.
Si immagini di sovrapporre alla bitmap della pagina memorizzata nella stampante una griglia uniforme di celle quadrate, ottenute raggruppando n*n pixel. Questa griglia possiede una frequenza (numero di celle per pollice) ed un angolo (angolo di orientamento della griglia rispetto al sistema di coordinate, espresso in gradi). Ciascuna cella può essere resa di un particolare tono di grigio dipingendo di bianco un certo numero di pixel e di nero gli altri. Numericamente, la percentuale di nero che si ottiene è pari al rapporto tra il numero di pixel anneriti ed il numero dei pixel che formano ogni cella; questo significa che se una cella ha il lato di n pixel, e ne contiene quindi n*n, può riprodurre n*n+1 toni di grigio, percentualmente spaziati in modo uniforme fra 0 (nero pieno) e 1 (bianco pieno). Non è quindi in generale possibile riprodurre esattamente una particolare sfumatura di grigio, ma solo approssimarla con la più vicina consentita dalla grandezza della cella utilizzata. A parità di risoluzione della periferica PostScript, aumentando il numero di pixel che formano una cella si possono riprodurre un maggior numero di toni di grigio; questo va però a scapito della frequenza del retino, e quindi della risoluzione dell'immagine finale. In parole povere, si deve trovare un compromesso tra risoluzione dell'immagine e numero di toni di grigio, in modo da non avere quelle scalettature che spesso compaiono nelle zone che sfumano gradatamente da bianco al nero.
Supponiamo di far variare il tono di grigio di una cella dal nero al bianco. per far questo è necessario definire l'ordine in cui i pixel devono essere resi bianchi, che sarà poi sempre lo stesso; se quindi un certo tono di grigio corrisponde a dipingere di bianco un ben definito insieme di pixel della cella, un altro tono più chiaro sarà realizzato aggiungendo al precedente insieme di pixel qualche altro pixel bianco. L'ordine col quale viene eseguito questo sbiancamento si chiama spot function del retino.
Si noti che il retino è definito nel sistema di coordinate
della periferica, e che quindi non viene modificato dagli
operatori di scalatura e rotazione del sistema di coordinate
utente che modificano la CTM (scale, translate, ecc.).
È anche possibile agire sui toni di grigio di un'immagine,
alterandone globalmente il valore, per mezzo della transfer
function (funzione di trasferimento) del PostScript; la
funzione di trasferimento mette in correlazione il tono di grigio
di una zona dell'immagine (definito dall'operatore setgray)
con la frazione di pixel bianchi nella cella. Questa funzione è
per default una proporzionalità diretta; questo significa che un
gray level di 0.2 fa stampare in bianco il 20% dei pixel di ogni
cella, ed in nero il rimanente 80%; questa banale funzione di
trasferimento può però essere sostituita con altre più
complesse al fine di ottenere risultati particolari, come vedremo
fra poco.
I nuovi operatori
Prima di procedere oltre, è necessario definire gli operatori
che vengono utilizzati per modificare retino e funzione di
trasferimento:
La spot function, come abbiamo detto, definisce l'ordine in cui i pixel di una cella devono essere resi bianchi, al fine di ottenere un certo tono di grigio; l'interprete PostScript la realizza in un modo indiretto, al fine di minimizzare l'interferenza con la frequenza e l'angolo del retino prescelto. Si consideri il sistema di riferimento che descrive le coordinate di ciascun pixel all'interno della cella; in tale sistema l'origine è nel centro della cella, e le coordinate x ed y variano fra -1 e +1. Il pixel al centro della cella ha quindi coordinate nulle, e quelli ai quattro angoli coordinate uguali a +1 o -1. Per stabilire l'ordine in cui rendere bianchi i pixel, l'interprete PostScript pone le coordinate di ciascuno di essi sullo stack ed esegue la spot function; essa deve ritornare un singolo numero reale compreso fra -1 ed 1. L'operazione viene ripetuta per ciascun pixel, e quindi questi vengono ordinati in modo crescente rispetto al valore della spot function posseduto. I singoli valori della spot function non sono quindi significativi; l'informazione importante è infatti solo l'ordine in cui essa pone i vari pixel. Nel caso che due o più pixel abbiano lo stesso valore della spot function, l'ordine viene scelto arbitrariamente dall'interprete stesso.
Quando l'interprete PostScript deve stampare un certo tono di
grigio in una zona dell'immagine, calcola per mezzo della
funzione di trasferimento la frazione di pixel da rendere
bianchi, definisce il numero m di pixel bianchi moltiplicando
questa frazione per il numero di pixel che costituiscono la cella
(arrotondando in eccesso il risultato), calcola la bitmap della
cella dipingendo di bianco i primi m pixel nell'ordine stabilito
dalla spot function corrente ed in nero tutti gli altri, e
stampa ripetutamente la bitmap così calcolata in tutta la zona
da riempire.
Un esempio pratico
La spot function, stabilendo l'ordine con cui i pixel devono
essere dipinti di bianco, definisce anche l'aspetto geometrico
del retino. Vediamo quali valori essa assume, insieme agli altri
parametri che definiscono retino e funzione di trasferimento, nel
caso di una LaserWriter; per far questo colleghiamoci
all'interprete PostScript in modalità interactive
(ricordiamo che PS> è il prompt dell'interprete in
modalità interattiva, e che il doppio carattere meno tra cui
sono racchiusi alcuni operatori è solo una rappresentazione
usata dall'operatore pstack per indicare una situazione particolare
di sostituzione all'interno della procedura, che può essere
tranquillamente ignorata per i nostri scopi).
Da questa breve sessione interattiva, iniziata rendendo accessibile statusdict che contiene alcune delle informazioni necessarie, possiamo rilevare la memoria totale e utilizzata, il tipo di stampante a cui siamo collegati e la versione dell'interprete che stiamo usando.
L'operatore currenttransfer ci permette di vedere che la funzione di trasferimento corrente è una procedura nulla, che si limita a lasciare il livello di grigio sullo stack, facendolo direttamente utilizzare come frazione dei pixel da rendere bianchi.
L'operatore currentscreen ci consente invece di sapere
che la LaserWriter utilizza per default un retino a 60 linee per
pollice con angolo di 45 gradi. La spot function è definita,
come si può vedere, dalla formula
1 - (X^2 +Y^2)
Poiché, come abbiamo visto, le coordinate dei pixel nella cella variano tra -1 e +1, questo ordina i pixel in modo tale che i primi ad essere resi bianchi sono quelli agli angoli della cella, mentre l'ultimo è quello al centro.
Nella pratica tipografica la frequenza del retino è il
parametro fondamentale di lavoro, e può variare dalle 60 alle
150 linee per pollice. Come abbiamo appena detto, la LaserWriter,
che ha una risoluzione di 300 punti per pollice, utilizza
normalmente un retino di frequenza 60 linee; questo significa che
la cella ha un lato di
300 / 60 = 5 pixel
e che il numero di differenti toni di grigio riproducibili è
quindi di
5*5 + 1 = 26 toni
Questo valore piuttosto basso provoca di solito vistose
scalettature delle sfumature, che possono essere fatte scomparire
stampando lo stesso programma PostScript su una periferica a
risoluzione maggiore. Ad una risoluzione di 2540 punti per
pollice, comune nelle unità di fotocomposizione, queste
scalettature scompaiono, perché a 60 linee per pollice il numero
di toni di grigio riproducibili sale a
(2540/60) ^ 2 + 1 = 1793
Questo è un esempio di come il PostScript permetta di sfruttare al massimo le caratteristiche della periferica di stampa, potendo usare lo stesso identico programma di descrizione della pagina, ed ottenendo sempre il miglior risultato possibile.
A completamento di quanto esposto sopra dobbiamo aggiungere
che, poiché il numero di pixel che costituiscono il lato di una
cella è una variabile intera, non tutti i valori delle coppie
frequenza/angolo sono ammissibili. Per questo motivo, quando si
utilizza l'operatore setscreen, l'interprete può apportare
correzioni ai suddetti valori senza però darne alcun avviso; è
bene quindi esaminare, con l'ausilio dell'operatore currentscreen,
i valori effettivamente adottati dall'interprete PostScript, specialmente
nel caso che essi siano critici (ad esempio per una separazione
in quadricromia).
Avanti con gli esercizi!
Nel programma del Listato 1, il cui risultato è
mostrato in Figura 1, viene stampata per quattro volte una
stringa; la prima con il retino di default e le altre con un
retino a linee. Quest'ultimo viene ottenuto con una spot function
che scarta la coordinata x ed ordina i pixel in base all'inverso
del valore assoluto della coordinata y. La terza stringa mostra
l'effetto di una variazione dell'angolo, mentre la quarta mostra
come il retino cambia al variare del tono di grigio (si noti che
le prime tre stringhe sono stampate con lo stesso valore del tono
di grigio).
Il programma del Listato 2 (vedi Figura 2) è la
modifica di uno degli esempi delle precedenti puntate, e mostra,
nella zona della scala dei grigi, come varia la struttura del
retino lineare all'aumentare della percentuale di grigio.
Qualche parola in più merita il programma del Listato 3,
il cui risultato è mostrato in Figura 3. Anche questo
programma è una modifica di un vecchio esempio (rispetto alla
precedente versione questa rispetta anche le regole di
conformità PostScript), e mostra come si possa, con l'aggiunta
di una sola riga che modifica la funzione di trasferimento,
ottenere il negativo della pagina precedentemente stampata.
Questo può essere molto utile per produrre direttamente l'output utilizzabile
in fotocomposizione per quei sistemi di stampa che richiedono una
pellicola negativa. In questo caso la procedura che definisce la
funzione di trasferimento è semplicemente
{ 1 exch sub}
Possono essere facilmente realizzate procedure che consentono
effetti più complessi; come esercizio potreste provare a
scrivere le procedure delle funzioni di trasferimento che
permettono di ottenere una stampa ad alto contrasto od una
posterizzazione a sei livelli di un'immagine.
Saldiamo un debito
Due mesi fa, abbiamo fatto l'esempio (Parte IX, Programma 4)
di un programma PostScript che permetteva di stampare del testo
lungo una linea qualsiasi, rimandandone la spiegazione per motivi di
spazio. È giunto ormai il momento di saldare questo debito,
cominciando innanzitutto a segnalare che il precedente listato
contiene un errore che ne può impedire il funzionamento. Le due
(arcinote) procedure inch e tracciaCornice sono
state erroneamente poste all'interno del dizionario stampaSuLineatDict,
che è locale alla sola procedura stampaSuLinea; questo
provoca un errore del tipo undefined, a meno che le due
procedure non siano già state memorizzate nella stampante da un
programma precedente compreso nello stesso job. Per correggere il
problema, basta spostare le due procedure incriminate all'inizio
del programma (subito dopo il %%EndProlog), in modo che
non vengano incluse nel dizionario stampaSuLineatDict.
Il listato, che inizia con i soliti commenti di conformità,
genera un dizionario, destinato a contenere tutte le
sottoprocedure necessarie, la procedura stampaSuLinea, ed
esegue poi il programma principale (che è come sempre
incapsulato in una coppia gsave/grestore), terminando con il
classico End-Of-File.
Il programma principale salva lo stato grafico, traccia come
sempre la cornice della pagina e inizializza il font corrente (il
solito Times-Bold); viene poi generata, come path corrente, la
linea lungo la quale deve essere disposto il testo, avendo cura
di resettare la path all'inizio per evitare di includerne una
preesistente. Il programma pone quindi sullo stack la stringa
contenente il testo da stampare ed un numero che fornisce l'offset,
cioè la distanza, misurata dall'inizio della path, alla quale
disporre il primo carattere della stringa. Da ultimo viene
eseguita la chiamata alla procedura principale stampaSuLinea;
seguono la rimozione dell'incapsulamento e la stampa della
pagina.
La procedura stampaSuLinea apre per prima cosa il
dizionario stampaSuLineaDict, nel quale sono contenute
tutte le sottoprocedure necessarie al suo funzionamento, pone i
due argomenti che le sono stati passati per mezzo dello stack in
due variabili, ed inizializza altre tre variabili (la lunghezza
percorsa sulla linea, la lunghezza di testo già stampata ed il
contatore dei caratteri stampati). Dopo un altro incapsulamento,
viene eseguito l'operatore flattenpath, che, come ricordiamo,
modifica la path corrente, che in questo caso è formata da
segmenti ed archi di circonferenza, approssimandola con una unica
spezzata che consiste solo di segmenti (cioè di operatori moveto, lineto e
closepath). Il loop principale della procedura viene ottenuto
ponendo quattro sottoprocedure sullo stack ed eseguendo
l'operatore pathforall; come ricordiamo esso esegue, per
ogni elemento che costituisce la path corrente ed a seconda del
suo tipo, una delle quattro procedure passate come argomenti.
Prima di terminare, la procedura stampaSuLinea rimuove
l'incapsulamento, cancella la path corrente e chiude il
dizionario precedentemente aperto.
Il dizionario stampaSuLineaDict contiene (non in questo
ordine) le seguenti sottoprocedure:
C'e' programma e programma !
In questi mesi, abbiamo più volte sottolineato come sia importantissimo, programmando in PostScript, scrivere un codice leggibile e ben commentato; i programmi PostScript infatti, come e più di quelli scritti con altri linguaggi, tendono a diventare rapidamente illeggibili anche per chi li ha scritti. Un lettore ci ha detto Ho notato che i programmi PostScript contenuti nei preprocessor delle applicazioni o nello stesso LaserPrep Apple sono scritti in maniera ben diversa da quelli del corso; nessun commento e nomi brevi ed illeggibili. Per quale motivo?.
Le ragioni principali sono due; la prima è che l'utilizzo di nomi brevissimi abbrevia il programma, che è quindi downloadabile più rapidamente alla stampante, ed in ultima analisi è più efficiente. La seconda regione è che il codice PostScript di un preprocessor rappresenta un patrimonio della software house che lo ha scritto, ma deve essere reso di pubblico dominio, perché va incluso, in forma leggibile nell'applicazione stessa. E' logico quindi che si tenti di renderne il più difficile possibile l'interpretazione alla concorrenza; per questo motivo, prima di rilasciare il programma all'esterno, si rimuovono tutti i commenti dal sorgente originale, e si sostituiscono, con una serie di find & replace, tutti i nomi di variabili e procedure con nomi brevissimi ed incomprensibili, di solito formati da uno o due caratteri.
Per dimostrare l'efficacia di questo modo di operare,
consideriamo il programma del Listato 4; si tratta
dell'esempio che stampa il testo lungo una linea qualsiasi. La
sua lunghezza si e' ridotta da 4500 a 1500 caratteri, e la
struttura e' assolutamente incomprensibile, eppure (riuscendo a
batterlo senza errori) funziona perfettamente, ed è leggermente
più veloce dell'originale.
... to be continued
La prossima puntata, che sarà l'ultima del corso, introdurrà
gli operatori di gestione del colore; dedicheremo inoltre la
parte finale ad alcune considerazioni su cloni PostScript e su
come è cambiato, nell'anno che abbiamo passato insieme, il
panorama del PostScript e dei linguaggi di composizione della
pagina in generale.
Copyright © 1985: Marco A. Calamari