e-Lite - Politecnico di Torino
Transcript
e-Lite - Politecnico di Torino
Politecnico di Torino Facoltà di Ingegneria Corso di Laurea in Ingegneria Informatica Dipartimento di Automatica e Informatica Studio e realizzazione di un sistema di puntamento oculare per l’accesso al computer da parte di disabili motori gravi Relatore: Ing. Fulvio CORNO CoRelatore: Ing. Laura FARINETTI Tesi di Laurea di: Alessandro GARBO Anno Accademico 2002/2003 In tutti questi anni non ho mai capito in che misura la fortuna abbia influenzato l’andare delle cose. Probabilmente anche in questo caso si deve utilizzare una sorta di teorema sulla relatività. In quanto, ciò che per qualcuno è fortuna per altri è sfortuna oppure, ciò che in un primo momento sembra malasorte col tempo si addolcisce e prende i contorni di un’immensa fortuna. Le avversità che ho incontrato costantemente sul mio cammino mi hanno fatto spesso pensare alle relative fortune di altri che, puntavano al mio stesso obiettivo. Esami superati con una preparazione insufficiente o voti elevati conseguiti solo per aver studiato quello che veniva di solito chiesto all’esame facevano parte di un mondo a cui io non appartenevo. I lunghissimi mesi passati a studiare l’intero programma di ogni esame se non, in alcuni casi, accresciuto da testi ausiliari, mi portavano a maledire la sorte che mi obbligava a sottostare a certe regole. Vedere la strada semplice e soleggiata dove molti si avventuravano, mentre ero costretto a percorrere un sentiero critico e irto d’insidie, mi ha insegnato a lottare per ottenere ciò che volevo contro tutto e tutti. Ora, a distanza di anni, vedo quelle avversità non come una prova inutile e insignificante ma una possibilità d’inestimabile valore che mi è stata offerta. Il luogo che ho raggiunto è simile solo in parte a quello raggiunto da chi ha sempre intrapreso il percorso più semplice. In realtà quello che io ho raggiunto è il vero obiettivo che la scuola ha cercato di dare a tutti noi. Ora ho un’istruzione sufficiente per parlare con qualsiasi persona mi rivolga la parola, sia essa un fisico, un chimico, un elettronico, un meccanico o, e questo è ancor più importante per me, un operaio o un religioso. Per questa ragione dedico questa tesi alla sfortuna e alle avversità che mi hanno accompagnato nel mio percorso. Senza di esse non sarei quello che sono ora. Inoltre il lavoro qui svolto è dedicato a persone verso le quali le mie sfortune impallidiscono, spero che questa tesi permetta a loro un pizzico di libertà in più. Infine ringrazio il Professor Corno per la possibilità che mi ha dato e la famiglia per l’aiuto indiretto costante negli anni. Indice 1 Introduzione 2 Motivazione e obiettivi 2.1 Ausili 2.1.1 Sensori 2.1.2 Tastiere 2.1.3 Ausili per la comunicazione 2.1.4 Dispositivi di puntamento 2.1.5 Sistemi di puntamento oculare 2.2 Contesto 2.2.1 Definizione degli obiettivi 2.3 Analisi dei problemi 2.3.1 Ambiente di lavoro 2.3.2 Architettura del sistema 2.3.3 Acquisizione ed elaborazione dati 2.3.4 Integrazione nel sistema di applicazioni indipendenti 2.3.5 Calibrazione 3 BackGround 3.1 Paradigma di programmazione 3.1.1 Programmazione procedurale 3.1.2 Programmazione basata sugli oggetti 3.1.3 Programmazione orientata agli oggetti 3.2 Windows e i messaggi 3.2.1 Registrazione della window class 3.2.2 Creazione e visualizzazione della finestra 3.2.3 Ciclo dei messaggi 3.2.4 Window procedure 3.3 Multitasking e multithreading 3.4 Victor e le immagini 3.4.1 Codifica delle immagini 3.4.2 DIB 3.4.3 Victor 4 Architettura e algoritmi 4.1 Architettura 4.1.1 Acquisizione immagini 4.1.2 Scelta del riferimento 4.1.3 Motion capture 4.1.4 Definizione campo visivo 4.1.5 Indicazione dello sguardo 4.1.6 Gestione del sistema 1 5 6 6 7 8 9 10 12 13 14 15 17 18 22 23 27 28 28 28 29 29 30 31 31 31 32 33 34 36 36 39 40 43 44 45 45 47 48 4.2 Algoritmi 4.2.1 Scelta del riferimento 4.2.2 Cattura del movimento 4.2.3 Analisi del campo visivo 4.2.4 Definizione del punto fissato 5 Implementazione 5.1 Scelta del riferimento 5.1.1 Impostazione 5.1.2 Analisi immagine 5.1.3 Visualizzazione 5.1.4 Scelta 5.2 Cattura del movimento 5.2.1 Definizione parametri funzionali 5.2.2 Elaborazione svolta in una sequenza 5.2.3 Filtro 5.3 Definizione campo visivo 5.3.1 Introduzione di uno stato 5.3.2 HandShake 5.3.3 Elaborazione del singolo stato 5.3.4 Conclusione 5.3.5 Cablaggio manuale 5.4 Indicazione dello sguardo 5.4.1 Elaborazioni svolte in una sequenza 5.4.2 Messaggi alle applicazioni 6 Valutazioni sperimentali 6.1 I controlli 6.1.1 Controllo sulla velocità di esecuzione del thread principale 6.1.2 Numero di catture per attuare il filtro 6.1.3 Clic temporale 6.1.4 Regione relativa al clic 6.1.5 Focus 6.2 Le applicazioni 6.2.1 Applicazione Uno: Addestramento 6.2.2 Applicazione Due: Tastiera 7 Sviluppi futuri 7.1 Legami esterni 7.2 Struttura 7.3 Algoritmi 7.4 Applicazioni 7.5 Conclusioni 8 Conclusioni 48 50 53 56 58 61 63 64 65 68 68 69 71 72 74 76 77 79 79 81 81 82 83 86 87 88 89 90 91 92 93 94 94 96 99 100 101 102 103 106 109 Appendice A Elementi principali Main Dichiarazioni Messaggi Supervisore Appendice B Oggetti principali Cattura Palette Intercetta Decisore Thread principale Thread di calibrazione Appendice C Windows procedure delle sottofinestre Tavolozza Immagine Controlli Posiziona Appendice D Windows procedure delle applicazioni indipendenti Applicazione Uno: addestramento Applicazione Due: tastiera Spaziatore Applicazione Tre Appendice E Messaggi definiti da Windows Resource Oggetti relativi alla gestione Menu Errore Finestre Procedure di selezione comandi Menu Cattura Menu Intercetta Menu Posiziona Menu Applicazioni Menu Finestre 1 1 1 6 11 14 25 25 25 26 29 35 37 38 39 39 39 40 41 47 51 51 51 52 55 56 59 59 59 60 60 64 64 68 68 68 69 69 69 1 Introduzione Nel 1984 l’Ufficio del Servizio di Educazione Speciale e Riabilitazione del dipartimento dell’Educazione statunitense insieme alla Casa Bianca prese l’iniziativa di condurre un processo che portasse i produttori degli elaboratori, del software e gli utilizzatori a riflettere sulla questione riguardante l’accesso e l’uso dell’elaboratore e del software da parte di persone con disabilità. Il primo incontro di tale iniziativa si tenne nel Febbraio del 1984 presso la Casa Bianca. Il risultato ottenuto fu il riconoscimento del problema dell’accessibilità ad un ambiente informatico di comune utilizzo e la richiesta, da parte dei produttori, di ulteriori informazioni inerenti ai tipi di disabilità, alle limitazioni che incontra una persona disabile nell’utilizzare un elaboratore tradizionale, alle strategie che avrebbero potuto incrementare l’usabilità dell’elaboratore stesso. L’accesso al computer e ai programmi software deve garantire a chiunque, incluse le persone disabili, la possibilità di usare vantaggiosamente la tecnologia in ambito lavorativo, educativo e ricreativo. In seguito all’incontro di Febbraio, fu stesa la prima versione della White Paper che venne distribuita come preparazione per un secondo incontro tenutosi nell’Ottobre del 1985. Il risultato di questo secondo incontro fu la formazione di un gruppo di 1 lavoro volto ad identificare e documentare idee e considerazioni aventi lo scopo di incrementare l’accessibilità degli elaboratori standard da parte sia di persone disabili, che normodotate. A grandi linee il problema generale può essere suddiviso in una serie di questioni più semplici. Una porzione importante della popolazione presenta delle disabilità (acquisite alla nascita, o in seguito ad un incidente, a causa di una malattia o semplicemente dovute alla vecchiaia) che impediscono loro di utilizzare i computer standard e i software tradizionali. I costi per le modifiche necessarie da apportare agli elaboratori, per venire incontro alle esigenze delle persone disabili, sarebbero non eccessivi o addirittura irrisori ed incrementerebbero il numero di possibili utenti. La caratteristica di accessibilità di un prodotto ne rende anche più semplice l’utilizzo da parte delle persone non disabili, in quanto queste possono usufruire di benefici quali minor fatica, maggiore velocità e minor probabilità di errore. La maggior parte delle modifiche al design dovrebbero realizzarsi gratuitamente oppure ad un prezzo contenuto, per ottenere un beneficio diretto sul mercato di massa. La maggior parte dei cambiamenti necessari a rendere un prodotto accessibile dovrebbero essere già inclusi direttamente nel prodotto stesso. La White Paper rappresenta il primo passo compiuto dalle industrie del software per andare incontro alle esigenze dei disabili. Lo scopo di questo documento è triplice. In primo luogo si propone di informare le persone che operano nel campo della riabilitazione. In secondo luogo di documentare l’importanza e le ragioni del software accessibile. Infine di riuscire a sensibilizzare i produttori stimolandoli a lavorare, al fine di garantire alle persone disabili la possibilità di usufruire di tutte le prestazioni in condizioni di adeguata affidabilità ad autonomia. Le ripercussioni scaturite dalla presentazione della White Paper si sono fatte sentire anche in Europa. Nel Marzo del 1992 iniziò un progetto di elaborazione delle Nordic Guidelines for Computer Accessibility da parte della NNH Nordic Committee an Disability. La NNH è una cooperazione, sotto il Nordic Council of Minister, comprendente i governi danese, finlandese, norvegese e svedese. Lo scopo dell’NNH è la realizzazione di obiettivi nazionali di piena partecipazione nella società e di uguaglianza per le persone disabili. Il Nordic Guidelines for Computer Accessibility è il risultato di un progetto riguardante l’informazione tecnologica (IT). L’IT è una parte delle infrastrutture della società moderna. 2 Gli elaboratori sono ormai parte integrante nell’educazione e nel lavoro. È stato stimato che il 70% del lavoro d’ufficio in Europa è svolto in posti di lavoro informatizzati. La Cooperazione Internordica di Standardizzazione delle informazioni Tecnologiche (INSTAT/IT) ha supportato questo progetto con la seguente dichiarazione: “INSTAT/IT crede che si debba provvedere affinché le persone disabili abbiano la possibilità di usare attrezzature IT. INSTAT/IT accoglie favorevolmente il progetto e lo considera un buon inizio in questo settore. Stiamo attendendo i risultati, sotto forma di guida, e ci aspettiamo di poterli usare per un lavoro di standardizzazione regionale, nazionale ed internazionale”. Alla base di questa pubblicazione ci sono due documenti, il primo è la White Paper, mentre l’altro è una specifica di requisiti prodotta in collaborazione dall’Agenzia Svedese per lo Sviluppo Amministrativo e dall’Istituto Svedese di Handicap. L’Organizzazione Mondiale della Sanità stima la percentuale di popolazione disabile almeno nel 10%. Queste persone non costituiscono un gruppo omogeneo e statico, anche il tipo di disabilità può essere temporaneo o permanente e variare d’intensità. Nei paesi industrializzati si vive di più, ma le nascite diminuiscono. La persone hanno acquisito le abitudini introdotte dalle attuali tecnologie, ma per alcuni l’accesso ad esse può essere progressivamente ridotto a causa di limitazioni dovute all’età avanzata. Queste riflessioni devono sicuramente aiutare a riflettere sull’importanza della questione dell’accessibilità all’elaboratore. Con l’avvento e la diffusione della telematica, nuove possibilità di integrazione economica e sociale si aprono per la popolazione disabile ed anziana. Tuttavia a quest’interessante prospettiva si contrappongono problematiche che, se non opportunamente considerate, possono aumentare l’isolamento di queste categorie di persone. Rendere un terminale o servizio di facile uso per una persona disabile è garanzia di alta usabilità per ogni utente. Per fare ciò è però necessario lo sviluppo di nuove aree di conoscenza, che includono i tipi e il grado di disabilità, le conseguenze e le limitazioni che deve affrontare una persona disabile, gli aspetti psicologici ed economici ed infine le strategie per incrementare l’usabilità del prodotto. Resta ancora da definire cosa si intenda per accessibilità. L’accessibilità si riferisce alla progettazione dell’ambiente costruito, dei prodotti e dei servizi di uso generale in modo che questi possano essere utilizzati agevolmente dalla più ampia percentuale di popolazione, incluse le persone disabili od anziane. Più specificatamente per accessibilità informatica si intende la possibilità, anche da parte di persone con impedita capacità motoria, di accedere ad 3 un ambiente informatico di comune utilizzo e di fruire di tutte le prestazioni in condizioni di adeguata affidabilità ed autonomia. Oggi in Europa 37 milioni di cittadini soffrono varie forme di handicap. L’Italia, in base ad una stima della Divisione della popolazione dell’ONU, è fra i paesi a elevato tasso di invecchiamento per la più alta percentuale di ultrasessantenni (24%) e la più bassa percentuale (14%) di giovani di età inferiore ai 15 anni. In base alle previsioni dell’Istituto di ricerche sulla popolazione del CNR in futuro nel nostro paese ci saranno 17 milioni di anziani e 9 milioni di giovani. È evidente l’importanza crescente del tema della disabilità negli anni a venire. Il progressivo invecchiamento della popolazione italiana, infatti rende necessari interventi, anche attraverso tecnologie innovative, per migliorare le condizioni di vita delle persone che, a causa dell’età, soffrono di disabilità di varia natura. La continua innovazione tecnologica è riuscita a raggiungere risultati di rilievo, garantendo a molti disabili una vita uguale a quella delle persone normodotate è possibile per loro studiare, lavorare, viaggiare e praticare perfino sport a livello agonistico. In futuro la società si dovrà porre l’obiettivo di ricercare una sempre migliore integrazione dei disabili. Lo scopo di questa tesi è di apportare un piccolo contributo all’obiettivo appena citato. Tra i numerosi campi in cui è possibile agire si è scelto di aiutare le persone affette da problemi motori gravi. L’utilizzo da parte di queste persone del Personal Computer può essere compromesso dall’impossibilità di utilizzare le varie periferiche di acquisizione dati come il mouse e la tastiera. Tuttavia è possibile enfatizzare ed utilizzare le capacità motorie residue dell’utente al fine di ottenere un metodo alternativo per indicare all’elaboratore i comandi da eseguire. Grazie all’utilizzo di una videocamera connessa all’elaboratore si cercherà di carpire il movimento del viso dell’utente per comprendere dove sia rivolto il suo sguardo. Sulla base di una delle possibili soluzioni descritte verrà implementato un sistema che permetterà all’utente di selezionare degli oggetti sullo schermo del Personal Computer. Per ottenere questo risultato l’utente non dovrà far altro che rivolgere il viso nella direzione dell’oggetto. Infine si discuteranno le prestazioni e i possibili sviluppi futuri del sistema. 4 2 Motivazione e obiettivi Molte sono le barriere che una persona disabile deve affrontare e superare nella vita di tutti i giorni. Si va da quelle architettoniche fino a quelle più subdole, nascoste negli apparecchi di uso quotidiano. Spesso si dimentica come sia difficile, per non dire impossibile utilizzare gli elettrodomestici se, per esempio, non si possono usare le mani per premere i pulsanti che li controllano. Oppure leggere le istruzioni che regolano un dato apparecchio quando si hanno notevoli problemi di vista. Il problema più grave in cui ci si imbatte, quando si cercano delle soluzioni per migliorare l’esistenza delle persone disabili è che purtroppo non esistono insiemi ben definiti di disabilità. Esistono vari tipi di handicap che possono colpire una persona in modo più o meno invalidante, non è possibile generalizzare ma è opportuno studiare i vari problemi uno alla volta. Questo sfortunatamente implica che, la disabilità che colpisce una gruppo numeroso di persone sia studiata in maniera più estesa, mentre un handicap più raro venga accantonato in quanto solo poche persone ne sono affette. L’apporto che la tecnologia può riservare allo sviluppo di aiuti validi per le varie disabilità è estremamente elevato. Ora più che mai, in quanto tutta la società moderna si appoggia ai 5 servigi resi dagli elaboratori. È possibile, grazie alla versatilità che offre l’informatica, studiare ausili che aiutino in modo concreto tutti i portatori di handicap, trovando per ogni disabilità la soluzione più consona. 2.1 Ausili L’ausilio è un oggetto che permette a chi lo utilizza di instaurare delle relazioni con le persone che lo circondano. In questo sta la vera utilità dell’ausilio, dare alla persona disabile la possibilità di entrare in relazione con gli altri, a casa, a scuola, al lavoro, etc. Questa possibilità fa sì che la persona disabile abbia un’immagine di sé migliore, perché si sente parte attiva nei suoi rapporti con gli altri e non un emarginato o un diverso. Anche l’ausilio informatico è un oggetto, che serve a creare relazioni comunicative, che determinano apprendimenti cognitivi che possono aumentare e facilitare contesti relazionali e sociali, tutto ciò contribuisce al rafforzamento dell’identità della persona. Volendo dare una definizione tecnica, l’ausilio è uno strumento che consente al disabile di fare ciò che altrimenti non riuscirebbe a fare, oppure di farlo in maniera più sicura, veloce, meno faticosa, più accettabile psicologicamente o infine per prevenire l’instaurarsi o l’aggravarsi di una disabilità. 2.1.1 Sensori Un sensore (o pulsante, o switch) è un dispositivo che serve per trasformare una grandezza fisica (pressione, spostamento, suono, soffio …) in una grandezza elettrica che viene utilizzata per comandare dei dispositivi (giocattoli, computer …). In sostanza un sensore è un dispositivo capace di inviare un comando (acceso, spento) ad un altro strumento. È di grande utilità nei casi in cui l’utente non sia in grado di azionare un dispositivo utilizzando i normali interruttori. I sensori si dividono in due grandi categorie, da un lato si trovano i sensori singoli, dall’altro i sensori multipli. I sensori singoli sono sensori con un’unica funzione. Acceso oppure spento. Esistono sensori con modalità di attivazione a testa, a tocco, pneumatici. Esistono sensori per chi è in grado di muovere un solo dito, sensori che richiedono solo un movimento di pochi millimetri o sensori che richiedono invece movimenti più ampi. I sensori singoli della AbleNet sono dei pulsanti di diverse dimensioni azionabili premendo la superficie superiore. Il Big Red è un pulsante di grandi dimensioni, attivabile tramite gli arti superiori ed inferiori, il Jelly Beam di media dimensione, attivabile anche con il capo, ed infine lo Specs è un pulsante di piccole dimensioni, attivabile anche col movimento delle labbra o del mento. Il sensore Jaggle della Penny&Giles è caratterizzato dal fatto che la sua superficie può ruotare in senso orario o 6 antiorario facendo variare la resistenza del dispositivo (da un massimo di 1.5 Kg fino ad un minimo di 200 g) con quindici diverse posizioni. Altri sensori singoli sono quelli della Tash. Micra Light è un piccolo sensore molto sensibile e facilmente posizionabile. Left è un sensore a stelo costituito da un’asta metallica ed un’estremità rigida ricoperta con un disco di spugna. Quando si esercita la forza, per l’attivazione del sensore, l’estremità si flette leggermente. È indicato per essere utilizzato con il capo da parte di chi abbia un buon controllo fine del movimento del capo. I sensori multipli sono sensori con più funzioni raggruppate in un unico strumento. Si rilevano spesso ottime soluzioni perché in poco spazio racchiudono molte funzioni. Si possono trovare sensori multipli due in uno (due sensori in uno) come nel Pneumatic Switch. Questo è un sensore a pressione attiva o passiva (inspirazione o espirazione). Per attivare uno switch si soffia, per attivare l’altro si inspira. Nel Racker Switch ci sono due sensori, uno per ogni lato, che vengono attivati premendo ora sull’una ora sull’altra parte. Esistono poi sensori 5 a 1 come il Wafer il quale è costituito da cinque sensori a membrana disposti in sequenza. Penta è formato da cinque piccoli sensori disposti a croce, per persone con un buon controllo motorio fine. Nel Mini Joystick with Push quattro sensori vengono attivati col movimento della leva nelle quattro direzioni, il quinto è attivato premendo la leva del joystick. 2.1.2 Tastiere L’utilizzo della comune tastiera può essere complicato a causa di problemi nel controllo degli arti superiori. A volte è sufficiente adottare delle modifiche alle impostazioni software dell’elaborazione che si sta utilizzando, altre volte è possibile identificare delle soluzioni alternative. A tale scopo esistono tastiere ridotte o espanse, tastiere a membrana, riconfigurabili e a video su cui l’interazione avviene tramite un dispositivo di puntamento, o tastiere con display, indipendenti dal computer. Un’altra soluzione consiste ne ricorrere ai sistemi di controllo del computer a scansione, tramite l’uso di sensori esterni. Le tastiere ridotte sono tastiere speciali caratterizzate da una dimensione ridotta e tasti piccoli, ravvicinati e molto sensibili, al contrario le tastiere espanse sono tastiere di grandi dimensioni, con tasti ingranditi. Un esempio di tastiera espansa è la Big Keys Color Plus della Grayston Inc., in cui i tasti sono grandi il quadruplo di quelli di una tasiera normale. Un esempio di tastiera ridotta è la WinMini della Tash Inc., in cui i tasti hanno un’area di 1.3 cm quadrati e sono molto ravvicinati tra loro. Nelle tastiere a membrana o riconfigurabili si può trovare la tastiera Discover Board della Don Johnston Inc. Questa si presenta come una tavoletta su cui è possibile inserire delle membrane plastificate e delle membrane cartacee personalizzate. Anche la tastiera espansa 7 dell’IntelliKeys, avente un’area maggiore rispetto a quella delle tastiere standard, è riprogrammabile. SoftType è una tastiera a video, che permette alle persone con difficoltà motorie, di inviare comandi a qualsiasi applicazione di Windows. SoftType contiene un sistema di predizione di parola che permette di ridurre il numero di tasti da premere. I clic del mouse possono essere ottenuti usando la tecnica dell’Autoclic. Il clic del tasto sinistro del mouse è “realizzato” dopo che il puntatore è rimasto immobile, per un intervallo di tempo programmabile, sul punto desiderato. Si può avere accesso a qualsiasi applicazione o utilità Windows con l’uso esclusivo di un sensore, di SoftType e di un sistema di puntamento col capo come HeadMouse. AlphaSmart è una tastiera indipendente dal computer con un display a quattro righe, facilmente trasportabile ed ideale per chi ha problemi di scrittura. Esistono infine sistemi a scansione come il Discover Switch della Don Johnston Inc.. Questo è un sistema integrato hardware e software per Personal Computer per l’utilizzo ed il pieno controllo del computer attraverso la scansione. E possibile dare qualsiasi comando all’elaboratore azionando un sensore (scansione automatica e selezione indiretta) o due (scansione manuale e selezione diretta). Discover Switch consiste in un grosso sensore e in un software di gestione. Una volta installato, il software riproduce, sul monitor, delle tabelle su cui verrà attivata la scansione. Le tabelle sono personalizzabili. 2.1.3 Ausili per la comunicazione Con il termine AAC (Comunicazione Aumentativi ed Alternativa) si trattano tutte le strategie di comunicazione che sono alternative o incrementano la comunicazione verbale e grafica. La AAC si pone come obiettivo la compensazione di una disabilità temporanea o permanente del linguaggio espressivo. Vengono infatti create le condizioni affinché il disabile abbia l’opportunità di comunicare in modo efficace, ovvero di tradurre il proprio pensiero in una serie di segni intelligibili per l’interlocutore. Esistono sia prodotti hardware che software per la comunicazione aumentativa. Si parla di sistemi per la AAC con uscita in voce oppure senza uscita il voce. Nei prodotti hardware si può trovare il Big Mack. Questo è un ausilio per la AAC con uscita in voce, molto semplice. Attraverso la pressione di un grosso tasto si ottiene la riproduzione di un messaggio precedentemente registrato. Al messaggio può essere associata un’immagine o un simbolo. SpeakEasy è di nuovo un ausilio per la AAC con uscita in voce, che permette di registrare e riprodurre dodici messaggi di durata variabile. Ad ogni messaggio 8 corrisponde un’area della tastiera sulla quale viene fissato un disegno associato al messaggio registrato. Comunica, al contrario, è un software di comunicazione con uscita in voce, ideato e sviluppato dalla EasyLabs. Con Comunica è possibile costruire tabelle con celle di testo e di immagine, cui è possibile associare file audio personalizzati e l’integrazione nel programma della libreria completa dei simboli PCS (Picture Communication Symbols). Comunica è un valido programma di comunicazione per persone che necessitano dell’uscita in voce. I metodi di accesso vanno da quelli standard, quali mouse e tastiera, a quelli speciali come dispositivi di puntamento col capo, tastiere alternative, trackball, sensori utilizzati insieme alle modalità di scansione previste e touchscreen. 2.1.4 Dispositivi di puntamento Esistono mouse speciali in alternativa al mouse standard per coloro che hanno difficoltà nel controllare il movimento del puntatore e le funzioni dei tasti. Gli emulatori di mouse sono dispositivi che permettono di controllare, attraverso sistemi diversi, il movimento del puntatore e di svolgere tutte le funzioni del mouse. In alcuni casi si utilizza, per il movimento del puntatore, un mouse standard e per la selezione si fa ricorso a sistemi che consentano di realizzare il clic con l’ausilio di uno o più sensori esterni, tramite dispositivi d’interfaccia. In altri casi è possibile ricorrere a soluzioni software per l’esecuzione dei comandi del mouse. Dragger per Windows permette l’emulazione con un singolo clic delle funzioni del mouse. Dragger può essere utilizzato insieme alla tecnica definita Autoclic. Chi presenta disabilità motorie può avere facilmente accesso al mouse e alla maggior parte delle applicazioni Windows grazie a Dragger e un qualsiasi dispositivo di puntamento. L’accesso facilitato per Windows consente l’attivazione di un’opzione che permette di utilizzare il tastierino numerico come emulatore di mouse. Magic Touch è un pannello sensibile e trasparente che viene montato sullo schermo dell’elaboratore. Per spostare il puntatore del mouse è sufficiente toccare la superficie sensibile del pannello, in corrispondenza della posizione desiderata. Il Palm Mouse rientra nella categoria dei mouse speciali. Questo è un mouse molto piccolo, che consente di spostare il puntatore sul monitor utilizzando un minimo movimento delle dita. È indicato soprattutto per distrofici e persone con sclerosi. I mouse della Kensington, Expert mouse e Orbit Mouse, sono mouse di tipo trackball, in cui ruotando la sfera posta sulla base è possibile spostare il puntatore sul monitor. Ai tasti funzione (quattro nel primo e due nel secondo) è permesso associare funzioni specifiche. 9 I mouse speciali della Penny&Giles sono sia mouse a leva, sia Joystick, che Trackball. Sono muniti di uno scudo che consente di appoggiare il polso e la mano sul piano del mouse, impedendo di premere inavvertitamente i pulsanti abbinati alle funzioni del mouse. NoHandsMouse della Hunter Digital consente di spostare il puntatore del mouse sul monitor utilizzando il movimento dei piedi. Con un piede si controlla il movimento del puntatore, con l’altro si esegue il clic. HeadMouse della Origin Instruments consente di effettuare tutte le operazioni di un normale mouse attraverso i movimenti del capo. È il mouse standard per persone che non possono usare le mani, è infatti indicato per persone tetraplegiche e per chiunque sia in grado di usare solo i movimenti del capo. 2.1.5 Sistemi di puntamento oculare I dispositivi di puntamento rappresentano diversi modi per controllare il puntatore sul monitor al fine di eseguire le azioni che si desiderano, tuttavia i dispositivi trattati nel paragrafo precedente sono difficilmente accessibili a disabili motori per la difficoltà di compiere due azioni distinte, lo spostamento sul piano e la pressione del tasto di selezione. In questo, vengono in aiuto i sistemi basati sulla cattura dei movimenti dello sguardo per direzionare il puntatore sullo schermo. Questi dispositivi possono essere costituiti da una fascia o un caschetto che l’utente deve indossare, oppure possono essere dispositivi da fissare sulla montatura di un paio di occhiali. La soluzione migliore è però rappresentata da quei sistemi che non necessitano di alcun dispositivo collegato all’utente, che quindi rimane più libero nei movimenti. Il loro funzionamento si basa sulla presenza di una telecamera e in alcuni casi di un LED (emettitore di luce infrarossa), a seconda del metodo utilizzato nella predizione del punto di fissazione. Il sistema è costituito da un monitor, sotto cui è montata una telecamera ed un LED ad infrarossi. Il LED emette dei raggi infrarossi a bassa potenza, che illuminano l’occhio. La superficie della cornea riflette questi raggi. La riflessione genera il cosiddetto effetto occhi rossi. Questo effetto migliora l’immagine della pupilla alla telecamera e rende più facile alle funzioni di elaborazione delle immagini localizzare il centro pupilla. Il sistema calcola il punto fissato dall’occhio basandosi sulla posizione relativa del centro pupilla e sulla riflessione della cornea nell’immagine dell’occhio. 10 EyeGaze System della LC Technologies Inc. è un sistema progettato per individuare su quale punto dello schermo è fissato lo sguardo dell’utente. Il clic del mouse è realizzato tramite una pausa di fissazione dell’occhio sul punto desiderato. Le operazioni di elaborazione delle immagini e di calcolo del punto fissato vengono eseguite da un software apposito che viene installato nell’elaboratore dell’utente. Le rilevazioni vengono effettuate ad una velocità di campionamento di 60 Hertz. L’utilizzo di questo sistema richiede un buon controllo del capo, l’utente deve essere in grado di mantenere lo sguardo fisso su di un punto per almeno mezzo secondo, deve avere una buona vista e possedere adeguate capacità mentali. EyeGaze deve essere utilizzato lontano dalle finestre e in presenza di un numero limitato di sorgenti di luce infrarossa. Quick Glace Eye-tracking System della Eye Tech Digital Sistem è un dispositivo che emula il comportamento del mouse utilizzando il movimento degli occhi. Una telecamera montata sotto il monitor del Personal Computer si focalizza sull’occhio dell’utente. Due LED a bassa potenza sono montati ai lati del monitor. Quick Glace determina dove l’utente stia guardando sul video, cioè il punto fissato, analizzando la posizione della riflessione della cornea e il centro pupilla. Il cursore viene posizionato proprio sul punto calcolato in questo modo. Il clic del mouse è realizzato con un battito di ciglia oppure con una pausa dell’occhio o ancora tramite un sensore esterno. L’inseguimento dell’occhio avviene ad una velocità di 30 campioni al secondo. ERICA System della ERICA Inc. permette di inseguire e memorizzare il movimento degli occhi e la dilatazione della pupilla di una persona, sullo schermo di un computer. ERICA sta infatti per Eyegaze Response Interface Computer Aid System. Inseguendo il movimento degli occhi di una persona il sistema permette alla persona stessa di controllare il proprio elaboratore utilizzando lo sguardo. Come i due prodotti descritti sopra, anche ERICA non è un dispositivo intrusivo per l’utente, infatti si ha di nuovo una telecamera ed un LED, questa volta posti in una scatola sotto il monitor. Il sistema lavora alla velocità di 60 Hertz. 11 2.2 Contesto Prima di illustrare il contesto e con esso la strada che questa discussione intraprenderà nei capitoli successivi è importante rilevare una caratteristica molto importante sui metodi utilizzati per definire nuovi ausili utili alla comunità. Come è già stato introdotto precedentemente è pressoché impossibile definire con esattezza tutte le tipologie di handicap possibili. I disturbi sono così numerosi da rendere inutile la ricerca di una soluzione unica per ogni disabilità. La comparsa di una minorazione può associarsi ad altre e anche l’intensità con cui si presenta può variare notevolmente. Due sono le possibili vie da percorre per ottenere un risultato apprezzabile nel superamento delle barriere che l’handicap erige. La prima consiste nell’utilizzare a proprio favore la caratteristica di eterogeneità nelle diverse forme di disabilità. Come l’handicap si presenta sotto vari aspetti anche gli ausili devono proporre il più grande numero di soluzioni. Progettare e produrre ausili che risolvano in modo diretto un solo e singolo problema ma che non si ostacolino a vicenda e possano essere impiegati insieme. Questo permette di utilizzare, o adattare, un certo numero di ausili in maniera concorrente per soddisfare il maggior numero di utenti disabili, proponendo a ciascuno di loro il mix di ausili adatto alle loro necessità. L’altra via percorribile è progettare e generare degli ausili la cui caratteristica principale sia la versatilità, al fine di ottenere un numeroso bacino di utenza costituito da un gruppo abbastanza eterogeneo di utenti disabili. Ciò significa puntare su un sistema unico che aiuti a superare le diverse barriere erette da diversi handicap che hanno solo una radice comune, ma che per intensità e caratteristiche marginali possono essere molto diversi fra loro. Analizzando i diversi ausili presenti attualmente sul mercato balza subito all’occhio come essi orbitino intorno al primo metodo descritto precedentemente. Tutti sono abbastanza specifici e risolvono solo un problema alla volta, tuttavia l’utilizzo di un ausilio non preclude l’uso concorrente di altri sistemi. Ciò nonostante è sempre possibile non tenere conto in fase di progetto di importanti caratteristiche che il sistema deve possedere. Questo introduce un aspetto abbastanza subdolo, che interviene quando più ausili vengono utilizzati insieme. Le prestazioni globali sono vincolate al più modesto degli ausili utilizzati. Se, per esempio, uno degli ausili impiegati in concorrenza non tiene conto della caratteristica relativa alla comodità offerta all’utente è molto probabile che l’intero pacchetto venga menomato da questo problema, anche se gli altri ausili sono ben progettati. Infine la versatilità di un certo ausilio spesso è abbastanza rigida e non permette un impiego dell’ausilio diverso da quello programmato. 12 L’utilizzo di un sistema unico e più completo, potrebbe essere in grado di eliminare questi problemi. È infatti possibile avere sotto controllo tutti i passi nella progettazione e definire le linee guida che determineranno i compiti che il sistema dovrà portare a termine. È possibile discutere e riunire in fase di progettazione tutte le caratteristiche che faranno parte dell’ausilio, senza dipendere da fattori esterni che potrebbero ostacolare le prestazioni del sistema. Ovviamente una cattiva progettazione e la possibilità di trascurare fattori determinanti possono essere deleteri al fine del risultato, alla pari o ancor peggio della soluzione precedente. 2.2.1 Definizione degli obiettivi È arrivato il momento di trasferire i concetti appena descritti nella pratica, per definire in maniera completa il contesto che farà da sfondo per tutto il resto della discussione. A questo punto molte sono le possibili vie da percorrere, ogni disabilità merita una corretta analisi, tuttavia si deve compiere una scelta, non è possibile trattare in modo completo tutti i diversi tipi di handicap. Si deve focalizzare un problema tra i tanti e cercare di risolverlo. Quali risposte possono offrire le due correnti di pensiero se, per esempio, si vuole aiutare le persone incapaci di utilizzare gli arti per comandare le periferiche di input del Personal Computer come il mouse, ma che possiedono ancora un buon controllo del capo e una vista sufficiente. I seguaci della prima filosofia potrebbero consigliare a queste persone l’utilizzo del Magic Touch unito all’utilizzo di un bastoncino da tenere in bocca per indicare al sistema le scelte effettuate. Oppure l’utilizzo di uno dei tanti sistemi di puntamento oculare. Tuttavia sia l’una che l’altra soluzione comportano numerosi difetti. Alla prima si può associare quanto detto in precedenza sulla caratteristica di comodità, l’utente può affaticarsi se la sessione di lavoro si protrae nel tempo. In quanto il Magic Touch non è stato progettato per questi scopi ma per un utilizzo che includesse l’uso delle mani nella selezione. La seconda soluzione potrebbe risolvere in modo corretto il problema, ma chi l’ha proposta non ha tenuto conto della componente economica che spesso può costituire un aspetto importante. Una soluzione in linea con la seconda corrente di pensiero potrebbe, in questo caso, risolvere brillantemente i problemi che affliggono la soluzione precedente. La costituzione di un sistema unico e progettato espressamente per questo gruppo di disabilità può essere la risposta alla domanda. Avere la possibilità di progettare e costruire un sistema dalla base permette di avere sotto controllo l’intero processo e definire con precisione tutte le caratteristiche che l’ausilio deve possedere. In questo modo si può conservare ciò che di buono ha introdotto la soluzione precedente eliminando nel contempo i difetti. 13 Utilizzare il movimento del capo dell’utente per selezionare gli oggetti sul terminale video non è una cattiva idea, tuttavia si deve rimediare all’uso della bacchetta e nel contempo cercare di eliminare il Magic Touch con una soluzione più confortevole ed economica. La risposta si può ottenere dai sistemi di puntamento. Sarebbe ideale usare le potenzialità offerte dalla cattura video per sostituire la bacchetta e il Magic Touch. Elaborare le immagini che descrivono il volto dell’utente, al fine di cogliere il punto sul video dove l’utente ha rivolto il viso. Infine, per la componente economica, attuare questa cattura con una WebCam. Questa soluzione è estremamente confortevole per l’utente, in quanto quest’ultimo ha il volto libero e deve solo muovere il capo nella direzione dell’oggetto sullo schermo che vuole selezionare. Inoltre è possibile sfruttare in modo completo le potenzialità di un sistema progettato dalle fondamenta, rendendolo assai versatile. Questa caratteristica permette di utilizzare il sistema con vati tipi di handicap, in quanto è possibile spingersi fino alle prestazioni di un sistema di puntamento oculare. Ciò significa poter utilizzare l’ausilio anche per gli utenti che non hanno più la capacità motoria del capo ma che possiedono ancora un buon movimento oculare. Il sistema in questo caso elabora solo la parte di immagine relativa all’occhio dell’utente, divenendo una specie di versione economica di un sistema Eyetracking. In conclusione l’obiettivo proposto è cercare di simulare il comportamento di un mouse standard per Personal Computer utilizzando una camera economica come per esempio una WebCam che cattura immagini relative al volto dell’utente. Questo per aiutare il maggior numero di persone che, per vari motivi, non possono utilizzare le mani per imprimere i comandi ad un normale mouse. Infine si può intravedere anche un utilizzo da parte di persone normodotate che per qualche ragione hanno le mani occupate e devono poter utilizzare il Personal Computer. 2.3 Analisi dei problemi Purtroppo in un contesto del genere non esistono sistemi già esistenti a cui si possa fare riferimento e a cui ci si possa appoggiare per sviluppare un ausilio con le caratteristiche determinate in precedenza. In questo caso l’ausilio deve essere creato dal nulla e perciò nella determinazione dei problemi bisogna tenere conto anche di questo fatto. Questo porta ad una prima generale divisione dei problemi in due gruppi diversi, da un lato ci sono le incertezze dovute alla definizione di un nuovo sistema, dall’altro le incognite insite nella fase di sviluppo che si accompagnano sempre ad un’operazione di miglioramento di un dato sistema in crescita. Inoltre questi due insiemi di problemi non sono separati, ma interagiscono fra di loro 14 e la soluzione di un dato dubbio può influire positivamente o in modo negativo sulla trattazione di un altro problema. Quindi per risolverli occorre avere sempre in mente il disegno generale o contesto in cui il sistema si deve collocare. Un’ulteriore suddivisione dei problemi si intravede nella sostanza stessa dell’ausilio, esso infatti, oltre ad avere incertezze legate esclusivamente alla sua costituzione e di attinenza riservata agli sviluppatori, ha al suo interno un’ulteriore peculiarità. Deve, infatti, rendere un servizio ad una certa classe di utenza che, per sua natura, porta con sé alcune caratteristiche da prendere seriamente in esame. Questi utenti non interagiscono con i sistemi e gli ausili informatici allo stesso modo della maggioranza dei consumatori. Oltre ai normali problemi di generazione di un nuovo sistema, si vengono a creare delle incognite relative alle capacità residue e limitate degli utenti a cui questo ausilio deve dare il massimo aiuto possibile, per questo motivo il fine ultimo nella progettazione e a cui dobbiamo sempre fare riferimento, sarà ottimizzare le capacità residue di un numero limitato di utenti. In accordo con quanto appena descritto è possibile dividere la parte successiva del paragrafo in sezioni separate che descrivono i vari problemi che si incontreranno. Per quanto riguarda le incertezze relative alla definizione di un nuovo sistema, si possono individuare la scelta dell’ambiente di lavoro e la definizione dell’architettura del sistema. Per i problemi relativi alla parte di sviluppo si incontrano i dubbi relativi al numero, all’utilizzo e al posizionamento delle periferiche di acquisizione dati e la difficoltà di innestare nel sistema applicazioni indipendenti. Un’ulteriore incognita risiede nella possibilità di calibrare il sistema per le sessioni di lavoro. Quest’ultimo deve essere posto nell’insieme di incertezze legate all’ausilio stesso in quanto per risolverlo è necessario tenere in considerazione le capacità limitate dell’utente finale. È d’obbligo aggiungere che in questo capitolo non si può fare altro che elencare e analizzare i vari problemi, mentre la scelta delle varie opzioni possibili e la soluzione dei problemi è demandata ai capitoli successivi. 2.3.1 Ambiente di lavoro Il primo e più importante problema, la cui soluzione porrà le basi per lo sviluppo successivo, è la scelta del Sistema Operativo che verrà usato da piattaforma software per sviluppare e rendere attivo l’ausilio informatico. Decidere quale sia l’ambiente di lavoro non è un’impresa facile, infatti le qualità che dovranno caratterizzare l’ausilio sono spesso in contrasto fra di loro. Da una parte c’è il desiderio degli sviluppatori di avere una piattaforma software completamente raggiungibile e analizzabile come per esempio una piattaforma Unix. 15 Dall’altra c’è la necessità di ottenere il maggior numero di utenti possibile, e per far questo, bisogna orientare il lavoro verso ambienti Microsoft. Sia l’una che l’altra possibilità offrono vantaggi e svantaggi. Se si orienta lo sviluppo verso un Sistema Operativo della famiglia Unix come per esempio Linux, lo sviluppatore può avere sotto controllo tutto il sistema e quindi verificare autonomamente l’intera catena di acquisizione dati. Inoltre tale sistema offre delle librerie software estremamente potenti che permettono allo sviluppatore di creare una struttura complessa, ottimizzata ed elegante da un punto di vista architetturale, per esempio Unix nasce, tra le altre cose, con il supporto IPC (Inter Process Comunication) che permette agli sviluppatori la possibilità di progettare sistemi che lavorano in modalità concorrente e quindi, se il sistema viene progettato in maniera corretta, ottimizzata. Tuttavia esistono degli svantaggi: per esempio la maggioranza delle periferiche di acquisizione video non vengono fornite, dalla casa costruttrice, con i relativi driver per questa piattaforma, questo implica che la scelta sui dispositivi di cattura video è limitata. Questo Sistema Operativo, inoltre, viene visto come un sistema per gli “addetti ai lavori” e non ha incontrato ancora l’approvazione del grande pubblico che lo reputa una piattaforma difficile da usare. Anche l’ambiente Microsoft presenta pregi e difetti, ad esempio la sua vastissima diffusione, fa sì che un ausilio ideato per questa famiglia di Sistemi Operativi possa raggiungere un numero molto grande di utenti ed inoltre la maggioranza delle periferiche di acquisizione video sono fornite con gli elementi necessari all’installazione su questa piattaforma. Oltre a ciò numerosi sono gli ambienti di sviluppo, estremamente validi, che accompagnano questo ambiente, il che vuol dire avere un aiuto energico in fase di sviluppo e di controllo degli errori. Dall’altra parte, utilizzando queste piattaforme, è obbligatorio scendere a compromessi per quanto riguarda la visibilità sul sistema, infatti molte parti di esso sono vincolate dal copyright e quindi non permettono allo sviluppatore di accedere in modo libero al sorgente, questo può causare dei fastidi se si vuole andare molto in profondità nello sviluppo della catena di acquisizione dati. Inoltre questa famiglia di Sistemi Operativi non agevola lo sviluppo di applicazioni con strutture informatiche complesse come per esempio l’IPC di Unix, tuttavia permette agli sviluppatori di creare sistemi con una struttura ottima ed elegante anche se più semplice. Per affrontare in modo completo ed esauriente i dubbi sulla scelta dell’ambiente di lavoro, entrano in gioco anche gli obiettivi che abbiamo descritto a proposito del contesto a cui l’ausilio deve adattarsi. La possibilità di rendere il sistema molto versatile e raggiungibile dal più grande numero di utenti possibile indica la direzione da intraprendere per definire il 16 Sistema Operativo che farà da piattaforma software per in nostro sistema, infatti le garanzie per ottenere un simile risultato le può offrire solamente la famiglia Microsoft. Quindi si arriva al compromesso nella scelta dell’ambiente di sviluppo: a fronte di un sistema più semplice ma ugualmente ben strutturato ed elegante c’è la possibilità di raggiungere una popolazione di utenti enormemente vasta. In conclusione il sistema sviluppato permetterà il suo utilizzo sui seguenti Sistemi Operativi : Windows 98 e Windows 2000. 2.3.2 Architettura del sistema Scelta la piattaforma software la strada da percorrere per raggiungere il gli obiettivi preposti è, per così dire, solamente tracciata, i successivi passi sono l’individuazione di un adeguato linguaggio di programmazione e la definizione dell’architettura del sistema. Per quanto riguarda il primo dubbio in questa sezione è possibile dare già una soluzione, tuttavia sull’architettura in questo contesto non si può far altro che definire le varie possibilità e indicare quali caratteristiche il progetto deve possedere, sarà compito dei capitoli successivi descrivere le soluzioni che si sono adottate. La scelta del linguaggio di programmazione è nella maggior parte dei casi fortemente influenzata dalle preferenze soggettive dello sviluppatore, tuttavia questa peculiarità può indirizzare la scelta su un linguaggio non adatto che non è in grado cioè di soddisfare le proprietà che la struttura del sistema richiede. Infatti le caratteristiche che un sistema deve possedere variano a seconda del contesto in cui ci si ritrova ad operare, per esempio un programma che risiede in una pagina Web ha bisogno di un linguaggio di programmazione quale Java o JavaScript che permette a chi sviluppa di utilizzare al massimo le potenzialità che la rete offre. Un sistema che deve interrogare un Data Base e che vuole visualizzare in una finestra Windows i suoi risultati può, con successo, utilizzare Visual Basic e interrogare la banca dati con comandi SQL embedded oppure ODBC (OpenDataBaseConnectivity). Nel caso in cui all’utilizzo delle finestre Windows si unisce l’obbligo di definire una struttura complessa il linguaggio più adatto alle è il C++. Quest’ultimo offre la possibilità di utilizzare le librerie MFC, svariate altre potenzialità come la programmazione concorrente e le librerie Video for Windows che permettono la cattura e l’elaborazione delle immagini grazie alle videocamere. Definire l’architettura del sistema è analogo a progettare le fondamenta di un grattacielo, se il progetto non rispetta certi modelli o vincoli c’è il pericolo che tutta la struttura divenga poco solida e rischi di crollare quando diviene molto complessa ed elaborata. Questo esempio fa capire come una sua definizione accurata sia indispensabile per il sistema stesso. 17 Una prima qualità che la struttura deve possedere è la semplicità, un’architettura semplice porta innumerevoli vantaggi sia agli sviluppatori che al sistema stesso. Infatti, i primi possono godere di un codice sorgente facile, elegante e preciso da scrivere e da leggere, che permetta una chiara localizzazione degli errori e la capacità di comprendere velocemente in che punti agire per integrare nuove soluzioni. Inoltre nel concetto di semplicità è insita anche un’altra qualità estremamente importante per un sistema votato a crescere nel tempo. La capacità di permettere enormi sviluppi futuri senza sovraccaricare la struttura. Evitando, in questo modo, agli sviluppatori di arrivare al punto in cui non è più possibile aggiungere nuove potenzialità al sistema, senza stravolgere la struttura stessa. Anche la versatilità è un’importante qualità, infatti un’architettura che rispetta questa imposizione permette ai suoi sviluppatori di modificarne i contenuti, senza sconvolgerli, al fine di superare i problemi che vengono alla luce mentre l’implementazione procede. Inoltre se l’architettura è ben strutturata è possibile modificare gli obiettivi del sistema mentre il sistema stesso viene creato, questo significa che le finalità iniziali, spesso limitate, possono venire corrette o addirittura ampliate nella fase di sviluppo del sistema. 2.3.3 Acquisizione ed elaborazione dati Contrariamente alle due sezioni precedenti nel quale si discutevano incertezze che accompagnano sempre la fase di costituzione di una nuova applicazione e che perciò tendono a vedere l’ausilio nella sua totalità e nel suo fine ultimo, qui vengono descritti e analizzati i problemi dovuti alle caratteristiche dell’ausilio stesso, in pratica come l’applicazione dovrà operare. Tenendo ben presente quanto osservato nella parte relativa al contesto, l’ausilio deve essere in grado, tramite la cattura di immagini dell’utente posizionato davanti allo schermo del Personal Computer, di indicare quale zona del video ella stia osservando. Questa mansione porta con sé svariati problemi che, per una corretta osservazione, devono essere suddivisi in varie categorie. Nascono per esempio domande su quante videocamere possano essere utilizzate, se sia più utile usare motion capture intrusiva o non intrusiva, la posizione delle sorgenti video ed infine come i dati acquisiti debbano essere elaborati. La prima categoria di incognite, legate al numero delle videocamere, introduce un problema abbastanza spinoso. Infatti, non è possibile definire con precisione il numero esatto di videocamere ottimale per il sistema. Solo lo sviluppo può rispondere a questo dubbio, in questo contesto è possibile indicare solamente il massimo numero di videocamere collegabili al Personal Computer. Risulta evidente che più videocamere il sistema è in grado di utilizzare, 18 maggiore sarà l’insieme di dati che esso potrà elaborare. Tuttavia, si deve tener conto del fatto che un maggior numero di dati non è indice di maggior precisione. Sarà compito dell’implementazione gestire al meglio i dati ottenuti, anche con l’utilizzo di un numero minore di videocamere, al fine di ottenere buoni risultati. Inoltre c’è un limite fisico al numero di camere collegabili al sistema. Infatti, oltre al numero limitato di porte USB che un Personal Computer di solito supporta (in genere due), c’è il limite imposto dalla banda massima del Bus che collega le porte alla scheda madre. È questo il limite più restrittivo sui moderni elaboratori. Questo significa che, sebbene ci sia la possibilità di installare due o più camere grazie a diverse porte USB, fornite dal Personal Computer, quest’ultime non potranno acquisire ed elaborare i dati in maniera concorrente. Questo perché la banda disponibile risulta insufficiente per più di una camera. Anche l’elaborazione dei dati, attuata dal processore, potrebbe essere inscritta tra le limitazioni al numero di sorgenti video. Tuttavia una struttura del sistema ottimizzata e dei buoni algoritmi per l’acquisizione permettono di superare questa incertezza. Quindi l’unico limite fondamentale al numero delle camere è dato dal collo di bottiglia localizzato nel Bus dati. Se questo, in futuro, sarà più capiente ci sarà la possibilità di aggiungere ed elaborare più sorgenti video. Tuttora si arriva all’utilizzo di non più di due videocamere nei PC più recenti, mentre la maggioranza degli elaboratori ne supporta, in concorrenza, solo una. A questo punto è d’obbligo discutere come il sistema interpreta e percepisce il movimento nelle immagini catturate. Due sono le possibili strade da intraprendere per arrivare ad una soluzione, utilizzare motion capture non intrusiva oppure intrusiva. La tecnica non intrusiva ha il vantaggio di rendere più comoda la sessione di lavoro all’utente. In quanto quest’ultimo non ha l’obbligo di indossare nessun elemento estraneo. La cattura del movimento viene totalmente affidata all’elaboratore. Tuttavia ha lo svantaggio di essere poco precisa, onerosa per l’elaborazione ed estremamente suscettibile alle condizioni ambientali di luce. Questo comporta che, nella maggioranza dei casi, si propenda per la seconda ipotesi, cioè utilizzare motion capture intrusiva. Il vantaggio più evidente è la semplicità con cui essa porta a termine il suo compito. Grazie all’utilizzo di un riferimento colorato, per esempio un pallino fissato sull’elemento di cui si vuole conoscere il movimento, l’elaborazione risulta più semplice. Vengono evitati molti problemi relativi alla definizione delle immagini e ridotti quelli sull’illuminazione. Questo comporta una precisione maggiore e una gestione di errori relativamente più contenuti. Tuttavia ha lo svantaggio di costringe l’utente ad indossare un elemento estraneo che permetta la cattura del movimento. 19 Occorre, prima di parlare della collocazione delle videocamere, definire la posizione ottimale che l’utente utilizza per accedere all’ausilio. Più la posizione è ergonomia più l’utente può affrontare lunghe sessioni di lavoro, senza affaticarsi. Questo problema non comporta un esame laborioso, infatti non sono molte le posizioni che l’utente può avere davanti al video di un Personal Computer, l’unico obiettivo è l’obbligo di rendere comoda la sessione. Anche se questa decisione è molto soggettiva sembra ovvia la scelta di posizionarsi in asse con il centro dello schermo ed avere il bordo superiore dello stesso pressappoco alla medesima altezza degli occhi. Per discutere, ora, la collocazione delle videocamere è necessario definire i pregi e difetti di ogni possibile posizione, infatti ogni ubicazione valorizza certi aspetti mentre ne limita altri. Per esempio, se disponiamo di una sola videocamera, una posizione centrata con l’asse verticale dello schermo rende l’elaborazione dei dati simmetrica e lineare. Questo perché l’asse verticale dell’utente stesso è centrato su quello del video. Tuttavia esiste ancora un grado di libertà che porta a due ubicazioni differenti: sopra o sotto il video. Ognuna di queste posizioni porta con sé diverse caratteristiche. Infatti la collocazione inferiore consente alla camera di avere sempre sotto controllo la zona dell’occhio dell’utente. Comunque il riferimento colorato, se si usa motion capture intrusiva, subisce degli spostamenti non lineari e difficili da compensare in fase di elaborazione. La soluzione superiore d’altra parte intercetta movimenti lineari e costanti del riferimento ma rende precaria l’identificazione dell’occhio. Se per esempio, l’utente sta guardando verso il basso, l’occhio può venire coperto dal sopracciglio. Tuttavia l’utilizzo di una sola videocamera posta in asse con l’utente non offre al sistema dati sufficienti per comprendere tutti i movimenti che l’utilizzatore è libero di fare. Questo porta ad errori sulla valutazione e l’interpretazione di rotazioni o spostamenti laterali eseguite da chi usa il sistema. In questo caso diviene essenziale l’uso concorrente di almeno due videocamere per l’acquisizione delle immagini al fine di compensare questi errori di valutazione. Infatti la seconda videocamera posta lateralmente all’utente può fornire al sistema il modo con cui capire qualsiasi movimento che l’utilizzatore compie davanti allo schermo. Tuttavia anche questa soluzione porta con sé dei problemi, in questo caso puramente pratici, sulla collocazione fisica della videocamera. Difatti essa deve essere posta lateralmente all’altezza del capo dell’utente e raramente in questa posizione si può appoggiare qualcosa in quanto c’è aria libera. Un’ulteriore soluzione che utilizza due videocamere, nata grazie allo sviluppo stesso del sistema e che risulta abbastanza ingegnosa, è quella di posizionare la prima sopra il video, la 20 seconda sotto ed entrambe sull’asse verticale dello schermo. Una tale soluzione anche se non permette al sistema di elaborare strane rotazioni, fa sì che si abbia sempre sotto controllo il riferimento e la zona dell’occhio dell’utente. La videocamera posta in alto può, grazie al riferimento e alla motion capture intrusiva, definire con una buona precisione il punto dello schermo verso il quale l’utente ha rivolto il viso. La videocamera in basso può definire più accuratamente dove si posa lo sguardo, al fine di simulare le prestazioni di un sistema eyetracking. Oppure, non elaborare informazioni relative ai movimenti dell’occhio, ma attuare una sorta di selezione (clic) agendo sul riconoscimento della palpebra abbassata per alcuni istanti. Inoltre non ci sono problemi per quanto riguarda la posizione fisica nello spazio delle due videocamere, si ha sempre un base dove appoggiarle, la prima sul ripiano superiore del video, la seconda sul tavolo dove risiede il video stesso. Restano, a questo punto, da trattare le incertezze relative all’elaborazione delle immagini che le videocamere sono in grado di catturare. Anche in questo caso ci sono varie strade percorribili e ognuna ha pregi e difetti. C’è la possibilità di utilizzare i dati forniti dalle videocamere per generare un modello virtuale della posizione nello spazio dell’utente, per calcolare in maniera geometrica il punto fissato da quest’ultimo. Tuttavia una soluzione che adotta un tale sistema porta con sé più svantaggi che vantaggi. Diviene pressoché impossibile cablare il sistema in maniera corretta a causa dei parametri variabili che entrano in gioco. Per esempio devono essere definite le posizioni nello spazio delle camere, dello schermo e dell’utente nonché le sue dimensioni fisiche, cosicché le equazioni geometriche aumentano in maniera non controllabile. Quindi a fronte di una trattazione più rigorosa ed esatta dal punto di vista matematico e geometrico c’è il pericolo reale di introdurre nel sistema errori troppo grandi. I quali possono pregiudicare in maniera devastante l’elaborazione dei dati acquisiti grazie alle videocamere. Un altro percorso, al contrario, permette di evitare un tale salto nel buio e, sebbene porti ad una soluzione meno elaborata da un punto di vista matematico, promette allo sviluppatore di poter raggiungere una possibile risoluzione del problema. L’idea di base che caratterizza questa soluzione sta nello sfruttare la natura statica delle videocamere. Si può utilizzare un sistema di riferimento geometrico riconducibile ad una finestra da sovrapporre all’immagine stessa. Questa finestra delimiterà il campo d’azione del riferimento che è, se si usa motion capture intrusiva, solidale con il volto dell’utente. Se, per esempio, quest’ultimo fissa la parte superiore dello schermo, l’immagine catturata dalla camera rileverà che il riferimento è in prossimità del lato superiore della finestra e così di seguito, anche se rovesciati come in uno specchio, tutti gli altri punti fissati dall’utente sul video. Questa soluzione inoltre, ha il pregio estremamente importante di non creare difficoltà e rendere la 21 fase di calibrazione più leggera. Per rendere il sistema in grado di elaborare i dati relativi al riferimento, nella fase di taratura, l’utente non deve fare altro che definire, fissando gli estremi del video, la finestra utilizzata come sistema di riferimento. Ovviamente è in questa fase che si deve tener conto delle non linearità apportate dalla posizione scelta della videocamera. Se la posizione è quella relativa alla zona superiore del video, si otterranno dati che comportano meno problemi. La soluzione che prevede la videocamera al di sotto del terminale video invece apporta le non linearità che il sistema dovrà poi compensare. In conclusione, i problemi relativi all’acquisizione e all’elaborazione dei dati sono molteplici, tuttavia in questa, e nelle altre sezioni del capitolo, non si è fatto altro che esporli e descriverli in maniera esauriente. Solo nel capitolo successivo dedicato all’architettura e agli algoritmi si cercherà di definire delle soluzioni per questi problemi. 2.3.4 Integrazione nel sistema di applicazioni indipendenti Ha preso piede, ormai da parecchi anni, il concetto di programmazione orientata o basata sugli oggetti. Oltre ad avere un sorgente più ordinato e facile da comprendere, questa tecnica ha sconvolto il modo di concepire la struttura di qualsiasi sistema informatico. Infatti ha introdotto l’idea per cui un oggetto può essere utilizzato in molteplici modi, senza dover conoscere come le operazioni sono svolte al suo interno o gli algoritmi che il suo creatore ha utilizzato per il funzionamento. L’oggetto è visto come una scatola nera che risponde a determinate domande. La rivoluzione sta nel fatto che uno sviluppatore può utilizzare oggetti creati da altri, per produrre oggetti a loro volta utili agli sviluppatori. Anche codesto sistema dovrebbe seguire questa logica per attuare una sorta di stratificazione tra il sistema vero e proprio e le applicazioni ad esso correlate. Questo al fine di separare i due compiti e permettere a futuri sviluppatori di inserire le loro applicazioni all’interno dalla struttura in modo semplice. Evitando a quest’ultimi l’obbligo di dover comprendere il funzionamento interno del sistema come la catena di acquisizione dati o gli algoritmi di elaborazione, ma richiedere solamente l’elaborazione di pochi segnali che lo strato del sistema manda alle loro applicazioni. Una struttura che permette l’esistenza di questa caratteristica porta con sé un ulteriore vantaggio, in quanto è possibile differenziare il lavoro di sviluppo su due fronti distinti. Infatti, mantenendo fissi i messaggi che i due strati si scambiano, si è in grado di permettere ad un potenziale gruppo di sviluppatori di lavorare su applicazioni indipendenti tra di loro. Mentre sul versante opposto il sistema stesso può venire sviluppato da altri, per esempio ottimizzando e rendendo più complessi gli algoritmi di elaborazione dati utilizzati, senza compromettere o rendere inservibile il lavoro sulle applicazioni. 22 2.3.5 Calibrazione I dubbi insiti nella calibrazione dell’ausilio devono essere analizzati, come spiegato precedentemente, tenendo sempre in considerazione le capacità relativamente limitate dell’utente finale. Occorre, prima di tutto, definire con esattezza cosa si intende per calibrazione. Tutte le periferiche che si possono installare su un Personal Computer, siano esse di acquisizione che di visualizzazione dati, richiedono, subito dopo la fase di installazione, una fase di calibrazione dei parametri di funzionamento. Per esempio un mouse possiede alcuni parametri che devono essere definiti sulla base delle sensazioni che l’utente vuole avere quando utilizza l’ausilio. Una di queste può essere l’accelerazione che la freccia subisce quando si muove il mouse, oppure il tempo che trascorre tra un click e il successivo per definire il doppio click o ancora l’opportunità di rendere l’utilizzo più semplice a una persona mancina, agendo sulla possibilità di invertire i pulsanti. Tutti questi ed altri parametri vengono definiti grazie ad un’applicazione fornita dalla casa costruttrice della periferica. Dopodiché è possibile usare l’ausilio, senza più cablarlo, nelle sessioni di lavoro che l’utente realizza successivamente sul Personal Computer. Inoltre esiste un’ulteriore concetto che deve essere considerato prima di poter procedere nell’analisi di questo ausilio. Le periferiche utilizzano due principali tecniche per interpretare i dati, da un lato ci sono gli ausili che utilizzano un sistema di riferimento relativo, per elaborare e rendere coerenti gli input che ottengono. Dall’altro, quelli che usano un sistema assoluto di riferimento. Del primo insieme fanno parte la maggioranza delle periferiche di acquisizione dati, per esempio, un mouse utilizza questo metodo per visualizzare gli spostamenti della freccia sul video. Infatti ogni spostamento viene calcolato come distanza vettorizzata tra la posizione iniziale e finale che il mouse occupa nello spazio. Tuttavia esso non possiede un sistema di riferimento fisso perché l’utente è in grado di annullare il suo funzionamento, semplicemente sollevandolo dal tavolo. Questo implica che, ad ogni sollevamento esso modifica il suo sistema di riferimento. Un joystick, al contrario fa parte del secondo insieme perché utilizza un sistema di riferimento fisso e assoluto, centrato nella sua posizione di riposo. Ora è possibile analizzare con facilità le caratteristiche che definiscono la calibrazione dell’ausilio. Il problema può essere visto come due operazioni da svolgere in sequenza. Il primo passo consiste nel preparare il sistema alle condizioni ambientali in cui si troverà ad operare. Ciò significa che si devono impostare i parametri caratteristici dell’immagine, per esempio la luminosità, il contrasto e il colore che l’immagine catturata deve possedere, per 23 una buona elaborazione da parte del sistema. Questa prima fase di impostazione porta con sé alcuni dubbi. Infatti sarebbe ideale dover affrontare questa operazione una volta sola, subito dopo la fase di installazione del sistema sul Personal Computer. Tuttavia il funzionamento dell’ausilio è fortemente vincolato, in quanto elabora immagini del mondo reale, alle condizioni di luminosità insite intorno ad esso. Quindi, se per esempio, il sistema viene usato con la luce del giorno, potrebbe richiedere un ulteriore settaggio dei parametri di luminosità se successivamente viene usato di sera grazie ad un’illuminazione artificiale. Questo porta al problema di rendere semplice e poco dispendioso il compito di cablare le periferiche di acquisizione dati. La natura stessa dell’ausilio porta alla definizione dei compiti che il sistema deve affrontare per completare il secondo, ed ultimo, passo della calibrazione. Come spiegato precedentemente gli ausili utilizzano due correnti di pensiero differenti per elaborare i dati che si procurano, nel nostro caso c’è l’obbligo di usare un sistema di riferimento assoluto. Su questa asserzione non ci sono dubbi infatti, una volta cablato, l’ausilio utilizza il suo sistema di riferimento per tutta la sessione di lavoro. Tuttavia, sebbene si sia definita in modo univoco la natura del sistema di riferimento, la fase del suo settaggio porta con sé alcune incertezze che devono essere analizzate prima di poter proseguire. Per studiare in maniera esauriente il problema si potrebbe iniziare col pensare a come l’utente utilizzerà l’ausilio. Si può immaginare che quest’ultimo venga posizionato davanti allo schermo del Personal Computer grazie all’aiuto di un assistente, dopodiché il collaboratore dovrà, tramite le normali periferiche, attivare il sistema. A questo punto verrà richiesto all’utente di definire il sistema di riferimento dell’ausilio e alla conclusione di questa fase ci sarà la possibilità di accedere alle applicazioni. Il problema di maggior rilievo per definire questo passo è rappresentato dal fatto che ad ogni sessione di lavoro l’utente non sarà mai nella posizione esatta in cui era nella sessione precedente. Tra un utilizzo e il successivo dell’ausilio può trascorrere parecchio tempo nel quale l’utente ha la facoltà di cambiare posizione nello spazio tramite l’aiuto degli assistenti. Grazie a questo possibile scenario sull’utilizzo del sistema, si osserva che questa fase deve essere estremamente semplice dal punto di vista dell’utente, in quanto deve essere ripetuta ad ogni sessione di lavoro e quindi non deve affaticare o essere onerosa per chi usa l’ausilio. Dopo questa trattazione si arriva alla conclusione che ambedue le fasi della calibrazione devono essere estremamente semplici. La prima deve concedere all’assistente la possibilità di variare i parametri per l’acquisizione dell’immagine in modo corretto e veloce in quanto c’è la probabilità non del tutto remota che essi possano variare da una sessione di lavoro all’altra. La 24 seconda fase deve tenere inoltre in considerazione le caratteristiche dell’utente e fare in modo che non sia gravoso il lavoro che costui deve compiere. 25 26 3 Background I problemi che sono apparsi non appena si è tentato di creare un ausilio utile per il prossimo, e che hanno caratterizzato tutto il capitolo precedente hanno reso chiarissimo il concetto che il sistema che si vuole creare non è così semplice come poteva sembrare a prima vista. Tuttavia non si è indifesi nel combattere questi problemi, come supporto ci sono i decenni spesi nella ricerca informatica, i quali hanno portato a risultati che possono contribuire alla risoluzione delle incertezze. Una prima freccia per il nostro arco è costituita dalla scelta del linguaggio di programmazione, il C++ infatti consente una programmazione orientata agli oggetti che permette di scrivere un codice sorgente molto elegante e facile da comprendere, inoltre grazie al C++ è possibile utilizzare gli oggetti predefiniti di Windows e la capacità di scrivere applicazioni multithreaded. Per completare la nostra faretra non può mancare l’aiuto che ci viene dato dalle procedure Video For Windows per la cattura delle immagini provenienti dalle camere e il pacchetto software della Victor che permette di elaborarle, senza dimenticare l’aiuto dato dalla RamDisk. A questo punto, come avrebbe detto Manzoni : 27 “Son cose che chi conosce la storia le deve sapere; ma siccome, per un giusto sentimento di noi medesimi, dobbiam supporre che quest’opera non possa essere letta se non da ignoranti, così non sarà male che ne diciamo qui quanto basti per infarinare chi n’avesse bisogno” Perciò seguono varie sezioni dove si cerca di spiegare a grandi linee gli ausili informatici che ci hanno sorretto e aiutato nella costituzione del sistema. 3.1 Paradigma di programmazione Con questo termine si indica la relazione esistente tra gli algoritmi e i dati, che costituiscono le due componenti fondamentali di un programma. Entrambi sono rimasti, nella breve storia dell’informatica, aspetti invarianti, mentre si è evoluta la relazione esistente fra di loro. All’inizio si parlava di programmazione procedurale, poi col passare del tempo, verso gli anni settanta l’attenzione si è spostata dal paradigma procedurale a quello dei tipi di dato astratto ora noto come programmazione basata sugli oggetti, infine con il concetto di ereditarietà e di collegamento dinamico si è arrivati ad una programmazione orientata agli oggetti. Il C++ è un linguaggio multiparadigma, pur essendo considerato principalmente un linguaggio orientato agli oggetti, esso è di supporto anche per la programmazione procedurale e per quella basata sui tipi di dato astratto. Il vantaggio è che è possibile fornire una soluzione più adatta al problema: in pratica, infatti, nessun paradigma rappresenta la soluzione finale di un problema. Lo svantaggio sta nel fatto che ciò implica un linguaggio più grande e più complesso. 3.1.1 Programmazione procedurale In questo caso il problema è illustrato direttamente mediante un insieme di algoritmi o da una serie di procedure. I dati sono memorizzati separatamente, ed è possibile accede ad essi in una posizione globale oppure passandoli alle procedure. Tra i più importanti linguaggi procedurali ci sono il FORTRAN, il C e il Pascal. Come menzionato precedentemente il C++ supporta tutte le capacità offerte dal suo predecessore C. 3.1.2 Programmazione basata sugli oggetti Secondo questo paradigma il problema è modellato direttamente da un insieme di astrazioni sui dati: nel C++ queste astrazioni sono dette classi. Ad esempio, il sistema di gestione per una biblioteca con questo paradigma è rappresentato come l’interazione fra 28 oggetti che sono istanze di classi quali Libro, Utente, Prestito e cosi via, che rappresentano le astrazioni possibili in una biblioteca. Gli algoritmi associati a ogni classe sono chiamati interfaccia pubblica della classe. I dati sono memorizzati in maniera privata all’interno di ogni oggetto e non è possibile accedere da essi dal programma generale. CLU, Ada, Modula-2 e il C++ sono solo alcuni dei linguaggi che supportano questo paradigma. 3.1.3 Programmazione orientata agli oggetti In questo caso i tipi di dato astratto vengono estesi mediante i meccanismi di ereditarietà e di collegamento dinamico, il cui significato è riutilizzare un’implementazione oppure un’interfaccia pubblica già esistente. Quest’ultima caratteristica verrà ampliamente descritta nella sezione dedicata agli oggetti predefiniti di Windows. In questo modo sono rese disponibili relazioni speciali tipo/sottotipo fra tipi precedentemente indipendenti. Un libro, una videocassetta, un disco, sono tutti una sorta di materiale di biblioteca, anche se ciascuno ha le sue regole per il prestito. L’interfaccia pubblica condivisa e i dati privati sono posti in una classe astratta MaterialeBiblioteca. Ogni singolo materiale eredita il comportamento comune dalla classe astratta MaterialeBiblioteca e deve fornire soltanto gli algoritmi e i dati che supportano il suo comportamento. Tre importanti linguaggi, escluso il C++, che supportano il paradigma sono Simula, Smalltalk e Java. 3.2 Windows e i messaggi Quando si programma per Windows, si esegue un tipo di programmazione orientata agli oggetti. Per rendersene conto, basta osservare l’oggetto con cui si lavora più spesso in Windows: la finestra. Le finestre più ovvie che spiccano sul Desktop sono quelle delle applicazioni. Queste finestre comprendono una barra del titolo che riporta il nome del programma, un menu ed eventualmente una barra degli strumenti e una barra di scorrimento. L’utente vede queste finestre come oggetti sullo schermo e interagisce direttamente con esse utilizzando la tastiera o il mouse. Come fa l’applicazione a capire che l’utente sta cercando di comunicare con la finestra stessa? Per i programmatori abituati solo alla convenzionale programmazione in modo caratteri, non esiste mezzo che consenta al Sistema Operativo di trasmettere all’utente informazioni di questo tipo. Ne consegue che questa domanda è essenziale per comprendere l’architettura di Windows. Quando l’utente interagisce con una finestra per esempio modificandone le dimensioni, Windows invia un messaggio al programma per indicare le nuove dimensioni della finestra. Il programma può quindi regolare il contenuto della finestra per adattarlo alle nuove dimensioni. 29 L’affermazione “Windows invia un messaggio al programma” significa che Windows chiama una funzione all’interno del programma, una funzione che deve essere scritta e che costituisce una parte essenziale del programma. I parametri di questa funzione descrivono il messaggio specifico che viene inviato da Windows e ricevuto dal programma. Questa procedura all’interno del programma è detta window procedure. L’idea che un programma effettui chiamate al Sistema Operativo è familiare a tutti. Questo, per esempio, è il metodo utilizzato per aprire un file su disco. Al contrario, l’idea di un Sistema Operativo che effettua chiamate a un programma può risultare più estranea e tuttavia è fondamentale per l’architettura di Windows. Ogni finestra creata da un programma ha una window procedure associata. Quest’ultima è una funzione che potrebbe trovarsi nel programma stesso o in una libreria. Windows invia un messaggio a una finestra chiamando la window procedure, che provvede a eseguire la necessaria elaborazione e al termine restituisce il controllo a Windows. Più precisamente, una finestra viene sempre creata in base a una window class. La window class identifica la window procedure che elabora i messaggi rivolti alla finestra. Nella programmazione orientata agli oggetti, un oggetto è una combinazione di codice e dati: la finestra è l’oggetto, il codice costituisce una window procedure, mentre i dati sono le informazioni conservate dalla window procedure e quelle conservate da Windows per ogni finestra e window class che esiste nel sistema. Quando un programma Windows inizia l’esecuzione, Windows crea una coda dei messaggi, dove vengono archiviati i messaggi indirizzati a tutte le finestre che un programma potrebbe creare. In ogni applicazione Windows esiste uno spezzone di codice definito ciclo dei messaggi avente lo scopo di prelevare i messaggi della coda e trasmetterli alla window procedure appropriata. Altri messaggi vengono inviati direttamente alla window procedure senza essere inseriti nella coda. 3.2.1 Registrazione della window class Una finestra viene sempre creata sulla base di una window class. La window class identifica la window procedure che elabora i messaggi indirizzati alla finestra. È possibile creare più finestre basate sulla medesima window class. Quest’ultima definisce non solo la window procedure ma anche altre caratteristiche delle finestre create sulla base di detta classe. Prima di poter creare una finestra dell’applicazione, occorre registrare una window class chiamando RegisterClass. Grazie a questa funzione siamo in grado di iscrivere nel sistema la nostra applicazione definendo in maniera univoca le caratteristiche che la finestra dovrà avere. 30 3.2.2 Creazione e visualizzazione della finestra La window class definisce le caratteristiche generali di una finestra, per cui consente di utilizzare la stessa window class per creare numerose finestre diverse. Quando si crea una finestra chiamando CreateWindow, si specificano informazioni più dettagliate sulla finestra. In Windows, ogni finestra è dotata di un handle. Il programma utilizza quest’ultimo per riferirsi alla finestra, molte funzioni di Windows lo richiedono come argomento affinché Windows capisca a quale finestra si riferisce la funzione. Se un programma crea svariate finestre, ciascuna avrà un handle differente. Quando la chiamata della funzione CreateWindow ha restituito un risultato, significa che la finestra è stata creata internamente a Windows. Ma la finestra non compare ancora sullo schermo. Sono infatti necessarie ancora due chiamate: ShowWindow e UpdateWindow. 3.2.3 Ciclo dei messaggi Dopo la chiamata a UpdateWindow, la finestra viene completamente visualizzata sullo schermo. A questo punto, il programma deve prepararsi a leggere gli input della tastiera e del mouse operati dall’utente. Windows gestisce una coda di messaggi per ogni programma attualmente in esecuzione. Quando si verifica un evento di input, Windows traduce l’evento in un messaggio che viene inserito nella coda dei messaggi del programma. Un programma preleva i messaggi dalla coda eseguendo un blocco di codice conosciuto come ciclo dei messaggi 3.2.4 Window procedure Tutto quanto è stato descritto fino a questo punto non è che una semplice impostazione. La window class è stata registrata, la finestra è stata creata ed è stata visualizzata sullo schermo e il programma ha avviato un ciclo di messaggi per prelevare i messaggi dalla coda. L’azione vera e propria si verifica nella window procedure, che determina che cosa la finestra debba visualizzare al suo interno e in che modo la finestra debba rispondere all’input dell’utente. In genere, i programmi non chiamano le window procedure direttamente. La window procedure viene quasi sempre chiamata da Windows stesso. Un programma può chiamare indirettamente la propria window procedure grazie alla funzione denominata SendMessage. Al momento della creazione di una finestra, Windows chiama la window procedure e la richiama anche quando la finestra viene eliminata. Inoltre, Windows richiama questa procedura quando la finestra viene ridimensionata, spostata o ridotta a icona, quando un utente fa clic sulla finestra con il mouse, quando vengono digitati dei caratteri utilizzando la tastiera, quando è stata scelta una voce da un menu, quando viene utilizzata una barra di 31 scorrimento e quando l’area deve essere ridisegnata. Tutte queste chiamate alla window procedure avvengono sotto forma di messaggi. Nella maggior parte dei programmi in Windows, gran parte del codice è dedicato alla gestione di questi messaggi. In genere, i messaggi che Windows può inviare a un programma sono identificati da nomi che cominciano con le lettere WM e sono definiti nel file di intestazione WINUSER.H. L’idea di una routine all’interno del programma che venga chiamata dall’esterno dello stesso non è una novità nella programmazione in modo caratteri. In Unix, la funzione signal è in grado di intercettare un’interruzione Ctrl-C o altri interrupt emessi dal Sistema Operativo. I programmi più vecchi scritti per l’MS-DOS intercettavano spesso gli interrupt hardware. In Windows questo concetto è stato esteso a tutto. Tutto ciò che interessa una finestra viene trasferito alla window procedure sotto forma di messaggio. Dopodiché quest’ultima risponde al messaggio in qualche modo o passa il messaggio a DefWindowProc per un’elaborazione di default. 3.3 Multitasking e multithreading Il multitasking è la capacità di un Sistema Operativo di eseguire più programmi simultaneamente. In linea di principio, il Sistema Operativo utilizza un clock hardware per allocare intervalli di tempo a ogni processo in esecuzione. Se gli intervalli di tempo sono sufficientemente piccoli e la macchina non è sovraccaricata da troppi programmi che tentano di fare qualcosa, l’utente ha l’impressione che tutti i programmi vengano eseguiti simultaneamente. Il multitasking non è niente di nuovo. Sui grandi mainframe, il multitasking è un dato di fatto. A questi mainframe sono spesso collegati centinaia di terminali e ogni utente che lavora su un terminale ha l’impressione di godere di un accesso esclusivo a tutte le risorse della macchina. Il multitasking sui Personal Computer ha impiegato un tempo molto maggiore prima di divenire realtà. Le versioni a 32 bit di Windows supportano tutte sia il multitasking vero e proprio sia il multithreading. Il multithreading è la capacità di un programma di implementare il multitasking internamente. Il programma può dividersi in thread o thread di esecuzione separati che in apparenza vengono eseguiti contemporaneamente. La progettazione, la scrittura e il collaudo di un’applicazione multithreaded complessa è uno dei compiti più difficili per un programmatore Windows. Poiché un sistema multitasking, nella sua versione preemptive, può interrompere un thread in qualsiasi punto per passare il 32 controllo ad un altro thread, eventuali interazioni non desiderabili tra due thread possono rivelarsi occasionalmente, come se si verificassero casualmente. Il race condition, un bug comune in un programma multithreaded, si verifica quando un programmatore presume che un thread finisca di svolgere un determinato lavoro (per esempio, la preparazione di alcuni dati) prima che un altro thread richieda quei dati. Per aiutare a coordinare l’attività dei thread, i Sistemi Operativi richiedono varie forme di sincronizzazione. Una di queste viene chiamata semaforo e consente al programmatore di fermare l’esecuzione di un thread in un determinato punto del codice fino a quando un altro thread segnala che è possibile riprendere l’esecuzione. Ci sono tuttavia delle sezioni critiche, cioè porzioni di codice che non possono essere interrotte. I semafori inoltre introducono un altro bug comune nell’uso dei thread, che viene chiamato deadlock. Ciò si verifica quando due thread hanno fermato reciprocamente la loro esecuzione e possono sbloccarla solo procedendo. La funzione API per creare un nuovo thread di esecuzione si chiama CreateThread. Tuttavia, la maggior parte dei programmatori in Windows preferiscono utilizzare una libreria runtime del C chiamata _beginthread che viene dichiarata nel file di intestazione PROCESS.H. Infine i thread hanno bisogno di una loro temporizzazione, in quanto la loro elaborazione potrebbe avvenire troppo velocemente. La soluzione è fornita dalla funzione Sleep. Un thread può chiamare la funzione Sleep per sospendere volontariamente la sua esecuzione. L’unico parametro richiesto è un periodo di tempo espresso in millisecondi. La funzione Sleep non ripassa il controllo fino al termine del tempo specificato. Durante quel periodo, il thread viene sospeso e non vengono più allocate porzioni di tempo, sebbene ovviamente, il thread necessiti ancora di piccole quantità di tempo di elaborazione durante i clic del timer, quando il sistema deve determinare il thread che deve essere ripreso. Un argomento uguale a 0 passato a Sleep fa perdere al thread la parte di tempo rimanente assegnatagli. Quando un thread chiama la funzione Sleep, solo quel thread viene sospeso per il periodo di tempo specificato. Il sistema continua l’esecuzione degli altri thread, sia nello stesso processo sia in un altro. 3.4 Victor e le Immagini È essenziale, al fine di una corretta analisi sulla libreria Victor Image Processing prodotta dalla Catenary Systems, portare l’attenzione sui metodi utilizzati per definire, conservare e visualizzare le immagini su un moderno Personal Computer. Solo grazie a questa discussione 33 saremo in grado di studiare in maniera corretta le funzioni Victor in grado di elaborare le immagini. 3.4.1 Codifica delle immagini Diversi problemi devono trovare soluzione per ottenere questo risultato, per esempio si deve discutere in che modo possano essere caratterizzati i colori, oppure quale sia la struttura più adatta per contenere un’immagine. La natura stessa dei colori sostiene la ricerca della soluzione attinente al primo quesito. Infatti in natura qualsiasi colore è scomponibile nei tre colori primari rosso, verde e blu (RGB). Con questa tecnica è possibile definire qualunque sfumatura si voglia ottenere. Quindi non si deve far altro che definire una struttura che contenga i pesi dei tre colori primari per ottenere abbastanza informazioni sulla sfumatura desiderata. Di solito il peso di ogni colore primario è contenuto in un byte che permette un scala da 0 a 255, cosicché, per esempio il nero sarà definito da una struttura che contiene 0 in ogni campo. Una sfumatura di giallo avrà un valore molto alto sia nel campo attinente al rosso sia nel verde mentre conterrà un valore tendente a 0 nel campo relativo al blu. Infine un grigio avrà un valore costante e simile in tutti i tre campi dove, valori molto bassi porteranno ad un grigio scuro, mentre valori elevati produrranno grigi chiari. Con questa tecnica è possibile definire più di sedici milioni di colori quindi abbiamo a nostra disposizione una gamma estremamente valida per descrivere immagini del mondo reale. Per definire una struttura relativamente semplice che ci permetta di visualizzare le immagini in modo corretto possiamo sfruttare le tecniche utilizzate dai terminali video per il loro funzionamento. Quest’ultimi utilizzano dei punti, chiamati pixel, per dare la sensazione di colore all’occhio umano. Ogni pixel utilizza il metodo descritto prima, nel quale ci sono tre aree molto piccole e ravvicinate. Ciascuna delle quali può modulare l’intensità di un colore primario cosicché, da lontano, l’occhio percepisce una sola sfumatura, risultato della fusione dei tre colori primari. Più pixel formano la superficie dello schermo più elevata è la risoluzione dello stesso. L’immagine non è altro che una matrice di pixel organizzati in righe e colonne che definiscono i colori di ogni singolo punto. Quindi, per esempio, un’immagine indicata 480X640 significa che contiene 480 righe e 640 colonne di pixel. I pixel vengono immagazzinati, una riga dopo l’altra partendo dall’angolo in basso a sinistra, in un vettore. Varie sono le tecniche utilizzate per relazionare i pixel dell’immagine con l’informazione sul loro colore. Tuttavia, solo un paio di queste sembrano aver acquisito una notevole importanza ed entrambe hanno sia pregi che difetti. 34 La prima impone la creazione di una palette, che è paragonabile alla tavolozza che il pittore usa per contenere i colori che utilizza sulla tela. Questa palette è un vettore di strutture RGB che contengono al loro interno i colori primari per ogni sfumatura dell’immagine. Con questa tecnica il vettore che contiene i dati relativi ad ogni pixel dell’immagine può essere visto come un insieme di indici utilizzati per ritrovare i vari colori nella palette. Grazie a questo metodo è possibile indicare la definizione dell’immagine. Infatti con un solo bit per l’indice si può indirizzare solo due colori nella palette, uno con lo 0 e l’altro con 1, di solito sono il bianco e il nero. In questo caso ogni pixel viene caratterizzato da un solo bit nella struttura che li conserva. Quindi un byte contiene il colore di 8 pixel sulla stessa riga. Se si utilizza un byte per definire l’indice, si può indirizzare 256 colori nella palette e quindi nella struttura che contiene i pixel ognuno di essi è caratterizzato da 8 bit. Il vantaggio nell’utilizzare questa tecnica sta nel fatto di poter compattare in maniera corretta le informazioni sul colore dei pixel, in quanto pixel vicini, che potrebbero avere lo stesso colore, indicano lo stesso indice nella palette, con un considerevole risparmio di spazio, un byte al posto di tre. Tuttavia se si utilizza per esempio una palette con 256 colori, non è possibile avere immagini con più di 256 sfumature al loro interno, anche se quest’ultime possono reperire una sfumatura diversa in un insieme di sedici milioni. Per prevenire questo svantaggio si adotta la seconda tecnica che, a discapito di un vettore più capiente per contenere l’informazione sul colore dei pixel, permette di avere immagini con sedici milioni e più di colori, contemporaneamente al loro interno. Le immagini che utilizzano questa tecnica abbandonano la palette e i tre byte che corrispondono ai tre colori primari di ogni sfumatura vengono stoccati nel vettore che contiene i colori dei pixel, cosicché ogni pixel occupa tre byte in questo vettore. Queste immagini vengono, per questo, chiamate a 24 bit. È importante notare che numerose sono le variazioni che si possono ottenere. Per esempio si possono avere immagini caratterizzate da sfumature di grigio ottenute in quanto nella palette ci sono 256 grigi differenti che vanno dal più chiaro al più scuro. Oppure è possibile avere immagini che utilizzano quattro e non tre byte per definire i tre colori primari, in questo caso si privilegia il verde donandogli più bit, in quanto l’occhio umano percepisce maggiormente le sfumature di verde. Tuttavia per risolvere i problemi descritti nel capitolo precedente si utilizzano immagini a 24 bit, quindi la struttura che si andrà a trattare non avrà la palette ma ogni pixel verrà definito grazie a tre byte nel vettore che ha il compito di contenerli tutti. 35 3.4.2 DIB Un argomento di grande interesse che non è stato ancora trattato ma che riveste un ruolo importante nell’analisi, è come Windows identifichi le immagini. Quest’ultimo utilizza un oggetto chiamato DIB (Bitmap Indipendente dal Dispositivo) nel quale trovano collocazione le strutture che abbiamo analizzato precedentemente. La DIB è stata introdotta in Windows 3.0 allo scopo di fornire quel formato tanto necessario per lo scambio di immagini. In realtà, altri formati di file come GIF e JPEG sono molto più comuni su Internet e questo soprattutto perché i formati GIF e JPEG implementano schemi di compressione in grado di ridurre in maniera significativa i tempi di download. Sebbene sia stato definito uno schema di compressione anche per le DIB, questo viene usato di rado. Nella maggior parte delle DIB, i bit della Bitmap conservano la loro forma non compressa. Questo è senza dubbio un vantaggio se si desidera manipolare i bit della Bitmap nel proprio programma. Contrariamente ai file GIF e JPEG, la DIB è direttamente supportata dalla API di Windows. Se in memoria c’è una DIB, è possibile associarle puntatori come argomenti a svariate funzioni che consentono di visualizzarla. Un oggetto DIB comprende quattro sezioni principali: un’intestazione dell’oggetto, un’intestazione informativa, una tabella dei colori RGB (ma non sempre) e i bit per pixel della Bitmap. È possibile pensare alle prime due parti come a strutture di dati del C e alla terza parte come a un array di strutture di dati. Queste strutture sono documentate nel file di intestazione di Windows WINGDI.H. Una DIB salvata in memoria nel formato DIB compresso comprende tre sezioni: un’intestazione informativa, la palette (ma non sempre) e i bit per pixel della Bitmap. La parte più interessante che descrive l’immagine è contenuta nell’intestazione informativa è qui, infatti, che sono descritte le caratteristiche della Bitmap. Per esempio ci sono dei campi che contengono la dimensione dell’immagine in pixel e un campo che indica il numero di bit per pixel. Per le DIB questo può essere 1, 4, 8 oppure 24, che caratterizzano rispettivamente una DIB a 2, 16, 256 oppure full-color. Nei primi tre casi l’intestazione informativa e seguita da una palette, che invece non esiste per le DIB a 24 bit. La palette è un array di strutture RGB da tre byte, una per ciascun colore dell’immagine. 3.4.3 Victor Le funzioni Victor possono operare su un sotto gruppo delle DIB. La differenza sta nella compressione, nei bit per pixel e nella palette. Una DIB può essere compressa o non compressa. Le funzioni della librerie Victor possono operare solo su DIB non compresse. 36 Questo permette alle funzioni di lavorare sulle sezioni della DIB. Inoltre questa caratteristica è di vitale importanza nel sistema che si va a costituire. Infatti gli algoritmi di cattura ed elaborazione dati che si occuperanno di manipolare le immagini devono operare direttamente sulle sezioni della DIB. Windows permette alle DIB di avere 1, 4, 8 oppure 24 bit per pixel. Le funzioni Victor possono operare solo su DIB con 1, 8 o 24 bit per pixel. Le differenze sulla palette non vengono contemplate in quanto, per gli scopi prefissati, si utilizzano immagini a 24 bit che non impiegano la tabella dei colori. La più importante struttura nel pacchetto Victor è il descrittore dell’immagine. Tutte le informazioni necessarie alle funzioni, per operare su un’immagine sono contenute nel descrittore dell’immagine. Quest’ultimo è definito in VICDEFS.H ed è un argomento richiesto per molte funzioni Victor. Le potenzialità offerte dalla libreria Victor sono numerose. Ci sono funzioni che manipolano i colori dell’immagine per ottenere gli effetti più vari. Dall’effetto neve all’effetto metallico all’effetto bianco e nero. Esistono inoltre funzioni che permettono di analizzare l’immagine per produrre diagrammi relativi alle caratteristiche cromatiche o luminose. Infine è possibile utilizzare funzioni che operano sulle dimensioni dell’immagine oppure sono in grado di modificare il numero di bit per pixel. Per poter utilizzare le funzioni contenute nella libreria si devono effettuare alcune operazioni preliminari. Per prima cosa si deve allocare memoria per l’immagine, dopodiché si carica l’immagine. Solo a questo punto è possibile manipolarla grazie alle funzioni Victor. Un’altra caratteristica, che rende interessante l’oggetto incaricato da Victor di contenere l’immagine, è la possibilità di manipolare direttamente i bit che contengono il colore dei pixel. Quest’ultima opportunità avrà enorme rilievo nell’implementazione degli algoritmi che supportano l’elaborazione dell’immagine. È possibile aggiungere, già da ora, che questa operazione sarà l’unica effettuata sulle immagini contenute nell’oggetto Victor. Infatti per conseguire gli obiettivi preposti non è importante manipolare le immagini per ottenere qualche strano effetto. L’unica caratteristica della libreria Victor che viene sfruttata in questo contesto è la facilità con cui si accede ai dati che descrivono i pixel dell’immagine. Per allocare memoria si utilizza la funzione allocimage, per caricare nella memoria appena definita un’immagine si utilizza loadbmp. Infine per liberare la memoria, dopo l’elaborazione, si utilizza la funzione freeimage. 37 38 4 Architettura e algoritmi Grazie ai capitoli precedenti è possibile avere di fronte un disegno generale nel quale l’ausilio si colloca, si sono definiti sia un punto di partenza che di arrivo, i problemi che si devono affrontare, nonché gli aiuti esterni che ci possono sostenere nel nostro compito. Ora la strada è stata tracciata e possediamo i mezzi per costruirla, non dobbiamo fare altro che iniziare con i lavori. In questo capitolo si intraprenderà la discussione delle soluzioni proposte nel sistema. Tuttavia a questo punto dell’analisi le soluzioni non possono essere che teoriche, in quanto alla pratica si dovrà attendere il capitolo successivo relativo all’implementazione, infatti solo in esso compariranno le soluzioni concrete adottate nel sorgente. Il titolo di questo capitolo contiene già al suo interno un’informazione importante ed essenziale per il suo assetto. Infatti la discussione si dividerà su due aspetti fondamentali in stretta relazione fra di loro. Il primo tratta delle scelte intraprese al fine di risolvere i dubbi, che sono apparsi quando l’analisi si è rivolta all’architettura che il sistema deve possedere, in quel particolare momento sono emerse in realtà alcune caratteristiche che tutti i complessi devono ottenere per il loro buon funzionamento. Dall’altra parte devono essere discusse le soluzioni che sono state contemplate per risolvere i problemi relativi alla natura stessa 39 dell’ausilio, in questo caso gli algoritmi che sono stati definiti per portare a termine i compiti che il sistema deve offrire agli utenti. 4.1 Architettura Vengono trattate in questa sezione le soluzioni adottate per definire la struttura che il sistema deve possedere. La discussione successiva non ha altro scopo che produrre una base solida e stabile per la costruzione degli algoritmi che verranno trattati nella prossima sezione e che permettono al sistema di portare a termine il suo compito. Grazie al C++, il linguaggio di programmazione scelto per scrivere questo sistema, è possibile definire una architettura che utilizza gli oggetti come base per la sua struttura. È all’interno di questi oggetti che vengono incapsulati gli algoritmi, un singolo algoritmo per ogni oggetto. Quindi ogni oggetto porterà al suo interno determinate caratteristiche che lo collocheranno in un ruolo ben preciso all’interno del sistema. Varie sono le tipologie che si possono esaminare. Si può, per esempio, definire una struttura nella quale tutti gli oggetti sono sullo stesso piano, oppure delineare una struttura stratificata dove gli oggetti si trovano ad operare a diversi livelli. Infine si può scegliere di operare con una struttura master slave dove un oggetto è a capo di tutti gli altri. Ogni soluzione presenta sia vantaggi che svantaggi. Un’architettura che pone tutti gli oggetti sullo stesso livello porta con sé il privilegio di poterli fare comunicare liberamente fra di loro. Costituendo una struttura estremamente complessa, veloce e compatta. Tuttavia si rischia di produrre un sistema impossibile da comprendere e da manipolare. Questo perché le relazioni che intercorrono tra gli oggetti possono diventare troppo complesse ed intricate da poterle seguire facilmente e in un attimo, eliminando una relazione o modificandola, si può compromettere l’intero sistema. Questa struttura porta anche un difetto più subdolo da analizzare, infatti rende inutile una programmazione orientata agli oggetti e il paradigma di programmazione si sposta su una programmazione procedurale. Infatti gli algoritmi contenuti negli oggetti vengono elaborati uno dopo l’altro in sequenza rendendo vano lo sforzo di divedere i compiti su più oggetti, è come se gli algoritmi fossero in sequenza temporale all’interno di un programma monolitico. In più una tale soluzione entra in netto contrasto con un Sistema Operativo quale Windows nel quale si adottano messaggi per la comunicazione tra oggetti che devono essere ben definiti. Infine si genera un sistema nel quale uno sviluppatore non può estenderne una sola parte senza capire come funziona l’intera struttura. 40 La stratificazione degli oggetti potrebbe essere una soluzione più conveniente, oltre a rendere la struttura più semplice e intuitiva, permette agli oggetti di riacquistare il proprio valore e inserirsi, grazie alle loro caratteristiche, a livelli ben definiti all’interno della struttura. Tuttavia entra in gioco un’ulteriore domanda: quanti strati deve avere la struttura? Infatti c’è il pericolo di sovrastimare il numero di strati e rendere il sistema troppo lento nel rispondere agli stimoli esterni prodotti dall’utente, o per esempio, nella cattura e riconoscimento dei movimenti. Se il sistema è obbligato a tradurre le operazione da svolgere attraverso numerosi passaggi tra uno strato e l’altro, l’elaborazione può risultare complessa e divenire lenta nella sua globalità. L’ultima tipologia da discutere non è stata rilegata alla fine senza motivo, infatti questa è la soluzione che si adotterà per il sistema. La struttura master slave risponde alla domanda di poco fa sugli strati che deve possedere l’architettura, ogni oggetto che incapsula al suo interno un algoritmo diviene schiavo di un oggetto che presiede al controllo e alla gestione di tutto il sistema. In pratica la struttura si divide su due soli livelli, il livello più basso contiene l’oggetto che controlla l’intero sistema, mentre al livello superiore risiedono gli oggetti che contengono gli algoritmi per l’elaborazione dei dati. Un oggetto slave dello strato superiore, per poter comunicare con un oggetto dello stesso livello deve chiamare il master al livello inferiore informandolo sull’operazione che vuole svolgere, sarà quest’ultimo ad impartire gli ordini all’oggetto servo destinatario. Questo permette di trattare gli oggetti servi come delle scatole nere, utile nello sviluppo futuro del sistema in quanto gruppi di sviluppatori diversi possono interessarsi solo ad alcuni aspetti del sistema tralasciandone altri, senza entrare in conflitto. Per gestire le funzioni dello strato master viene incaricato un oggetto che, oltre a regolare gli oggetti schiavi, pensa ad organizzare e tradurre le azioni che l’utente fa sul sistema, visualizzando i risultati su schermo grazie ad un rapporto stretto con le funzioni Windows. Esiste un solo difetto che colpisce questa tipologia di struttura ed è rappresentato dalla velocità. Si è già incontrato questo aggettivo quando si discutevano i vantaggi di una struttura in cui tutti gli oggetti erano posti sullo stesso livello. Quindi potrebbe rivelarsi una strada valida da percorrere, cercare di portare ed inserire nella struttura le qualità che promettono la caratteristica della velocità. Per ottenere questo traguardo, si deve purtroppo infrangere una regola importante che costituisce una struttura master slave, e cioè, permettere che gli oggetti schiavi possano comunicare fra di loro eliminando il vincolo che li obbliga a parlare solamente al master. 41 In questo caso, si privilegia la caratteristica di velocità a dispetto della coerenza nella struttura. Tuttavia non si deve abusare di questa singolarità, essa è ammessa solo nei casi strettamente legati al tempo di esecuzione, permettendo agli oggetti servi di interagire direttamente senza l’ausilio del master. A questo punto è possibile contemplare la struttura del sistema nella sua interezza. Essa sarà gestita da un oggetto che farà le veci del master, quest’oggetto avrà il compito di supervisore in tutte le operazioni che il sistema dovrà essere in grado di compiere. Gli oggetti predisposti per interpretare i comandi decisi dall’utente dovranno comunicare al supervisore quale operazione è stata richiesta, quest’ultimo impartirà gli ordini agli oggetti su cui ricade quella operazione e i risultati ottenuti saranno visualizzati grazie ad altri oggetti sempre gestiti dal supervisore. È importante ancora discutere le qualità e definire le caratteristiche che ogni oggetto slave deve possedere. Per far questo è possibile iniziare col pensare a come l’ausilio opererà. La scala che permette di raggiungere il nostro obbiettivo è composta da numerosi scalini, ogni scalino è composto da uno o più oggetti servi che ne definiscono la natura. Tutta la scala, infine, viene tenuta insieme dal cemento costituito dal master o supervisore. Ogni scalino, e quindi gli oggetti che lo costituiscono, deve essere pensato come il telaio nel quale incastonare gli algoritmi che verranno presentati nel paragrafo successivo, sono infatti quest’ultimi la vera risorsa del sistema, sono essi a permettere il funzionamento dell’ausilio. Qui con la tipologia di struttura master slave prima e con l’analisi degli oggetti fra poco, non si fa altro che porre le fondamenta per accogliere gli algoritmi che manipolano i dati e che portano alla soluzione del problema. Vengono ora presentati grazie a sezioni separate tutti gli scalini che permettono di arrivare all’obiettivo. 42 4.1.1 Acquisizione immagini Una delle qualità più importanti che il sistema deve possedere è la capacità di agganciare e ottenere immagini dalle videocamere collegate al computer. Il sistema ha incaricato, per questa potenzialità, un singolo oggetto al quale è addossata l’intera responsabilità di questa operazione. Il suo compito non è solo quello di prelevare le immagini dalle videocamere ma deve gestire tutto quello che è attinente alle camere stesse. Sotto l’interrogazione del master, che d’ora in poi verrà chiamato Supervisore, deve fornire dei risultati attinenti alle domande oppure rispondere indicando un errore se qualcosa non può essere fatto. Questo oggetto non contiene al suo interno particolari algoritmi per l’elaborazione dei dati, è solo il tramite e il mezzo che il sistema utilizza verso il mondo delle videocamere. I risultati più importanti che l’oggetto deve essere in grado di ottenere sono l’aggancio delle camere nel sistema, la cattura di singoli fotogrammi, la possibilità di modificare parametri quali la luce e il contrasto delle immagini e infine poter rilasciare l’aggancio delle camere prima che il sistema venga terminato. La caratteristica del poter modificare alcuni importanti parametri relativi all’immagine si ricollega al discorso intrapreso nei capitoli precedenti sul problema di rendere semplice l’utilizzo del sistema da parte dell’utente e del suo assistente. Infatti, utilizzando questa caratteristica è possibile modificare i parametri dell’immagine direttamente dall’ausilio senza dover usare altri programmi forniti dalla casa costruttrice della videocamera. Questo permette di velocizzare queste operazioni e rendere meno pesante il lavoro richiesto all’utente. È importante notare che il sistema ha un forte bisogno di questo oggetto. Infatti grazie ad esso non si deve preoccupare di come le camere vengono gestite. Questo implica che uno 43 sviluppatore a cui non interessa questa parte del sistema non è assolutamente obbligato a capire come è implementato al suo interno quest’oggetto, ne deve soltanto richiedere le funzionalità. Al contrario uno sviluppatore interessato al miglioramento di questa parte del sistema può agire liberamente, con il solo vincolo di rispettare la coerenza sulle risposte che l’oggetto può dare al Supervisore rispetto alle versioni precedenti. 4.1.2 Scelta del riferimento In questa sezione si risponde ad un problema che era stato analizzato nel capitolo relativo al contesto dell’ausilio. In quella sezione era stata trattata la differenza tra motion capture intrusiva o non intrusiva elencando i pregi e i difetti di ciascuna soluzione. La nostra scelta non può che ricadere su un sistema che utilizza motion capture intrusiva, in quanto si cerca di privilegiare un sistema che introduca il minor numero di errori nell’elaborazione dell’immagine. Questo può essere fatto solo utilizzando la tecnica intrusiva, non priva di errori ovviamente, ma più sicura di quella non intrusiva. Tuttavia non si deve dimenticare il fatto che, grazie alla programmazione orientata agli oggetti è sempre possibile implementare una soluzione che incorpora la tecnica non intrusiva. Anche se passare da una soluzione all’altra possa essere visto come riedificare una parte delle fondamenta e quindi un operazione abbastanza complessa, tutto il lavoro si focalizza solo su pochi oggetti i quali sono adibiti a questa elaborazione all’interno del sistema. Ora grazie alla scelta caduta sulla motion capture intrusiva è possibile definire le caratteristiche che deve possedere l’oggetto o gli oggetti a cui è demandato il compito di intercettare il riferimento colorato nelle immagini catturate grazie all’oggetto descritto precedentemente. Gli oggetti che costituiscono questo passo, se interrogati dal Supervisore devono essere in grado di indicare quali colori sono presenti all’interno delle immagini catturate e dare la possibilità all’utente di scegliere il colore da intercettare, quest’ultimo sarà il colore che più si avvicina al colore del riferimento fisso sul volto dell’utente. Anche in questo caso il sistema non ha bisogno di conoscere come queste operazioni vengono effettuate all’interno degli oggetti, il Supervisore non deve fare altro che ottenere il colore scelto dall’utente e passarlo agli oggetti che permettono al sistema di proseguire nella sua elaborazione. Questo, come è gia stato ribadito più volte è il grande vantaggio di poter operare con gli oggetti. 44 4.1.3 Motion capture Gli oggetti che appartengono a questa parte del sistema sono il cuore della struttura stessa, è infatti in questo luogo che il sistema riesce a intuire il movimento del volto dell’utente grazie al riferimento fisso sulla sua fronte. Gli algoritmi contenuti all’interno di questi oggetti rendono possibile tutto questo e per questa ragione sono i più importanti di tutto il sistema. Ottenuto il colore del riferimento dal Supervisore che li gestisce e comanda in tutte le azioni che possono attuare, gli oggetti a cui è demandato quest’incarico devono essere in grado di fornire al sistema un’indicazione, la più precisa possibile, di dove si trovi il riferimento su ogni immagine acquisita dalle videocamere. A questo punto catturando un’immagine dopo l’altra è possibile tenere traccia dei movimenti del volto dell’utente. È qui che entra in gioco la singolarità che impone alla struttura di venir meno alle leggi imposte tra master e slave. Infatti, il fattore velocità diventa una carta importante da giocare in questa parte dell’elaborazione. Per privilegiare la rapidità si deve permettere agli oggetti che contengono gli algoritmi che elaborano le immagini catturate di poter comunicare direttamente con l’oggetto che è incaricato della cattura stessa. Scavalcando, in questo modo, la mediazione costituita dal Supervisore. Il vantaggio che risiede nella velocità di elaborazione supera di gran lunga lo svantaggio di destabilizzare la coerenza della struttura stessa. Infatti l’obbligo, da parte degli oggetti che costituiscono questa parte del sistema, di comunicare solo con il Supervisore e chiedere a quest’ultimo di caricare ogni immagine porta ad un overhead di operazioni troppo costoso e inutile. Inoltre lasciando liberi questi oggetti di comunicare si arriva ad una implementazione più chiara da comprendere per i futuri sviluppatori. Ciò non toglie che solo in questo caso si può attuare questa politica, per tutto il resto del sistema le regole stabilite da una struttura master slave devono essere strettamente rispettate, qui si è agito in questo modo solo perché nella lista di priorità si è posta la velocità prima di tutto il resto e quindi anche delle regole che determinano la struttura. Tuttavia nessun altra eccezione può essere fatta in quanto subito dopo la prontezza nell’elaborazione la lista delle priorità accoglie il dovere di rispettare diligentemente le regole che sanciscono la struttura. 4.1.4 Definizione campo visivo A questo punto del cammino il Supervisore deve gestire degli oggetti che custodiscono al loro interno gli algoritmi che permettono di associare ad una data posizione del riferimento la 45 direzione in cui è rivolto il viso dell’utente. Sono questi oggetti che contengono tutte le tematiche che si sono analizzate mentre si parlava della calibrazione del sistema. Una volta in grado di intercettare il movimento del riferimento nelle immagini catturate dalle videocamere, in che modo si può tradurre questa informazione in un dato utile? Il discorso già intrapreso nel capitolo attinente al contesto nel quale si parlava del cablaggio ha introdotto un concetto interessante quale il sistema di riferimento assoluto. È possibile infatti, definire un sistema di riferimento assoluto basato sull’immagine stessa catturata. Essa nasce dal sistema ottico di una videocamera che per definizione è fissa nello spazio. Quest’ultima viene appoggiata per esempio sul bordo superiore dello schermo e in quella posizione resta per tutto il tempo in cui il sistema è al lavoro senza essere più toccata. Ciò significa che sia lo schermo del Personal Computer sia la videocamera sono fermi uno rispetto all’altro. Questa caratteristica viene sfruttata per ricavarne la constatazione che se l’utente muove solo la testa e non tutto il corpo fissando il punto in alto a destra dello schermo, il riferimento solidale al suo volto, si troverà in una posizione ben precisa nel sistema assoluto dell’immagine. Tutte le volte che l’utente fisserà quel punto il riferimento si troverà sempre nella stessa posizione. Il trucco sta nel far fissare all’utente i quattro angoli dello schermo e da questi si ottiene un’area rettangolare nel sistema di riferimento assoluto dell’immagine nel quale il riferimento solidale al volto dell’utente ha libertà di movimento. Se, per esempio, l’utente rivolge il viso verso il centro dello schermo, il riferimento si troverà al centro dell’area rettangolare definita nel sistema di riferimento solidale con l’immagine. Mentre se quest’ultimo esce dall’area appena determinata significa che l’utente non ha il viso rivolto verso lo schermo. La determinazione di quest’area, scalata nel sistema di riferimento solidale con l’immagine catturata dalla videocamera, è il compito a cui gli oggetti che risiedono a questo punto del cammino verso il nostro obiettivo sono obbligati a compiere. O meglio, gli algoritmi contenuti in questi oggetti devono portare al sistema questo risultato. Quando si è discusso nei capitoli precedenti sulla disposizione delle videocamere, l’analisi non si è spinta oltre l’esame dei pregi e difetti di ogni soluzione. Precedentemente si è indicata la parte superiore dello schermo come una potenziale posizione della videocamera, ebbene questa sarà la posizione definitiva che la camera principale occuperà nel sistema. È stata scelta questa posizione per minimizzare al massimo i problemi di linearità insiti nella geometria spaziale che incorpora schermo, utente e videocamera. Infatti la soluzione che vedeva la camera posta al di sotto dello schermo del video porta all’introduzione di trasformazioni curvilinee tra riferimento e punto fissato difficili da 46 compensare, per esempio il rettangolo dell’area definita dal cablaggio non è più un rettangolo ma una famiglia di curve sempre più accentuate. Questo è dovuto al fatto che l’asse tra la camera e la fronte dell’utente dove risiede il riferimento è praticamente orizzontale nella soluzione nella quale la camera è sopra lo schermo, mentre è obliquo se la videocamera è posta sotto lo schermo ed è questo fatto a introdurre le non linearità nella relazione tra riferimento e punto fissato dall’utente. 4.1.5 Indicazione dello sguardo Si è quasi raggiunta la soluzione del problema, ora il Supervisore non deve fare altro che gestire un oggetto che ha il compito di riferire in quale direzione l’utente abbia rivolto il viso. Questo oggetto ha il compito di elaborare dati che sono già stati filtrati e a cui si è già dato un significato attraverso i numerosi oggetti precedentemente descritti. In pratica questo oggetto non deve fare altro che controllare la posizione del riferimento in ogni immagine catturata, confrontarla con l’area ottenuta nella calibrazione e, in base ad un algoritmo ben preciso, avvertire il Supervisore del punto sullo schermo sul quale l’utente ha rivolto lo sguardo. Entra ora in gioco l’analisi che è stata fatta sulla possibilità di connettere al sistema applicazioni indipendenti. In quella discussione era emerso che sarebbe stata un’ottima caratteristica poter inserire nel sistema applicazioni che non avevano nulla da condividere con l’ausilio, se non sfruttare le sue potenzialità per i loro obiettivi privati. La struttura che si è definita in queste pagine permette di risolvere questo problema in maniera egregia. Le applicazioni vengono trattate dal sistema come oggetti che appartengono allo strato superiore, dove risiedono tutti gli oggetti che sono stati definiti slave. In quest’ottica è possibile suddividere questo strato in due insiemi separati di oggetti schiavi, da un lato ci sono tutti gli oggetti che concorrono alla soluzione del problema, quindi tutti gli oggetti che si sono descritto fino ad ora. Nell’altro insieme risiedono gli oggetti che contengono al loro interno un’applicazione indipendente. Questo gruppo ha al suo interno solo un oggetto attivo alla volta ed è quest’ultimo che contiene l’applicazione attiva, cioè quella che riceve l’informazione sulla posizione del viso dell’utente. L’unico compito aggiuntivo per uno sviluppatore di applicazioni, che incorporano al loro interno le potenzialità dell’ausilio, sarà quello di iscrivere nella lista delle applicazioni tenuta dal Supervisore la sua applicazione e informare quest’ultimo che, tra le tante applicazioni esistenti all’interno del sistema, quella attiva è la sua. A questo punto il Supervisore, quando otterrà il punto fissato dal volto dell’utente grazie all’ultimo oggetto nella scala dell’elaborazione dei dati, avviserà con questa informazione l’applicazione che in quel dato istante è attiva. 47 Questa è la parte più elegante di tutta la struttura ed è al fine di raggiungere questa potenzialità che il sistema è stato concepito, questo è il fine ultimo dell’ausilio, tutto all’interno di esso spinge per rendere attiva questa possibilità. È per questo motivo che si è deciso di utilizzare questa tipologia di struttura in quanto se tutti gli oggetti fossero stati posti sullo stesso piano, il compito di capire quale applicazione indipendente era attiva sarebbe stato di difficile soluzione. Grazie al Supervisore, che tiene traccia di tutte le applicazioni all’interno del sistema e conosce con precisione quale delle tante sia in quell’istante attiva questo problema viene risolto in maniera elementare. 4.1.6 Gestione del sistema Il Supervisore non ha solo il compito di controllare tutta la catena di acquisizione ed elaborazione dati, nonché la gestione di tutte le applicazioni indipendenti iscritte nel sistema, ma deve gestire anche degli oggetti che non concorrono direttamente alla soluzione finale. Questi oggetti risiedono, come tutti gli altri, nello strato superiore descritto come slave. Possono quasi essere visti come un terzo insieme tra quelli descritti in precedenza, quest’ultimi si occupano di tutta la gestione indiretta del sistema, dalla gestione del menu, alla gestione degli errori, alla visualizzazione delle finestre attive e infine alla gestione delle liste che contengono le applicazioni. Possiamo vederli come oggetti che lavorano dietro le quinte e che aiutano il Supervisore nei suoi compiti più pesanti ma che esulano dall’elaborazione più importante. Il loro compito principale è quello di rendere l’architettura semplice e ben ordinata, per permettere una facile lettura del codice sorgente e per non affaticare in maniera pesante le operazioni svolte dal Supervisore. Quest’ultimo demanda i compiti più pesanti e noiosi a questi oggetti trattenendo per sé le elaborazioni più ricche di significato per il sistema. Nel corso della trattazione è possibile riferirsi a questi oggetti in maniera superficiale, ma solamente nelle appendici verranno esaminati nel loro complesso e nella loro totalità. 4.2 Algoritmi Ora, finalmente, non si cammina più su un terreno inesplorato. Ora esiste una base a cui si è cercato di dare un’impostazione più solida possibile. Le fondamenta, descritte nel paragrafo precedente, su cui nascerà tutto il resto sembrano robuste, ora non resta da fare altro che definire degli algoritmi che sfruttino al massimo le potenzialità offerte dall’architettura del sistema. La parola algoritmo contiene al suo interno un concetto estremamente importante per il mondo annesso all’informatica e all’elaborazione compiuta dal cosiddetto cervello 48 elettronico. Chi non ha mai avuto contatti con questo mondo reputa questa disciplina fredda e a volte oscura, tanto da tenersi alla larga e a trattarla con diffidenza. Tuttavia nulla come l’informatica dà prova delle potenzialità su cui può contare la mente umana. Questa disciplina deve essere collocata al pari di molte altre, nelle conquiste che la razza umana ha ottenuto nei milioni di anni combattuti per l’evoluzione. Il calore e l’emozione che può dare, per esempio, l’arte musicale producendo una meravigliosa sonata devono essere percepiti anche nelle creazioni imputabili alla nostra arte, infatti ambedue sono opere dell’intelletto umano e contribuiscono a rendere particolare ciò che noi siamo. L’algoritmo, è per molti versi il soffio di vita che percorre l’elemento inanimato costituito dal cervello elettronico, è la traccia lasciata da un essere umano in un oggetto inorganico. È per questa ragione che non si deve temere questa disciplina, in essa possiamo ritrovare solo il lavoro di noi stessi, come qualunque altra cosa prodotta dall’uomo. Gli algoritmi che vengono presentati in questo paragrafo non recano neppure lontanamente l’ambizione descritta precedentemente, sono solo granelli di sabbia in una spiaggia vastissima, ma nel loro piccolo cercano di migliorare la vita di qualche essere umano che può trovare giovamento da questo umile ausilio. A questo punto possiamo iniziare l’analisi dei vari algoritmi che si incontreranno negli oggetti che costituiscono la struttura del sistema. Essi verranno divisi e studiati in varie sezioni che si sovrappongono alle sezioni trattate nel paragrafo precedente. Come già accennato quando si è parlato dell’oggetto candidato alla cattura delle immagini, nessun algoritmo è contenuto al suo interno, questo è dovuto al fatto che il suo solo compito è prelevare le immagini dalla camera. Il primo algoritmo che si incontrerà permette all’utente di scegliere il colore del riferimento per le catture successive, poi verrà analizzato l’algoritmo che permette di catturare il movimento del volto dell’utente. A questo punto si incontrerà quello che aiuta a calibrare il sistema e infine verrà presentato l’algoritmo che risiederà nell’oggetto, il cui incarico è dare l’informazione sul punto fissato sullo schermo dall’utente, al Supervisore. È di vitale importanza comprendere che in questo capitolo avviene l’analisi teorica degli algoritmi, quindi si discute la catena logica di avvenimenti che rendono possibile l’elaborazione dati. Ogni algoritmo viene discusso in maniera isolata dagli altri e l’unico punto di contatto sono i risultati che l’algoritmo a monte dona alla procedura che sta a valle. Tuttavia, come si vedrà nel prossimo capitolo l’implementazione è più complessa dell’analisi teorica. In quanto, escluso l’algoritmo che permette all’utente di scegliere il riferimento, gli altri sono estremamente correlati e potrebbero essere trattati quasi come un unico algoritmo. 49 La struttura che li organizza è ancora quella descritta in questo capitolo ma, alcuni oggetti incapsulano più di un algoritmo al loro interno. 4.2.1 Scelta del riferimento Il problema, che deve essere risolto da questo algoritmo, riguarda la possibilità, da parte dell’utente, di scegliere il colore più conveniente al fine di riconoscere il riferimento fisso sulla sua fronte. La motion capture intrusiva ci permette di rendere relativamente semplice la risoluzione di questo problema. Prima di tutto dobbiamo scegliere un colore, da applicare alla fronte dell’utente, che sia particolare, cioè non troppo comune. Infatti esso deve risaltare nell’immagine catturata dalla camera nella quale, presumibilmente, verranno visualizzati oltre al volto dell’utente anche lo sfondo del luogo dove si trova e i capi di abbigliamento che coprono le sue spalle. Per esempio si può scegliere un rosso acceso oppure un verde forte. Tuttavia il sistema dovrebbe essere in grado di intercettare in modo corretto più colori per risultare abbastanza versatile. Questa caratteristica è molto importante in quanto ci potremmo ritrovare in situazioni strane, dove si cerca di intercettare un rosso in una stanza con la tappezzeria rossa, quindi risulterebbe positivo, poter cambiare il colore del riferimento per esempio in un verde ed evitare i problemi di intercettazione. Questo accade perché non siamo in grado di prevedere dove il sistema verrà utilizzato e quindi non possiamo imporre dei determinati colori allo sfondo e ai vestiti dell’utente. A questo punto, affrontando l’analisi dell’algoritmo in maniera superficiale e senza introdurre tutte le problematiche che entrano in gioco realmente, potremmo definire con facilità le operazioni che esso deve compiere. La procedura non dovrebbe fare altro che, elencare i vari colori che appaiono in un’immagine catturata presa come modello. Da questa lista l’utente dovrebbe selezionare il colore che più si avvicina al colore del riferimento e l’algoritmo dovrebbe concludere informando il Supervisore del colore scelto. Purtroppo l’algoritmo, per ottenere un risultato valido e utilizzabile nelle elaborazioni successive, non può essere così semplice. Ci sono fattori che ci costringono a produrre una procedura più complessa di quanto possa sembrare. L’unico punto in comune tra la soluzione appena descritta e l’algoritmo vero e proprio e il passo iniziale nel quale viene preso come modello un’immagine catturata. Quest’ultima può essere vista come la prima immagine, del mondo reale, catturata dal sistema, essa è il modello su cui si baseranno tutte le immagini catturate successivamente. Infatti solo piccoli spostamenti del volto dell’utente faranno la differenza tra questa e tutte le altre immagini. 50 Per cominciare, un fattore che preclude completamente la possibilità di costituire un algoritmo semplice come è stato descritto è la definizione delle immagini che vengono elaborate dal sistema. Nel nostro caso utilizziamo immagini a 24 bit che ammettono più di sedici milioni di colori. Risulta ovvio che, sarebbe impossibile per l’utente poter eseguire una scelta sensata in una lista così grande. Inoltre, esiste un problema ancora più subdolo che è quasi paragonabile ad un errore. Se catturiamo due immagini, anche vicine temporalmente, dello stesso oggetto, non otterremo mai gli stessi colori dei pixel che lo definiscono. Questo è causato dall’illuminazione che può variare, dall’angolazione differente dell’oggetto nelle due immagini rispetto alla sorgente di luce, da imprecisioni dovute alla periferica di cattura ed errori nell’ottica di quest’ultima. Quindi due immagini che l’occhio umano vede come pressoché identiche possono avere differenze notevoli a livello di colore dei singoli pixel, basta una leggera sfumatura diversa per identificare nell’elaborazione colori completamente differenti. Può anche capitare che nell’immagine in alcune zone molto chiare o molto scure vengano catturati colori che non hanno senso. L’algoritmo deve accorgersi di queste incongruenze e cercare di correggerle oppure informarne l’utente. Tenendo conto di questi fattori, che tentano di destabilizzare il nostro algoritmo, quest’ultimo deve proporre delle contromisure valide per ottenere il risultato che ci prefiggiamo. Prima di tutto la procedura non analizza più i colori singolarmente ma attua una sorta di filtro, introducendo un concetto nuovo quale la sfumatura. Quest’ultima è una sorta di insieme di colori che hanno una caratteristica in comune, sono cioè molto simili nella loro gradazione, per esempio è una sfumatura il rosso scuro o il verde molto chiaro. Questo permette di aggirare il problema della lista dei colori molto grande, infatti limitando il numero delle sfumature che il sistema riconosce possiamo presentare all’utente un numero limitato di opzioni nel quale può scegliere e rendere così più semplice il suo compito. Per la definizione di un gruppo di colori in una sfumatura ci soccorre la discussione che abbiamo intrapreso in un capitolo precedente riguardo ai colori e come essi sono interpretati dall’elaboratore. Grazie alla scissione di un dato colore nei tre colori primari rosso,verde e blu, siamo in grado, per esempio, di definire un rosso come un colore dove la componente rossa è molto elevata mentre sia la componente verde che quella blu sono contenute. Quindi possiamo definire una sfumatura, quell’insieme di colori che si discostano poco nel rapporto tra le tre componenti. Per riprendere l’esempio di poco fa, una sfumatura di rosso acceso conterrà tutti i colori che hanno una componente di rosso prossima a 255 (ricordiamo che ogni componente in una 51 struttura RGB è definita da un byte che permette un range da 0 a 255), mentre avranno un numero prossimo allo 0 nelle altre due componenti. Per questo motivo due colori caratterizzati rispettivamente da 250,10,10 e 252,8,8 faranno parte entrambi della stessa sfumatura in quanto possono essere entrambi definiti dei rossi accesi anche se il secondo è leggermente più brillante del primo. Per risolvere l’incongruenza prodotta dal secondo fattore viene introdotta un’ulteriore idea che ci può aiutare. Ogni sfumatura porta con sé l’informazione del luogo nel quale è collocata nell’immagine. Per fare questo si introduce un sistema di riferimento solidale all’immagine, che reca l’origine nell’angolo in basso a sinistra della figura con gli assi diretti parallelamente ai bordi della stessa, l’unità di misura è il pixel quindi le ascisse hanno un range che va da 0 al numero di colonne dell’immagine, mentre le ordinate vanno da 0 al numero di righe. Con questo metodo possiamo indicare e tenere traccia di tutti i punti che costituiscono l’immagine stessa. Grazie a questo sistema di riferimento possiamo indicare in che area è localizzata una certa sfumatura. Per esempio se il nostro riferimento è di un rosso acceso, tra le molte sfumature che il sistema può riconoscere ce ne sarà una che indica il rosso acceso e l’area nella quale risiede combacerà sull’immagine nel punto esatto dove il riferimento si trova. Questo è stato fatto per evitare che l’utente scelga sfumature che non sono corrette e che non intercettano il riferimento in maniera corretta. Può capitare infatti, che più di una sfumatura possa essere ricondotta al colore del riferimento. Questo avviene perché il sistema non definisce una sola sfumatura per il colore ma ne indica alcune. Per esempio il sistema riconosce varie sfumature di rosso dal più brillante al più scuro e cupo al fine di permettere al sistema di funzionare sia con una luce perfetta sia con una carenza nell’illuminazione dell’utente. Quindi non si deve fare altro che, scegliere la sfumatura che ricalca più fedelmente il contorno del riferimento in quanto le altre potrebbero essere guastate da colori che rientrano nella sfumatura ma che non hanno niente a che fare con il riferimento. Si attua in pratica una specie di filtro che permette al sistema di individuare e scartare gli errori nella colorazione dei pixel nell’immagine catturata. Ora, grazie a questi concetti e alle idee analizzate fino a questo punto possiamo definire i passi che l’algoritmo deve compiere per arrivare al suo completamento. Per prima cosa si deve scorrere l’array che contiene l’informazione sul colore dei pixel scegliendo la sfumatura adatta per ognuno. Tutte le volte che viene trovata una sfumatura nuova, che il sistema può intercettare, si indica la zona che localizza la sfumatura come l’area che incorpora la posizione di quel pixel. Se vengono, successivamente trovati altri pixel che appartengono a 52 quella sfumatura, l’area viene modificata per contenere anche quel punto. Tutte le sfumature che vengono intercettate sono poste in una lista che praticamente indica una palette filtrata dei colori che compongono l’immagine. Ovviamente la maggioranza dei colori che costituiscono l’immagine deve essere scartata in quanto indicano zone della stessa di poco interesse, i colori non bene identificabili come i grigi o colori molto scuri vengono inseriti in una sfumatura che essenzialmente fa da cestino dei rifiuti. La nostra attenzione è focalizzata sulle sfumature che il nostro riferimento può adottare, quindi sfumature di rosso, verde o blu. A questo punto la procedura deve visualizzare questa lista di sfumature al fine di far scegliere all’utente il colore migliore. Per aiutare quest’ultimo nella sua scelta il sistema deve indicare l’area di competenza sull’immagine catturata di ogni sfumatura, si sceglierà quella che possiede un’area che combacia meglio con i contorni del riferimento. L’algoritmo termina come nella sua versione più semplice, informando il Supervisore della scelta attuata dall’utente. 4.2.2 Cattura del movimento Prima di analizzare come questa operazione possa essere attuata dall’algoritmo contenuto a questo punto della catena di acquisizione ed elaborazione dati, dobbiamo discutere un problema che a prima vista può sembrare estraneo al nostro scopo ma che invece riveste una fondamentale importanza. Dobbiamo descrivere le caratteristiche fisiche che deve possedere il riferimento. Questa discussione è strettamente correlata alla capacità della camera di riprodurre fedelmente i colori del mondo reale che si appresta a catturare. A seconda di un riferimento lucido od opaco oppure con una superficie piana o curvilinea il sistema si comporterà in maniera differente. Questa caratteristica è dovuta al modo di riflettere il colore se il riferimento viene colpito dalla luce da diverse angolazioni. Se, per esempio, utilizziamo un riferimento con una superficie piana c’è il rischio che, in alcune angolazioni, la luce si rifletta come su uno specchio, inibendo la capacità della camera di catturare il colore. Questo effetto può sembrare strano, ma non lo è, se si pensa che abbiamo a che fare con camere che non sono molto potenti e che possono essere ingannate da simili eventi. Mentre una superficie curvilinea è sempre in grado di avere almeno una zona che colpisce la luce nella stessa angolatura e permette al riferimento di indicare lo stesso colore anche se in maniera parziale sulla superficie. Anche una superficie opaca può portare a problemi, infatti abbiamo bisogno di rendere il più brillante possibile il riferimento, per sfruttare le situazioni di poca luce al meglio. Dunque un 53 riferimento opaco potrebbe essere difficile da intercettare quando ci troviamo ad operare con un’illuminazione carente. Potrebbe sembrare una soluzione valida adottare un riferimento dotato di luce propria come, per esempio, un led colorato. Tuttavia c’è il pericolo, non del tutto remoto, che la camera venga, per così dire, abbagliata dalla luce e indicare nell’immagine una zona bianca invece che colorata. Per i nostri scopi quindi, il riferimento dovrebbe avere una superficie lucida per riflettere il più possibile il colore e non avere una superficie piana ma curvata per evitare la riflessione totale del colore. La natura del riferimento, unita ai fattori destabilizzanti introdotti nella sezione precedente rendono questo algoritmo estremamente complesso anche se, la sua funzione di tenere traccia degli spostamenti del volto dell’utente è un’operazione abbastanza semplice. Questo è dovuto al fatto che, l’algoritmo utilizza, per intercettare il riferimento, la sfumatura definita grazie alla procedure del passo precedente e non ha la possibilità di scegliere una sfumatura differente per ogni immagine utilizzando sempre la migliore per una data angolazione della luce. In tal modo si possono catturare immagini dove la sfumatura scelta non ricalca fedelmente i contorni del riferimento ma ne intercetta solo alcuni, cambiando forma ad ogni immagine. In più entrano in gioco anche gli errori nell’ottica della camera che potrebbero inserire nell’immagine colori attinenti alla sfumatura cercata ma in zone che non hanno niente a che vedere con il riferimento. Questa possibilità produce delle zone nel quale è contenuta la sfumatura troppo grandi che non permettono di indicare con una certa precisione il riferimento. A questo punto, dopo aver delineato tutti i problemi che l’algoritmo deve cercare di evitare o almeno ridimensionare, possiamo analizzare la procedura attuata per raggiungere il nostro scopo. È logico pensare che, per avere la sensazione di movimento dobbiamo catturare un’immagine dietro l’altra del volto dell’utente. L’algoritmo deve andare ad intercettare su ogni figura la zona dove risiede la sfumatura scelta precedentemente che indica il riferimento. Tuttavia non è possibile ottenere un risultato apprezzabile elaborando direttamente questa informazione ma siamo costretti a utilizzare una specie di filtro per ottenere una soluzione accettabile. Questo viene realizzato attendendo la cattura di un certo numero di immagini che vengono elaborate per presentare una soluzione. Precisamente sono le zone dove risiede la sfumatura ad essere analizzate, scartando le zone che sembrano troppo grandi per contenere il riferimento e mediando le altre per ottenere una zona che si avvicina il più possibile ai 54 contorni del riferimento. Scartare le zone più grandi serve per ridurre al minimo gli errori descritti in precedenza sulle ottiche in quanto vengono scartate zone che comprendono gli errori nei pixel e che non sono attinenti al riferimento. Mediare significa evitare i problemi riguardo al riferimento stesso dove ci possono essere delle riflessioni che modificano troppo velocemente la zona della sfumatura che sì, indica il riferimento, ma ne indica solo alcuni contorni per volta. In pratica si devono mediare tanti piccoli rettangoli che indicano il punto del riferimento ma che variano troppo la loro forma da una cattura all’altra, producendo in questo caso un rettangolo che dovrebbe definire in maniera abbastanza corretta i contorni del riferimento. Questo metodo porta ovviamente dei ritardi nell’indicazione della posizione del riferimento, tuttavia è essenziale per ottenere un risultato accettabile per le elaborazioni future. Agire sulla velocità della cattura delle immagini può essere una valida alternativa per ottenere delle indicazioni che si avvicinano all’elaborazione real time ed indicare più prontamente la posizione del riferimento. Anche diminuire il numero di campioni può essere un fattore per avvicinarsi all’elaborazione in tempo reale tuttavia ridurlo di molto inficia l’utilità del filtro. Quest’ultimo metodo è utile se possiamo utilizzare il sistema con un’illuminazione ottima che rende possibile definire zone precise sull’immagine della sfumatura del riferimento. Se possiamo usufruire di questo vantaggio non siamo costretti a mediare o scartare molte immagini e otteniamo un’elaborazione molto veloce e precisa. Un ultima funzione delegata dal sistema all’algoritmo impone che quest’ultimo produca una sorta di controllo sui dati che elabora. Se la zona che descrive la sfumatura diventa troppo piccola nella sequenza di immagini catturate e contiene pochi pixel che hanno un colore corretto, l’algoritmo deve informare il Supervisore di questo fatto. Questa operazione ha l’utilità di avvertire l’utente che forse la sfumatura che ha scelto non è la più corretta ed è conveniente ripetere la valutazione sul colore del riferimento. Infine, l’algoritmo deve proporre delle soluzioni che permettano un’elaborazione più efficiente dei dati a disposizione, questo può essere fatto sfruttando la natura del riferimento. Ciò significa che il volto dell’utente non subisce spostamenti repentini e di grande ampiezza, cosicché è possibile ricercare la zona della sfumatura, non su tutta l’immagine, ma su una zona definibile grazie alla posizione del riferimento nell’immagine precedente. Questo permette di diminuire in maniera significativa il lavoro del processore che può dedicare il suo tempo a operazioni più importanti. Tuttavia questo implica che l’algoritmo possa perdere l’aggancio col riferimento, se ciò avviene non resta che cercare di nuovo la sfumatura su tutta l’immagine. 55 Il Supervisore deve venir informato anche se l’algoritmo perde l’aggancio con il riferimento e non riesce più ad individuarlo nell’immagine. Questo può essere causato da uno spostamento eccezionalmente grande dell’utente davanti alla camera che perde dal suo campo visivo l’immagine del riferimento. 4.2.3 Analisi del campo visivo È importante far notare che d’ora in poi si presume di lavorare con dati che non sono più affetti da errori o almeno gli algoritmi vengono informati se il sistema non riesce a controllarli in maniera corretta. Tra i due algoritmi presentati nelle sezioni precedenti e quelli che verranno analizzati in questa e nella successiva si intravede una netta divisione sulla loro natura. Mentre le procedure che sono state descritte avevano a che fare con il mondo reale e dovevano essere in grado di elaborare immagini che da questo mondo provenivano, gli algoritmi che ci apprestiamo a osservare lavorano su dei dati che sono, per così dire, un modello matematico del mondo reale, ci troviamo ad un livello di astrazione superiore. Questa caratteristica permette una più facile analisi delle procedure qui di seguito presentate in quanto i dati che devono elaborare non sono più complessi come i dati che erano tenuti ad elaborare gli algoritmi precedenti. Guardando il cammino che dobbiamo ancora percorrere ed appoggiandoci a quanto detto nel paragrafo precedente dove si analizzava l’architettura del sistema, ci troviamo di fronte all’algoritmo che permette al sistema di definire il campo visivo che l’utente utilizza per spostare lo sguardo sul terminale video del Personal Computer. Il compito assegnato a questa procedura è quello di cablare il sistema ed ottenere dei dati sufficienti per l’ultimo algoritmo che permette di indicare in che posizione l’utente ha rivolto il viso. Per ottenere questo risultato la procedura deve assicurarsi una specie di zona, sempre indicata tramite il sistema di riferimento fisso sull’immagine, nel quale il riferimento ha libertà di movimento ed ogni posizione occupata all’interno di quest’area è relazionabile ad una posizione fissa del volto dell’utente. Come già descritto nel paragrafo precedente se, per esempio, l’utente rivolge il viso verso il centro dello schermo, il riferimento si deve trovare al centro della zona ottenuta con questo algoritmo. È a questo punto che entra in gioco l’analisi che è stata fatta nel capitolo nel quale si discutevano i vari problemi legati all’ausilio e particolarmente, sul problema di tenere fortemente in considerazione le capacità residue dell’utente. Infatti, mentre il lavoro richiesto all’utente dai precedenti algoritmi è praticamente nullo, è l’assistente a fare il grosso del lavoro mentre l’utente non deve fare altro che indossare il riferimento e posizionarsi davanti 56 allo schermo del Personal Computer. In questo caso è l’utilizzatore che riveste un ruolo importante ed è per questa ragione che all’algoritmo viene chiesta una caratteristica notevole che sta nel rendere le richieste all’utente semplici e non gravose. Questo, come è già stato analizzato nel capitolo riguardante il contesto, è dovuto al fatto che l’operazione di cablaggio, per la natura dell’ausilio, deve essere riproposta ad ogni sessione di lavoro e quindi l’utente deve ripetere ciò che l’algoritmo gli indica ogni volta che vuole utilizzare l’ausilio. L’algoritmo ottiene il suo obiettivo richiedendo all’utente di fissare per alcuni istanti delle zone ben definite dello schermo. Grazie alla posizione intercettata, dalla procedura descritta precedentemente del riferimento, questo algoritmo permette di delineare un’area che contiene al suo interno tutte queste zone. In pratica l’algoritmo chiede all’utente di rivolgere il viso sui contorni dello schermo e in particolare negli angoli di quest’ultimo. Tutte le volte che l’utente ha il desiderio di cablare il sistema per poterlo utilizzare richiede all’ausilio le funzionalità attribuite a questo algoritmo. Quest’ultimo inizia avvertendo l’utente che la fase di cablaggio è in corso dopodiché presenta un pallino colorato che l’utente deve fissare con tutto il viso. Ogni volta che l’algoritmo è sicuro di aver intercettato ed immagazzinato la posizione della sfumatura grazie al sistema di riferimento solidale con l’immagine, il pallino colorato cambia posizione sullo schermo e percorre con questa tecnica i punti che si ritengono più importanti per definire l’area dell’immagine che definisce i contorni del video. Una funzionalità che è estremamente importante è garantire l’informazione sulla stabilità della cattura del riferimento da parte del sistema. Per ottenere questo l’algoritmo deve sempre informare l’utente dell’aggancio sul riferimento, in quanto se si perde la traccia del riferimento la procedura di cablaggio è incapace di portare a termine il suo compito. È importante far notare, infine, che l’algoritmo analizzato nella sezione precedente continua a lavorare anche quando la procedura qui descritta è in corso. Ciò significa che le informazioni dell’algoritmo che permette l’intercettazione del riferimento vengono utilizzate per gli scopi di cablaggio quindi se l’algoritmo che intercetta la sfumatura del riferimento avvisa il sistema che l’aggancio è scarso oppure è stato perso, la procedura di cablaggio deve informare l’utente di questo fatto. Mentre il sistema veniva creato si è incappati in una caratteristica che a prima vista non era stata analizzata ma che permette all’algoritmo di essere più versatile e rendere il lavoro dell’utente estremamente semplice. Invece di obbligare l’utente a seguire il pallino per ottenere il cablaggio possiamo ottenere lo stesso risultato permettendo all’utilizzatore di fissare liberamente gli angoli del video. Questo ovviamente deve essere fatto da un utente che 57 ha già in mente i punti da fissare, se il sistema viene usato per la prima volta è consigliabile eseguire il cablaggio seguito passo a passo dal sistema grazie al pallino colorato da fissare. Se l’utente è esperto e sa già come il sistema funziona può utilizzare questa scorciatoia. Avvisando il sistema che ha intenzione di cablarlo e poi definendo l’area che delimita il campo d’azione del riferimento in modo manuale fissando i quattro angoli del video e controllando l’effettivo ridimensionamento dell’area direttamente sull’immagine e sul sistema di riferimento ad essa relativo. In questo caso si parla di cablaggio manuale mentre la procedura di cablaggio gestita dal sistema grazie al pallino colorato prende il nome di cablaggio automatico. 4.2.4 Definizione del punto fissato A questo punto, nella catena di acquisizione ed elaborazione dei dati, siamo arrivati nel luogo più alto e quindi più astratto dell’intero sistema. Questo significa che l’algoritmo presentato in questa sezione si trova ad elaborare dati che rappresentano un modello matematico e geometrico della realtà ma che con essa non hanno più niente a che vedere. Si presume che a questo punto i dati che ci troviamo ad elaborare siano privi di errori o almeno subiscano in modo lieve i danni di quest’ultimi. Il fine ultimo di questa procedura è avvertire il Supervisore circa la zona dello schermo sul quale l’utente ha rivolto il viso. Per fare questo utilizza i dati che i precedenti algoritmi hanno prodotto. Grazie all’area che definisce il campo visivo e all’uso dell’algoritmo che intercetta il riferimento questa procedura ricava il punto fissato. Tutte le volte che la procedura che individua il riferimento fisso sul volto dell’utente produce dei risultati stabili nei quali si sono filtrati gli errori, quindi è in grado di definire una zona ben precisa nel sistema di riferimento solidale con l’immagine, entra in gioco questo algoritmo. Le operazioni svolte non sono estremamente complesse, il suo compito sta nel prendere questa precisa zona e correlarla con il campo visivo ottenuto dalla procedura precedente. L’unica trappola sta nella specularità delle immagini catturate, ciò significa che la camera cattura un’immagine che è paragonabile alla figura riflessa da uno specchio. Quindi l’algoritmo deve compensare questo evento e produrre dei dati che abbiano senso. Per esempio, se l’utente fissa il punto in alto a destra dello schermo, la procedura si ritroverà ad elaborare una zona della sfumatura scelta che, nel sistema di riferimento solidale all’immagine, si troverà nelle vicinanze dell’angolo in alto a sinistra della zona che definisce il campo visivo. Quindi non deve fare altro che invertire il riferimento nella zona del campo visivo per ottenere il punto corretto fissato. Fatta questa operazione l’algoritmo conclude 58 avvisando il Supervisore della zona in cui l’utente ha rivolto il viso, sarà quest’ultimo ad avvisare l’applicazione che in quell’istante è attiva. Prima di concludere questa sezione e con essa il capitolo dobbiamo analizzare una caratteristica del sistema che non è ancora stata discussa. Per simulare il comportamento di un mouse dobbiamo incorporare nel sistema un metodo per riprodurre il clic, questo per permettere all’utente di attuare una sorta di selezione degli oggetti che fissa sullo schermo. I metodi per attuare questa operazione sono strettamente legati alla posizione delle camere e al loro numero. Principalmente due sono le possibilità, attuare un clic basandosi sul tempo in cui l’utente rimane a fissare un certo oggetto, oppure utilizzare la palpebra abbassata come indicatore del clic. Il primo metodo è attuabile se possiamo utilizzare una sola camera, in quanto la scelta di posizionarla al di sopra dello schermo non permette di analizzare in maniera corretta l’occhio. Questo perché, come abbiamo spiegato nel capitolo che analizzava i problemi, se l’utente fissa un punto in basso dello schermo l’angolatura non permette alla camera di vedere in maniera corretta l’occhio in quanto quest’ultimo è coperto dal sopracilio. Tuttavia se c’e la possibilità di utilizzare due camere la seconda può essere posta sotto lo schermo ed in questa posizione siamo in grado di avere sempre l’aggancio sulla zona dell’occhio, potendo in questo modo attuare il secondo metodo per identificare i clic. Il sistema è stato studiato per attuare entrambe le soluzioni tuttavia, a causa del collo di bottiglia identificabile sul Bus dati, la maggior parte dei Personal Computer tuttora sul mercato non permettono di utilizzare in concorrenza più di una camera. Per questo motivo il sistema, per ora, simula il clic valutando il tempo che l’utente passa a fissare un certo punto sullo schermo. Questa operazione viene svolta dall’algoritmo analizzato in questa sezione. Esso tiene traccia degli spostamenti del riferimento e se essi si concentrano in un punto per un determinato valore del tempo la procedura informa il Supervisore che c’è stato un clic. Sarà quest’ultimo ad informare l’applicazione attiva di questo evento. 59 60 5 Implementazione Potrà sembrare strano e, sicuramente il lettore si sarà chiesto per quale motivo un documento che dovrebbe studiare un'applicazione informatica non abbia ancora analizzato una sola riga di codice. Questa domanda, seppur legittima, trova risposta nella volontà di mettere il lettore nella posizione più comoda e proficua per comprendere appieno il contenuto di questo capitolo. Il lavoro fin qui svolto ha un enorme significato, in quanto ha permesso di comprendere in maniera completa l’analisi teorica degli algoritmi che costituiscono il sistema. I precedenti capitoli hanno risposto alle domande sul perché è nata la necessità di tali procedure e hanno fornito risposte adeguate a come gli algoritmi devono provvedere ai compiti riservati a loro dal sistema. Sono gli algoritmi la risorsa più importante contenuta nell’ausilio, è da ricercare in essi la scintilla di ingegno che permette al sistema di portare a termine il suo compito. La struttura che si trova in secondo piano e che ha il compito di incastonare le pietre preziose costituite dalle procedure, assume contorni indefiniti negli scopi che si sono prefissi, per non appesantire in maniera sproporzionata la discussione. Tuttavia la sua ombra è sempre presente nello studio che si intraprenderà sull’implementazione dei vari algoritmi. Ignorare l’implementazione della struttura nel contesto in cui ci si ritrova costa un caro prezzo in 61 quanto questo risultato è stato raggiunto con dei grandi sforzi paragonabili a quelli compiuti per ottenere gli algoritmi. Quindi è con immensa pena che nel presente capitolo non viene affrontata l’analisi dettagliata sulla gestione del sistema, mentre tutta l’attenzione si focalizza su come le procedure sono state implementate. A questo punto, prima di analizzare in maniera dettagliata l’implementazione degli algoritmi, è importante riprendere un concetto che era stato solo introdotto nel capitolo precedente. In quel contesto si era detto che, per semplificare l’analisi, la discussione sugli algoritmi seguiva un filo logico che legava quest’ultimi come in una catena. I risultati ottenuti da una procedura erano utilizzati come base di partenza per l’algoritmo che stava a valle. Tuttavia, in realtà, nell’implementazione ciò avviene solo per il primo algoritmo, che permette di scegliere la sfumatura del riferimento. Le restanti procedure sono talmente correlate tra di loro che potrebbero essere analizzate insieme. Infatti quest’ultime sono implementate negli stessi oggetti e rappresentano una sorta di algoritmo monolitico che è l’unione omogenea delle tre procedure divise logicamente nel capitolo precedente. Quindi sono due le strade che si possono percorrere per analizzare l’implementazione. Si può enfatizzare la logica che regola i rapporti tra gli algoritmi oppure privilegiare l’implementazione stessa. Nel primo caso l’analisi risulta semplice e lineare anche se si è obbligati a descrivere separatamente il sorgente analizzando solo alcune parti di esso per ogni algoritmo. Scegliendo la seconda via è possibile analizzare totalmente le procedure contenute negli oggetti sminuendo il discorso logico fin qui fatto. La scelta non può che ricadere sul primo metodo, infatti la logica che regola gli algoritmi è un punto di forza dell’intera trattazione e non può essere abbandonata proprio a questo punto. Resta ancora da affrontare una tematica che abbraccia nel complesso la trattazione di questo capitolo. Di solito si è tentati di utilizzare tutte le potenzialità che ci vengono offerte sia dal Sistema Operativo che dal linguaggio di programmazione. Tuttavia agire in questo modo non sempre porta a risultati soddisfacenti. Non significa che se il linguaggio di programmazione permette di utilizzare costrutti molto compatti ma estremamente complessi per scrivere il sorgente essi debbano per forza essere utilizzati. Se lo stesso risultato può essere raggiunto scrivendo un sorgente più semplice non c’è motivo nell’utilizzare il costrutto più complesso. Per questa ragione molte potenzialità offerte dal C++ non sono state utilizzate. Per esempio la caratteristica dell’ereditarietà, i template di funzione e classe e il sovraccarico degli operatori non compaiono nel sorgente. Questo non è indice di incompetenza oppure di inesperienza da parte di chi scrive il sorgente, ma rispecchia l’obbligo di rendere quest’ultimo scorrevole e semplice da comprendere. 62 Nulla ora vieta la possibilità di analizzare efficacemente come gli algoritmi sono stati implementati. Il capitolo avrà una struttura simile al precedente nel quale ogni paragrafo studierà un algoritmo, conservando l’ordine imposto dalla logica di elaborazione dati descritta in precedenza. 5.1 Scelta del riferimento Come analizzato dettagliatamente nel capitolo precedente questo algoritmo deve indicare al sistema quale colore è stato scelto dall’utente per intercettare il riferimento. Per ottenere questa informazione la procedura si divide in quattro passaggi intermedi. Per prima cosa il sistema definisce i parametri essenziali dell’oggetto, che ha il compito di contenere le procedure che implementano l’algoritmo, successivamente si compone la tavolozza di sfumature intercettabili nell’immagine. Come terzo passo viene visualizzata sullo schermo questa palette per informare l’utente dei colori disponibili. Infine si permette all’utente di scegliere il colore e viene informato il sistema della scelta. Prima di analizzare i quattro passi è importante descrivere una caratteristica alquanto particolare insita nel codice scritto. Si potranno notare in esso dei nomi di variabili piuttosto insolite, che potrebbero suonare strane. Questo è stato fatto per rendere il sorgente più semplice da leggere. Per esempio, i compiti del Supervisore, che è stato descritto ampliamente nel paragrafo sull’architettura, sono incapsulati in una classe definita grazie al C++ chiamata a 63 sua volta Supervisore. L’istanza di tale classe è un oggetto che prende il nome di Angelo. È stato scelto questo nome per ricordare le caratteristiche del Supervisore, in quanto esso presiede a tutte le operazioni eseguibili del sistema. Altri strani nomi verranno analizzati quando ci imbatteremo in essi nel corso della discussione. 5.1.1 Impostazione Come si è visto, l’obbligo di focalizzare la discussione solamente sull’implementazione degli algoritmi, porta a dover analizzare punti nel codice che non sono ordinati temporalmente. Ciò significa che il sistema, o meglio la struttura che incapsula gli algoritmi, ha già svolto del lavoro ed è in grado di fornire alla procedura il materiale adatto per la sua elaborazione. Per esempio, in questo caso, il sistema ha già ottenuto l’aggancio alla camera per la cattura delle immagini grazie alla libreria Video For Windows e all’oggetto incaricato di questa mansione. Inoltre la prima immagine, utilizzata come modello, è stata catturata e il sistema la offre, grazie alla libreria Victor, alla procedura che la elaborerà. Prima di passare all’elaborazione vera e propria il Supervisore informa gli oggetti che incorporano le funzionalità dell’algoritmo che presto verrà richiesto il loro apporto e per questo motivo chiede a quest’ultimi di definire i parametri e le strutture di elaborazione dati che l’algoritmo utilizza per i suoi scopi. L’istanza della classe Palette chiamata Tavolozza si serve di due liste doppiamente concatenate per contenere l’elaborazione della procedura. 64 La prima contiene l’insieme delle sfumature intercettabili sull’immagine catturata. Ogni elemento è costituito dalla struttura seguente, LC significa Lista Complessa: typedef struct CELLA_COLORE_LC { COLORE Colore; SCOSTAMENTO Scostamento; UINT TipoColore; RECT Zona; int Bersagli; int Posizione; struct CELLA_COLORE_LC *Precedente,*Successivo; }; Sono indicate molte informazioni, ci sono i contorni dove è localizzata la sfumatura nel sistema di riferimento relativo all’immagine (Zona), il numero di pixel dell’immagine che fanno parte della sfumatura (Bersagli) e i parametri che identificano il tipo di sfumatura (Colore, TipoColore e Scostamento). Per le sfumature non intercettabili che includono tutti i colori che appartengono all’immagine ma che non hanno valore per la nostra elaborazione viene utilizzato il primo elemento della lista che rappresenta una sorta di cestino dei rifiuti. La seconda è utilizzata come merce di scambio tra i vari oggetti che concorrono alla riuscita dell’algoritmo e contiene solo i tipi di sfumature intercettati, con i valori dei tre colori primari del colore identificativo della sfumatura, LS indica Lista Semplice. typedef struct CELLA_COLORE_LS { BYTE Blu,Verde,Rosso; UINT TipoColore; struct CELLA_COLORE_LS *Precedente,*Successivo; }; Una volta definite queste strutture dati e generato i vari puntatori che permettono di utilizzarle l’oggetto Tavolozza è pronto a partire con l’elaborazione vera e propria dell’algoritmo. Esiste anche la possibilità di eliminare le liste se per caso sono già state richieste le funzionalità dell’algoritmo e si decide di riproporlo con un’altra immagine presa come modello. 5.1.2 Analisi immagine È in questa sezione che viene analizzata la parte più importante della procedura per la determinazione del colore del riferimento. L’oggetto Tavolozza ha a sua disposizione il puntatore alla variabile Victor che contiene l’immagine catturata. Non deve fare altro che scorrere il vettore dove sono contenuti i colori primari di ogni pixel, si ricorda che vengono elaborate immagini a 24 bit che non utilizzano una palette ma conservano direttamente il colore di ogni pixel. Grazie al ciclo: 65 i = j = 0; X = Y = 0; while (i < ((int) Immagine->bmh->biSizeImage)) { Colore.Blu = Immagine->ibuff[i]; Colore.Verde = Immagine->ibuff[i+1]; Colore.Rosso = Immagine->ibuff[i+2]; this->In(Colore); i = i + 3; j++; X = j % 320; Y = j / 320; } si può scorrere tutto il vettore. Ad ogni passaggio viene conservato il colore di un pixel nella struttura Colore, non altro che l’unione di tre byte. Dopodiché si lancia la procedura che elabora le caratteristiche del colore, del quale identifica la sfumatura di appartenenza e la inserisce nella lista se non è stata già incontrata. Le variabili i e j servono per scorrere il vettore, mentre le variabili X e Y identificano il pixel nel sistema di riferimento solidale con l’immagine e permettono di definire i contorni della sfumatura, si utilizza il valore 320 perché le immagini elaborate sono 480X320. Passando ora ad analizzare la procedura In() notiamo che essa rappresenta il cuore di tutta l’implementazione dell’algoritmo: void Palette::In(COLORE Colore) { // Creo la Cella Colore Lista Complessa TempLC = new(CELLA_COLORE_LC); TempLC->Colore.Blu = Colore.Blu; TempLC->Colore.Verde = Colore.Verde; TempLC->Colore.Rosso = Colore.Rosso; TempLC->Scostamento.BV = Colore.Blu - Colore.Verde; TempLC->Scostamento.VR = Colore.Verde - Colore.Rosso; TempLC->Scostamento.RB = Colore.Rosso - Colore.Blu; TempLC->TipoColore = TipoColore(TempLC); TempLC->Posizione = Elementi; if (!Trovato(TempLC)) { // Inserisco la Cella in coda alla Lista Complessa TempLC->Precedente = CodaLC; TempLC->Successivo = NULL; CodaLC->Successivo = TempLC; CodaLC = TempLC; // Creo la Cella Colore Lista Semplice TempLS = new(CELLA_COLORE_LS); TempLS->Blu = Colore.Blu; TempLS->Verde = Colore.Verde; TempLS->Rosso = Colore.Rosso; TempLS->TipoColore = TempLC->TipoColore; // Inserisco la Cella in coda alla Lista Semplice TempLS->Precedente = CodaLS; TempLS->Successivo = NULL; CodaLS->Successivo = TempLS; CodaLS = TempLS; 66 Elementi += 1; } else delete TempLC; } La procedura genera le due liste che contengono le informazioni sulle sfumature intercettabili nell’immagine. Per produrre questo risultato riserva, nella memoria di massa, spazio per un elemento della Lista Complessa dove verranno indicate tutte le qualità del colore passato come parametro. Grazie alle procedure TipoColore() e Trovato() analizza rispettivamente in che sfumatura risiede il colore e se è già stata incontrata questa sfumatura. Se è stata intercettata una nuova sfumatura la procedura la inserisce in coda alla Lista Complessa e genera un nuovo elemento della Lista Semplice (LS) inserendolo in coda a quest’ultima. Mentre se abbiamo a che fare con un colore che appartiene ad una sfumatura già intercettata, lo scartiamo e liberiamo, grazie alla delete, lo spazio in memoria riservato all’elemento della Lista Complessa. La procedura chiamata TipoColore() permette di determinare a che sfumatura appartiene il colore del pixel. Grazie alla scomposizione nei tre colori primari si è in grado di definire qualsiasi sfumatura ma la procedura identifica solo poche sfumature focalizzate sui colori che hanno un nesso col colore del riferimento. Si hanno sei sfumature differenti di rosso, verde e blu che vanno dal più brillante al più scuro. Il sistema è in grado di intercettare anche alcune sfumature di colori composti come il giallo, l’azzurrino e il violetto, nonché diverse sfumature di grigio. La procedura Trovato() merita una discussione più dettagliata: bool Palette::Trovato(CELLA_COLORE_LC *Cella) { IndiceLC = TestaLC; while(IndiceLC != NULL) { if(IndiceLC->TipoColore == Cella->TipoColore) { if(X < IndiceLC->Zona.left) IndiceLC->Zona.left = X; if(Y > IndiceLC->Zona.top) IndiceLC->Zona.top = Y; if(X > IndiceLC->Zona.right) IndiceLC->Zona.right = X; if(Y < IndiceLC->Zona.bottom) IndiceLC->Zona.bottom = Y; IndiceLC->Bersagli++; return true; } IndiceLC = IndiceLC->Successivo; } Cella->Zona.left = Cella->Zona.right = X; Cella->Zona.top = Cella->Zona.bottom = Y; Cella->Bersagli = 1; 67 return false; } Il compito svolto da questa funzione è lievemente più complesso di quello svolto dalle precedenti. Prima di tutto la procedura ricerca nella Lista Complessa un elemento che possieda lo stesso TipoColore dell’elemento passato come parametro. Se questa ricerca va a buon fine significa che la sfumatura era già stata precedentemente individuata dall’algoritmo. A questo punto non resta che modificare i contorni della zona che indica la posizione della sfumatura nel sistema di riferimento solidale con l’immagine e incrementare il contatore del numero di pixel appartenenti alla sfumatura. Se si arriva alla fine della Lista Complessa senza aver trovato la sfumatura significa che il sistema ha intercettato una nuova sfumatura e l’elemento deve essere incluso nella lista. Vengono definiti i contorni della sfumatura come le coordinate del pixel e il conto dei pixel relativi viene posto a 1. Quindi si avvisa la procedura chiamante In() che l’elemento deve essere inserito in coda alla Lista Complessa. 5.1.3 Visualizzazione Ora la parte di elaborazione può ritenersi conclusa. Non resta che visualizzare sul terminale video i risultati e aspettare la scelta dell’utente. È a questo punto che entra in gioco la seconda lista denominata Semplice. Quest’ultima viene prelevata dall’Angelo e indirizzata alla window procedure della finestra preposta alla visualizzazione dei risultati di questo algoritmo. Una dopo l’altra le varie sfumature intercettate nell’immagine, usata come modello, vengono indicate sullo schermo sotto forma di piccoli rettangoli colorati posti in fila. Dopodiché la procedura si pone in attesa della scelta dell’utente. 5.1.4 Scelta Quando l’utente si posiziona sul piccolo rettangolo che contraddistingue la sfumatura e preme il pulsante sinistro del mouse indica alla procedura che è stata selezionata una sfumatura. Questo evento genera una serie di operazioni che avvengono in cascata. La window procedure che gestisce la finestra dove vengono visualizzate le sfumature avvisa l’Angelo della scelta. Quest’ultimo compie la sua mansione di supervisore chiedendo le coordinate della zona nel quale risiede la sfumatura direttamente all’oggetto Tavolozza istanza di Palette, grazie alla procedura: RECT* Palette::GetZonaColore(UINT TipoColore) { IndiceLC = TestaLC; while(IndiceLC != NULL) { if(IndiceLC->TipoColore == TipoColore) return &IndiceLC->Zona; 68 IndiceLC = IndiceLC->Successivo; } return NULL; } Quindi invia un messaggio alla window procedure relativa alla finestra dove viene visualizzata l’immagine. Questo messaggio porta con sé i contorni della zona della sfumatura che vengono sovrapposti all’immagine stessa. Se la sfumatura scelta è adatta i suoi contorni dovrebbero intercettare il riferimento. È importante analizzare perché l’algoritmo si comporti in questo modo quando potrebbe concludere indicando solamente all’Angelo la sfumatura scelta. Ci troviamo di fronte questo comportamento per implementare ciò che è stato detto nel capitolo precedente. Si richiedeva alla procedura di aiutare l’utente nella scelta del colore del riferimento, non solo per facilitare il compito dell’utilizzatore ma far si che non venisse scelta una sfumatura che poteva condurre all’errore. Grazie a questo accorgimento l’algoritmo è in grado di identificare la zona dell’immagine dove risiede la sfumatura. Questo permette all’utente di scegliere quella che più si adatta ai contorni del riferimento inquadrato nell’immagine, evitando le sfumature simili ma che introducono gli errori elencati nel capitolo precedente. In pratica l’utente può scegliere la sfumatura che meglio si adatta ai contorni del riferimento, provando a selezionare le varie sfumature presentate grazie ai piccoli rettangoli colorati. 5.2 Cattura del movimento Si apre con questo paragrafo la discussione dell’algoritmo monolitico descritto in precedenza. Quest’ultimo è costituito dalle tre procedure logiche analizzate nel capitolo precedente che permettono la cattura del movimento, la definizione del campo visivo e l’indicazione dello sguardo. La prima, discussa in questo contesto, è la più importante in quanto costituisce la base sulla quale le altre due verranno costituite. Tuttavia essendo estremamente correlata, in quanto parte dell’algoritmo monolitico, con le altre, alcune sue parti non potranno essere analizzate in questo paragrafo ma si dovrà attendere il momento propizio per farlo. Solo alla conclusione del capitolo si avrà sotto controllo il disegno generale del sistema. Prima di poter analizzare a fondo questa procedura è essenziale introdurre un’ulteriore caratteristica che contraddistingue gli algoritmi. Un aspetto importante che è stato tralasciato nella discussione fin qui svolta è il tempo che impiega la procedura per elaborare i dati iniziali al fine di ottenere dei risultati. Per esempio, l’algoritmo che è stato appena discusso impiegava un breve intervallo di tempo per portare a termine la sua elaborazione. Tuttavia non sempre si ha a che fare con procedure di questo tipo. L’algoritmo che viene presentato in 69 questo paragrafo è di natura completamente differente. Non è stato pensato per offrire un risultato singolo alla fine dell’elaborazione ma, il suo compito è fornire al sistema informazioni continue sul mondo reale che lo circonda. Quindi non è possibile a priori stabilire quanto tempo occorra alla sua elaborazione. Questo porta ad una diversa struttura in grado di sorreggerlo. La funzione principale affidata al Supervisore è la cattura e l’elaborazione in tempo utile di tutti i messaggi che arrivano all’applicazione sia dal Sistema Operativo sia dall’utente stesso. Per esempio, la scelta di una voce dal menu oppure il ridimensionamento della finestra che include l’applicazione sono operazioni che non devono essere rallentate oppure posticipate ma, hanno estrema urgenza di essere eseguite. Quindi, mentre il Supervisore poteva permettersi di visionare direttamente gli algoritmi che promettevano tempi di elaborazione estremamente compatti non può assolutamente addossarsi l’onere di controllare un tale algoritmo, dove l’elaborazione si protrae per un tempo indeterminato. Per questo motivo si utilizzano le potenzialità offerte dalla programmazione concorrente. Si lascia libero il Supervisore di rispondere non appena arriva una richiesta, mentre il lavoro attuato dall’algoritmo viene visionato da un thread che utilizza l’elaboratore in modo concorrente al sistema principale. In questo modo al Supervisore restano poche e veloci operazioni da compiere in quanto deve solo attivare, terminare il thread ed utilizzare le informazioni che regolarmente quest’ultimo gli fornisce. Per analizzare in maniera adeguata questa procedura, quindi, si devono abbandonare le vecchie convinzioni sugli algoritmi che avevano un inizio ed una fine ma pensare che le operazioni fatte rappresentano un continuo. Entra in gioco ora l’analisi svolta nel capitolo precedente sulle caratteristiche che doveva possedere la procedura. Si era detto che non era possibile indicare ad ogni cattura la posizione del riferimento, in quanto esistevano errori che potevano vanificare l’elaborazione. Era quindi necessaria una specie di filtro che scartasse i dati incoerenti e producesse informazioni utili al sistema, prive o almeno con un’attenuazione sugli errori. Questo viene implementato introducendo il concetto di sequenza e sfruttando la natura del thread. Ogni cattura ed elaborazione di un’immagine è inserita in una sequenza, che rappresenta le operazioni svolte dalla procedura ogni volta che il thread ottiene dal Sistema Operativo l’accesso alla CPU. Tuttavia in questa fase l’algoritmo non informa il sistema sulla posizione del riferimento ma si limita a conservare le informazioni relative ai contorni dello stesso senza produrre nessun risultato. L’informazione, che rappresenta lo scopo della procedura, viene costituita quando si ottengono un certo numero di catture dunque quando si sono effettuati svariate sequenze. A 70 questo punto l’algoritmo attua il filtro e scarta i dati che ritiene inconsistenti mediando gli altri per ottenere un risultato apprezzabile. 5.2.1 Definizione parametri funzionali Sebbene la procedura qui descritta sia completamente differente da un punto di vista concettuale rispetto a quella descritta in precedenza, entrambe utilizzano una prima fase dove è possibile indicare ai vari oggetti che incapsulano l’algoritmo i parametri di funzionamento utili all’elaborazione che si apprestano a compiere. È in questa fase che si istruisce l’algoritmo su quale sfumatura deve essere intercettata, utilizzando i risultati ottenuti dalla procedura precedente. Per far questo si utilizza una struttura atta a contenere tutte queste informazioni: typedef struct LIMITI_COLORE { bool bool UINT RECT RECT RECT RECT RECT RECT short ColoreAttivo; ColoreTrovato; TipoColore; Zona; Dominio; Focus; Lento; FocusEye; ZonaEye; RossoMin,RossoMax,VerdeMin,VerdeMax,BluMin,BluMax; }; Per esempio, nel campo TipoColore viene inserita l’informazione ottenuta grazie alla scelta dell’utente, nel campo Zona risiede l’indicazione dei contorni della sfumatura e nei campi RossoMin, RossoMax ecc… ci sono i limiti espressi nei colori primari che identificano la sfumatura. Gli altri campi sono utilizzati per l’elaborazione e verranno introdotti e analizzati successivamente. Questa fase si conclude con la definizione dei parametri che permettono al thread un corretto funzionamento. Per ottenere questo risultato si utilizza una zona di memoria condivisa dove il thread è in grado di reperire le informazioni che il sistema gli comunica. In questo caso si utilizza una struttura composta in questo modo: typedef struct { UINT HWND Supervisore* Intercetta* Decisore* imgdes* imgdes* RETTANGOLI* UINT Tipo; hwnd; Angelo; Mago; Oz; Immagine; ImmagineEye; Rettangoli; Stato; 71 bool } PARAMS, *PPARAMS; Ucciso; I due campi più importanti sono Ucciso, nel quale il Supervisore comunica al thread che è conclusa la sua elaborazione e Angelo, che indica al thread la possibilità di comunicare con il Supervisore. È importante notare come all’oggetto thread si offra la possibilità di utilizzare direttamente gli altri oggetti (Mago e Oz) aggirando l’obbligo di comunicare solamente con l’Angelo. Questo è da attribuirsi all’analisi fatta nei capitoli precedenti in cui era emerso che il sistema, in alcuni casi, prediligeva la velocità di elaborazione ad una struttura rigida e strettamente regolamentata. 5.2.2 Elaborazione svolta in una sequenza Prima di poter analizzare in maniera capillare il codice che permette l’implementazione dell’algoritmo è importante discutere la relazione che lega il thread al sistema. Entra in gioco a questo punto il discorso fatto all’inizio del capitolo. Il thread fa parte dell’implementazione globale dell’algoritmo monolitico e per questa ragione non deve essere solamente ricondotto all’implementazione dell’algoritmo logico descritto in questo paragrafo. Questo significa che tutte e tre le procedure logiche che costituiscono l’algoritmo globale utilizzano il thread per i loro scopi. Infatti questa caratteristica è visibile nella struttura stessa del codice sorgente che implementa il thread. Sia la natura unitaria sia l’elemento logico sono evidenti nella sua struttura. Il thread è composto da un grande ciclo che rispecchia la natura dell’algoritmo monolitico. Mentre l’impronta logica è data da cicli minori che permettono al thread di svolgere svariati compiti utili agli algoritmi. La natura monolitica è esaltata anche dal fatto che il Supervisore attiva il thread all’inizio di questa procedura e lo disattiva solo quando l’utente decide di fermare l’algoritmo logico che identifica lo sguardo. Questo significa che i tre algoritmi logici descritti in questo e nei prossimi due paragrafi sono visti dal Supervisore come un’unica entità. Il compito dei cicli minori è implementare una specie di clock per le funzioni che descrivono i tre algoritmi. Il thread dà il ritmo e permette di creare la sensazione che gli algoritmi elaborino in modo continuo le immagini del mondo reale. È il cuore pulsante ed il motore per la vita delle procedure. Ogni ciclo minore si compone di quattro passaggi fondamentali. Per prima cosa interroga gli oggetti che incapsulano le funzioni che caratterizzano l’algoritmo, dopodiché analizza i risultati e avvisa il sistema se ci sono dei cambiamenti o errori. Prima di rilasciare la CPU, visualizza i dati ottenuti nella finestra dell’applicazione. Il ciclo minore che permette l’elaborazione discussa in questo paragrafo è il seguente: 72 while ((!pparams->Ucciso) && (pparams->Stato == STATO_ZERO)) { TemporaneoCattura = pparams->Mago->GetPosizioneI(); if(StatoCattura != TemporaneoCattura) { StatoCattura = TemporaneoCattura; pparams->Angelo->Messaggio(AN_ASK_STATO_CATTURA_MUTATO, pparams->Tipo,StatoCattura); } pparams->Rettangoli = pparams->Mago->GetRettangoli(); InvalidateRect(pparams->hwnd,NULL,false); Sleep(SLEEP_THREAD_FRONTALE); } Il primo passo, fatto dal ciclo minore, è attivare le funzione che permette l’elaborazione relativa all’algoritmo descritto in questo paragrafo. Dopodiché il thread controlla se l’algoritmo non presenta errori come, per esempio, la perdita dell’aggancio sulla sfumatura del riferimento. Infine, prima di “addormentarsi” e rilasciare la CPU avverte l’applicazione che dei nuovi dati sono pronti per essere visualizzati. Ad ogni battito del ciclo minore il thread richiama la procedura: UINT Intercetta::GetPosizioneI() { // Carica una nuova immagine Camera->GetFoto(); Camera->GetFile(); loadbmp(NomeFile,&Immagine); if(Colore.ColoreAttivo) { 73 // Setto la zona per accogliere le hit Colore.Zona.left = 320; Colore.Zona.right = 0; Colore.Zona.bottom = 240; Colore.Zona.top = 0; Hit[Indice].Bersagli = 0; // Setto il Dominio e il Focus per questa chiamata if (Colore.ColoreTrovato) Colore.ColoreTrovato = TrovaColoreFocus(); else Colore.ColoreTrovato = TrovaColoreDominio(); // Metto la zona trovata nel vettore delle catture CopyRect(&Hit[Indice].Zona,&Colore.Zona); // Dopo NUMERO_CATTURE dò dei risultati più stabili if(Indice >= (NUMERO_AQUISIZIONI - 1)) return SetLento(); else Indice++; } return StatoCattura; } Il compito riservato a questa funzione è quello di richiedere direttamente all’oggetto che si occupa della camera di catturare una nuova immagine per l’elaborazione, anche in questo caso gli oggetti comunicano direttamente fra di loro senza passare dall’Angelo, questo per ottenere un’elaborazione estremamente più veloce. Quando si attiva il thread per la prima volta e si inizia l’elaborazione di questa procedura grazie al ciclo minore, la funzione GetPosizioneI() non lavora ancora a pieno regime. Infatti per operare al massimo delle sue possibilità ha bisogno di dati che solo il prossimo algoritmo può fornirgli. Questo implica che per ottenere una spiegazione esauriente si deve attendere il paragrafo successivo. Nel contesto in cui ci si ritrova è sufficiente indicare che il compito affidato all’algoritmo viene comunque portato a termine. Grazie al campo della struttura che identifica la sfumatura, l’algoritmo tiene traccia della posizione del riferimento. Il campo Zona indica i contorni della sfumatura in ogni immagine catturata. Per ottenere questo risultato non si deve fare altro che ricercare nel vettore che identifica i pixel i colori che rientrano nei parametri che delineano la sfumatura. 5.2.3 Filtro L’implementazione svolta dell’algoritmo permette, a questo punto, di attuare in maniera abbastanza semplice l’operazione di filtro sui dati ottenuti dall’elaborazione. Ogni volta che la funzione GetPosizioneI() conclude la sua sequenza di operazioni e quindi intercetta i contorni della sfumatura, quest’ultimi vengono salvati in un vettore nel quale ogni elemento è una struttura del tipo: typedef struct HIT { 74 int RECT RECT Bersagli; Zona; ZonaEye; }; che conservano sia la zona indicata nel sistema di riferimento solidale con l’immagine sia il numero di pixel che appartengono alla sfumatura. Quando sono avvenute tante sequenze quanti sono gli elementi del vettore si attiva la funzione che permette all’algoritmo di produrre dei dati più stabili da poter offrire al sistema per le elaborazioni successive. Avviene quello che è stato chiamato filtro: UINT Intercetta::SetLento() { short i,Miss,Less; int Lunghezza,Larghezza; short Cont = 0; RECT Sum; Miss = Less = 0; Indice = 0; Sum.left = Sum.right = Sum.top = Sum.bottom = 0; Larghezza = Colore.Lento.right - Colore.Lento.left; Lunghezza = Colore.Lento.top - Colore.Lento.bottom; for(i=0; i<NUMERO_AQUISIZIONI ; i++) { if(((Hit[i].Zona.right - Hit[i].Zona.left) <= (Larghezza + 10)) && ((Hit[i].Zona.top - Hit[i].Zona.bottom) <= (Lunghezza + 10)) && (Hit[i].Zona.left < Hit[i].Zona.right)) { Sum.left = Sum.left + Hit[i].Zona.left; Sum.right = Sum.right + Hit[i].Zona.right; Sum.bottom = Sum.bottom + Hit[i].Zona.bottom; Sum.top = Sum.top + Hit[i].Zona.top; Cont++; } if((Hit[i].Bersagli > 0) && (Hit[i].Bersagli < MASSIMO_LESS)) Less++; if(Hit[i].Bersagli == 0) Miss++; } if(Cont > 0) { Colore.Lento.left = (Sum.left / Cont); Colore.Lento.right = (Sum.right / Cont); Colore.Lento.bottom = (Sum.bottom / Cont); Colore.Lento.top = (Sum.top / Cont); CopyRect(&Rettangoli.Lento,&Colore.Lento); } if(Miss == NUMERO_AQUISIZIONI) StatoCattura = STATO_CATTURA_ROSSO; if(((Miss > 2) && (Miss < NUMERO_AQUISIZIONI)) || (Less > 5)) StatoCattura = STATO_CATTURA_GIALLO; if((Miss <= 2) && (Less < 5)) StatoCattura = STATO_CATTURA_VERDE; return StatoCattura; } 75 Il campo che contiene l’informazione filtrata è Lento. Per ottenere questo risultato si elabora il vettore nel quale risiedono i contorni del riferimento acquisiti nelle varie sequenze proposte dal ciclo minore. L’analisi compiuta nel capitolo precedente riguardo al filtro in questa funzione trova la sua corretta implementazione. Prima di tutto si scartano i dati che evidenziano dei contorni che si discostano troppo dalle dimensioni normali del riferimento. Questo potrebbe essere dovuto al fatto che l’algoritmo ha intercettato un pixel che appartiene alla sfumatura ma che è il risultato di un errore sulle ottiche della camera e che non ha niente a che vedere con il riferimento. I dati che passano questo controllo vengono mediati per ottenere un’indicazione più stabile sulla posizione del riferimento. Questo serve per compensare gli effetti che può dare la luce riflessa sul riferimento. In quanto, se si utilizza come base una superficie curvilinea per il colore la luce può cambiare repentinamente il colore e ingannare l’algoritmo. Tuttavia i piccoli rettangoli definiti da Zona sono tutti molto vicini fra loro sull’immagine e la loro media permette di intercettare in modo corretto il riferimento. Un’altro incarico importante implementato da questa funzione è quello di dare una sorta di controllo sulla cattura ed elaborazione dell’immagine. Se il numero di pixel attinenti alla sfumatura diminuisce in maniera notevole l’algoritmo deve informare il sistema che non è più in grado di fornire dati corretti. Quest’ultimo indica all’utente che probabilmente la sfumatura scelta non è valida e si dovrebbero ripetere le operazioni offerte dalla procedura descritta nel paragrafo precedente. In conclusione il sistema è ora in grado di avere sempre sotto controllo la posizione del riferimento nel sistema di riferimento solidale con l’immagine. Ovviamente l’elaborazione per ricavare dati attendibili non può essere in tempo reale, tuttavia il ritardo che apporta l’algoritmo è accettabile e verrà ampliante discusso nel capitolo successivo. 5.3 Definizione campo visivo Questa procedura è sicuramente quella più dinamica tra le tre che costituiscono l’algoritmo monolitico. Il suo compito è di ottenere la dimensione del campo visivo dell’utente. Questa zona fissa nel sistema di riferimento solidale con l’immagine indica l’insieme di coordinate dove il riferimento ha libertà di movimento. In pratica si indicano le posizioni del volto che hanno un’attinenza con i punti del terminale video. Se il riferimento esce dai contorni definiti dal campo visivo significa che l’utente non ha il viso rivolto verso lo schermo. La procedura deve chiedere all’utente di fissare i lati dello schermo e definire in questo modo, appoggiandosi all’algoritmo descritto nel paragrafo precedente, il campo visivo. 76 Per ottenere questo risultato l’algoritmo utilizza l’elaborazione di un’ulteriore thread che lavorando in simbiosi con il precedente ricava i risultati voluti. La comunicazione tra i due thread è demandata all’Angelo. L’algoritmo si evolve per stati, ogni stato rappresenta una posizione sullo schermo che l’utente deve fissare con il viso. Per rendere la fase di cablaggio il più veloce e semplice possibile per l’utente, si sono ridotti al minimo i punti da fissare. Quest’ultimi sono disposti sui lati dell’applicazione e sono rappresentati da un pallino colorato, quando la finestra dell’applicazione è distesa su tutto lo schermo i punti combaciano con i lati del terminale video e questo permette di ricavare il campo visivo. In ogni stato l’algoritmo deve essere in grado di catturare un certo numero di immagini al fine di ottenere un’informazione adeguata e filtrata dagli errori che si sono già discussi. Il thread introdotto in questo paragrafo ha la funzione di scandire i vari stati e con essi le posizioni che l’utente deve fissare. Quando il thread principale ha ottenuto un risultato accettabile riguardo ad un punto fissato avvisa il Supervisore che è pronto a passare ad un altro stato. Quest’ultimo fa in modo che il thread che scandisce gli stati passi al successivo e l’elaborazione si ripete. Un altro motivo per cui si deve attendere la cattura di più immagini per ogni stato è il periodo di assestamento che il viso dell’utente ha nello spostarsi da un punto dello schermo all’altro. Questo comporta che alcune immagini debbano essere scartate in quanto indicano zone intermedie tra i due punti che identificano stati differenti. L’analisi dettagliata non avviene per oggetti, ma si studierà l’intero procedimento che definisce un singolo stato. Si inizierà con l’analizzare il thread che scandisce gli stati, le richieste che questo fa all’Angelo, l’elaborazione fatta dal thread principale ed infine i messaggi che permettono di passare allo stato successivo. Ovviamente l’elaborazione completa dell’algoritmo è la somma dell’elaborazione avvenuta nei vari stati. 5.3.1 Introduzione di uno stato L’algoritmo prende vita nella procedura che implementa il thread che scandisce i vari stati. Quest’ultimo non ha altro compito che avvertire il sistema e gli oggetti che prendono parte all’elaborazione dello stato in cui ci si ritrova: void ThreadPosiziona(PVOID pvoid) { UINT TemporaneoStato; PPARAMS_POSIZIONA pparams = (PPARAMS_POSIZIONA) pvoid; Sleep(1000); while(!pparams->Ucciso) { 77 InvalidateRect(pparams->hwnd,NULL,true); if(pparams->Stato <= STATO_NOVE) { TemporaneoStato = pparams->Stato; pparams->Angelo->HandShake(AN_ASK_INIZIO_STATO, AN_THREAD_POSIZIONA, pparams->Stato); while((!pparams->Ucciso) && (pparams->Stato == TemporaneoStato)) Sleep(200); } else { Sleep(2000); pparams->Angelo->HandShake(AN_ASK_FINE_SETTAGGIO, AN_THREAD_POSIZIONA,0); pparams->Ucciso = true; } } pparams->Stato = STATO_ZERO; _endthread(); } Finché l’Angelo non lo avverte che lo stato si è concluso il thread, dopo aver inviato un messaggio all’Angelo, si pone in attesa. La sottofinestra correlata col thread utilizza l’informazione sullo stato raggiunto per indicare, tramite un pallino colorato, la posizione sullo schermo su cui l’utente deve rivolgere il viso. La comunicazione tra l’Angelo e il thread avviene grazie ad una struttura non dissimile da quella usata dal thread principale, nel quale il parametro più importante è il campo Ucciso che permette al Supervisore di terminare il thread e con esso questo algoritmo. 78 5.3.2 HandShake Il messaggio che il thread invia all’Angelo fa partire l’elaborazione vera e propria che contraddistingue uno stato. Quest’ultimo viene elaborato dal Supervisore grazie al codice: case AN_ASK_INIZIO_STATO: SendMessage(Finestre>GetFinestreSecondarie(FINESTRA_IMMAGINE_FRONTALE), WM_STATO_MUTATO,0, (long) Parametro); break; Il quale trasferisce al thread principale l’informazione sullo stato raggiunto. In questo modo si rispettano in maniera completa le regole che sono state definite nei capitoli precedenti. È il supervisore a gestire i rapporti fra gli oggetti. In quanto in questo caso non è importante la velocità di esecuzione e allo stesso tempo l’elaborazione svolta dall’Angelo non compromette il suo compito principale nel rispondere ai messaggi che provengono dall’esterno. 5.3.3 Elaborazione del singolo stato Torna qui di attualità l’analisi svolta nel paragrafo precedente sui cicli ed i cicli minori contenuti nel codice sorgente del thread principale. Per asservire i compiti specifici di questa procedura si utilizza un altro ciclo minore che imprime all’algoritmo la sensazione di continuità e scandisce il ritmo delle catture delle immagini che vengono utilizzate per ricavare l’informazione voluta. if((pparams->Stato >= STATO_UNO) && (pparams->Stato <= STATO_NOVE)) { pparams->Mago->UnLock(pparams->Stato); TemporaneoStato = pparams->Stato; StatoProgresso = STATO_PROGRESSO_INIZIO; // Ciclo Minore while ((!pparams->Ucciso) && (pparams->Stato == TemporaneoStato)) { TemporaneoCattura = pparams->Mago->GetPosizioneI(); if(StatoCattura != TemporaneoCattura) { StatoCattura = TemporaneoCattura; pparams->Angelo>Messaggio(AN_ASK_STATO_CATTURA_MUTATO, pparams->Tipo,StatoCattura); } TemporaneoStatoProgresso = pparams->Mago->GetProgresso(); if(StatoProgresso != TemporaneoStatoProgresso) { StatoProgresso = TemporaneoStatoProgresso; if(StatoProgresso == STATO_PROGRESSO_FINE) { pparams->Angelo->HandShake(AN_ASK_FINE_STATO, AN_THREAD_INTERCETTA, pparams->Stato); pparams->Mago->Lock(); StatoProgresso = STATO_PROGRESSO_INIZIO; 79 } pparams->Angelo->Messaggio(AN_ASK_STATO_PROGRESSO_MUTATO, pparams->Tipo,StatoProgresso); } Sleep(SLEEP_THREAD_FRONTALE); } } Anche in questo caso si possono trovare le fasi che contraddistinguono ogni ciclo minore. Per prima cosa vengono interrogati gli oggetti che permettono la cattura e l’elaborazioni delle immagini. Dopodiché si passa a controllare se ci sono errori oppure se lo stato è arrivato alla sua conclusione, infine, prima di rilasciare la CPU, si avverte il Supervisore se è necessario procedere all’elaborazione dello stadio successivo o si è giunti alla conclusione della procedura. La fase che permette l’interrogazione degli oggetti che contengono il sorgente atto all’elaborazione svolta da questo algoritmo potrebbe risultare, a prima vista, molto simile a quella discussa nel paragrafo precedente. Tuttavia in essa si nasconde una rilevante differenza. Per comprendere appieno l’elaborazione svolta in questo contesto, si deve introdurre il campo Dominio, contenuto nella struttura che identifica la sfumatura, che non è stato ancora trattato. Nel Dominio risiede il rettangolo che descrive il campo visivo, nel sistema di riferimento solidale con l’immagine. Una proprietà estremamente importante che accompagna il Dominio è la possibilità di modificarlo solo quando il sistema permette questa operazione, per il resto il Dominio rimane fisso ed è impossibile, per le procedure, ridimensionarlo. Nel ciclo minore preso qui in esame si può notare come, prima di iniziare la cattura e l’elaborazione delle immagini, si dà la possibilità di modificare i contorni del Dominio grazie alla funzione UnLock(). A questo punto la funzione GetPosizioneI() non si limita all’elaborazione discussa nel paragrafo precedente ma modifica anche il Dominio e decide quando l’analisi dello stato è conclusa bloccando il ridimensionamento del Dominio grazie alla funzione Lock(). L’analisi dettagliata di tutte le funzioni che permettono le operazioni descritte precedentemente e contenute negli oggetti che fanno da supporto per l’algoritmo non vengono qui descritte. Questo perché la loro discussione potrebbe appesantire in modo estremo il discorso fin qui fatto. Per un’analisi completa del sorgente si rimanda alle appendici. L’unica caratteristica che merita una discussione approfondita e che completa il discorso introdotto nel paragrafo precedente è il procedimento utilizzato da GetPosizioneI() per elaborare le immagini. Si era detto che nell’algoritmo precedente la funzione non lavorava in maniera completa ma solo parzialmente. Ora grazie al Dominio la funzione inizia a operare al 80 massimo regime. È di vitale importanza capire che ogni sequenza, che elabora un’immagine catturata, non è isolata. Viene influenzata dalla precedente e influenza a sua volta la successiva. Per ottimizzare la ricerca della sfumatura non si analizza tutta l’immagine ma solo parte di essa. Grazie al campo Focus si riduce l’elaborazione ad un sottoinsieme di pixel. Nel funzionamento normale, ogni volta che si richiama GetPosizioneI(), si ricerca la sfumatura nella zona delimitata da Focus, tuttavia se, per esempio, l’utente cambia repentinamente la posizione del volto c’è la possibilità di perdere l’aggancio con il riferimento. Se si avvera questa possibilità la funzione non ricerca il riferimento su Focus ma sul Dominio ed in questo modo si è sicuri che, se l’utente rivolge il viso allo schermo, sicuramente il riferimento si troverà nella zona delineata dal Dominio. Quindi se una sequenza perde l’aggancio del riferimento, avvisa la successiva che si deve ricercare il riferimento nel Dominio. Questo comunque ottimizza ancora la ricerca in quanto il Dominio rappresenta solo una parte dell’immagine completa. 5.3.4 Conclusione Quando l’elaborazione di uno stato si conclude il ciclo minore avverte l’Angelo. Quest’ultimo non deve fare altro che informare il thread che scandisce gli stati. Due sono le possibilità che si incontrano a questo punto. La prima indica la conclusione di uno stato intermedio, mentre l’altra indica la conclusione dell’ultimo stato che determina la fine dell’algoritmo preso in esame in questo paragrafo. Se a concludere è uno stato intermedio la procedura inizia un’elaborazione simile a quella descritta, con l’unica differenza che nella finestra di applicazione il pallino colorato ha cambiato posizione. Se, invece, si è concluso l’ultimo stato, l’Angelo termina il thread che scandisce gli stati e riporta il sistema nella configurazione che aveva prima che l’utente attivasse questo algoritmo. L’unica differenza è che a questo punto il Supervisore ha in mano un Dominio valido per le elaborazioni successive. 5.3.5 Cablaggio manuale Per facilitare il più possibile il compito dell’utente il sistema propone anche un metodo alternativo per affrontare la fase di cablaggio. Invece di seguire passo a passo le istruzioni che compaiono nella finestra e seguire il pallino colorato nelle varie posizioni sullo schermo, c’è la possibilità di delineare in maniera manuale il campo visivo. Questo metodo, comunque, è da preferire solamente se l’utente ha già utilizzato il sistema ed è in grado di prevenire le richieste dell’applicazione. 81 Questa soluzione è da ricondurre all’analisi svolta nei capitoli precedenti, in quanto permette di cablare il sistema in maniera più veloce e intuitiva, aiutando l’utente in un compito che deve affrontare obbligatoriamente ogni volta che utilizza l’ausilio. Anche in questo caso c’è un ciclo minore che dà vita alla procedura, nel thread principale: if(pparams->Stato == STATO_MANUALE) { pparams->Mago->UnLock(pparams->Stato); while ((!pparams->Ucciso) && (pparams->Stato == STATO_MANUALE)) { TemporaneoCattura = pparams->Mago->GetPosizioneI(); if(StatoCattura != TemporaneoCattura) { StatoCattura = TemporaneoCattura; pparams->Angelo->Messaggio(AN_ASK_STATO_CATTURA_MUTATO, pparams->Tipo,StatoCattura); } pparams->Rettangoli = pparams->Mago->GetRettangoli(); InvalidateRect(pparams->hwnd,NULL,false); Sleep(SLEEP_THREAD_FRONTALE); } } Come si può notare, prima di iniziare il ciclo minore si dà la possibilità di modificare i contorni del Dominio grazie alla già discussa funzione UnLock(). Dopodiché, anche in questo caso, si evidenziano le quattro fasi che costituiscono il ciclo. Si richiama la funzione che permette la cattura e l’elaborazione dell’immagine, si controlla che i dati ottenuti siano privi d’errore ed infine, prima di rilasciare il controllo della CPU si informa il sistema dei risultati conseguiti. Una volta attivata la procedura manuale grazie al menu, l’utente non deve fare altro che fissare per un breve periodo di tempo i quattro angoli dello schermo e controllare nelle finestre dell’applicazione se effettivamente il rettangolo che delimita il Dominio modifica le sue dimensioni. Per concludere è necessario notare che questa procedura non termina automaticamente come la precedente, quindi deve essere l’utente che ne decreta la conclusione utilizzando i comandi che reperisce nel menu. A questo punto è il Supervisore che impedisce la possibilità di modificare il Dominio con la funzione Lock(). 5.4 Indicazione dello sguardo Questa procedura conclude sia l’analisi degli algoritmi descritti teoricamente nel capitolo precedente sia lo studio dell’algoritmo che è stato chiamato monolitico. Tra quelle presentate fino ad ora risulta essere la più semplice, in quanto si trova ad elaborare dati che rappresentano sostanzialmente un modello matematico e geometrico della realtà che circonda 82 l’applicazione. Il suo compito è di indicare al sistema dove sia rivolto il viso dell’utente. Per ottenere questo risultato relaziona la posizione del riferimento con il campo visivo ottenuto in precedenza. Considerando l’analisi che è stata svolta nel capitolo relativo all’architettura e agli algoritmi è possibile discutere senza esitazioni l’implementazione pratica della procedura. Come le precedenti, anche in questo caso l’algoritmo si basa su un ciclo minore contenuto nel thread principale per elaborare le operazioni che fanno parte di una sequenza. Inoltre è in grado di avvertire il sistema se l’utente fissa un punto sullo schermo per un certo periodo di tempo al fine di simulare il clic del mouse. 5.4.1 Elaborazioni svolte in una sequenza Anche in quest’ultimo caso la sensazione di continuità nell’elaborazione è data da un ciclo minore contenuto nel thread principale: while ((!pparams->Ucciso) && (pparams->Stato == STATO_LAVORO)) { TemporaneoCattura = pparams->Mago->GetPosizioneI(); if(StatoCattura != TemporaneoCattura) { StatoCattura = TemporaneoCattura; 83 pparams->Angelo->Messaggio(AN_ASK_STATO_CATTURA_MUTATO, pparams->Tipo,StatoCattura); } switch (pparams->Oz->GetDecisione()) { case AN_RET_NO_DECISIONE: break; case AN_RET_SI_DECISIONE: pparams->Angelo->Messaggio(AN_ASK_DECISIONE_AVVENUTA); break; case AN_RET_CLICK_TEMPORALE: pparams->Angelo->Messaggio(AN_ASK_CLICK_TEMPORALE_AVVENUTA); break; } Sleep(SLEEP_THREAD_FRONTALE); } L’unica fase del ciclo che si discosta dalle precedenti e merita un’analisi dettagliata è la parte in cui si avvisa l’Angelo se avviene un evento degno di nota. L’elaborazione svolta in GetDecisione() si basa sul campo Lento descritto in precedenza, quindi un risultato accettabile non è pronto ad ogni sequenza ma si deve attendere che la procedura che intercetta il movimento deliberi le informazioni contenute in quel campo. In questo modo solo dopo un certo numero di sequenze viene inviato un messaggio al Supervisore. La procedura GetDecisione(): UINT Decisore::GetDecisione() { Contatore++; if(Contatore >= (NUMERO_AQUISIZIONI - 1)) { Contatore = 0; // Trovo il punto mediano di Lento: X = (float)((Rettangoli->Lento.right + Rettangoli->Lento.left) / 2); Y = (float)((Rettangoli->Lento.top + Rettangoli->Lento.bottom) / 2); // Controllo se il Punto mediano di Lento è nelle "vicinanze" del Dominio: if((X > (Rettangoli->Dominio.left - BORDO_DOMINIO)) && (X < Rettangoli->Dominio.left)) X = (float)Rettangoli->Dominio.left; if((X < (Rettangoli->Dominio.right + BORDO_DOMINIO)) && (X > Rettangoli->Dominio.right)) X = (float)Rettangoli->Dominio.right; if((Y > (Rettangoli->Dominio.bottom - BORDO_DOMINIO)) && (Y < Rettangoli->Dominio.bottom)) Y = (float)Rettangoli->Dominio.bottom; if((Y < (Rettangoli->Dominio.top + BORDO_DOMINIO)) && (Y > Rettangoli->Dominio.top)) Y = (float)Rettangoli->Dominio.top; // Se il Punto mediano di Lento è contenuto nel Dominio: if((X >= Rettangoli->Dominio.left) && (X <= Rettangoli->Dominio.right) && (Y >= Rettangoli->Dominio.bottom) && (Y <= Rettangoli->Dominio.top)) { // Lo setto nel SdR del Dominio: X = X - Rettangoli->Dominio.left; 84 Y = Y - Rettangoli->Dominio.bottom; // Lo traslo sulla Finestra Applicazione e lo metto nell'area publica: Punto.X = (1 - (X / NumeroDiColonne)); Punto.Y = (1 - (Y / NumeroDiLinee)); // Controllo se il punto è attinente al Click: if((X >= Click.Regione.left) && (X <= Click.Regione.right) && (Y >= Click.Regione.bottom) && (Y <= Click.Regione.top)) { Click.Attinenze++; if(Click.Attinenze > NUMERO_AQUISIZIONI_AREA_CLICK) { Click.Attinenze = 0; SetRect(&this->Click.Regione, (int)X-BORDO_CLICK, (int)Y+BORDO_CLICK, (int)X+BORDO_CLICK, (int)Y-BORDO_CLICK); return AN_RET_CLICK_TEMPORALE; } } else { Click.Attinenze = 0; SetRect(&this->Click.Regione, (int)X-BORDO_CLICK, (int)Y+BORDO_CLICK, (int)X+BORDO_CLICK, (int)Y-BORDO_CLICK); } return AN_RET_SI_DECISIONE; } } return AN_RET_NO_DECISIONE; } Questa funzione oltre che a compensare l’effetto specchio delle immagini catturate dalla camera decide se il riferimento è rimasto nello stesso luogo per un certo periodo di tempo. Se questo avviene l’algoritmo avvisa l’Angelo che l’utente molto probabilmente vuole simulare il clic. Si parla di clic temporale in quanto si utilizza il tempo come parametro di decisione per avvisare il sistema del clic. Per ora il sistema utilizza solo questo metodo per simulare il clic ma ci sono svariate possibilità per attuare questa opzione. Se ci fosse la possibilità, in futuro, di connettere più camere si potrebbe simulare il clic semplicemente abbassando la palpebra e incaricare una camera di catturare questo movimento. Per concludere questa sezione è importante notare che le coordinate del punto fissato sono indicate con valori che vanno da 0 a 1. In questo modo, per esempio, l’angolo in basso a destra è indicato dalla coppia 1, 1 e il punto centrale della finestra dell’applicazione viene risolto con 0.5 e 0.5 e così via. Questo metodo permette di separare completamente gli oggetti che descrivono questo algoritmo dagli oggetti che identificano le applicazioni indipendenti, in 85 quanto la procedura non deve sapere la dimensione effettiva dell’applicazione ma rilascia valori assoluti che poi, verranno convertiti in coordinate relative alla finestra. 5.4.2 Messaggi alle applicazioni Anche se questa discussione esula dall’algoritmo vero e proprio qui descritto è importante descrivere i messaggi che le applicazioni indipendenti ricevono. Oltre ai messaggi che invia il Sistema Operativo le window procedure devono elaborare altri messaggi che arrivano direttamente dal sistema. Per prima cosa il sistema invia all’applicazione il messaggio WM_CREATE_PUNTO che permette all’applicazione indipendente di agganciare la zona di memoria dove può reperire con facilità i dati appena elaborati dall’algoritmo, indicanti le coordinate del punto fissato. Con il messaggio WM_NON_VALIDO si avverte l’applicazione indipendente che l’algoritmo ha dei dati nuovi è più recenti sul punto fissato. Infine, per quanto riguarda i messaggi relativi all’elaborazione di questa procedura il Supervisore invia all’applicazione il messaggio WM_CLICK che indica il clic temporale. Per concludere il sistema invia un’ulteriore messaggio che permette all’applicazione indipendente di indicare all’utente se la cattura e l’aggancio sul riferimento sono elaborati correttamente oppure se il sistema non è più in grado di intercettare il riferimento. Questo avviene con l’invio del messaggio WM_STATO_CATTURA_MUTATO. Il Supervisore si limita ad inviare questi messaggi, è compito dell’applicazione indipendente elaborarli in maniera corretta. Il sistema è stato concepito in questo modo per lasciare completa libertà d’azione agli sviluppatori delle applicazioni. Infatti varia da applicazione ad applicazione la reazione richiesta per ogni messaggio. Un’applicazione può persino scartare e non elaborare parte dei messaggi che il sistema gli propone, tutto dipende dalle caratteristiche e dal fine che vuole ottenere l’applicazione indipendente. 86 6 Valutazioni sperimentali L’analisi fin qui svolta non può ritenersi conclusa se non si discutono le prestazioni che il sistema è in grado di fornire. È importante studiare e comprendere se gli algoritmi, descritti nei capitoli precedenti, sono validi e portano a termine gli obiettivi che l’ausilio si proponeva di conseguire. Nell’implementazione delle procedure si è cercata la soluzione migliore ad ogni problema ma, solo l’analisi delle prestazioni può rispondere correttamente ai dubbi sulla validità della soluzione. Il fattore più importante che influenza le prestazioni in modo pressoché completo sfugge purtroppo dal controllo del sistema. Gli algoritmi che compongono la catena di acquisizione ed elaborazione dati entrano in contatto con il mondo reale. Questo implica che le immagini catturate siano solo una rappresentazione della realtà. Questa rappresentazione è afflitta, come è già stato descritto, da numerosi errori che vengono in qualche modo soppressi o attenuati dal lavoro delle procedure. Tuttavia la caratteristica che da sola decide sul funzionamento del sistema è l’illuminazione dell’utente quando si trova davanti al terminale video. Se questa non è sufficiente, gli errori insiti nelle immagini vengono enfatizzati e risulta difficile se non impossibile agli algoritmi compensare per ottenere un risultato utile. L’illuminazione non è un elemento che il sistema possa controllare, quindi, le prestazioni del sistema sono legate 87 direttamente a questo fattore. Se l’illuminazione è ottima anche le prestazioni saranno ottime, mentre se la luce che colpisce l’utente è mediocre, mediocri saranno le prestazioni del sistema. Non è possibile in questo contesto seguire la catena di passi utilizzata per i precedenti due capitoli. Mentre non ha significato analizzare le prestazioni di ogni singolo algoritmo, è di grande utilità focalizzare invece l’attenzione sull’algoritmo monolitico precedentemente introdotto. Quest’ultimo rappresenta l’unità elaborativa centrale di tutto il sistema ed è grazie ad essa che l’ausilio può portare a termine il suo compito. Prima di poter studiare in profondità le prestazioni che questo algoritmo monolitico offre, occorre tuttavia discutere ed analizzare una caratteristica estremamente importante che accompagna il sistema. È rilevante notare che l’elaborazione delle immagini catturate dalla camera non avviene in modo rigido e fissato ma, il sistema permette all’utente di modificare l’elaborazione mentre questa si sta compiendo. I parametri di funzionamento dell’algoritmo monolitico vengono controllati direttamente dall’utente grazie ad una sottofinestra visibile nella finestra dell’applicazione. Agendo su questi controlli, l’utilizzatore è in grado di adattare il sistema alle condizioni ambientali che lo circondano. Quindi è possibile migliorare l’elaborazione anche se l’illuminazione risulta carente. Solo dopo aver analizzato questi controlli, è possibile studiare le prestazioni che il sistema può offrire. Per fare questo si è implementata una coppia di applicazioni indipendenti, il cui unico scopo è testare il sistema. Infatti non esiste altro modo per verificare se l’ausilio raggiunge gli obiettivi che si erano posti all’inizio. 6.1 I controlli La maggior parte dei sistemi, inclusi i sistemi informatici, devono possedere una sorta di controllo per portare a termine i loro compiti. Non è possibile lasciare che l’elaborazione avvenga in modo predefinito, senza modificarsi in base alle condizioni ambientali che circondano il sistema. Solo se il sistema si trova a lavorare in un ambiente isolato non è irragionevole, ma possibile tralasciare la caratteristica di controllo, in quanto non esistono azioni esterne che possano turbare l’attività del sistema. In questo caso, al contrario, l’ausilio è estremamente vincolato all’ambiente che lo circonda ed è impensabile non attuare una specie di controllo. Se per assurdo, il sistema non attuasse un controllo ed elaborasse in maniera fissa i dati che ottiene dall’ambiente circostante, i risultati che si otterrebbero potrebbero essere completamente errati e privi di significato. È per questa ragione che si è data all’utente la possibilità di modificare i parametri che definiscono il comportamento 88 dell’elaborazione dell’immagine. Grazie a semplici ed intuitivi controlli attivabili in una sottofinestra dell’applicazione principale è possibile agire in maniera diretta sui parametri che regolano l’algoritmo monolitico che rappresenta la mente di tutto il sistema. È da considerarsi inoltre l’implementazione come prototipo per l’ausilio che si vuole produrre. Questo comporta che molti controlli non sono automatici in quanto gli algoritmi che lo compongono non sono così complessi da attuare una sorta di retroazione al loro interno. Nel contesto in cui si trova ad operare il fine ultimo è ottenere una soluzione che possa concretizzare gli obiettivi eletti dall’ausilio e non un’ottimizzazione degli stessi. Prima di analizzare i vari controlli singolarmente è utile aggiungere che il sistema è stato testato su un Personal Computer che monta una scheda madre Intel con processore Celeron da 2.0 Ghz. con 128 Mb di Ram e si sono utilizzate due WebCam 5 della Creative per la cattura delle immagini, connesse alle porte USB dell’elaboratore. 6.1.1 Controllo sulla velocità di esecuzione del thread principale È importante aprire una piccola parentesi sul thread e la funzione Sleep() da lui richiamata, prima di passare alla discussione vera e propria del controllo. Come è già stato studiato nel capitolo precedente il thread principale utilizza dei piccoli cicli, detti cicli minori, per rappresentare le varie procedure che costituiscono l’algoritmo monolitico. Ogni ciclo minore ha, per ultima fase, la chiamata alla funzione Sleep(). Questa operazione è fortemente correlata alle operazioni svolte dal Sistema Operativo per assegnare la CPU ai vari processi. Se il ciclo minore non utilizzasse la funzione Sleep() la CPU verrebbe sopraffatta dall’elaborazione del thread. Infatti alla conclusione della sequenza il Sistema Operativo porrebbe nella coda dei processi attivi il thread pronto per una sequenza successiva. Se per caso, ma la probabilità è alta, non ci fossero altri processi in attesa, il thread otterrebbe di nuovo l’utilizzo della CPU. Questo porterebbe alla saturazione completa della CPU per l’elaborazione del thread e si avrebbe anche un rallentamento importante nelle altre funzioni svolte dal Sistema Operativo, come l’apertura di finestre, menu e così via. L’utilizzo della funzione Sleep() evita tutto questo. Il thread, quando conclude il ciclo minore, avverte il Sistema Operativo che è pronto a rilasciare la CPU. Quando il Sistema Operativo vede che il thread ha richiamato la funzione Sleep() non lo mette più nella coda relativa ai processi attivi in attesa della CPU finché non è trascorso il tempo indicato nella Sleep(). Solo quando, un timer impostato sul tempo di attesa avverte il Sistema Operativo della scadenza, il thread viene di nuovo posto nella coda dei processi attivi è può così riottenere la CPU. 89 Il controllo non fa altro che variare l’argomento della Sleep(). Cosicché è possibile, dalla finestra di applicazione, modificare il tempo di esecuzione dell’algoritmo monolitico. La gamma di possibilità va da 1 a 100 millisecondi. Più il tempo di attesa si porta verso il millisecondo più le catture delle immagini e l’elaborazione delle stesse avvengono con un ritmo accelerato. In un Personal Computer potente come quello su cui si è testato il sistema, attese prossime al millisecondo ingannano l’occhio umano che non è più in grado di percepire l’attesa tra un’immagine e l’altra, mentre la CPU non viene utilizzata più del 15%. Tuttavia in elaboratori meno evoluti potrebbe non essere possibile arrivare a certi livelli, ed è per questo motivo che si è mantenuta una gamma abbastanza cospicua per poter agire su questo parametro. Per concludere è importante notare che il tempo di attesa non definisce in modo completo il tempo utilizzato da ogni ciclo minore per l’elaborazione. Infatti è possibile scomporre il tempo utilizzato da un ciclo in tre parti distinte. Per prima cosa c’è il tempo che il ciclo occupa a catturare l’immagine dalla camera, dopodiché c’è l’intervallo di tempo utilizzato per l’elaborazione e solo a questo punto sopraggiunge il tempo di attesa implementato grazie alla Sleep(). Solo gli ultimi due possono essere modificati e sono sotto il controllo del sistema. Il tempo di attesa è direttamente modificabile dall’utente, mentre il tempo di elaborazione può essere migliorato implementando algoritmi più complessi ed ottimizzati. In un elaboratore recente il tempo di elaborazione non supera le frazioni del millisecondo in quanto le operazioni svolte in una sequenza non sono molte. Detto questo si potrebbe pensare che limitando il tempo di attesa al millisecondo si potrebbe arrivare ad elaborare un migliaio di sequenze al secondo. Purtroppo ciò non accade perché si è tralasciato il tempo che il ciclo minore passa per catturare e caricare l’immagine dalla camera. Questo ritardo non dipende dal sistema ma e imputabile alla velocità del Bus che collega la porta USB alla scheda madre ed è maggiore di alcuni ordini di grandezza rispetto agli altri due. Precedentemente si è scritto che se il controllo della velocità di elaborazione del thread viene posto al millisecondo, la CPU non ha un utilizzo maggiore del 15%. Questo perché anche il processore deve attendere che l’immagine venga trasportata dal Bus prima di poterla elaborare. C’è il cosiddetto collo di bottiglia che interessa il trasporto dell’immagine dalla camera al processore. Questo implica che non si possano elaborare più di una decina di immagini al secondo anche con il tempo di attesa che sfiora il millisecondo. 6.1.2 Numero di catture per attuare il filtro Come si è già detto all’inizio di questo capitolo il sistema è fortemente legato all’ambiente che lo circonda. Il controllo descritto in questa sezione non fa altro che assecondare 90 l’illuminazione che colpisce il volto dell’utente. Inoltre non si deve pensare ai controlli come entità separate ma, l’azione su uno di essi deve essere bilanciata con l’utilizzo di altri controlli. Tutti i controlli concorrono al miglioramento delle prestazioni del sistema. Se, per esempio, l’illuminazione è scarsa occorre elaborare più immagini per ottenere un risultato accettabile. Questo comporta che, se la velocità del thread resta invariata, si dovrà attendere un tempo maggiore prima di vedere i risultati dell’elaborazione e quindi il sistema risulterà più lento. Tuttavia se a fronte di un filtro che elabora più immagini si accelera l’elaborazione del thread è possibile ottenere le stesse prestazioni anche se il sistema è obbligato, per via dell’illuminazione, ad elaborare più immagini. Stessa analisi si può effettuare se le immagini catturate presentano un’illuminazione ottima. Se il riferimento viene intercettato dal sistema con facilità non è indicato attuare un filtro con molte immagini. Il controllo in questo caso può essere utilizzato per diminuire le catture utilizzate dal filtro per ottenere un’elaborazione più veloce ma sempre precisa. In questo caso, se il processore è sovraccarico, è possibile diminuire la velocità di elaborazione del thread senza ridurre le prestazioni. Questo controllo utilizzato in simbiosi con il precedente assicura al sistema una reazione in base alle condizioni ambientali presenti intorno all’utente ed al suo viso. Ovviamente nulla è possibile fare se l’illuminazione è estremamente carente. Se il riferimento non è intercettabile il filtro scarterà tutte le immagini e anche se il controllo imporrà al filtro l’elaborazione di 30 immagini, limite superiore del range, non si otterranno risultati utilizzabili. Per concludere è importante descrivere una caratteristica piuttosto singolare del controllo. Il suo range va da 1 a 30 immagini catturate per l’elaborazione del filtro. Se si sceglie solo un’immagine catturata il filtro cessa la sua funzione ed il sistema lavora come se ogni immagine portasse ad un rilevamento. Per utilizzare le variabili descritte nel capitolo precedente si può dire che il Lento si va a sovrapporre al campo Zona. 6.1.3 Clic temporale Per analizzare in modo approfondito questo controllo si deve riprendere la discussione fatta nel capitolo precedente. In quel contesto si era studiata la tecnica utilizzata dal sistema per rilevare, o meglio, simulare il clic. Dopo un certo numero ben definito di rilevamenti, attuati dall’algoritmo monolitico, nello stesso punto del video, si era in grado di avvertire il sistema che era avvenuto un clic temporale. Dall’esperienza maturata con l’implementazione dell’ausilio si è posto a circa due secondi il tempo in cui l’utente deve fissare un punto per ottenere il clic temporale. Si è scelto questo intervallo di tempo per rendere l’utilizzo del sistema, da parte dell’utente, il più semplice e comodo possibile. Infatti un intervallo 91 maggiore potrebbe, alla lunga, affaticare l’utilizzatore, mentre una pausa più ristretta potrebbe ingannare il sistema e rilevare un clic anche se l’utente sta solo ricercando le informazioni sul video e rallenta gli spostamenti del volto. Per queste ragioni entra in gioco il controllo qui descritto. Se vengono modificati, grazie ai controlli precedentemente descritti, la velocità delle catture e il numero di rilevamenti per unità di tempo è molto probabile che il limite dei due secondi per rilevare il clic non sia più rispettato. Se, per esempio, viene aumentata la velocità delle catture anche il numero di rilevamenti per secondo aumenta proporzionatamente. Questo implica che occorreranno meno di due secondi per far indicare dal sistema all’applicazione indipendente l’evento del clic temporale. Ecco che con l’aiuto di questo controllo è possibile ristabilire il margine di tempo voluto, in base alla velocità delle catture e alla velocità di elaborazione del filtro. Questo è fatto aumentando il numero di rilevamenti che devono avere le stesse coordinate nel sistema di riferimento solidale con l’immagine. In questo modo si dilatano i tempi per decidere se si è di fronte ad un clic temporale, riequilibrando nel contempo il sistema sui fatidici due secondi per clic. 6.1.4 Regione relativa al clic Nella sezione precedente non si è tenuto conto del fatto che il filtro, purtroppo, non è una barriera insormontabile per tutti gli errori. Questo causa degli effetti collaterali che possono alterare il funzionamento dell’ausilio. In particolare c’è un effetto che risulta estremamente collegato all’ambiente che circonda il sistema. Più l’illuminazione è carente più si evidenzia questo fenomeno. Nel funzionamento normale dell’ausilio si nota che il punto che indica sullo schermo la zona fissata dall’utente oscilla. L’oscillazione è tanto più marcata tanto più l’illuminazione del viso dell’utente è insufficiente. Se l’illuminazione risulta ottima l’oscillazione è quasi impercettibile. Questo implica che, per definire un clic, non si possa attuare un’elaborazione semplice. Come è già stato discusso nel capitolo precedente si utilizza una zona che racchiude il punto fissato, se il rilevamento fatto dal filtro nella sequenza successiva è contenuto ancora in questa zona è alta la probabilità che l’utente stia effettuando un clic temporale. Se un numero di rilevamenti successivi, pari al valore definito grazie al controllo precedentemente descritto, è incluso in questa zona allora il sistema avverte l’applicazione indipendente che è avvenuto un clic temporale. Il controllo qui descritto permette di modificare le dimensioni della zona che aiuta il sistema nel capire se l’utente sta fissando un punto preciso dello schermo. In questo modo si possono assecondare le oscillazioni dovute agli errori non trattenuti dal filtro. Questo è fatto 92 perché se le oscillazioni sono troppo marcate e la zona ha dimensioni inferiori all’ampiezza dell’oscillazione stessa, non sarà possibile al sistema intercettare i clic temporali. Mentre è utile ridurre le dimensioni, se l’illuminazione è buona, per ottenere una definizione maggiore dei punti fissati nell’applicazione indipendente. Questo porta ad avere un sistema che si avvicina nelle prestazioni ad un mouse collegato al Personal Computer. Il sistema, a questo punto dello sviluppo, riesce a intercettare, in condizione di luce buona, oggetti dalle dimensioni di pochi centimetri (1.5-2.0) come per esempio le icone poste nel DeskTop. 6.1.5 Focus Questo controllo permette al sistema un’elaborazione più veloce. Si è già discusso di come l’algoritmo monolitico utilizzi una tecnica particolare per attuare meno elaborazioni quando si trova davanti una nuova immagine. Invece di cercare su tutta l’immagine la sfumatura da intercettare la si cerca in un sottoinsieme di pixel indicato dal rettangolo Focus, elaborato dalla sequenza precedente. In questo modo si accelera la ricerca e si è relativamente sicuri che la sfumatura si troverà in questo rettangolo se l’utente non ha eseguito movimenti repentini con il volto. Grazie a questo controllo è possibile modificare la dimensione del Focus. Più si restringe quest’area più l’elaborazione è veloce, viceversa se si aumentano le dimensioni, l’algoritmo dovrà processare più pixel e l’elaborazione ne risulterà rallentata. Dall’esperienza fatta è utile sovradimensionare i contorni del Focus nella fase di cablaggio del sistema. In quanto l’algoritmo monolitico non lavora ancora a pieno regime e l’intercettazione del riferimento si basa esclusivamente sul Focus. Questo implica che, se l’utente ha un movimento repentino, c’è la possibilità che il sistema perda l’aggancio. Con un rettangolo del Focus sovradimensionato questa possibilità diviene del tutto remota. Conclusa la fase di cablaggio è possibile ridurre i contorni del Focus, in quanto, ora, l’algoritmo monolitico funziona a pieno regime e se anche l’utente attua un movimento veloce il sistema non perde l’aggancio con il riferimento. Se l’utente continua a rivolgere il viso allo schermo l’algoritmo può ritrovare il riferimento grazie al rettangolo definito dal Dominio che identifica il campo visivo. In un elaboratore potente, questo artifizio può sembrare evitabile, infatti sono relativamente poche le operazioni che vengono eluse. Non c’è grossa differenza tra l’elaborare un’immagine completa o solo parte di essa. Tuttavia il fine del sistema è offrire un’elaborazione ottimizzata e il più possibile veloce, quindi ogni metodo per processare meno operazioni è ben accetto. Inoltre c’è la possibilità che l’ausilio venga eseguito su un Personal 93 Computer meno potente ed in questo caso ogni aiuto possibile può ripercuotersi sulle prestazioni finali. 6.2 Le applicazioni A questo punto l’analisi si può spostare sulla discussione delle due applicazioni implementate per testare le prestazioni del sistema. Entrambe hanno solamente un carattere dimostrativo. Oltre a costituire una base di studio sulle potenzialità del sistema sono state utilizzate da quest’ultimo per capire quali fossero i messaggi più convenienti per una comunicazione adeguata. Grazie alla loro implementazione è stato possibile separare i compiti svolti sui due fronti. Da un lato si è capito cosa dovesse fare il sistema e dall’altra parte si è compreso quali fossero i doveri dell’applicazione indipendente. Grazie al lavoro svolto si è definito l’insieme dei messaggi che sono stati discussi nel capitolo precedente. Come si è avuto modo di vedere le operazioni svolte dal Supervisore sono limitate all’invio dei messaggi e tutta l’elaborazione viene demandata all’applicazione. Questo modello nel concepire la comunicazione fra Supervisore e applicazioni indipendenti è dovuto a numerosi motivi. Prima di tutto, se parte dell’elaborazione fosse sotto il controllo del Supervisore, gli sviluppatori di applicazioni indipendenti dovrebbero conoscere, oltre ai messaggi scambiati, anche le operazioni svolte dal Supervisore sulle loro applicazioni. Questo dissolverebbe una delle regole più importanti imposte dall’architettura del sistema e cioè la netta separazione tra sistema di elaborazione delle immagini e applicazioni indipendenti. Inoltre le ipotetiche operazioni svolte dal Supervisore sulle applicazioni potrebbero essere valide per un certo tipo di applicazioni, mentre potrebbero rendere impossibile l’implementazioni di altre. Infine agendo in questo modo gli sviluppatori hanno la possibilità di implementare codice che può reagire in qualsiasi maniera ai messaggi che il Supervisore gli spedisce. 6.2.1 Applicazione Uno: Addestramento Tenendo ben presente lo studio fatto nel capitolo relativo al contesto, l’utente che si appresta ad utilizzare il sistema è dotato di capacità limitate. Questa caratteristica amplifica ancor di più il dovere, da parte del sistema, di offrire il maggior aiuto possibile all’utente. Un’operazione importante non ancora discussa è la fase di apprendimento. Infatti, come per qualsiasi utensile prodotto per l’uomo, anche in questo caso ci sarà una fase nel quale l’utente dovrà prendere confidenza con il sistema. Solo grazie all’esperienza è possibile utilizzare l’ausilio in maniera ottimizzata. 94 Questa applicazione è stata implementata al fine di servire l’operazione appena descritta. Si dà la possibilità all’utente di imparare a gestire i movimenti del viso per assecondare e prevenire le reazioni del sistema. Inoltre l’impostazione appresa grazie a questa applicazione sarà utile in tutte le applicazioni che entreranno a far parte dell’ausilio in futuro. Tutto si svolge come nel gioco del tiro a segno, nel quale si deve cercare di colpire l’area delimitata da un rettangolo. Al fine di imparare a selezionare gli oggetti visualizzati nelle applicazioni si deve fissare il rettangolo per alcuni istanti per indicare al sistema che un clic temporale è stato eseguito. Solo se il clic è attinente all’area delimitata dal rettangolo è possibile procedere all’intercettazione di un altro obiettivo. La caratteristica che colpisce maggiormente è che, in un sistema appena creato, solo al momento dell’utilizzo è possibile definire il comportamento da seguire per un buon utilizzo. Grazie all’esperienza fatta con questa applicazione si è in grado di elencare alcuni atteggiamenti che facilitano l’apprendimento sull’uso dell’ausilio. Per prima cosa si deve pensare che la visualizzazione del punto fissato arriva con un certo ritardo, modificabile grazie ai controlli descritti nel paragrafo precedente. A condizioni di luce sufficiente, un’informazione più accurata è accompagnata da un ritardo maggiore nell’elaborazione. Questo implica che i movimenti del viso dell’utente sono rappresentati sul terminale video del Personal Computer con un ritardo dovuto all’elaborazione. Si consiglia, per questo motivo di fare sempre movimenti fluidi, armonici e progressivi senza scatti improvvisi. Se, per esempio, si deve intercettare un rettangolo in alto a destra e il viso è rivolto verso il basso a sinistra è possibile, muovere velocemente il viso verso la posizione in alto a destra. Tuttavia tanto più ci si avvicina alla posizione del rettangolo tanto più i movimenti del viso devono essere fluidi e contenuti. Si deve pensare al percorso che unisce i due punti e percorrerlo con il movimento del volto, assecondando il punto che si vede sullo schermo, lavorando in simbiosi con il sistema. Se si passa con un movimento repentino dal punto in basso a sinistra a quello in alto a destra è possibile che si inneschino strane oscillazioni dovute al ritardo attuato dall’elaborazione. In pratica si cerca di posizionare il riferimento all’interno del rettangolo tramite movimenti veloci e a scatti che pregiudicano il funzionamento. Si vedrà il punto che indica il riferimento oscillare sui contorni esterni del rettangolo senza poterlo intercettare in modo preciso. E importante rallentare e compiere movimenti dolci e contenuti non appena si arriva in prossimità dell’oggetto che si vuole selezionare, in questo caso il rettangolo colorato. 95 Solo grazie all’esperienza l’utente sarà in grado di imparare ad usare il sistema correttamente, tuttavia il tempo richiesto per questa fase non è estremamente lungo. Nel giro di pochi minuti si può ottenere un ottimo feeling con l’ausilio e passare alle applicazioni più complesse senza problemi. 6.2.2 Applicazione Due: Tastiera L’implementazione di una applicazione indipendente più complessa rispetto alla precedente, permette di analizzare altre caratteristiche importanti che possono essere rilevate solo con una sperimentazione diretta. Una proprietà al quale lo sviluppatore deve sempre porre grande attenzione è la capacità, da parte dell’applicazione, di lavorare con qualsiasi condizione ambientale. Sia con un’illuminazione ottima sia con una mediocre l’applicazione deve sempre fornire un servizio. Non si può accettare il fatto che l’ausilio funzioni solo con condizioni ambientali ottime. L’applicazione deve assecondare le prestazioni del sistema. Tanto più l’illuminazione risulta mediocre, tanto più la possibilità di intercettare oggetti di dimensione ridotta si affievolisce. Quindi è indispensabile tenere conto di questo fatto quando si implementano nuove applicazioni indipendenti. È fondamentale attuare una certa flessibilità sulle dimensioni degli oggetti selezionabili, al fine di rendere l’applicazione utile con qualsiasi condizione ambientale. Ovviamente se le condizioni di luce sono talmente scarse da inibire addirittura il funzionamento del sistema nulla può essere richiesto all’applicazione, semplicemente l’ausilio non è utilizzabile. L’applicazione discussa in questa sezione implementa una semplice tastiera sulla quale l’utente può comporre delle frasi. Grazie al compito svolto da questa applicazione è possibile analizzare il comportamento descritto precedentemente. La rappresentazione della tastiera non è fissa ma l’utente, o meglio l’assistente, può scegliere diverse alternative per utilizzare al meglio le prestazioni del sistema. Lo spazio riservato all’applicazione sullo schermo del Personal Computer deve essere ottimizzato per contenere i pulsanti. Più caratteri possono essere utilizzati per comporre le frasi, più piccole saranno le dimensioni di ogni pulsante che rappresenta un carattere. L’applicazione permette di definire cinque tastiere differenti, ognuna delle quali conta un diverso numero di pulsanti e quindi di caratteri. Si và da una tastiera che contiene solo le lettere dell’alfabeto ad una tastiera che contiene tutti i possibili caratteri reperibili su una tastiera collegata al Personal Computer. Se le prestazioni del sistema lo permettono è possibile utilizzare la tastiera con più caratteri, in quanto l’utente è in grado di selezionare i pulsanti, anche se la loro dimensione è ridotta. Tuttavia se le prestazioni sono mediocri in quanto rispecchiano condizioni ambientali mediocri, l’applicazione può essere 96 ancora utilizzata selezionando una tastiera che contiene meno caratteri ma che è composta da pulsanti di dimensioni maggiori. Il servizio minimo viene garantito anche se il sistema si trova ad elaborare dati non buoni. Questa è la caratteristica che differenzia un’applicazione scritta per essere collegata all’ausilio da una normale applicazione Windows. Lo sviluppatore di applicazioni indipendente deve prendere in considerazione questa peculiarità se vuole ottenere applicazioni che rispondano in maniera versatile alle prestazioni del sistema e offrano anche in condizioni limite un servizio minimo. 97 98 7 Sviluppi futuri Tutto sembra semplice quando si conosce la strada che conduce nel luogo dove si vuole andare, non esiste niente di più facile che seguire un itinerario che si è già percorso o si compie il cammino in compagnia di qualcuno, che ha ben preciso in mente le strade da percorrere. Purtroppo il compito si complica notevolmente se si ignora il modo di arrivare alla meta. L’unica soluzione è partire e cercare in qualche modo di arrivare a destinazione, lasciarsi andare e compiere il balzo nel vuoto che si apre davanti ai nostri piedi. Molte saranno le strade possibili per arrivare a destinazione, indubbiamente esisteranno strade facili da percorrere, scorciatoie oppure difficili valichi impraticabili. Tuttavia, in un modo o nell’altro, si potranno ottenere dei risultati. La soluzione proposta per questo ausilio non è altro che una fra le tante possibili. L’informatica, d’altro canto, è caratterizzata dal fatto di offrire numerose soluzione per qualsiasi problema. Come i percorsi per arrivare a destinazione le soluzioni potranno essere facili, complesse oppure presentare delle scorciatoie che semplificano di molto l’implementazione del sistema. Il salto nel buio compiuto è visibile nel codice che implementa l’ausilio. Se lo si osserva attentamente è facile notare che il percorso intrapreso non è lineare e preciso fino alla meta, 99 ma presenta itinerari appena accennati e non ulteriormente sviluppati che si discostano dal cammino principale e si perdono nell’oscurità. Quest’ultimi rappresentano possibili soluzioni che non sono state approfondite quando si sono trovate, sul percorso, due strade ugualmente percorribili. Tuttavia ciò non vuol dire che la scelta scartata sia impraticabile o inutile ma significa solamente che si sono fatte delle valutazioni su certi obiettivi da raggiungere a discapito di altri. Questi sentieri appena accennati non sono stati eliminati completamente dal sorgente perché rappresentano le porte dalle quali si potrà, in futuro, sviluppare nuove potenzialità del sistema. Gli obiettivi delineati all’inizio dell’analisi spingevano sulla ricerca di una soluzione, per questo motivo non si sono studiate a fondo tutte le scelte possibili ma solo quelle che promettevano dei risultati concreti. Tuttavia, a questo punto dello sviluppo, occorre approfondire questi sentieri singolarmente e cercare di comprendere se possono portare ad un miglioramento delle prestazioni del sistema. 7.1 Legami esterni Mai, come in questo caso, la frase “il fine giustifica i mezzi” è appropriata. Dimostrare la possibilità di creare un ausilio che assecondasse tutti gli obiettivi descritti nei capitoli precedenti vantava una priorità maggiore di qualsiasi obiettivo sull’ottimizzazione. Ora che questo fine è stato raggiunto, è compito dello sviluppo mettere in primo piano l’ottimizzazione. Questo significa che, gli aiuti ricevuti dall’esterno, erano giustificati fintanto che il fine era la creazione del sistema, tuttavia a questo punto dello sviluppo si deve pensare anche all’ottimizzazione. Per questa ragione i legami che vincolano il sistema con l’esterno devono essere recisi. Il sistema deve svilupparsi al punto di gestire al suo interno le operazioni che, per ora, gli vengono fornite dalle librerie della Victor e dalla RamDisk implementata dalla Cenatec. Questo passo è imposto da svariate ragioni, prima fra tutte la semplificazione delle fasi di installazione del sistema sul Personal Computer dell’utente. Non è ammissibile, infatti, che l’utente, o meglio l’assistente, debba addossarsi un elevato numero di operazioni per rendere operativo l’ausilio. In quanto già l’installazione del software relativo al funzionamento della camera implica un notevole lavoro. Un’altra ragione è puramente economica. Sia le librerie della Victor sia la RamDisk della Cenatec vengono fornite a pagamento, quindi non è possibile addossare all’utente una spesa che si aggiunge al costo della camera. Una soluzione di ripiego potrebbe essere quella di 100 utilizzare le versioni di valutazione gratuite reperibili in rete, tuttavia quest’ultime hanno una scadenza che impone un periodico download. Infine, un sistema che è autosufficiente rispecchia una soluzione più elegante rispetto ad un sistema che per lavorare ha bisogno di aiuti esterni. Per questi motivi, prima di sviluppare qualsiasi altra sezione del codice, è importante rimuovere gli aiuti esterni, in quanto il loro compito ha perso d’importanza ed inoltre sono diventati d’impaccio per il sistema stesso. 7.2 Struttura Come è già stato analizzato nel capitolo relativo all’architettura è importante creare una base solida su cui costruire. Se le fondamenta sono salde anche la struttura, che su di esse appoggerà, sarà robusta. Se il percorso fin qui fatto rappresenta solo una prima tappa di un cammino molto più lungo e articolato, è necessario sviluppare e rafforzare ancor di più la struttura che si trova alla base del sistema. Ciò non significa che il lavoro svolto fino a questo punto sia da scartare ma, come per il resto del sistema, deve evolversi e migliorarsi. È impensabile tralasciare lo sviluppo di questa parte del sistema per dedicarsi allo sviluppo della struttura che su di essa poggia. L’architettura implementata a questo punto dello sviluppo è più che ottima per gli obiettivi che si sono delineati all’inizio, tuttavia rappresenta solo il modello da seguire nello sviluppo futuro. Molto deve essere ancora fatto per rendere reale la soluzione teorica che è stata analizzata nei capitoli precedenti. Per ora la divisione dei compiti tra oggetti differenti non è ancora completa. Anche se concettualmente la separazione è evidente, purtroppo l’implementazione non rispecchia a fondo questa caratteristica. Le operazioni svolte dai diversi oggetti sono ancora legate dalle chiamate dirette di funzione delle varie procedure, il codice non è separato come lo sono concettualmente gli oggetti. Lo stack unisce ancora le varie operazioni fatte dai diversi oggetti. Quando, per esempio, il thread richiama le funzionalità del Supervisore, quest’ultime vengono elaborate basandosi sullo stack del thread come in una normale chiamata a funzione, inibendo la separazione che si vuole ottenere tra il codice del thread e del Supervisore. Questo porta ad una struttura implementata abbastanza complessa che deve essere semplificata se l’obiettivo e produrre un sistema più evoluto. Una possibile soluzione potrebbe essere quella di utilizzare pesantemente le code di messaggi che il Sistema Operativo Windows permette di implementare quando si definisce una finestra. In pratica la separazione tra i vari oggetti utilizza le finestre come portale per la comunicazione. Questo fa sì che anche a livello di codice le funzionalità racchiuse negli 101 oggetti vengano separate. Tuttavia questa non è altro che una congettura, una strada possibile da percorrere, solo l’implementazione potrà verificare se è possibile intraprendere questo cammino. Introducendo i messaggi e le rispettive code è importante testare se le operazioni vengono ancora svolte in sequenza oppure si è obbligati ad introdurre una sorta di IPC per regolare la comunicazione tra i vari oggetti. Molteplici sono le soluzioni per superare questi problemi, tuttavia essi devono essere superati per pensare di portare avanti il lavoro ed evolvere il sistema. La struttura che regola ora il sistema è paragonabile alle solide fondamenta di una piccola casa, è impensabile costruire su di esse un grattacielo. 7.3 Algoritmi Gli algoritmi, come è già stato detto, sono il cuore del sistema. Se supportati da una struttura solida e ben regolamentata risulta possibile introdurre nuove potenzialità e, nello stesso tempo, ottimizzare quelle già esistenti. Ogni procedura descritta nei capitoli precedenti ha potenzialmente un ampio margine di sviluppo. A questo stadio di maturazione si è cercato di trovare una implementazione che permettesse di raggiungere gli obiettivi prefissati. Tuttavia non appena una soluzione per una procedura si è rivelata valida, la ricerca si è spostata verso gli algoritmi successivi, tralasciando lo sviluppo per conseguire le mete prestabilite. Il compito di migliorare correttamente ogni algoritmo sarà affidato allo sviluppo futuro. Per esempio, la prima procedura descritta nel capitolo dedicato all’implementazione può e deve essere rivista per includere nuove potenzialità. Dallo studio attuato sul sistema fino ad ora, è emerso che è restrittivo utilizzare una sola sfumatura per intercettare il riferimento fisso sul volto dell’utente. In quanto, diverse angolazioni del volto rispetto alla fonte luminosa enfatizzano diverse sfumature dello stesso colore. Il sistema dovrebbe valutare questa caratteristica. Per far questo si dovrebbe fornire un’analisi più accurata sulla palette di colori disponibile. Invece di studiare solo un’immagine si potrebbe stilare una casistica su più catture ed indicare all’utente la scelta che si è rivelata più probante. Anche la procedura che intercetta il movimento del riferimento non dovrebbe basare la sua ricerca su una sola sfumatura ma attuare in parallelo l’intercettazione delle diverse sfumature dello stesso colore. Verificando se durante la sessione di lavoro alcune di esse diventano più efficaci di quella scelta all’inizio. Addirittura la procedura che indica al sistema il punto fissato dall’utente sullo schermo del Personal Computer potrebbe essere ottimizzata. Per ora la sua funzione si limita ad analizzare 102 i dati che ottiene dagli altri algoritmi ed indicare in coordinate assolute il punto. Tuttavia nessuna compensazione viene attuata e l’elaborazione si basa su operazioni lineari. Questo ostacola la libertà di scelta sulla posizione della camera nello spazio delineato intorno al video. Le posizioni che introducono delle non linearità devono essere scartate. Inoltre potrebbe essere possibile ottenere una sorta di previsione sul comportamento tenuto dall’utente al fine di anticipare le sue scelte ed assecondare i suoi movimenti. In conclusione i margini di sviluppo per ogni algoritmo sono enormi, tuttavia esiste una regola importante che deve essere sempre rispettata. Le evoluzioni future del sistema devono mantenere una coerenza nei messaggi che vengono inviati alle applicazioni indipendenti. È impensabile che lo sviluppo più recente del sistema inibisca l’utilizzo delle applicazioni scritte precedentemente per il solo fatto che i messaggi sono stati modificati. Se, in futuro, si individueranno nuovi messaggi possibili, da inviare alle applicazioni, per accrescere le loro potenzialità, il sistema dovrà comunque fornire un insieme di base che permette alle applicazioni meno recenti di utilizzare ancora il sistema. Il pregio più importante che il sistema possiede e che si sono divisi in maniera netta i compiti svolti dal sistema da quelli svolti dalle applicazioni. Dal punto di vista dell’applicazione indipendente è ininfluente l’elaborazione che sta dietro ad ogni messaggio, l’importante è che lo stesso messaggio venga inviato da tutte le possibili versioni future del sistema. 7.4 Applicazioni Sullo sviluppo futuro delle applicazioni indipendenti poco si può prevedere, in quanto solo l’estro e la fantasia degli sviluppatori è un sicuro limite alle loro prestazioni. Le applicazioni non sono altro che programmi sviluppati per operare sotto i Sistemi Operativi della famiglia Microsoft a cui vengono aggiunte delle potenzialità offerte dall’ausilio. Per questa ragione sono imprevedibili le strade che possono essere percorse, in quanto qualsiasi problema può essere risolto implementando una valida applicazione. Pochi sono i miglioramenti che a questo punto dello sviluppo ha senso analizzare. Per esempio, sfruttare le potenzialità che l’ausilio offre, non solo per le applicazioni ma anche per il sistema stesso. La scelta dell’applicazione da attivare viene ora eseguita grazie al menu, tuttavia questa operazione deve essere effettuata dall’assistente mentre potrebbe essere positivo lasciare la scelta all’utente stesso. Per ottenere questo risultato, il sistema dovrebbe visualizzare la finestra di un’applicazione, per così dire principale, dove vengono inserite tutte le applicazioni, iscritte nel sistema, che l’utente può selezionare. Questo permetterebbe 103 all’utente di acquistare più autonomia e poter passare da una applicazione all’altra senza l’aiuto dell’assistente. Per attivare questa opzione è possibile intraprendere due strade differenti. Da un lato è possibile implementare delle applicazioni indipendenti che comprendano la volontà dell’utente di passare ad un’altra applicazione ed informino, di conseguenza, il sistema. Dall’altro addossare al sistema stesso l’onere di intercettare le scelte dell’utente. La prima soluzione non è percorribile perché infrange la regola base che stabilisce il rapporto tra sistema e applicazioni indipendenti. Solo il sistema deve sapere quali sono le applicazioni iscritte, mentre le applicazioni non devono e non possono comunicare con il Supervisore ma solo elaborare i messaggi spediti da quest’ultimo. La seconda soluzione, oltre ad essere l’unica percorribile, apre nuove strade nello sviluppo non solo relativo alle applicazioni ma, come si è già detto, dell’intero sistema. Il sistema deve elaborare parallelamente all’applicazione le scelte fatte dall’utente. Per ottenere questo è possibile utilizzare una specie di banner sempre visibile, anche quando il sistema visualizza la finestra dell’applicazione attiva. Invece di dimensionare la finestra dell’applicazione a pieno schermo potrebbe essere utile sacrificare un po’ di spazio, per esempio in basso, per una speciale finestra di controllo gestita direttamente dal sistema. Lo spazio offerto dallo schermo del Personal Computer viene così condiviso sia dall’applicazione attiva, che ne occupa la maggior parte, sia dal sistema stesso. L’algoritmo incaricato di decidere dove l’utente stia fissando può filtrare l’informazione e inviare i messaggi all’applicazione solo se lo sguardo è relativo allo spazio occupato dall’applicazione. Nell’altro caso è possibile elaborare direttamente la posizione del riferimento per identificare i comandi che l’utente vuole impartire al sistema stesso. Il banner potrebbe implementare una serie di pulsanti di scelta rapida che permettono all’utente di ritornare alla finestra principale dove è possibile riformulare la scelta dell’applicazione che si vuole utilizzare. Oppure ancora, fornire la possibilità di modificare direttamente i controlli descritti nel capitolo precedente senza l’aiuto dell’assistente. In questo modo l’utente potrebbe gestire in prima persona tutte le potenzialità offerte dall’ausilio ed essere in questo modo completamente indipendente per tutta la sessione di lavoro svolta con il sistema. Infine, lo sviluppo deve toccare anche la struttura che regola i rapporti tra sistema e applicazioni indipendenti. Per ora sono molti i punti del sorgente che lo sviluppatore deve manipolare per poter agganciare al sistema la sua creazione. Un miglioramento in questo senso potrebbe ridurre al minimo queste zone di collegamento. Tuttavia ci si potrebbe 104 chiedere fino a che punto è possibile diminuire le zone del sorgente dove è possibile ancorare le applicazioni indipendenti. La scelta del numero di punti di ancoraggio è un fattore talmente importante che tutta la struttura può risentirne. Due sono le possibili soluzioni, da una parte si continua a mantenere uno o più agganci, mentre dall’altra si eliminano completamente le zone di ancoraggio. La prima soluzione rispecchia un’evoluzione della struttura presente, in quanto devono essere migliorate solamente le potenzialità offerte dal Supervisore. Rendere automatico l’aggancio, agendo in una sola sezione del codice. Obbligare il Supervisore ad addossarsi tutte le operazioni che devono essere fatte per permettere all’utente di poter scegliere ed utilizzare l’applicazione che preferisce. La struttura in questo caso non subisce modifiche, le regole definite nei capitoli precedenti restano invariate e continuano a valere. Il sistema ha il pieno controllo delle applicazioni e in ogni istante conosce quale è attiva. Tuttavia, solo una applicazione può essere attiva in un certo istante. Questo porta a domandarsi se sia limitativo permettere all’utente di utilizzare solo un’applicazione alla volta. La risposta può essere ricercata nella natura stessa dell’ausilio. Le applicazioni occupano, nel loro funzionamento, tutto lo schermo del Personal Computer. Quindi è irrilevante se altre applicazioni sono attive contemporaneamente in quanto solo una alla volta potrà occupare tutto le spazio a disposizione. Se per alcune elaborazioni particolari, si dovranno utilizzare le potenzialità di due distinte applicazioni è sempre possibile fonderle in una sola e utilizzare due sottofinestre per simulare i diversi compiti. Sarà l’applicazione stessa ad elaborare ed istradare i comandi alle due sottofinestre. Dall’altra parte, eliminare totalmente l’iscrizione delle applicazioni indipendenti nel sistema, permette di utilizzare più applicazioni attive nello stesso istante. I messaggi che devono essere spediti alle applicazioni dovranno essere inoltrati al Sistema Operativo, il quale avrà il compito di notificarli alle applicazioni indipendenti attive in quel momento. Questo concetto rivoluziona totalmente la struttura del sistema. Si divide completamente il sistema dalle applicazioni indipendenti, l’ausilio è composto solamente dalla parte che elabora le immagini catturate dalla camera e inoltra i messaggi al Sistema Operativo. L’ausilio lavora, per così dire, in background mentre le applicazioni lavorano in foreground o in primo piano elaborando i messaggi che il Sistema Operativo gli inoltra. Questa tecnica tuttavia introduce più svantaggi che vantaggi. Infatti diventa complessa la comunicazione tra Supervisore e applicazioni in quanto devono essere utilizzate zone condivise di memoria. Anche i messaggi non sono più leggeri da elaborare come nel caso precedente ma comportano una complessità maggiore sia da parte del Supervisore sia da parte delle applicazioni. Inoltre anche il lavoro 105 degli sviluppatori di applicazioni diventa complesso in quanto non esiste modo di debaggare in maniera semplice il sorgente scritto. Gli ambienti di sviluppo, per ora, non possono analizzare più di un programma alla volta. Tuttavia separando in maniera completa il sistema dalle applicazioni è possibile compilare in maniera indipendente il codice. Ogni volta che una nuova applicazione viene prodotta non si è obbligati a ricompilare il codice relativo al sistema ma la compilazione riguarda solamente il sorgente relativo all’applicazione. 7.5 Conclusioni Purtroppo nei libri che cercano di insegnare l’informatica e la programmazione si tralascia un aspetto importante che deve caratterizzare il codice sorgente. Questo perché forse non è semplice trattarlo oppure è troppo intuitivo e solo l’esperienza nella programmazione può farlo intravedere. Sfortunatamente è difficile anche trovare un nome che possa definirlo, quello che più si avvicina al concetto che si vuole analizzare forse è massa critica. Con il termine massa critica, o meglio limite della massa critica, si intende il punto in cui non è più possibile toccare il sorgente per poter aggiungere nuove potenzialità. Il sorgente è talmente complesso (o disorganizzato) che ogni operazione fatta su di esso può compromettere il funzionamento dell’intero sistema. Più il limite è basso, prima si raggiungerà la massa critica, ciò significa che anche un programma descritto da poche decine di Kbyte di sorgente può diventare impossibile da gestire e da sviluppare. Invece un sistema in cui il limite viene alzato a dismisura ha ampi margini di sviluppo prima di raggiungere la massa critica. Il concetto di massa critica è inoltre relativo al soggetto che si pone davanti al codice sorgente. Per il creatore il livello sarà sempre più alto rispetto ad un estraneo che legge il sorgente per la prima volta. Questo è dovuto al fatto che chi ha sviluppato dall’inizio il sorgente ha ben preciso in mente quello che ha fatto, mentre l’estraneo non può immaginare quello che è passato per la mente del creatore. Il limite che più interessa è ha utilità a fini pratici può essere chiamato limite assoluto di massa critica, questo limite si trova tra i due relativi descritti in precedenza. È più alto del limite visto dall’estraneo in quanto quest’ultimo può studiare il sorgente e a mano a mano capirlo sempre più in profondità. Mentre è più basso del limite visto dal creatore perché quest’ultimo non tiene in considerazione la fase di apprendimento che l’estraneo deve fare sul suo sorgente. Più il sorgente è stato scritto con cura e con attenzione, più il limite assoluto si avvicinerà al limite relativo del creatore, in quanto l’estraneo potrà capire, con lo studio, le tecniche usate dal creatore. Più il codice sarà scritto male e disorganizzato più il limite assoluto si avvicinerà 106 a quello dell’estraneo, perché quest’ultimo non potrà capire, anche analizzandoli, i concetti che ha utilizzato il creatore. Il limite assoluto può essere modificato grazie alle operazioni svolte dallo sviluppatore. Uno studio dettagliato sull’architettura, la scrittura del sorgente con tecniche e impostazioni classiche, l’introduzione dei commenti e l’utilizzo di algoritmi semplici possono innalzare i limiti relativi e di conseguenza l’assoluto, producendo un sorgente che allontana l’ombra della massa critica. Al contrario, una superficialità diffusa nella creazione del sorgente, l’utilizzo di numerose chiamate a funzione, obiettivi annebbiati e non ben delineati, una scrittura che non segue le impostazioni classiche, una mancanza di commenti e una esasperazione sull’ottimizzazione del sistema porta velocemente il sorgente alla massa critica, abbassando di conseguenza tutti i limiti. Questi concetti hanno accompagnato tutto il lavoro fin qui svolto, ora tocca ai futuri sviluppatori tenere bene a mente queste caratteristiche se si vuole produrre un sistema che abbia un futuro. Infatti il sistema, oltre ad offrire il suo supporto all’utente, deve promettere ampi margini di miglioramento che solo un limite assoluto della massa critica estremamente elevato possono assicurare. Quindi l’unico consiglio che si può dare ai futuri sviluppatori è di evitare le fughe in avanti. Il compito principale che deve essere sempre tenuto in considerazione consiste nel far crescere in maniera omogenea l’intera struttura. Pensando nel contempo che il lavoro svolto sarà la base di partenza per un altro sviluppatore, esclusi ovviamente coloro che sviluppano nuove applicazioni indipendenti. 107 108 8 Conclusioni Nulla è più possibile aggiungere. L’utilità dell’ausilio è in mano a coloro i quali avranno, in futuro, il potere di svilupparlo e donarlo a chi ne ha più bisogno. Il lavoro fin qui fatto è solo il primo piccolo passo verso un aiuto concreto alle persone disabili a cui questo sistema si rivolge. Gli obiettivi posti all’inizio dovrebbero essere stati raggiunti. L’ausilio cercava di dare un aiuto alle persone con gravi problemi motori e per questo il sistema permette di selezionare oggetti sul video del Personal Computer con il solo movimento del volto. L’ausilio doveva utilizzare tecnologie economiche e per questo motivo il sistema utilizza solamente una periferica di acquisizione video estremamente modesta come può esserlo una WebCam. Infine si è cercato di rendere la sessione di lavoro il più confortevole possibile all’utente che deve solo indossare un piccolo oggetto che permette al sistema di intercettare il movimento. Ovviamente ulteriori miglioramenti saranno possibili anche grazie alla collaborazione diretta di persone disabili che, con le loro sensazioni, potranno dirigere il lavoro futuro. 109 Appendice A Elementi principali Main #include <windows.h> #include <process.h> #include "resource.h" // Prima parte delle inclusioni private: #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include TCHAR "Dichiarazioni.h" "Messaggi.h" "Supervisore.h" "Cattura.h" "Intercetta.h" "Palette.h" "Decisore.h" "Menu.h" "Errore.h" "Finestre.h" "MenuFinestre.h" "MenuCattura.h" "MenuIntercetta.h" "MenuPosiziona.h" "MenuDemo.h" "ProprietaDlg.h" szAppName[] szAppImmagineFrontale[] szAppImmagineLaterale[] szAppPaletteFrontale[] szAppPaletteLaterale[] szAppWebUno[] szAppWebDue[] szAppControlli[] szAppPosiziona[] = = = = = = = = = TEXT TEXT TEXT TEXT TEXT TEXT TEXT TEXT TEXT ("Eye"), ("ImmagineFrontale"), ("ImmagineLaterale"), ("PaletteFrontale"), ("PaletteLaterale"), ("WebUno"), ("WebDue"), ("Controlli"), ("Posiziona"); // Nome delle Applicazioni: TCHAR szAppDemoUno[] szAppDemoDue[] szAppDemoTre[] //... Supervisore = TEXT ("DemoUno"), = TEXT ("DemoDue"), = TEXT ("DemoTre"); Angelo; int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { // Handler per le Finestre di Eye: HWND FinestraPrincipale, FinestraImmagineFrontale, FinestraImmagineLaterale, FinestraPaletteFrontale, FinestraPaletteLaterale, FinestraWebUno, FinestraWebDue, FinestraControlli, // Handler per le finestre di Applicazione: HWND FinestraDemoUno, FinestraDemoDue, FinestraDemoTre; // ... MSG msg; WNDCLASS wndVirtuale; HMENU MenuPrincipale; Cattura CameraUno, CameraDue; Intercetta MagoFrontale,MagoLaterale; Palette TavolozzaFrontale,TavolozzaLaterale; Decisore Oz; GestoreMenu Menu; GestoreErrore Errore; 1 // // // // // // // // // // // GestoreFinestre Finestre; Parametri standard per tutte le classi delle finestre wndVirtuale.style = CS_HREDRAW | CS_VREDRAW; wndVirtuale.cbClsExtra = 0; wndVirtuale.cbWndExtra = 0; wndVirtuale.hInstance = hInstance; wndVirtuale.lpszMenuName = NULL; wndVirtuale.hCursor = LoadCursor (NULL, IDC_ARROW); wndVirtuale.hbrBackground = (HBRUSH) GetStockObject(WHITE_BRUSH); Parametri per definire la classe della Finestra Principale e registrazione wndVirtuale.hIcon = LoadIcon(hInstance,MAKEINTRESOURCE(IDI_ICONA_PRINCIPALE)); wndVirtuale.lpfnWndProc = WndProc; wndVirtuale.lpszClassName = szAppName; if (!RegisterClass (&wndVirtuale)) { MessageBox (NULL, TEXT ("This program requires Windows NT!"), szAppName, MB_ICONERROR); return 0 ; } Parametri per definire le Finestre Secondarie: Parametri per definire la classe della Finestra ImmagineFrontale e registrazione wndVirtuale.hIcon = NULL; // diventa parametro standard wndVirtuale.lpfnWndProc = ImmagineFrontaleProc; wndVirtuale.lpszClassName = szAppImmagineFrontale; RegisterClass(&wndVirtuale); Parametri per definire la classe della Finestra ImmagineLaterale e registrazione wndVirtuale.lpfnWndProc = ImmagineLateraleProc; wndVirtuale.lpszClassName = szAppImmagineLaterale; RegisterClass(&wndVirtuale); Parametri per definire la classe della Finestra PaletteFrontale e registrazione wndVirtuale.lpfnWndProc = PaletteFrontaleProc; wndVirtuale.lpszClassName = szAppPaletteFrontale; RegisterClass(&wndVirtuale); Parametri per definire la classe della Finestra PaletteLaterale e registrazione wndVirtuale.lpfnWndProc = PaletteLateraleProc; wndVirtuale.lpszClassName = szAppPaletteLaterale; RegisterClass(&wndVirtuale); Parametri per definire la classe della Finestra Web Uno e registrazione wndVirtuale.lpfnWndProc = WebUnoProc; wndVirtuale.lpszClassName = szAppWebUno; RegisterClass(&wndVirtuale); Parametri per definire la classe della Finestra Web Due e registrazione wndVirtuale.lpfnWndProc = WebDueProc; wndVirtuale.lpszClassName = szAppWebDue; RegisterClass(&wndVirtuale); Parametri per definire la classe della Finestra Controlli e registrazione wndVirtuale.lpfnWndProc = ControlliProc; wndVirtuale.lpszClassName = szAppControlli; RegisterClass(&wndVirtuale); Parametri per definire la classe della Finestra Posiziona e registrazione wndVirtuale.lpfnWndProc = PosizionaProc; wndVirtuale.lpszClassName = szAppPosiziona; RegisterClass(&wndVirtuale); // Parametri per definire le Finestre di Applicazione: // Demo Uno: wndVirtuale.lpfnWndProc = DemoUnoProc; wndVirtuale.lpszClassName = szAppDemoUno; RegisterClass(&wndVirtuale); // Demo Due: wndVirtuale.lpfnWndProc = DemoDueProc; wndVirtuale.lpszClassName = szAppDemoDue; RegisterClass(&wndVirtuale); // Demo Tre: wndVirtuale.lpfnWndProc = DemoTreProc; wndVirtuale.lpszClassName = szAppDemoTre; RegisterClass(&wndVirtuale); // Successive Demo... // Creazione dell'aggancio al Menù Principale MenuPrincipale = LoadMenu(hInstance,MAKEINTRESOURCE (IDR_MENUPRINCIPALE)) ; // Creazione della Finestra Principale FinestraPrincipale = CreateWindow (szAppName, // nome della classe TEXT ("Eye_6_1"), // titolo WS_OVERLAPPEDWINDOW, // stile della finestra 150, // posizione iniziale x 50, // posizione iniziale y 700, // larghezza 650, // lunghezza NULL, // finestra genitore MenuPrincipale, // menu hInstance, // instanza del programma NULL) ; // parametri di creazione 2 // Settaggio dell'Angelo e delle sottoFinestre attive Angelo.Set(FinestraPrincipale, // Finestra Principale MenuPrincipale, // Menu Principale (char*) szAppName, // Nome della Classe &CameraUno,&CameraDue, // Oggetti Cattura &MagoFrontale,&MagoLaterale, // Oggetti Intercetta &TavolozzaFrontale,&TavolozzaLaterale, // Oggetti Palette &Oz, // Oggetto Decisore &Menu, // Oggetto GestoreMenu &Errore, // Oggetto GestoreErrore &Finestre); // Oggetto GestoreFinestre // Creazione delle Finestre Secondarie // Finestra Immagine Frontale: FinestraImmagineFrontale = CreateWindow(szAppImmagineFrontale,NULL, WS_CHILDWINDOW | WS_BORDER | WS_VISIBLE,0,0,0,0, FinestraPrincipale,NULL,hInstance,NULL); Angelo.SetFinestreSecondarie(FINESTRA_IMMAGINE_FRONTALE,FinestraImmagineFrontale); // Finestra Immagine Laterale: FinestraImmagineLaterale = CreateWindow(szAppImmagineLaterale,NULL, WS_CHILDWINDOW | WS_BORDER | WS_VISIBLE,0,0,0,0, FinestraPrincipale,NULL,hInstance,NULL); Angelo.SetFinestreSecondarie(FINESTRA_IMMAGINE_LATERALE,FinestraImmagineLaterale); // Finestra Palette Frontale: FinestraPaletteFrontale = CreateWindow(szAppPaletteFrontale,NULL, WS_CHILDWINDOW | WS_BORDER | WS_VISIBLE,0,0,0,0, FinestraPrincipale,NULL,hInstance,NULL); Angelo.SetFinestreSecondarie(FINESTRA_PALETTE_FRONTALE,FinestraPaletteFrontale); // Finestra Palette Laterale: FinestraPaletteLaterale = CreateWindow(szAppPaletteLaterale,NULL, WS_CHILDWINDOW | WS_BORDER | WS_VISIBLE,0,0,0,0, FinestraPrincipale,NULL,hInstance,NULL); Angelo.SetFinestreSecondarie(FINESTRA_PALETTE_LATERALE,FinestraPaletteLaterale); // Finestra Web Uno: FinestraWebUno = CreateWindow(szAppWebUno,NULL, WS_CHILDWINDOW | WS_BORDER | WS_VISIBLE,0,0,0,0, FinestraPrincipale,NULL,hInstance,NULL); Angelo.SetFinestreSecondarie(FINESTRA_WEB_UNO,FinestraWebUno); // Finestra Web Due: FinestraWebDue = CreateWindow(szAppWebDue,NULL, WS_CHILDWINDOW | WS_BORDER | WS_VISIBLE,0,0,0,0, FinestraPrincipale,NULL,hInstance,NULL); Angelo.SetFinestreSecondarie(FINESTRA_WEB_DUE,FinestraWebDue); // Finestra Controlli: FinestraControlli = CreateWindow(szAppControlli,NULL, WS_CHILDWINDOW | WS_BORDER | WS_VISIBLE,0,0,0,0, FinestraPrincipale,NULL,hInstance,NULL); Angelo.SetFinestreSecondarie(FINESTRA_CONTROLLI,FinestraControlli); // Finestra Posiziona: FinestraPosiziona = CreateWindow(szAppPosiziona,NULL, WS_CHILDWINDOW | WS_BORDER | WS_VISIBLE,0,0,0,0, FinestraPrincipale,NULL,hInstance,NULL); Angelo.SetFinestreSecondarie(FINESTRA_POSIZIONA,FinestraPosiziona); // Iscrizione nell'Angelo delle Applicazioni: // Demo Uno: FinestraDemoUno = CreateWindow(szAppDemoUno,NULL, WS_CHILDWINDOW | WS_BORDER | WS_VISIBLE,0,0,0,0, FinestraPrincipale,NULL,hInstance,NULL); Angelo.SetFinestreApplicazione(FINESTRA_APPLICAZIONE_DEMO_UNO,FinestraDemoUno,true); // Demo Due: FinestraDemoDue = CreateWindow(szAppDemoDue,NULL, WS_CHILDWINDOW | WS_BORDER | WS_VISIBLE,0,0,0,0, FinestraPrincipale,NULL,hInstance,NULL); Angelo.SetFinestreApplicazione(FINESTRA_APPLICAZIONE_DEMO_DUE,FinestraDemoDue,false); // Demo Tre: FinestraDemoTre = CreateWindow(szAppDemoTre,NULL, WS_CHILDWINDOW | WS_BORDER | WS_VISIBLE,0,0,0,0, FinestraPrincipale,NULL,hInstance,NULL); Angelo.SetFinestreApplicazione(FINESTRA_APPLICAZIONE_DEMO_TRE,FinestraDemoTre,false); // Successive Demo... // Visualizzazione e creazione coda dei messaggi per la Finestra Principale ShowWindow(FinestraPrincipale,iCmdShow); UpdateWindow(FinestraPrincipale); while (GetMessage(&msg,NULL,0,0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam ; } 3 LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { static HINSTANCE hInstance; HMENU hMenu; int cxClient,cyClient; switch (message) { case WM_CREATE: hInstance = ((LPCREATESTRUCT) lParam)->hInstance; Angelo.SethInstance(hInstance); return 0; case WM_SIZE: cxClient = LOWORD (lParam); cyClient = HIWORD (lParam); // Gestione delle dimensioni delle Finestre Secondarie: if(Angelo.VisibileSecondarie(FINESTRA_POSIZIONA)) MoveWindow(Angelo.GetFinestreSecondarie(FINESTRA_POSIZIONA), 0, 0, cxClient,cyClient,true); else MoveWindow(Angelo.GetFinestreSecondarie(FINESTRA_POSIZIONA), 0,0,0,0,true); if(Angelo.VisibileSecondarie(FINESTRA_IMMAGINE_FRONTALE)) MoveWindow(Angelo.GetFinestreSecondarie(FINESTRA_IMMAGINE_FRONTALE), 0, 50, cxClient/2,(cyClient/2)-50,true); else MoveWindow(Angelo.GetFinestreSecondarie(FINESTRA_IMMAGINE_FRONTALE), 0,0,0,0,true); if(Angelo.VisibileSecondarie(FINESTRA_IMMAGINE_LATERALE)) MoveWindow(Angelo.GetFinestreSecondarie(FINESTRA_IMMAGINE_LATERALE), cxClient/2, 50, cxClient/2,(cyClient/2)-50,true); else MoveWindow(Angelo.GetFinestreSecondarie(FINESTRA_IMMAGINE_LATERALE), 0,0,0,0,true); if(Angelo.VisibileSecondarie(FINESTRA_PALETTE_FRONTALE)) MoveWindow(Angelo.GetFinestreSecondarie(FINESTRA_PALETTE_FRONTALE), 0, 0, cxClient/2,50,true); else MoveWindow(Angelo.GetFinestreSecondarie(FINESTRA_PALETTE_FRONTALE), 0,0,0,0,true); if(Angelo.VisibileSecondarie(FINESTRA_PALETTE_LATERALE)) MoveWindow(Angelo.GetFinestreSecondarie(FINESTRA_PALETTE_LATERALE), cxClient/2, 0, cxClient/2,50,true); else MoveWindow(Angelo.GetFinestreSecondarie(FINESTRA_PALETTE_LATERALE), 0,0,0,0,true); if(Angelo.VisibileSecondarie(FINESTRA_CONTROLLI)) MoveWindow(Angelo.GetFinestreSecondarie(FINESTRA_CONTROLLI), 0, cyClient/2, cxClient,100,true); else MoveWindow(Angelo.GetFinestreSecondarie(FINESTRA_CONTROLLI), 0,0,0,0,true); if(Angelo.VisibileSecondarie(FINESTRA_WEB_UNO)) MoveWindow(Angelo.GetFinestreSecondarie(FINESTRA_WEB_UNO), 0, (cyClient/2)+100, cxClient/2,(cyClient/2)-100,true); else MoveWindow(Angelo.GetFinestreSecondarie(FINESTRA_WEB_UNO), 0,0,0,0,true); if(Angelo.VisibileSecondarie(FINESTRA_WEB_DUE)) MoveWindow(Angelo.GetFinestreSecondarie(FINESTRA_WEB_DUE), cxClient/2, (cyClient/2)+100, cxClient/2,(cyClient/2)-100,true); else MoveWindow(Angelo.GetFinestreSecondarie(FINESTRA_WEB_DUE), 0,0,0,0,true); // Gestione delle dimensioi delle Finestre di Applicazione: 4 if(Angelo.VisibileApplicazione(FINESTRA_APPLICAZIONE_DEMO_UNO)) MoveWindow(Angelo.GetFinestreApplicazione(FINESTRA_APPLICAZIONE_DEMO_UNO), 0, 0, cxClient,cyClient,true); else MoveWindow(Angelo.GetFinestreApplicazione(FINESTRA_APPLICAZIONE_DEMO_UNO), 0,0,0,0,true); if(Angelo.VisibileApplicazione(FINESTRA_APPLICAZIONE_DEMO_DUE)) MoveWindow(Angelo.GetFinestreApplicazione(FINESTRA_APPLICAZIONE_DEMO_DUE), 0, 0, cxClient,cyClient,true); else MoveWindow(Angelo.GetFinestreApplicazione(FINESTRA_APPLICAZIONE_DEMO_DUE), 0,0,0,0,true); if(Angelo.VisibileApplicazione(FINESTRA_APPLICAZIONE_DEMO_TRE)) MoveWindow(Angelo.GetFinestreApplicazione(FINESTRA_APPLICAZIONE_DEMO_TRE), 0, 0, cxClient,cyClient,true); else MoveWindow(Angelo.GetFinestreApplicazione(FINESTRA_APPLICAZIONE_DEMO_TRE), 0,0,0,0,true); // ... return 0; case WM_COMMAND: hMenu = GetMenu (hwnd) ; switch (LOWORD (wParam)) { case ID_FINESTRE_FINESTRAIMMAGINEFRONTALE: case ID_FINESTRE_FINESTRAIMMAGINELATERALE: case ID_FINESTRE_FINESTRAPALETTEFRONTALE: case ID_FINESTRE_FINESTRAPALETTELATERALE: case ID_FINESTRE_FINESTRACONTROLLI: case ID_FINESTRE_FINESTRAWEBUNO: case ID_FINESTRE_FINESTRAWEBDUE: case ID_FINESTRE_FINESTRAPOSIZIONA: MenuFinestre(wParam,&Angelo); return 0; case ID_CATTURA_AGGANCIO: case ID_CATTURA_FINESTRA_FORMATO: case ID_CATTURA_FINESTRA_SORGENTE: case ID_CATTURA_FOTO: case ID_CATTURA_RILASCIO: case ID_CATTURA_SETTAGGI_OVERLAY: case ID_CATTURA_SETTAGGI_INFORMAZIONI: case ID_CATTURA_ONOFF_2WEB: case ID_CATTURA_TARGET_FILE: case ID_CATTURA_PROPRIETA_PROPRIETAUNO: case ID_CATTURA_PROPRIETA_PROPRIETADUE: MenuAggancio(wParam,&Angelo); return 0; case ID_INTERCETTA_PALETTE_FRONTALE: case ID_INTERCETTA_PALETTE_LATERALE: case ID_INTERCETTA_VISUALIZZA_FRONTALE: case ID_INTERCETTA_START_FRONTALE: case ID_INTERCETTA_STOP_FRONTALE: case ID_INTERCETTA_VISUALIZZA_LATERALE: case ID_INTERCETTA_START_LATERALE: case ID_INTERCETTA_STOP_LATERALE: case ID_INTERCETTA_VISUALIZZA_FRONTALE_II: case ID_INTERCETTA_START_FRONTALE_II: case ID_INTERCETTA_STOP_FRONTALE_II: MenuIntercetta(wParam,&Angelo); return 0 ; case ID_POSIZIONA_AUTOMATICO: case ID_POSIZIONA_INTERROMPI: case ID_POSIZIONA_MANUALE: case ID_POSIZIONA_STOP: case ID_POSIZIONA_OK: case ID_POSIZIONA_RITORNA: MenuPosiziona(wParam,&Angelo); return 0; case ID_APPLICAZIONI_DEMOUNO: case ID_APPLICAZIONI_DEMODUE: case ID_APPLICAZIONI_DEMOTRE: // ... MenuDemo(wParam,&Angelo); return 0; } case WM_DESTROY: 5 PostQuitMessage (0) ; return 0 ; } return DefWindowProc (hwnd, message, wParam, lParam) ; } // Seconda parte delle inclusioni private: #include "ThreadIntercettaFrontale.h" #include "ThreadIntercettaLaterale.h" #include "ThreadIntercettaII.h" #include "ThreadPosiziona.h" #include "FinestreCamereProc.h" #include "PaletteFrontaleProc.h" #include "PaletteLateraleProc.h" #include "ImmagineFrontaleProc.h" #include "ImmagineLateraleProc.h" #include "ControlliProc.h" #include "PosizionaProc.h" // Inclusioni per le applicazioni: #include "DemoUnoProc.h" #include "DemoDueProc.h" #include "DemoTreProc.h" //... Dichiarazioni #include <vfw.h> #include "Vicdefs.h" // Dichiarazione delle WinProc per Eye: LRESULT CALLBACK WndProc LRESULT CALLBACK WebUnoProc LRESULT CALLBACK WebDueProc LRESULT CALLBACK PaletteFrontaleProc LRESULT CALLBACK PaletteLateraleProc LRESULT CALLBACK ImmagineFrontaleProc LRESULT CALLBACK ImmagineLateraleProc LRESULT CALLBACK ControlliProc LRESULT CALLBACK PosizionaProc BOOL CALLBACK ProprietaDlgProc (HWND,UINT,WPARAM,LPARAM); (HWND,UINT,WPARAM,LPARAM); (HWND,UINT,WPARAM,LPARAM); (HWND,UINT,WPARAM,LPARAM); (HWND,UINT,WPARAM,LPARAM); (HWND,UINT,WPARAM,LPARAM); (HWND,UINT,WPARAM,LPARAM); (HWND,UINT,WPARAM,LPARAM); (HWND,UINT,WPARAM,LPARAM); (HWND,UINT,WPARAM,LPARAM); // Dichiarazioni delle WinProc per le Applicazioni: LRESULT CALLBACK DemoUnoProc (HWND,UINT,WPARAM,LPARAM); LRESULT CALLBACK DemoDueProc (HWND,UINT,WPARAM,LPARAM); LRESULT CALLBACK DemoTreProc (HWND,UINT,WPARAM,LPARAM); //... // Dichiarazione delle Classi class class class class class class class class Cattura; Intercetta; Supervisore; GestoreMenu; GestoreErrore; GestoreFinestre; Palette; Decisore; // Dichiarazioni delle Costanti short short short short short short SLEEP_THREAD_FRONTALE SLEEP_THREAD_LATERALE NUMERO_AQUISIZIONI NUMERO_AQUISIZIONI_AREA_CLICK BORDO_CLICK BORDO_FOCUS = = = = = = 10; 10; 10; 4; 2; 20; const const const const const const short short short short short short = = = = = = 10; 52; 5; 0; 70; 6; BORDO_DOMINIO FOCUS_EYE MASSIMO_LESS LIMITE_BERSAGLI_MINIMO LUMINOSITA NUMERO_AQUISIZIONI_PROGRESSO // deve essere un multiplo di 4 // Dichiarazioni delle Strutture typedef struct CELLA_COLORE_LS { 6 BYTE Blu,Verde,Rosso; UINT TipoColore; struct CELLA_COLORE_LS *Precedente,*Successivo; } CELLA_COLORE_LS; typedef struct RETTANGOLI { RECT Zona; RECT Focus; RECT Dominio; RECT Lento; RECT FocusEye; } RETTANGOLI; typedef struct CELLA_POSIZIONE { RECT Posizione; UINT Stato; struct CELLA_POSIZIONE *Successivo,*Precedente; } CELLA_POSIZIONE; typedef struct { BYTE Blu,Verde,Rosso; } COLORE; typedef struct { short BV,VR,RB; } SCOSTAMENTO; typedef struct { UINT Camera; int ImageWidth, ImageHeight; char* NomeDriver; bool LiveWindow, OverlayWindow, Scale, UsingDefaultPalette, AudioHardware, CapFileExists, CapturingNow, HasOverlay, HasDlgVideoSource, HasDlgVideoFormat, HasDlgVideoDisplay, CaptureInitialized, DriverSuppliesPalettes; } PROPRIETA; typedef struct { UINT Tipo; HWND hwnd; Supervisore* Angelo; Intercetta* Mago; Decisore* Oz; imgdes* Immagine; imgdes* ImmagineEye; RETTANGOLI* Rettangoli; UINT Stato; bool Ucciso; } PARAMS, *PPARAMS; typedef struct { HWND hwnd; Supervisore* Angelo; UINT Stato; bool Ucciso; } PARAMS_POSIZIONA, *PPARAMS_POSIZIONA; typedef struct { bool DueWeb; bool Segnale; bool Aggancio; RECT Dimensioni; } CUBE,*PCUBE; typedef struct { float X,Y; } PUNTO; // Classi class Supervisore { public: // Angelo 7 inline void Supervisore() {} Set(HWND, HMENU, char*, Cattura*,Cattura*, Intercetta*,Intercetta*, Palette*,Palette*, Decisore*, GestoreMenu*, GestoreErrore*, GestoreFinestre*); SethInstance(HINSTANCE); GetDueWeb(); void bool // Setto l'handler delle Finestre Secondarie e d'Applicazione: void void SetFinestreSecondarie(UINT,HWND); SetFinestreApplicazione(UINT,HWND,bool); // Ottengo l'handler delle Finestre Secondarie e d'Applicazione: HWND HWND GetFinestreSecondarie(UINT); GetFinestreApplicazione(UINT); // Controllo se sono visibili le Finestre Secondarie e d'Applicazione: bool bool VisibileSecondarie(UINT); VisibileApplicazione(UINT); // Funzione che definisce l'Applicazione prescelta: void Prescelta(UINT); // Funzioni che gestiscono i messaggi: void void void Messaggio(UINT); Messaggio(UINT,UINT); Messaggio(UINT,UINT,UINT); // Funzione che permette l'handshake tra i Thread: void HandShake(UINT,UINT,UINT); public: CUBE private: Cube; void Scossa(); private: HWND HINSTANCE Cattura Intercetta Palette Decisore GestoreMenu GestoreErrore GestoreFinestre static char FinestraDlgSettaggi; hInstance; *CameraUno, *CameraDue; *MagoFrontale, *MagoLaterale; *TavolozzaFrontale, *TavolozzaLaterale; *Oz; *Menu; *Errore; *Finestre; NomeFileUno[20], NomeFileDue[20]; }; class GestoreMenu // Menu { public: void Set(HMENU,PCUBE,GestoreFinestre*); void Messaggio(UINT); private: HMENU MenuPrincipale; GestoreFinestre *Finestre; PCUBE Cube; // devono essere solo letti }; class GestoreErrore { public: void void private: void HWND // Errore Set(HWND,char*); Messaggio(UINT); SetMessageBox(char*); FinestraPrincipale; 8 char* szAppName; }; class GestoreFinestre // Finestre { public: // Settaggio dell'Oggetto: void Set(HWND); // Setto l'handler delle Finestre Secondarie e d'Applicazione: void void SetFinestreSecondarie(UINT,HWND); SetFinestreApplicazione(UINT,HWND,bool); // Ottengo l'handler delle Finestre Secondarie e d'Applicazione: HWND HWND HWND GetFinestreSecondarie(UINT); GetFinestreApplicazione(UINT); GetFinestreApplicazione(); // Verifico la visibilità delle Finestre Secondarie e d'Applicazione: bool bool GetVisibileSecondarie(UINT); GetVisibileApplicazione(UINT); // Setto la Visibilità delle Finestre Secondarie: void SetVisibileSecondarie(UINT,bool); // Setto l'Applicazione prescelta: void SetPresceltaApplicazione(UINT); // Verifico se l'Applicazione è prescelta: bool GetPresceltaApplicazione(UINT); // Gestore dei Messaggi: void Messaggio(UINT); private: typedef struct CELLA_FINESTRA { UINT Tipo; HWND hwndFinestra; bool Visibile; bool Ripristino; struct CELLA_FINESTRA *Successivo; }; typedef struct CELLA_APPLICAZIONE { UINT Tipo; HWND hwndApplicazione; bool Prescelta; bool Visibile; struct CELLA_APPLICAZIONE *Successivo; }; private: HWND FinestraPrincipale; struct CELLA_FINESTRA *Testa,*Coda,*Indice,*Temp; struct CELLA_APPLICAZIONE *TestaLA,*CodaLA,*IndiceLA,*TempLA; }; class Cattura // CameraUno,CameraDue { public: inline Cattura() {} bool Disconnetti(); UINT Set(HWND,UINT,char*,int,int,int,int); UINT GetFormato(); UINT GetSorgente(); // bool GetInformazioni(); bool GetFoto(); bool GetFile(); void GetSettaggi(PROPRIETA*); HWND GetFinestraCattura(); // UINT OverLay(); private: HWND FinestraPadre, FinestraCattura; CAPTUREPARMS ParametriCattura; 9 CAPDRIVERCAPS CAPSTATUS char int UINT char char char* int CapacitaDriver; StatoCattura; cNomeDriver[80]; x,y,larghezza,lunghezza; Indice; Nome[80]; Versione[80]; NomeFile; Width; }; class Intercetta // Mago { public: inline Intercetta() {} UINT GetPosizioneI(); void GetPosizioneEye(); bool GetPosizioneII(); imgdes* GetImmagine(); imgdes* GetImmagineEye(); bool GetAttivo(); void SetAttivo(bool); UINT Set(Cattura*,char*); RETTANGOLI* SetColoreAttivo(UINT,RECT*); RETTANGOLI* GetRettangoli(); CELLA_POSIZIONE* GetPosizioni(); void Lock(); void UnLock(UINT); UINT GetProgresso(); void Free(); public: imgdes Immagine; imgdes ImmagineEye; RETTANGOLI Rettangoli; CELLA_POSIZIONE *Posizioni; private: typedef struct LIMITI_COLORE { bool ColoreAttivo; bool ColoreTrovato; UINT TipoColore; RECT Zona; RECT Dominio; RECT Focus; RECT Lento; RECT FocusEye; RECT ZonaEye; short RossoMin,RossoMax, VerdeMin,VerdeMax, BluMin,BluMax; }; typedef struct HIT { int Bersagli; RECT Zona; RECT ZonaEye; }; typedef struct PROGRESSO { RECT Lento; }; private: bool TrovaColoreFocus(); bool TrovaColoreDominio(); void ConfrontaColore(COLORE,int,int); void Dominio(); void Focus(); UINT SetLento(); private: bool Attivo; bool DominioAttivo; Cattura* Camera; HWND FinestraSorgente; char* NomeFile; struct LIMITI_COLORE Colore; struct HIT Hit[100]; struct PROGRESSO Progresso[NUMERO_AQUISIZIONI_PROGRESSO]; short Indice; short IndiceProgresso; UINT StatoCattura; UINT StatoPosiziona; UINT StatoProgresso; CELLA_POSIZIONE *Testa,*Coda,*Temp; 10 int Width,Height,BitCount; }; class Palette // Tavolozza { public: inline Palette() {} void Set(); void SetAttivo(bool); bool GetAttivo(); void Pulisci(); void Colora(imgdes*); CELLA_COLORE_LS* GetListaColore(); RECT* GetZonaColore(UINT); public: struct CELLA_COLORE_LS *TestaLS,*CodaLS,*IndiceLS,*TempLS; private: typedef struct CELLA_COLORE_LC { COLORE Colore; SCOSTAMENTO Scostamento; UINT TipoColore; RECT Zona; int Bersagli; int Posizione; struct CELLA_COLORE_LC *Precedente,*Successivo; }; struct CELLA_COLORE_LC *TestaLC,*IndiceLC,*CodaLC,*TempLC; int Elementi; int X,Y; bool Attivo; private: bool Trovato(CELLA_COLORE_LC*); void In(COLORE); UINT TipoColore(CELLA_COLORE_LC*); }; class Decisore // Oz { public: inline Decisore() {} void Set(RETTANGOLI*,CELLA_POSIZIONE*,int,int); void SetAttivo(bool); void SetDimensioniFinestra(int,int); bool GetAttivo(); UINT GetDecisione(); void Pulisci(); PUNTO *GetPunto(); public: PUNTO Punto; private: typedef struct SCOSTAMENTO { short x,y; } SCOSTAMENTO; typedef struct CELLA_LINEA { short NumeroLinea; SCOSTAMENTO *Linea; struct CELLA_LINEA *Precedente,*Successivo; }; typedef struct CLICK { RECT Regione; short Attinenze; }; struct CELLA_LINEA *Testa,*Coda,*Indice,*Temp; struct CLICK Click; RETTANGOLI *Rettangoli; int NumeroDiLinee,NumeroDiColonne; short Contatore; float X,Y; SCOSTAMENTO *Scostamento; bool Attivo; private: SCOSTAMENTO* GetScostamento(long,long); }; Messaggi // Identificativi 11 // Lista Semplice // Lista Complessa #define #define #define #define AN_WEB_FRONTALE AN_WEB_LATERALE AN_THREAD_POSIZIONA AN_THREAD_INTERCETTA 1 2 3 4 #define #define #define #define STATO_CATTURA_INIZIO STATO_CATTURA_VERDE STATO_CATTURA_GIALLO STATO_CATTURA_ROSSO 11 12 13 14 #define #define #define #define #define #define #define #define #define #define #define #define #define STATO_ZERO STATO_UNO STATO_DUE STATO_TRE STATO_QUATTRO STATO_CINQUE STATO_SEI STATO_SETTE STATO_OTTO STATO_NOVE STATO_FINE STATO_MANUALE STATO_LAVORO 100 101 102 103 104 105 106 107 108 109 110 111 #define #define #define #define #define STATO_PROGRESSO_INIZIO STATO_PROGRESSO_UNO STATO_PROGRESSO_DUE STATO_PROGRESSO_TRE STATO_PROGRESSO_FINE 112 200 201 202 203 204 #define FINESTRE_ATTIVE #define FINESTRA_PRINCIPALE #define FINESTRA_IMMAGINE_FRONTALE #define FINESTRA_IMMAGINE_LATERALE #define FINESTRA_PALETTE_FRONTALE #define FINESTRA_PALETTE_LATERALE #define FINESTRA_WEB_UNO #define FINESTRA_WEB_DUE #define FINESTRA_CONTROLLI #define FINESTRA_POSIZIONA #define FINESTRA_APPLICAZIONE #define FINESTRE_SECONDARIE 300 301 302 303 304 305 306 307 308 309 310 311 #define FINESTRA_APPLICAZIONE_DEMO_UNO #define FINESTRA_APPLICAZIONE_DEMO_DUE #define FINESTRA_APPLICAZIONE_DEMO_TRE //... 400 401 402 // I messaggi successivi sono usati nelle WinProc e possono creare conflitto ,se // li definisco con una mia metrica privata con i messaggi di Windows ,usando // WM_USER sono sicuro che essi sono unici // Messaggi per le WinProc #define WM_START_THREAD_I (WM_USER + 1) // da Supervisore per ImmagineFrontale\LateraleProc #define WM_STOP_THREAD_I (WM_USER + 2) // da Supervisore per ImmagineFrontale\LateraleProc #define WM_SINGOLA_IMMAGINE_I (WM_USER + 3) // da Supervisore per ImmagineFrontale\LateraleProc #define WM_START_THREAD_II (WM_USER + 4) // da Supervisore per ImmagineFrontaleProc #define WM_STOP_THREAD_II (WM_USER + 5) // da Supervisore per ImmagineFrontaleProc #define WM_SINGOLA_IMMAGINE_II(WM_USER + 6) // da Supervisore per ImmagineFrontaleProc #define WM_TAVOLOZZA_PRONTA (WM_USER + 7) // da Supervisore per PaletteProc #define WM_COLORE_PRONTO (WM_USER + 8) // da Supervisore per ImmagineFrontale\LateraleProc #define WM_TAVOLOZZA_ATTIVA (WM_USER + 9) // da Supervisore per PaletteProc #define WM_STATO_CATTURA_MUTATO_FRONTALE (WM_USER + 10) // da Supervisore per SemaforiProc #define WM_STATO_CATTURA_MUTATO_LATERALE (WM_USER + 11) // da Supervisore per SemaforiProc #define WM_STATO_CATTURA_MUTATO (WM_USER + 12) // da Supervisore per ApplicazioneProc #define WM_START_POSIZIONA (WM_USER + 13) // da Supervisore per PosizionaProc #define WM_STATO_MUTATO (WM_USER + 14) // da Supervisore per ImmagineFrontaleProc #define WM_STOP_POSIZIONA (WM_USER + 15) // da Supervisore per PosizionaProc #define WM_STATO_PROGRESSO_MUTATO (WM_USER + 16) // da Supervisore per PosizionaProc #define WM_POSIZIONI_PRONTE (WM_USER + 17) // da Supervisore per ImmagineFrontale #define WM_CREATE_PUNTO (WM_USER + 18) // da Supervisore per ApplicazioneProc #define WM_CLICK (WM_USER + 19) // da Supervisore per ApplicazioneProc #define WM_NON_VALIDO (WM_USER + 20) // da Supervisore per ApplicazioneProc // I messaggi successivi sono usati nelle mie procedure dunque non creano conflitto // e posso definirli con una mia metrica // Messaggi di operazioni avvenute correttamente #define AN_OK_AGGANCIO #define AN_OK_RILASCIO #define AN_OK_START_FRONTALE 1001 1002 1003 // // // 12 da Supervisore per GestoreMenu da Supervisore per GestoreMenu da Supervisore per GestoreMenu #define AN_OK_STOP_FRONTALE 1004 // da #define AN_OK_START_LATERALE 1005 // da #define AN_OK_STOP_LATERALE 1006 // da #define AN_OK_START_FRONTALE_II 1007 // da #define AN_OK_STOP_FRONTALE_II 1008 // da #define AN_OK_DUE_WEB_NO 1009 // da #define AN_OK_DUE_WEB_SI 1010 // da #define AN_OK_VISUALIZZA_FRONTALE 1025 // da #define AN_OK_VISUALIZZA_LATERALE 1026 // da #define AN_OK_COLORE_SCELTO_FRONTALE 1027 // da #define AN_OK_COLORE_SCELTO_LATERALE 1028 // da #define AN_OK_FINESTRA_IMMAGINE_FRONTALE_VISIBILE_NO #define AN_OK_FINESTRA_IMMAGINE_FRONTALE_VISIBILE_SI #define AN_OK_FINESTRA_IMMAGINE_LATERALE_VISIBILE_NO #define AN_OK_FINESTRA_IMMAGINE_LATERALE_VISIBILE_SI #define AN_OK_FINESTRA_PALETTE_FRONTALE_VISIBILE_NO #define AN_OK_FINESTRA_PALETTE_FRONTALE_VISIBILE_SI #define AN_OK_FINESTRA_PALETTE_LATERALE_VISIBILE_NO #define AN_OK_FINESTRA_PALETTE_LATERALE_VISIBILE_SI #define AN_OK_FINESTRA_CONTROLLI_VISIBILE_NO #define AN_OK_FINESTRA_CONTROLLI_VISIBILE_SI #define AN_OK_FINESTRA_WEB_UNO_VISIBILE_NO #define AN_OK_FINESTRA_WEB_UNO_VISIBILE_SI #define AN_OK_FINESTRA_WEB_DUE_VISIBILE_NO #define AN_OK_FINESTRA_WEB_DUE_VISIBILE_SI 1042 #define AN_OK_START_POSIZIONA 1043 #define AN_OK_STOP_POSIZIONA 1044 #define AN_OK_VISUALIZZA_FRONTALE_II 1045 #define AN_OK_START_MANUALE 1046 #define AN_OK_STOP_MANUALE 1047 #define AN_OK_OK 1048 #define AN_OK_RITORNA 1049 #define AN_OK_APPLICAZIONE_PRESCELTA 1050 Supervisore per GestoreMenu Supervisore per GestoreMenu Supervisore per GestoreMenu Supervisore per GestoreMenu Supervisore per GestoreMenu Supervisore per GestoreMenu Supervisore per GestoreMenu Supervisore per GestoreMenu Supervisore per GestoreMenu Supervisore per GestoreMenu Supervisore per GestoreMenu 1029 // da Supervisore per GestoreMenu 1030 // da Supervisore per GestoreMenu 1031 // da Supervisore per GestoreMenu 1032 // da Supervisore per GestoreMenu 1033 // da Supervisore per GestoreMenu 1034 // da Supervisore per GestoreMenu 1035 // da Supervisore per GestoreMenu 1036 // da Supervisore per GestoreMenu 1037 // da Supervisore per GestoreMenu 1038 // da Supervisore per GestoreMenu 1039 // da Supervisore per GestoreMenu 1040 // da Supervisore per GestoreMenu 1041 // da Supervisore per GestoreMenu // da Supervisore per GestoreMenu // da Supervisore per GestoreMenu // da Supervisore per GestoreMenu // da Supervisore per GestoreMenu // da Supervisore per GestoreMenu // da Supervisore per GestoreMenu // da supervisore per GestoreMenu // da Supervisore per GestoreMenu // da Supervisore per GestoreMenu // Messaggi di operazioni fallite #define #define #define #define #define #define #define #define #define #define #define #define AN_NO_AGGANCIO AN_NO_DATI AN_NO_FORMATO_ERRORE AN_NO_FINESTRA_FORMATO AN_NO_SORGENTE_ERRORE AN_NO_FINESTRA_SORGENTE AN_NO_FOTO AN_NO_RILASCIO_WEB_UNO AN_NO_FILE AN_NO_ALLOCIMAGE AN_NO_ELABORAZIONE AN_NO_RILASCIO_WEB_DUE 2001 2002 2003 2004 2005 2006 2007 2008 2009 2010 2011 2012 // // // // // // // // // // // // da da da da da da da da da da da da Supervisore Supervisore Supervisore Supervisore Supervisore Supervisore Supervisore Supervisore Supervisore Supervisore Supervisore Supervisore per per per per per per per per per per per per GestoreErrore GestoreErrore GestoreErrore GestoreErrore GestoreErrore GestoreErrore GestoreErrore GestoreErrore GestoreErrore GestoreErrore GestoreErrore GestoreErrore 3001 // da MenuCattura per Supervisore // da // da // da // da // da // da // da // da // da // da // da // da // da // da // da // da // da 4019 4020 4021 4022 4023 4024 4025 4026 4027 4028 4029 MenuCattura per Supervisore MenuCattura per Supervisore MenuCattura per Supervisore MenuCattura per Supervisore MenuCattura per Supervisore MenuCattura per Supervisore MenuCattura per Supervisore MenuIntercetta per Supervisore MenuIntercetta per Supervisore MenuIntercetta per Supervisore MenuIntercetta per Supervisore MenuIntercetta per Supervisore MenuIntercetta per Supervisore MenuIntercetta per Supervisore PaletteProc per Supervisore; ThreadIntercettaFrontale/Laterale per Supervisore MenuPosiziona per Supervisore // da MenuSet per Supervisore // da MenuSet per Supervisore // da MenuSet per Supervisore // da MenuSet per Supervisore // da MenuSet per Supervisore // da MenuSet per Supervisore // da MenuSet per Supervisore // da ThreadPosiziona per Supervisore // da ThreadIntercettaFrontale per Supervisore // da ThreadPosiziona per Supervisore // da ThreadIntercettaFrontale per Supervisore // Messaggi di OnOff #define AN_ONOFF_DUE_WEB // Messaggi di richiesta #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define AN_ASK_AGGANCIO 4001 AN_ASK_FINESTRA_FORMATO 4002 AN_ASK_FINESTRA_SORGENTE 4003 AN_ASK_FOTO 4004 AN_ASK_RILASCIO 4005 AN_ASK_DLG_PROPRIETA 4006 AN_ASK_FILE 4007 AN_ASK_VISUALIZZA 4008 AN_ASK_START 4009 AN_ASK_STOP 4010 AN_ASK_VISUALIZZA_II 4011 AN_ASK_START_II 4012 AN_ASK_STOP_II 4013 AN_ASK_PALETTE 4014 AN_ASK_INTERCETTA_COLORE 4016 AN_ASK_STATO_CATTURA_MUTATO 4017 AN_ASK_START_POSIZIONA 4018 AN_ASK_FINESTRA_IMMAGINE_FRONTALE AN_ASK_FINESTRA_IMMAGINE_LATERALE AN_ASK_FINESTRA_PALETTE_FRONTALE AN_ASK_FINESTRA_PALETTE_LATERALE AN_ASK_FINESTRA_CONTROLLI AN_ASK_FINESTRA_WEB_UNO AN_ASK_FINESTRA_WEB_DUE AN_ASK_INIZIO_STATO AN_ASK_FINE_STATO AN_ASK_FINE_SETTAGGIO AN_ASK_STATO_COMPLETATO 13 #define #define #define #define #define #define #define #define AN_ASK_STOP_POSIZIONA AN_ASK_STATO_PROGRESSO_MUTATO AN_ASK_START_MANUALE AN_ASK_STOP_MANUALE AN_ASK_OK AN_ASK_RITORNA AN_ASK_DECISIONE_AVVENUTA AN_ASK_CLICK_TEMPORALE_AVVENUTA 4030 4031 4032 4033 4034 4035 4036 4037 // // // // // // // // da da da da da da da da MenuPosiziona per Supervisore ThreadIntercettaFrontale per Supervisore MenuPosiziona per Supervisore MenuPosiziona per Supervisore MenuPosiziona per Supervisore MenuPosiziona per Supervisore ThreadIntercettaFrontale per Supervisore ThreadIntercettaFrontale per Supervisore // // // // // // // // // // // // // Cattura Cattura Cattura Cattura Cattura Cattura Cattura Cattura Cattura Intercetta Decisore Decisore Decisore // Codici di ritorno delle varie funzioni #define AN_RET_OK #define AN_RET_AGGANCIO_NO #define AN_RET_DATI_NO #define AN_RET_FORMATO_ERRORE #define AN_RET_FORMATO_NO #define AN_RET_SORGENTE_ERRORE #define AN_RET_SORGENTE_NO #define AN_RET_OVERLAY_ERRORE #define AN_RET_OVERLAY_NO #define AN_RET_ALLOC_NO #define AN_RET_NO_DECISIONE #define AN_RET_SI_DECISIONE #define AN_RET_CLICK_TEMPORALE 6001 6002 6003 6004 6005 6006 6007 6008 6009 6010 6011 6012 6013 da da da da da da da da da da da da da per per per per per per per per per per per per per Supervisore Supervisore Supervisore Supervisore Supervisore Supervisore Supervisore Supervisore Supervisore Supervisore ThreadIntercettaFrontale ThreadIntercettaFrontale ThreadIntercettaFrontale // Codici per i colori nella Tavolozza #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define GRIGIO_1 GRIGIO_2 GRIGIO_3 GRIGIO_4 GRIGIO_5 ROSSO_1 ROSSO_2 ROSSO_3 ROSSO_4 ROSSO_5 ROSSO_6 VERDE_1 VERDE_2 VERDE_3 VERDE_4 VERDE_5 VERDE_6 BLU_1 BLU_2 BLU_3 BLU_4 BLU_5 BLU_6 GIALLO_1 GIALLO_2 GIALLO_3 CYANO_1 CYANO_2 CYANO_3 VIOLETTO_1 VIOLETTO_2 VIOLETTO_3 INDEFINITO 7001 7002 7003 7004 7005 7006 7007 7008 7009 7010 7011 7012 7013 7014 7015 7016 7017 7018 7019 7020 7021 7022 7023 7024 7025 7026 7027 7028 7029 7030 7031 7032 7033 // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // da da da da da da da da da da da da da da da da da da da da da da da da da da da da da da da da da TrovaGrigio TrovaGrigio TrovaGrigio TrovaGrigio TrovaGrigio TrovaColore TrovaColore TrovaColore TrovaColore TrovaColore TrovaColore TrovaColore TrovaColore TrovaColore TrovaColore TrovaColore TrovaColore TrovaColore TrovaColore TrovaColore TrovaColore TrovaColore TrovaColore TrovaColore TrovaColore TrovaColore TrovaColore TrovaColore TrovaColore TrovaColore TrovaColore TrovaColore TrovaColore (Palette) (Palette) (Palette) (Palette) (Palette) (Palette) (Palette) (Palette) (Palette) (Palette) (Palette) (Palette) (Palette) (Palette) (Palette) (Palette) (Palette) (Palette) (Palette) (Palette) (Palette) (Palette) (Palette) (Palette) (Palette) (Palette) (Palette) (Palette) (Palette) (Palette) (Palette) (Palette) (Palette) Supervisore // Definizioni membri statici: char Supervisore::NomeFileUno[20] = "Z:\\FileUno.bmp"; char Supervisore::NomeFileDue[20] = "Z:\\FileDue.bmp"; // Definizioni funzioni membro: void Supervisore::SethInstance(HINSTANCE hInstance) { this->hInstance=hInstance; } bool Supervisore::GetDueWeb() { return Cube.DueWeb; } // Setto l'handler delle Finestre Secondarie e d'Applicazione: void Supervisore::SetFinestreSecondarie(UINT Tipo,HWND Finestra) { Finestre->SetFinestreSecondarie(Tipo,Finestra); } void Supervisore::SetFinestreApplicazione(UINT Tipo,HWND Finestra,bool Prescelta) { Finestre->SetFinestreApplicazione(Tipo,Finestra,Prescelta); } // Ottengo l'handler delle Finestre Secondarie e d'Applicazione: HWND Supervisore::GetFinestreSecondarie(UINT Tipo){ return Finestre->GetFinestreSecondarie(Tipo); } HWND Supervisore::GetFinestreApplicazione(UINT Tipo) { return Finestre->GetFinestreApplicazione(Tipo); } 14 // Controllo la visibilità delle Finestre Secondarie e d'Applicazione: bool Supervisore::VisibileSecondarie(UINT Tipo){ return Finestre->GetVisibileSecondarie(Tipo); } bool Supervisore::VisibileApplicazione(UINT Tipo){ return Finestre->GetVisibileApplicazione(Tipo); } // Definisco l'Applicazione prescelta: void Supervisore::Prescelta(UINT Tipo) { Finestre->SetPresceltaApplicazione(Tipo); Menu->Messaggio(AN_OK_APPLICAZIONE_PRESCELTA); } // Settaggio dell'Angelo: void Supervisore::Set( HWND HMENU char* Cattura* Cattura* Intercetta* Intercetta* Palette* Palette* Decisore* GestoreMenu* GestoreErrore* GestoreFinestre* FinestraPrincipale, MenuPrincipale, szAppName, CameraUno, CameraDue, MagoFrontale, MagoLaterale, TavolozzaFrontale, TavolozzaLaterale, Oz, Menu, Errore, Finestre) { this->CameraUno = CameraUno; this->CameraDue = CameraDue; this->MagoFrontale = MagoFrontale; this->MagoLaterale = MagoLaterale; this->TavolozzaFrontale = TavolozzaFrontale; this->TavolozzaLaterale = TavolozzaLaterale; this->Oz = Oz; this->Menu = Menu; this->Errore = Errore; this->Finestre = Finestre; this->Menu->Set(MenuPrincipale,&Cube,Finestre); this->Errore->Set(FinestraPrincipale,szAppName); this->Finestre->Set(FinestraPrincipale); Cube.DueWeb = false; Cube.Aggancio = false; GetWindowRect(FinestraPrincipale,&Cube.Dimensioni); MagoFrontale->SetAttivo(false); MagoLaterale->SetAttivo(false); TavolozzaFrontale->SetAttivo(false); TavolozzaLaterale->SetAttivo(false); Oz->SetAttivo(false); } void { Supervisore::Scossa() GetWindowRect(Finestre->GetFinestreSecondarie(FINESTRA_PRINCIPALE),&Cube.Dimensioni); MoveWindow(Finestre->GetFinestreSecondarie(FINESTRA_PRINCIPALE), Cube.Dimensioni.left, Cube.Dimensioni.top, Cube.Dimensioni.right-Cube.Dimensioni.left, Cube.Dimensioni.bottom-Cube.Dimensioni.top-1,true); MoveWindow(Finestre->GetFinestreSecondarie(FINESTRA_PRINCIPALE), Cube.Dimensioni.left, Cube.Dimensioni.top, Cube.Dimensioni.right-Cube.Dimensioni.left, Cube.Dimensioni.bottom-Cube.Dimensioni.top,true); } void { Supervisore::Messaggio(UINT Messaggio) switch (Messaggio) { // Gestione di OnOff: // Due Web: case AN_ONOFF_DUE_WEB: if(Cube.DueWeb) { Cube.DueWeb = false; Menu->Messaggio(AN_OK_DUE_WEB_NO); } else { Cube.DueWeb = true; Menu->Messaggio(AN_OK_DUE_WEB_SI); } InvalidateRect(Finestre->GetFinestreSecondarie(FINESTRA_CONTROLLI),NULL,true); break; 15 // Gestione delle finestre Visibili: //Finestra Immagine Frontale: case AN_ASK_FINESTRA_IMMAGINE_FRONTALE: if(Finestre->GetVisibileSecondarie(FINESTRA_IMMAGINE_FRONTALE)) { Finestre->SetVisibileSecondarie(FINESTRA_IMMAGINE_FRONTALE,false); Menu->Messaggio(AN_OK_FINESTRA_IMMAGINE_FRONTALE_VISIBILE_NO); } else { Finestre->SetVisibileSecondarie(FINESTRA_IMMAGINE_FRONTALE,true); Menu->Messaggio(AN_OK_FINESTRA_IMMAGINE_FRONTALE_VISIBILE_SI); } Scossa(); break; // Finestra Immagine Laterale: case AN_ASK_FINESTRA_IMMAGINE_LATERALE: if(Finestre->GetVisibileSecondarie(FINESTRA_IMMAGINE_LATERALE)) { Finestre->SetVisibileSecondarie(FINESTRA_IMMAGINE_LATERALE,false); Menu->Messaggio(AN_OK_FINESTRA_IMMAGINE_LATERALE_VISIBILE_NO); } else { Finestre->SetVisibileSecondarie(FINESTRA_IMMAGINE_LATERALE,true); Menu->Messaggio(AN_OK_FINESTRA_IMMAGINE_LATERALE_VISIBILE_SI); } Scossa(); break; // Finestra Palette Frontale: case AN_ASK_FINESTRA_PALETTE_FRONTALE: if(Finestre->GetVisibileSecondarie(FINESTRA_PALETTE_FRONTALE)) { Finestre->SetVisibileSecondarie(FINESTRA_PALETTE_FRONTALE,false); Menu->Messaggio(AN_OK_FINESTRA_PALETTE_FRONTALE_VISIBILE_NO); } else { Finestre->SetVisibileSecondarie(FINESTRA_PALETTE_FRONTALE,true); Menu->Messaggio(AN_OK_FINESTRA_PALETTE_FRONTALE_VISIBILE_SI); } Scossa(); break; // Finestra Palette Laterale: case AN_ASK_FINESTRA_PALETTE_LATERALE: if(Finestre->GetVisibileSecondarie(FINESTRA_PALETTE_LATERALE)) { Finestre->SetVisibileSecondarie(FINESTRA_PALETTE_LATERALE,false); Menu->Messaggio(AN_OK_FINESTRA_PALETTE_LATERALE_VISIBILE_NO); } else { Finestre->SetVisibileSecondarie(FINESTRA_PALETTE_LATERALE,true); Menu->Messaggio(AN_OK_FINESTRA_PALETTE_LATERALE_VISIBILE_SI); } Scossa(); break; // Finestra Semafori: case AN_ASK_FINESTRA_CONTROLLI: if(Finestre->GetVisibileSecondarie(FINESTRA_CONTROLLI)) { Finestre->SetVisibileSecondarie(FINESTRA_CONTROLLI,false); Menu->Messaggio(AN_OK_FINESTRA_CONTROLLI_VISIBILE_NO); } else { Finestre->SetVisibileSecondarie(FINESTRA_CONTROLLI,true); Menu->Messaggio(AN_OK_FINESTRA_CONTROLLI_VISIBILE_SI); } Scossa(); break; // Finestra Web Uno: case AN_ASK_FINESTRA_WEB_UNO: if(Finestre->GetVisibileSecondarie(FINESTRA_WEB_UNO)) { Finestre->SetVisibileSecondarie(FINESTRA_WEB_UNO,false); Menu->Messaggio(AN_OK_FINESTRA_WEB_UNO_VISIBILE_NO); } 16 else { Finestre->SetVisibileSecondarie(FINESTRA_WEB_UNO,true); Menu->Messaggio(AN_OK_FINESTRA_WEB_UNO_VISIBILE_SI); } Scossa(); break; // Finestra Web Due: case AN_ASK_FINESTRA_WEB_DUE: if(Finestre->GetVisibileSecondarie(FINESTRA_WEB_DUE)) { Finestre->SetVisibileSecondarie(FINESTRA_WEB_DUE,false); Menu->Messaggio(AN_OK_FINESTRA_WEB_DUE_VISIBILE_NO); } else { Finestre->SetVisibileSecondarie(FINESTRA_WEB_DUE,true); Menu->Messaggio(AN_OK_FINESTRA_WEB_DUE_VISIBILE_SI); } Scossa(); break; // Richiesta di posizionamento automatico: case AN_ASK_START_POSIZIONA: Finestre->Messaggio(FINESTRA_POSIZIONA); Menu->Messaggio(AN_OK_START_POSIZIONA); Scossa(); Cube.Segnale = false; SendMessage(Finestre->GetFinestreSecondarie(FINESTRA_POSIZIONA), WM_START_POSIZIONA,0,0); break; case AN_ASK_STOP_POSIZIONA: SendMessage(Finestre->GetFinestreSecondarie(FINESTRA_IMMAGINE_FRONTALE), WM_STATO_MUTATO,0,(long) STATO_ZERO); if(Cube.DueWeb == true) SendMessage(Finestre->GetFinestreSecondarie(FINESTRA_IMMAGINE_LATERALE), WM_STATO_MUTATO,0,(long) STATO_ZERO); SendMessage(Finestre->GetFinestreSecondarie(FINESTRA_POSIZIONA), WM_STOP_POSIZIONA,0,0); Finestre->Messaggio(FINESTRE_ATTIVE); Menu->Messaggio(AN_OK_STOP_POSIZIONA); Scossa(); break; // Richiesta di posizionamento manuale: case AN_ASK_START_MANUALE: Menu->Messaggio(AN_OK_START_MANUALE); SendMessage(Finestre->GetFinestreSecondarie(FINESTRA_IMMAGINE_FRONTALE), WM_STATO_MUTATO,0,(long) STATO_MANUALE); break; case AN_ASK_STOP_MANUALE: RETTANGOLI *Rettangoli; SendMessage(Finestre->GetFinestreSecondarie(FINESTRA_IMMAGINE_FRONTALE), WM_STATO_MUTATO,0,(long) STATO_ZERO); Menu->Messaggio(AN_OK_STOP_MANUALE); Rettangoli = MagoFrontale->GetRettangoli(); if(Oz->GetAttivo()) Oz->Pulisci(); Oz->Set(Rettangoli,NULL, (Cube.Dimensioni.right - Cube.Dimensioni.left), (Cube.Dimensioni.bottom - Cube.Dimensioni.top)); break; // Richiesta per il lancio dell'Applicazione: case AN_ASK_OK: PUNTO *Punto; SendMessage(Finestre->GetFinestreSecondarie(FINESTRA_IMMAGINE_FRONTALE), WM_STATO_MUTATO,0,(long) STATO_LAVORO); Menu->Messaggio(AN_OK_OK); Finestre->Messaggio(FINESTRA_APPLICAZIONE); Punto = Oz->GetPunto(); SendMessage(Finestre->GetFinestreApplicazione(), WM_CREATE_PUNTO,0,(long) Punto); SendMessage(Finestre->GetFinestreApplicazione(), WM_STATO_CATTURA_MUTATO,0,(long) STATO_CATTURA_VERDE); Scossa(); break; case AN_ASK_RITORNA: SendMessage(Finestre->GetFinestreSecondarie(FINESTRA_IMMAGINE_FRONTALE), 17 WM_STATO_MUTATO,0,(long) STATO_ZERO); Menu->Messaggio(AN_OK_RITORNA); Finestre->Messaggio(FINESTRE_SECONDARIE); Scossa(); break; // Richiesta di decisione avvenuta: case AN_ASK_DECISIONE_AVVENUTA: SendMessage(Finestre->GetFinestreApplicazione(),WM_NON_VALIDO,0,0); break; // richiesta di click temporale: case AN_ASK_CLICK_TEMPORALE_AVVENUTA: SendMessage(Finestre->GetFinestreApplicazione(),WM_CLICK,0,0); break; } } void Supervisore::Messaggio(UINT Messaggio,UINT Generatore) { switch (Generatore) { // Gestione richieste da web: case AN_WEB_FRONTALE: switch (Messaggio) { // Aggancio case AN_ASK_AGGANCIO: switch (CameraUno->Set(Finestre->GetFinestreSecondarie(FINESTRA_WEB_UNO), AN_WEB_FRONTALE,(char*) &NomeFileUno,0,0,400,400)) { case AN_RET_AGGANCIO_NO: Errore->Messaggio(AN_NO_AGGANCIO); break; case AN_RET_DATI_NO: Errore->Messaggio(AN_NO_DATI); break; case AN_RET_OK: Menu->Messaggio(AN_OK_AGGANCIO); Cube.Aggancio = true; break; } break; // Finestra Formato case AN_ASK_FINESTRA_FORMATO: switch (CameraUno->GetFormato()) { case AN_RET_FORMATO_ERRORE: Errore->Messaggio(AN_NO_FORMATO_ERRORE); break; case AN_RET_FORMATO_NO: Errore->Messaggio(AN_NO_FINESTRA_FORMATO); break; case AN_RET_OK: break; } break; // Finestra Sorgente case AN_ASK_FINESTRA_SORGENTE: switch (CameraUno->GetSorgente()) { case AN_RET_SORGENTE_ERRORE: Errore->Messaggio(AN_NO_SORGENTE_ERRORE); break; case AN_RET_SORGENTE_NO: Errore->Messaggio(AN_NO_FINESTRA_SORGENTE); break; case AN_RET_OK: break; } break; // Foto case AN_ASK_FOTO: if(!CameraUno->GetFoto()) Errore->Messaggio(AN_NO_FOTO); break; // Rilascio case AN_ASK_RILASCIO: if(!CameraUno->Disconnetti()) Errore->Messaggio(AN_NO_RILASCIO_WEB_UNO); else Menu->Messaggio(AN_OK_RILASCIO); Cube.Aggancio = false; 18 SendMessage(Finestre->GetFinestreSecondarie(FINESTRA_IMMAGINE_FRONTALE), WM_STOP_THREAD_I,0,0); SendMessage(Finestre->GetFinestreSecondarie(FINESTRA_IMMAGINE_FRONTALE), WM_STOP_THREAD_II,0,0); break; // File case AN_ASK_FILE: if(!CameraUno->GetFile()) Errore->Messaggio(AN_NO_FILE); break; // Palette case AN_ASK_PALETTE: if(TavolozzaFrontale->GetAttivo()) TavolozzaFrontale->Pulisci(); TavolozzaFrontale->Set(); TavolozzaFrontale->Colora(MagoFrontale->GetImmagine()); SendMessage(Finestre->GetFinestreSecondarie(FINESTRA_PALETTE_FRONTALE), WM_TAVOLOZZA_PRONTA,0, (long) TavolozzaFrontale->GetListaColore()); break; // Visualizza case AN_ASK_VISUALIZZA: if(MagoFrontale->GetAttivo()) MagoFrontale->Free(); switch (MagoFrontale->Set(CameraUno,(char*) &NomeFileUno)) { case AN_RET_ALLOC_NO: Errore->Messaggio(AN_NO_ALLOCIMAGE); break; case AN_RET_OK: break; } MagoFrontale->GetPosizioneI(); SendMessage(Finestre->GetFinestreSecondarie(FINESTRA_IMMAGINE_FRONTALE), WM_SINGOLA_IMMAGINE_I,0,(long) MagoFrontale->GetImmagine()); Menu->Messaggio(AN_OK_VISUALIZZA_FRONTALE); break; // Start case AN_ASK_START: SendMessage(Finestre->GetFinestreSecondarie(FINESTRA_IMMAGINE_FRONTALE), WM_START_THREAD_I,(long) Oz,(long) MagoFrontale); Menu->Messaggio(AN_OK_START_FRONTALE); break; // Stop case AN_ASK_STOP: SendMessage(Finestre->GetFinestreSecondarie(FINESTRA_IMMAGINE_FRONTALE), WM_STOP_THREAD_I,0,0); SendMessage(Finestre->GetFinestreSecondarie(FINESTRA_PALETTE_FRONTALE), WM_STOP_THREAD_I,0,0); Menu->Messaggio(AN_OK_STOP_FRONTALE); break; // Visualizza II case AN_ASK_VISUALIZZA_II: if(MagoFrontale->GetAttivo()) MagoFrontale->Free(); switch (MagoFrontale->Set(CameraUno,(char*) &NomeFileUno)) { case AN_RET_ALLOC_NO: Errore->Messaggio(AN_NO_ALLOCIMAGE); break; case AN_RET_OK: break; } if(!MagoFrontale->GetPosizioneII()) Errore->Messaggio(AN_NO_ELABORAZIONE); SendMessage(Finestre->GetFinestreSecondarie(FINESTRA_IMMAGINE_FRONTALE), WM_SINGOLA_IMMAGINE_II,0,(long) MagoFrontale->GetImmagine()); Menu->Messaggio(AN_OK_VISUALIZZA_FRONTALE_II); break; // Start II case AN_ASK_START_II: SendMessage(Finestre->GetFinestreSecondarie(FINESTRA_IMMAGINE_FRONTALE), WM_START_THREAD_II,0,(long) MagoFrontale); Menu->Messaggio(AN_OK_START_FRONTALE_II); break; // Stop II case AN_ASK_STOP_II: SendMessage(Finestre->GetFinestreSecondarie(FINESTRA_IMMAGINE_FRONTALE), WM_STOP_THREAD_II,0,0); Menu->Messaggio(AN_OK_STOP_FRONTALE_II); break; 19 // DLG Proprietà case AN_ASK_DLG_PROPRIETA: PROPRIETA SettaggiCamera = { 0,0,0,NULL, false,false,false,false,false, false,false,false,false,false,false,false,false }; SettaggiCamera.Camera=AN_WEB_FRONTALE; CameraUno->GetSettaggi(&SettaggiCamera); DialogBoxParam(hInstance,TEXT ("PROPRIETADLG"), Finestre->GetFinestreSecondarie(FINESTRA_PRINCIPALE), ProprietaDlgProc,(long) &SettaggiCamera); break; } break; case AN_WEB_LATERALE: switch (Messaggio) { // Aggancio case AN_ASK_AGGANCIO: switch (CameraDue->Set(Finestre->GetFinestreSecondarie(FINESTRA_WEB_DUE), AN_WEB_LATERALE,(char*) &NomeFileDue,0,0,400,400)) { case AN_RET_AGGANCIO_NO: Errore->Messaggio(AN_NO_AGGANCIO); break; case AN_RET_DATI_NO: Errore->Messaggio(AN_NO_DATI); break; case AN_RET_OK: Menu->Messaggio(AN_OK_AGGANCIO); break; } break; // Finestra Formato case AN_ASK_FINESTRA_FORMATO: switch (CameraDue->GetFormato()) { case AN_RET_FORMATO_ERRORE: Errore->Messaggio(AN_NO_FORMATO_ERRORE); break; case AN_RET_FORMATO_NO: Errore->Messaggio(AN_NO_FINESTRA_FORMATO); break; case AN_RET_OK: break; } break; // Finestra Sorgente case AN_ASK_FINESTRA_SORGENTE: switch (CameraDue->GetSorgente()) { case AN_RET_SORGENTE_ERRORE: Errore->Messaggio(AN_NO_SORGENTE_ERRORE); break; case AN_RET_SORGENTE_NO: Errore->Messaggio(AN_NO_FINESTRA_SORGENTE); break; case AN_RET_OK: break; } break; // Foto case AN_ASK_FOTO: if(!CameraDue->GetFoto()) Errore->Messaggio(AN_NO_FOTO); break; // Rilascio case AN_ASK_RILASCIO: if(!CameraDue->Disconnetti()) Errore->Messaggio(AN_NO_RILASCIO_WEB_DUE); else Menu->Messaggio(AN_OK_RILASCIO); SendMessage(Finestre->GetFinestreSecondarie(FINESTRA_IMMAGINE_LATERALE), WM_STOP_THREAD_I,0,0); break; // File case AN_ASK_FILE: if(!CameraDue->GetFile()) Errore->Messaggio(AN_NO_FILE); break; // Palette case AN_ASK_PALETTE: if(TavolozzaLaterale->GetAttivo()) TavolozzaLaterale->Pulisci(); TavolozzaLaterale->Set(); 20 TavolozzaLaterale->Colora(MagoLaterale->GetImmagine()); SendMessage(Finestre->GetFinestreSecondarie(FINESTRA_PALETTE_LATERALE), WM_TAVOLOZZA_PRONTA,0,(long) TavolozzaLaterale->GetListaColore()); break; // Visualizza case AN_ASK_VISUALIZZA: if(MagoLaterale->GetAttivo()) MagoLaterale->Free(); switch (MagoLaterale->Set(CameraDue,(char*) &NomeFileDue)) { case AN_RET_ALLOC_NO: Errore->Messaggio(AN_NO_ALLOCIMAGE); break; case AN_RET_OK: break; } MagoLaterale->GetPosizioneI(); SendMessage(Finestre->GetFinestreSecondarie(FINESTRA_IMMAGINE_LATERALE), WM_SINGOLA_IMMAGINE_I,0,(long) MagoLaterale->GetImmagine()); Menu->Messaggio(AN_OK_VISUALIZZA_LATERALE); break; // Start case AN_ASK_START: SendMessage(Finestre->GetFinestreSecondarie(FINESTRA_IMMAGINE_LATERALE), WM_START_THREAD_I,(long) Oz,(long) MagoLaterale); Menu->Messaggio(AN_OK_START_LATERALE); break; // Stop case AN_ASK_STOP: SendMessage(Finestre->GetFinestreSecondarie(FINESTRA_IMMAGINE_LATERALE), WM_STOP_THREAD_I,0,0); SendMessage(Finestre->GetFinestreSecondarie(FINESTRA_PALETTE_LATERALE), WM_STOP_THREAD_I,0,0); Menu->Messaggio(AN_OK_STOP_LATERALE); break; // DLG Proprietà case AN_ASK_DLG_PROPRIETA: PROPRIETA Settaggi = { 0,0,0,NULL,false,false,false,false,false,false, false,false,false,false,false,false,false }; Settaggi.Camera=AN_WEB_LATERALE; CameraDue->GetSettaggi(&Settaggi); DialogBoxParam(hInstance,TEXT ("PROPRIETADLG"), Finestre->GetFinestreSecondarie(FINESTRA_PRINCIPALE), ProprietaDlgProc,(long) &Settaggi); break; } break; } } void Supervisore::Messaggio(UINT Messaggio,UINT Generatore,UINT Parametro) { switch (Generatore) { case AN_WEB_FRONTALE: switch (Messaggio) { // Intercetta Colore case AN_ASK_INTERCETTA_COLORE: RECT Zona; RETTANGOLI* Rettangoli; CopyRect(&Zona,TavolozzaFrontale->GetZonaColore(Parametro)); Rettangoli = MagoFrontale->SetColoreAttivo(Parametro,&Zona); SendMessage(Finestre->GetFinestreSecondarie(FINESTRA_IMMAGINE_FRONTALE), WM_COLORE_PRONTO,0,(long) Rettangoli); Menu->Messaggio(AN_OK_COLORE_SCELTO_FRONTALE); break; case AN_ASK_STATO_CATTURA_MUTATO: if(Finestre->GetVisibileSecondarie(FINESTRA_CONTROLLI)) { SendMessage(Finestre->GetFinestreSecondarie(FINESTRA_CONTROLLI), WM_STATO_CATTURA_MUTATO_FRONTALE,0,(long) Parametro); break; } if(Finestre->GetVisibileSecondarie(FINESTRA_POSIZIONA)) { SendMessage(Finestre->GetFinestreSecondarie(FINESTRA_POSIZIONA), WM_STATO_CATTURA_MUTATO_FRONTALE,0,(long) Parametro); break; } SendMessage(Finestre->GetFinestreApplicazione(), WM_STATO_CATTURA_MUTATO,0,(long) Parametro); break; case AN_ASK_STATO_PROGRESSO_MUTATO: 21 if(Finestre->GetVisibileSecondarie(FINESTRA_POSIZIONA)) SendMessage(Finestre->GetFinestreSecondarie(FINESTRA_POSIZIONA), WM_STATO_PROGRESSO_MUTATO,0,(long) Parametro); break; /* */ } void { } break; case AN_WEB_LATERALE: switch (Messaggio) { case AN_ASK_INTERCETTA_COLORE: RECT Zona; RETTANGOLI* Rettangoli; CopyRect(&Zona,TavolozzaLaterale->GetZonaColore(Parametro)); Rettangoli = MagoLaterale->SetColoreAttivo(Parametro,&Zona); MagoLaterale->GetPosizioneEye(); SendMessage(Finestre->GetFinestreSecondarie(FINESTRA_IMMAGINE_LATERALE), WM_COLORE_PRONTO,(long) MagoFrontale->GetImmagineEye(), (long) Rettangoli); Menu->Messaggio(AN_OK_COLORE_SCELTO_LATERALE); break; case AN_ASK_STATO_CATTURA_MUTATO: if(Finestre->GetVisibileSecondarie(FINESTRA_CONTROLLI)) SendMessage(Finestre->GetFinestreSecondarie(FINESTRA_CONTROLLI), WM_STATO_CATTURA_MUTATO_LATERALE,0,(long) Parametro); if(Finestre->GetVisibileSecondarie(FINESTRA_POSIZIONA)) SendMessage(Finestre->GetFinestreSecondarie(FINESTRA_POSIZIONA), WM_STATO_CATTURA_MUTATO_LATERALE,0,(long) Parametro); break; case AN_ASK_STATO_PROGRESSO_MUTATO: if(Finestre->GetVisibileSecondarie(FINESTRA_POSIZIONA)) SendMessage(Finestre->GetFinestreSecondarie(FINESTRA_POSIZIONA), WM_STATO_PROGRESSO_MUTATO,0,(long) Parametro); break; } break; } Supervisore::HandShake(UINT Messaggio,UINT Generatore,UINT Parametro) switch (Generatore) { case AN_THREAD_POSIZIONA: switch (Messaggio) { case AN_ASK_INIZIO_STATO: SendMessage(Finestre->GetFinestreSecondarie(FINESTRA_IMMAGINE_FRONTALE), WM_STATO_MUTATO,0,(long) Parametro); if(Cube.DueWeb) SendMessage(Finestre->GetFinestreSecondarie(FINESTRA_IMMAGINE_LATERALE), WM_STATO_MUTATO,0,(long) Parametro); break; case AN_ASK_FINE_SETTAGGIO: CELLA_POSIZIONE *Posizioni; RETTANGOLI *Rettangoli; SendMessage(Finestre->GetFinestreSecondarie(FINESTRA_IMMAGINE_FRONTALE), WM_STATO_MUTATO,0,(long) STATO_ZERO); Posizioni = MagoFrontale->GetPosizioni(); SendMessage(Finestre->GetFinestreSecondarie(FINESTRA_IMMAGINE_FRONTALE), WM_POSIZIONI_PRONTE,0,(long) Posizioni); Rettangoli = MagoFrontale->GetRettangoli(); if(Oz->GetAttivo()) Oz->Pulisci(); Oz->Set(Rettangoli,Posizioni, (Cube.Dimensioni.right - Cube.Dimensioni.left), (Cube.Dimensioni.bottom - Cube.Dimensioni.top)); if(Cube.DueWeb) SendMessage(Finestre->GetFinestreSecondarie(FINESTRA_IMMAGINE_LATERALE), WM_STATO_MUTATO,0,(long) STATO_ZERO); Finestre->Messaggio(FINESTRE_ATTIVE); Menu->Messaggio(AN_OK_STOP_POSIZIONA); Scossa(); break; } break; case AN_THREAD_INTERCETTA: switch (Messaggio) { case AN_ASK_FINE_STATO: if(!Cube.DueWeb) SendMessage(Finestre->GetFinestreSecondarie(FINESTRA_POSIZIONA), WM_STATO_MUTATO,0,(long) Parametro+1); else { 22 if(!Cube.Segnale) Cube.Segnale = true; else { SendMessage(Finestre->GetFinestreSecondarie(FINESTRA_POSIZIONA), WM_STATO_MUTATO,0,(long) Parametro+1); Cube.Segnale = false; } } break; } break; } } 23 24 Appendice B Oggetti principali Cattura HWND Cattura::GetFinestraCattura() { return FinestraCattura; } UINT Cattura::Set(HWND FinestraPadre,UINT Indice, char* NomeFile,int x,int y,int larghezza,int lunghezza) { // Settaggi this->FinestraPadre = FinestraPadre; this->Indice = Indice; this->NomeFile = NomeFile; this->x = x; this->y = y; this->larghezza = larghezza; this->lunghezza = lunghezza; // Cerco di agganciare la Web alla Finestra di Cattura FinestraCattura=capCreateCaptureWindow(TEXT ("Finestra di cattura"), WS_CHILD | WS_VISIBLE, x,y,larghezza,lunghezza, FinestraPadre, 0); if(!capDriverConnect(FinestraCattura,0)) return AN_RET_AGGANCIO_NO; if(!(capCaptureGetSetup(FinestraCattura,&ParametriCattura,sizeof ParametriCattura)) || !(capDriverGetCaps(FinestraCattura,&CapacitaDriver,sizeof CapacitaDriver)) || !(capDriverGetName(FinestraCattura,&cNomeDriver,sizeof cNomeDriver)) || !(capGetStatus(FinestraCattura,&StatoCattura,sizeof StatoCattura))) return AN_RET_DATI_NO; return AN_RET_OK; } bool Cattura::Disconnetti() { if(capDriverDisconnect(FinestraCattura)) return true; return false; } UINT Cattura::GetFormato() { if(CapacitaDriver.fHasDlgVideoFormat == 1) { if(!capDlgVideoFormat(FinestraCattura)) return AN_RET_FORMATO_ERRORE; return AN_RET_OK; } else return AN_RET_FORMATO_NO; } UINT Cattura::GetSorgente() { if(CapacitaDriver.fHasDlgVideoSource == 1) { if(!capDlgVideoSource(FinestraCattura)) return AN_RET_SORGENTE_ERRORE; return AN_RET_OK; } else return AN_RET_SORGENTE_NO; } bool Cattura::GetFoto() { if(capGrabFrame(FinestraCattura)) return true; return false; } bool Cattura::GetFile() { if(capFileSaveDIB(FinestraCattura,NomeFile)) return true; return false; } void Cattura::GetSettaggi(PROPRIETA *Settaggi) { Settaggi->ImageWidth = StatoCattura.uiImageWidth; Settaggi->ImageHeight = StatoCattura.uiImageHeight; 25 Settaggi->NomeDriver = (char*) &cNomeDriver; if(StatoCattura.fLiveWindow == 1) if(StatoCattura.fOverlayWindow == 1) if(StatoCattura.fScale == 1) if(StatoCattura.fUsingDefaultPalette == 1) if(StatoCattura.fAudioHardware == 1) if(StatoCattura.fCapFileExists == 1) if(StatoCattura.fCapturingNow == 1) if(CapacitaDriver.fHasOverlay == 1) if(CapacitaDriver.fHasDlgVideoSource == 1) if(CapacitaDriver.fHasDlgVideoFormat == 1) if(CapacitaDriver.fHasDlgVideoDisplay == 1) if(CapacitaDriver.fCaptureInitialized == 1) if(CapacitaDriver.fDriverSuppliesPalettes == 1) Settaggi->LiveWindow = true; Settaggi->OverlayWindow = true; Settaggi->Scale = true; Settaggi->UsingDefaultPalette = true; Settaggi->AudioHardware = true; Settaggi->CapFileExists = true; Settaggi->CapturingNow = true; Settaggi->HasOverlay = true; Settaggi->HasDlgVideoSource = true; Settaggi->HasDlgVideoFormat = true; Settaggi->HasDlgVideoDisplay = true; Settaggi->CaptureInitialized = true; Settaggi->DriverSuppliesPalettes = true; } Palette CELLA_COLORE_LS* void bool Palette::GetListaColore() Palette::SetAttivo(bool Attivo) Palette::GetAttivo() { return this->TestaLS; } { this->Attivo = Attivo;} { return this->Attivo; } void Palette::Set() { TestaLS = CodaLS = IndiceLS = NULL; TestaLC = CodaLC = IndiceLC = NULL; Elementi = 0; // Creo la Cella Colore Lista Complessa che servirà da rifiuto per tutti i colori indefiniti TempLC = new(CELLA_COLORE_LC); TempLC->Colore.Blu = 0; TempLC->Colore.Verde = 0; TempLC->Colore.Rosso = 0; TempLC->Scostamento.BV = 0; TempLC->Scostamento.VR = 0; TempLC->Scostamento.RB = 0; TempLC->TipoColore = INDEFINITO; TempLC->Zona.left = 0; TempLC->Zona.top = 240; TempLC->Zona.right = 320; TempLC->Zona.bottom = 0; TempLC->Bersagli = 0; TempLC->Posizione = Elementi; // Inserisco la Cella in testa (primo elemento) TempLC->Precedente = NULL; TempLC->Successivo = NULL; TestaLC = CodaLC = TempLC; // Creo la Cella Colore Lista Semplice TempLS = new(CELLA_COLORE_LS); TempLS->Blu = 0; TempLS->Verde = 0; TempLS->Rosso = 0; TempLS->TipoColore = INDEFINITO; // Inserisco la Cella in testa (primo elemento) TempLS->Precedente = NULL; TempLS->Successivo = NULL; TestaLS = CodaLS = TempLS; Elementi += 1; this->Attivo = true; } void Palette::Pulisci() { // Elimino gli elementi della lista Complessa IndiceLC = CodaLC->Precedente; delete CodaLC; while(IndiceLC != NULL) { CodaLC = IndiceLC; IndiceLC = CodaLC->Precedente; delete CodaLC; } TestaLC = CodaLC = IndiceLC = TempLC = NULL; // Elimino gli elementi della lista Semplice IndiceLS = CodaLS->Precedente; delete CodaLS; while(IndiceLS != NULL) { CodaLS = IndiceLS; IndiceLS = CodaLS->Precedente; delete CodaLS; } TestaLS = CodaLS = IndiceLS = TempLS = NULL; // Setto il numero di elementi a 0 26 Elementi = 0; this->Attivo = false; } RECT* Palette::GetZonaColore(UINT TipoColore) { IndiceLC = TestaLC; while(IndiceLC != NULL) { if(IndiceLC->TipoColore == TipoColore) return &IndiceLC->Zona; IndiceLC = IndiceLC->Successivo; } return NULL; } bool Palette::Trovato(CELLA_COLORE_LC *Cella) { IndiceLC = TestaLC; while(IndiceLC != NULL) { if(IndiceLC->TipoColore == Cella->TipoColore) { if(X < IndiceLC->Zona.left) IndiceLC->Zona.left = X; if(Y > IndiceLC->Zona.top) IndiceLC->Zona.top = Y; if(X > IndiceLC->Zona.right) IndiceLC->Zona.right = X; if(Y < IndiceLC->Zona.bottom) IndiceLC->Zona.bottom = Y; IndiceLC->Bersagli++; return true; } IndiceLC = IndiceLC->Successivo; } Cella->Zona.left = Cella->Zona.right = X; Cella->Zona.top = Cella->Zona.bottom = Y; Cella->Bersagli = 1; return false; } void Palette::Colora(imgdes* Immagine) { int i = 0; // Contatore di BYTE int j = 0; // Contatore di pixel COLORE Colore; X = Y = 0; while (i < ((int) Immagine->bmh->biSizeImage)) { Colore.Blu = Immagine->ibuff[i]; Colore.Verde = Immagine->ibuff[i+1]; Colore.Rosso = Immagine->ibuff[i+2]; this->In(Colore); i = i + 3; j++; X = j % 320; Y = j / 320; } } void Palette::In(COLORE Colore) { // Creo la Cella Colore Lista Complessa TempLC = new(CELLA_COLORE_LC); TempLC->Colore.Blu = Colore.Blu; TempLC->Colore.Verde = Colore.Verde; TempLC->Colore.Rosso = Colore.Rosso; TempLC->Scostamento.BV = Colore.Blu - Colore.Verde; TempLC->Scostamento.VR = Colore.Verde - Colore.Rosso; TempLC->Scostamento.RB = Colore.Rosso - Colore.Blu; TempLC->TipoColore = TipoColore(TempLC); TempLC->Posizione = Elementi; if (!Trovato(TempLC)) { // Inserisco la Cella in coda alla Lista Complessa TempLC->Precedente = CodaLC; TempLC->Successivo = NULL; CodaLC->Successivo = TempLC; CodaLC = TempLC; // Creo la Cella Colore Lista Semplice TempLS = new(CELLA_COLORE_LS); TempLS->Blu = Colore.Blu; TempLS->Verde = Colore.Verde; TempLS->Rosso = Colore.Rosso; TempLS->TipoColore = TipoColore(TempLC); // da rivedere // Inserisco la Cella in coda alla Lista Semplice TempLS->Precedente = CodaLS; TempLS->Successivo = NULL; CodaLS->Successivo = TempLS; CodaLS = TempLS; 27 Elementi += 1; } else delete TempLC; } UINT Palette::TipoColore(CELLA_COLORE_LC *Cella) { // Controllo se il colore è un grigio if((Cella->Scostamento.BV <= 10) && (Cella->Scostamento.BV >= -10) && (Cella->Scostamento.VR <= 10) && (Cella->Scostamento.VR >= -10) && (Cella->Scostamento.RB <= 10) && (Cella->Scostamento.RB >= -10)) { // Definisco il tipo di grigio da molto scuro (1) a bianco (5) if((Cella->Colore.Blu + Cella->Colore.Verde + Cella->Colore.Rosso) return GRIGIO_1; if((Cella->Colore.Blu + Cella->Colore.Verde + Cella->Colore.Rosso) return GRIGIO_2; if((Cella->Colore.Blu + Cella->Colore.Verde + Cella->Colore.Rosso) return GRIGIO_3; if((Cella->Colore.Blu + Cella->Colore.Verde + Cella->Colore.Rosso) return GRIGIO_4; if((Cella->Colore.Blu + Cella->Colore.Verde + Cella->Colore.Rosso) return GRIGIO_5; } // Controllo se il colore è un rosso if((Cella->Colore.Rosso <= 255) && (Cella->Colore.Rosso > 220) && (Cella->Colore.Verde <= 100) && (Cella->Colore.Blu <= 100)) return ROSSO_1; if((Cella->Colore.Rosso <= 220) && (Cella->Colore.Rosso > 180) && (Cella->Colore.Verde <= 100) && (Cella->Colore.Blu <= 100)) return ROSSO_2; if((Cella->Colore.Rosso <= 180) && (Cella->Colore.Rosso > 150) && (Cella->Colore.Verde <= 100) && (Cella->Colore.Blu <= 100)) return ROSSO_3; if((Cella->Colore.Rosso <= 150) && (Cella->Colore.Rosso > 120) && (Cella->Colore.Verde <= 100) && (Cella->Colore.Blu <= 100)) return ROSSO_4; if((Cella->Colore.Rosso <= 120) && (Cella->Colore.Rosso > 100) && (Cella->Colore.Verde <= 90) && (Cella->Colore.Blu <= 90)) return ROSSO_5; if((Cella->Colore.Rosso <= 100) && (Cella->Colore.Rosso > 90) && (Cella->Colore.Verde <= 80) && (Cella->Colore.Blu <= 80)) return ROSSO_6; // Controllo se il colore è un verde if((Cella->Colore.Verde <= 255) && (Cella->Colore.Verde > 220) && (Cella->Colore.Rosso <= 100) && (Cella->Colore.Blu <= 100)) return VERDE_1; if((Cella->Colore.Verde <= 220) && (Cella->Colore.Verde > 180) && (Cella->Colore.Rosso <= 100) && (Cella->Colore.Blu <= 100)) return VERDE_2; if((Cella->Colore.Verde <= 180) && (Cella->Colore.Verde > 150) && (Cella->Colore.Rosso <= 100) && (Cella->Colore.Blu <= 100)) return VERDE_3; if((Cella->Colore.Verde <= 150) && (Cella->Colore.Verde > 120) && (Cella->Colore.Rosso <= 100) && (Cella->Colore.Blu <= 100)) return VERDE_4; if((Cella->Colore.Verde <= 120) && (Cella->Colore.Verde > 100) && (Cella->Colore.Rosso <= 90) && (Cella->Colore.Blu <= 90)) return VERDE_5; if((Cella->Colore.Verde <= 100) && (Cella->Colore.Verde > 90) && (Cella->Colore.Rosso <= 80) && (Cella->Colore.Blu <= 80)) return VERDE_6; // Controllo se il colore è un blu if((Cella->Colore.Blu <= 255) && (Cella->Colore.Blu > 220) && (Cella->Colore.Rosso <= 100) && (Cella->Colore.Verde <= 100)) return BLU_1; if((Cella->Colore.Blu <= 220) && (Cella->Colore.Blu > 180) && (Cella->Colore.Rosso <= 100) && (Cella->Colore.Verde <= 100)) return BLU_2; if((Cella->Colore.Blu <= 180) && (Cella->Colore.Blu > 150) && (Cella->Colore.Rosso <= 100) && (Cella->Colore.Verde <= 100)) return BLU_3; if((Cella->Colore.Blu <= 150) && (Cella->Colore.Blu > 120) && (Cella->Colore.Rosso <= 100) && (Cella->Colore.Verde <= 100)) return BLU_4; if((Cella->Colore.Blu <= 120) && (Cella->Colore.Blu > 100) && (Cella->Colore.Rosso <= 90) && (Cella->Colore.Verde <= 90)) return BLU_5; if((Cella->Colore.Blu <= 100) && (Cella->Colore.Blu > 90) && (Cella->Colore.Rosso <= 80) && (Cella->Colore.Verde <= 80)) return BLU_6; // Controllo se il colore è un giallo if((Cella->Colore.Rosso <= 255) && (Cella->Colore.Rosso > 220) && 28 <= 120) <= 300) <= 480) <= 690) <= 765) (Cella->Colore.Verde <= 255) && (Cella->Colore.Verde > 220) (Cella->Colore.Blu <= 50)) return GIALLO_1; if((Cella->Colore.Rosso <= 220) && (Cella->Colore.Rosso > 180) && (Cella->Colore.Verde <= 220) && (Cella->Colore.Verde > 180) (Cella->Colore.Blu <= 50)) return GIALLO_2; if((Cella->Colore.Rosso <= 180) && (Cella->Colore.Rosso > 150) && (Cella->Colore.Verde <= 180) && (Cella->Colore.Verde > 150) (Cella->Colore.Blu <= 50)) return GIALLO_3; // Controllo se il colore è un cyano if((Cella->Colore.Blu <= 255) && (Cella->Colore.Blu > 220) && (Cella->Colore.Verde <= 255) && (Cella->Colore.Verde > 220) (Cella->Colore.Rosso <= 50)) return CYANO_1; if((Cella->Colore.Blu <= 220) && (Cella->Colore.Blu > 180) && (Cella->Colore.Verde <= 220) && (Cella->Colore.Verde > 180) (Cella->Colore.Rosso <= 50)) return CYANO_2; if((Cella->Colore.Blu <= 180) && (Cella->Colore.Blu > 150) && (Cella->Colore.Verde <= 180) && (Cella->Colore.Verde > 150) (Cella->Colore.Rosso <= 50)) return CYANO_3; // Controllo se il colore è un violetto if((Cella->Colore.Blu <= 255) && (Cella->Colore.Blu > 220) && (Cella->Colore.Rosso <= 255) && (Cella->Colore.Rosso > 220) (Cella->Colore.Verde <= 50)) return VIOLETTO_1; if((Cella->Colore.Blu <= 220) && (Cella->Colore.Blu > 180) && (Cella->Colore.Rosso <= 220) && (Cella->Colore.Rosso > 180) (Cella->Colore.Verde <= 50)) return VIOLETTO_2; if((Cella->Colore.Blu <= 180) && (Cella->Colore.Blu > 150) && (Cella->Colore.Rosso <= 180) && (Cella->Colore.Rosso > 150) (Cella->Colore.Verde <= 50)) return VIOLETTO_3; return INDEFINITO; } && && && && && && && && && Intercetta imgdes* Intercetta::GetImmagine() imgdes* Intercetta::GetImmagineEye() bool Intercetta::GetAttivo() CELLA_POSIZIONE* Intercetta::GetPosizioni() UINT Intercetta::GetProgresso() void Intercetta::SetAttivo(bool Attivo) void Intercetta::Lock() { this->DominioAttivo = false; this->StatoProgresso = STATO_PROGRESSO_INIZIO; } void Intercetta::UnLock(UINT StatoPosiziona) { this->DominioAttivo = true; this->StatoPosiziona = StatoPosiziona; this->IndiceProgresso = 0; } UINT Intercetta::Set(Cattura* Camera,char* NomeFile) { int errore; BITMAPINFOHEADER Info; { { { { { { return &Immagine; } return &ImmagineEye; } return Attivo; } return Posizioni; } return StatoProgresso; } this->Attivo = Attivo; } this->Camera = Camera; this->NomeFile = NomeFile; this->FinestraSorgente = Camera->GetFinestraCattura(); errore = bmpinfo(NomeFile,&Info); this->Width = Info.biWidth; this->Height = Info.biHeight; this->BitCount = Info.biBitCount; if(allocimage(&Immagine,Info.biWidth,Info.biHeight,Info.biBitCount) != NO_ERROR) return AN_RET_ALLOC_NO; if(allocimage(&ImmagineEye,FOCUS_EYE,FOCUS_EYE,24) != NO_ERROR) return AN_RET_ALLOC_NO; this->Attivo = true; this->Colore.ColoreAttivo = false; this->DominioAttivo = true; this->StatoCattura = STATO_CATTURA_INIZIO; this->StatoPosiziona = STATO_ZERO; this->StatoProgresso = STATO_PROGRESSO_INIZIO; // Creo la lista delle posizioni: 29 Temp = new(CELLA_POSIZIONE); Temp->Precedente = Temp->Successivo = NULL; Testa = Coda = Posizioni = Temp; IndiceProgresso = 0; return AN_RET_OK; } RETTANGOLI* Intercetta::GetRettangoli() { CopyRect(&Rettangoli.Zona,&Colore.Zona); CopyRect(&Rettangoli.Focus,&Colore.Focus); CopyRect(&Rettangoli.Dominio,&Colore.Dominio); CopyRect(&Rettangoli.Lento,&Colore.Lento); CopyRect(&Rettangoli.FocusEye,&Colore.FocusEye); return &this->Rettangoli; } void Intercetta::Free() { freeimage(&Immagine); freeimage(&ImmagineEye); // Cancello la lista delle posizioni: Temp = Coda->Precedente; delete Coda; while(Temp != NULL) { Coda = Temp; Temp = Coda->Precedente; delete Coda; } Testa = Coda = Temp = NULL; IndiceProgresso = 0; this->Attivo = false; } RETTANGOLI* Intercetta::SetColoreAttivo(UINT TipoColore,RECT* Zona) { Colore.TipoColore = TipoColore; // Posizionamento della Zona sull'Immagine CopyRect(&Colore.Zona,Zona); // Posizionamento del Dominio sull'Immagine CopyRect(&Colore.Dominio,&Colore.Zona); // Posizionamento del Lento sull'Immagine CopyRect(&Colore.Lento,&Colore.Zona); // Posizionamento del Focus sull'Immagine if(Colore.Zona.left > BORDO_FOCUS) Colore.Focus.left = (Colore.Zona.left - BORDO_FOCUS); else Colore.Focus.left = 0; if(Colore.Zona.top < (Height - BORDO_FOCUS)) Colore.Focus.top = (Colore.Zona.top + BORDO_FOCUS); else Colore.Focus.top = (Height - 1); if(Colore.Zona.right < (Width - BORDO_FOCUS)) Colore.Focus.right = (Colore.Zona.right + BORDO_FOCUS); else Colore.Focus.right = (Width - 1); if(Colore.Zona.bottom > 10) Colore.Focus.bottom = (Colore.Zona.bottom - BORDO_FOCUS); else Colore.Focus.bottom = 0; // Settaggio dei limiti del colore switch (TipoColore) { case GRIGIO_1: // non entrerò mai in questi case li ho messi solo case GRIGIO_2: // per evitare errori. case GRIGIO_3: case GRIGIO_4: case GRIGIO_5: Colore.RossoMin = Colore.VerdeMin = Colore.BluMin = 0; Colore.RossoMax = Colore.VerdeMax = Colore.BluMax = 0; break; case ROSSO_1: Colore.RossoMin = 221; Colore.RossoMax = 255; Colore.VerdeMin = Colore.BluMin = 0; Colore.VerdeMax = Colore.BluMax = 100; break; case ROSSO_2: Colore.RossoMin = 181; Colore.RossoMax = 220; Colore.VerdeMin = Colore.BluMin = 0; Colore.VerdeMax = Colore.BluMax = 100; break; case ROSSO_3: Colore.RossoMin = 151; 30 case case case case case case case case case case case case case case Colore.RossoMax Colore.VerdeMin Colore.VerdeMax break; ROSSO_4: Colore.RossoMin Colore.RossoMax Colore.VerdeMin Colore.VerdeMax break; ROSSO_5: Colore.RossoMin Colore.RossoMax Colore.VerdeMin Colore.VerdeMax break; ROSSO_6: Colore.RossoMin Colore.RossoMax Colore.VerdeMin Colore.VerdeMax break; VERDE_1: Colore.VerdeMin Colore.VerdeMax Colore.RossoMin Colore.RossoMax break; VERDE_2: Colore.VerdeMin Colore.VerdeMax Colore.RossoMin Colore.RossoMax break; VERDE_3: Colore.VerdeMin Colore.VerdeMax Colore.RossoMin Colore.RossoMax break; VERDE_4: Colore.VerdeMin Colore.VerdeMax Colore.RossoMin Colore.RossoMax break; VERDE_5: Colore.VerdeMin Colore.VerdeMax Colore.RossoMin Colore.RossoMax break; VERDE_6: Colore.VerdeMin Colore.VerdeMax Colore.RossoMin Colore.RossoMax break; BLU_1: Colore.BluMin Colore.BluMax Colore.RossoMin Colore.RossoMax break; BLU_2: Colore.BluMin Colore.BluMax Colore.RossoMin Colore.RossoMax break; BLU_3: Colore.BluMin Colore.BluMax Colore.RossoMin Colore.RossoMax break; BLU_4: Colore.BluMin Colore.BluMax Colore.RossoMin Colore.RossoMax break; BLU_5: = 180; = Colore.BluMin = 0; = Colore.BluMax = 100; = = = = 121; 150; Colore.BluMin = 0; Colore.BluMax = 100; = = = = 101; 120; Colore.BluMin = 0; Colore.BluMax = 90; = = = = 91; 100; Colore.BluMin = 0; Colore.BluMax = 80; = = = = 221; 255; Colore.BluMin = 0; Colore.BluMax = 100; = = = = 181; 220; Colore.BluMin = 0; Colore.BluMax = 100; = = = = 151; 180; Colore.BluMin = 0; Colore.BluMax = 100; = = = = 121; 150; Colore.BluMin = 0; Colore.BluMax = 100; = = = = 101; 120; Colore.BluMin = 0; Colore.BluMax = 90; = = = = 91; 100; Colore.BluMin = 0; Colore.BluMax = 80; = = = = 221; 255; Colore.VerdeMin = 0; Colore.VerdeMax = 100; = = = = 181; 220; Colore.VerdeMin = 0; Colore.VerdeMax = 100; = = = = 151; 180; Colore.VerdeMin = 0; Colore.VerdeMax = 100; = = = = 121; 150; Colore.VerdeMin = 0; Colore.VerdeMax = 100; 31 Colore.BluMin = 101; Colore.BluMax = 120; Colore.RossoMin = Colore.VerdeMin = 0; Colore.RossoMax = Colore.VerdeMax = 90; break; case BLU_6: Colore.BluMin = 91; Colore.BluMax = 100; Colore.RossoMin = Colore.VerdeMin = 0; Colore.RossoMax = Colore.VerdeMax = 80; break; } // Settaggio iniziale per la struttura RETTANGOLI CopyRect(&Rettangoli.Zona,&Colore.Zona); CopyRect(&Rettangoli.Focus,&Colore.Focus); CopyRect(&Rettangoli.Dominio,&Colore.Dominio); CopyRect(&Rettangoli.Lento,&Colore.Lento); CopyRect(&Rettangoli.FocusEye,&Colore.FocusEye); // Settaggio iniziale per il vettore di strutture HIT Hit[0].Bersagli = 0; CopyRect(&Hit[0].Zona,&Colore.Zona); Indice = 1; // Il colore è stato scelto Colore.ColoreAttivo = true; Colore.ColoreTrovato = true; return &Rettangoli; } void Intercetta::GetPosizioneEye() { int i,j,k; short IndiceTemp; // Posizionamento del FocusEye sull'Immagine if(Indice >= 1) IndiceTemp = (Indice - 1); else IndiceTemp = (NUMERO_AQUISIZIONI - 1); if(Hit[IndiceTemp].Zona.left > FOCUS_EYE) Colore.FocusEye.left = (Hit[IndiceTemp].Zona.left - FOCUS_EYE); else Colore.FocusEye.left = 0; if(Hit[IndiceTemp].Zona.bottom > FOCUS_EYE) Colore.FocusEye.bottom = (Hit[IndiceTemp].Zona.bottom - FOCUS_EYE); else Colore.FocusEye.bottom = 0; Colore.FocusEye.right = Hit[IndiceTemp].Zona.left; Colore.FocusEye.top = Hit[IndiceTemp].Zona.bottom; k = 0; i = this->Colore.FocusEye.bottom * (320*3); while (i < (this->Colore.FocusEye.top * (320*3))) { j = (this->Colore.FocusEye.left * 3); while (j < (this->Colore.FocusEye.right * 3)) { if((Immagine.ibuff[(i+j)+0] > LUMINOSITA) && (Immagine.ibuff[(i+j)+1] > LUMINOSITA) && (Immagine.ibuff[(i+j)+2] > LUMINOSITA)) ImmagineEye.ibuff[k+0] = ImmagineEye.ibuff[k+1] = ImmagineEye.ibuff[k+2] = 255; else ImmagineEye.ibuff[k+0] = ImmagineEye.ibuff[k+1] = ImmagineEye.ibuff[k+2] = 0; j = j + 3; k = k + 3; } i = i + (320*3); } // Cerco il contorno dell'occhio e setto la ZonaEye } UINT Intercetta::GetPosizioneI() { // Carica una nuova immagine Camera->GetFoto(); Camera->GetFile(); loadbmp(NomeFile,&Immagine); if(Colore.ColoreAttivo) { // Setto la zona per accogliere le hit Colore.Zona.left Colore.Zona.right Colore.Zona.bottom Colore.Zona.top Hit[Indice].Bersagli = 0; = = = = 320; 0; 240; 0; 32 // Setto il Dominio e il Focus per questa chiamata if (Colore.ColoreTrovato) Colore.ColoreTrovato = TrovaColoreFocus(); else Colore.ColoreTrovato = TrovaColoreDominio(); // Metto la zona trovata nel vettore delle catture CopyRect(&Hit[Indice].Zona,&Colore.Zona); // Dopo NUMERO_CATTURE dò dei risultati più stabili if(Indice >= (NUMERO_AQUISIZIONI - 1)) return SetLento(); else Indice++; } return StatoCattura; } UINT Intercetta::SetLento() { short i,Miss,Less; int Lunghezza,Larghezza; short Cont = 0; RECT Sum; Miss = Less = 0; Indice = 0; Sum.left = Sum.right = Sum.top = Sum.bottom = 0; Larghezza = Colore.Lento.right - Colore.Lento.left; Lunghezza = Colore.Lento.top - Colore.Lento.bottom; for(i=0; i<NUMERO_AQUISIZIONI ; i++) { if(((Hit[i].Zona.right - Hit[i].Zona.left) <= (Larghezza + 10)) && ((Hit[i].Zona.top - Hit[i].Zona.bottom) <= (Lunghezza + 10)) && (Hit[i].Zona.left < Hit[i].Zona.right)) { Sum.left = Sum.left + Hit[i].Zona.left; Sum.right = Sum.right + Hit[i].Zona.right; Sum.bottom = Sum.bottom + Hit[i].Zona.bottom; Sum.top = Sum.top + Hit[i].Zona.top; Cont++; } if((Hit[i].Bersagli > 0) && (Hit[i].Bersagli < MASSIMO_LESS)) Less++; if(Hit[i].Bersagli == 0) Miss++; } if(Cont > 0) { Colore.Lento.left = (Sum.left / Cont); Colore.Lento.right = (Sum.right / Cont); Colore.Lento.bottom = (Sum.bottom / Cont); Colore.Lento.top = (Sum.top / Cont); CopyRect(&Rettangoli.Lento,&Colore.Lento); } if(Miss == NUMERO_AQUISIZIONI) StatoCattura = STATO_CATTURA_ROSSO; if(((Miss > 2) && (Miss < NUMERO_AQUISIZIONI)) || (Less > 5)) StatoCattura = STATO_CATTURA_GIALLO; if((Miss <= 2) && (Less < 5)) StatoCattura = STATO_CATTURA_VERDE; return StatoCattura; } bool Intercetta::TrovaColoreFocus() { int i,j; int X,Y; COLORE Colore; Y = this->Colore.Focus.bottom; i = this->Colore.Focus.bottom * (320*3); while (i <= (this->Colore.Focus.top * (320*3))) { X = this->Colore.Focus.left; j = (this->Colore.Focus.left * 3); while (j <= (this->Colore.Focus.right * 3)) { Colore.Blu = Immagine.ibuff[(i+j)+0]; Colore.Verde = Immagine.ibuff[(i+j)+1]; Colore.Rosso = Immagine.ibuff[(i+j)+2]; ConfrontaColore(Colore,X,Y); j = j + 3; X++; } i = i + (320*3); Y++; } if(Hit[Indice].Bersagli > LIMITE_BERSAGLI_MINIMO) 33 { Dominio(); Focus(); return true; } return false; } bool Intercetta::TrovaColoreDominio() { int i,j; int X,Y; COLORE Colore; Y = this->Colore.Dominio.bottom; i = this->Colore.Dominio.bottom * (320*3); while (i <= (this->Colore.Dominio.top * (320*3))) { X = this->Colore.Dominio.left; j = (this->Colore.Dominio.left * 3); while (j <= (this->Colore.Dominio.right * 3)) { Colore.Blu = Immagine.ibuff[(i+j)+0]; Colore.Verde = Immagine.ibuff[(i+j)+1]; Colore.Rosso = Immagine.ibuff[(i+j)+2]; ConfrontaColore(Colore,X,Y); j = j + 3; X++; } i = i + (320*3); Y++; } if(Hit[Indice].Bersagli > LIMITE_BERSAGLI_MINIMO) { Focus(); return true; } return false; } void Intercetta::ConfrontaColore(COLORE Colore,int X,int Y) { if((Colore.Verde >= this->Colore.VerdeMin) && (Colore.Verde <= this->Colore.VerdeMax) && (Colore.Rosso >= this->Colore.RossoMin) && (Colore.Rosso <= this->Colore.RossoMax) && (Colore.Blu >= this->Colore.BluMin) && (Colore.Blu <= this->Colore.BluMax)) { // Setto la Zona if(X < this->Colore.Zona.left) this->Colore.Zona.left = X; if(Y > this->Colore.Zona.top) this->Colore.Zona.top = Y; if(X > this->Colore.Zona.right) this->Colore.Zona.right = X; if(Y < this->Colore.Zona.bottom) this->Colore.Zona.bottom = Y; // Setto la Hit Hit[Indice].Bersagli++; } } void Intercetta::Dominio() { short i; RECT Sum; if(this->DominioAttivo) { // Setto il Dominio if(this->Colore.Lento.left < this->Colore.Dominio.left) this->Colore.Dominio.left = this->Colore.Lento.left; if(this->Colore.Lento.top > this->Colore.Dominio.top) this->Colore.Dominio.top = this->Colore.Lento.top; if(this->Colore.Lento.right > this->Colore.Dominio.right) this->Colore.Dominio.right = this->Colore.Lento.right; if(this->Colore.Lento.bottom < this->Colore.Dominio.bottom) this->Colore.Dominio.bottom = this->Colore.Lento.bottom; // Setto il progresso if((Indice == 0) && (StatoCattura == STATO_CATTURA_VERDE)) { CopyRect(&Progresso[IndiceProgresso].Lento,&Colore.Lento); IndiceProgresso++; } if(IndiceProgresso < 2) this->StatoProgresso = STATO_PROGRESSO_INIZIO; if((IndiceProgresso >= 2) && (IndiceProgresso < 4)) this->StatoProgresso = STATO_PROGRESSO_UNO; if((IndiceProgresso >= 4) && (IndiceProgresso < 5)) this->StatoProgresso = STATO_PROGRESSO_DUE; if((IndiceProgresso >= 5) && (IndiceProgresso < NUMERO_AQUISIZIONI_PROGRESSO)) this->StatoProgresso = STATO_PROGRESSO_TRE; if(IndiceProgresso == NUMERO_AQUISIZIONI_PROGRESSO) 34 { Sum.left = Sum.right = Sum.top = Sum.bottom = 0; for(i=2; i<NUMERO_AQUISIZIONI_PROGRESSO ; i++) { Sum.left = Sum.left + Progresso[i].Lento.left; Sum.right = Sum.right + Progresso[i].Lento.right; Sum.bottom = Sum.bottom + Progresso[i].Lento.bottom; Sum.top = Sum.top + Progresso[i].Lento.top; } Sum.left = Sum.left / ((NUMERO_AQUISIZIONI_PROGRESSO)-2); Sum.right = Sum.right / ((NUMERO_AQUISIZIONI_PROGRESSO)-2); Sum.bottom = Sum.bottom / ((NUMERO_AQUISIZIONI_PROGRESSO)-2); Sum.top = Sum.top / ((NUMERO_AQUISIZIONI_PROGRESSO)-2); // setto una nuova cella della lista delle posizioni Temp = new(CELLA_POSIZIONE); CopyRect(&Temp->Posizione,&Sum); Temp->Stato = this->StatoPosiziona; Temp->Successivo = NULL; Temp->Precedente = Coda; Coda->Successivo = Temp; Coda = Temp; IndiceProgresso = 0; this->StatoProgresso = STATO_PROGRESSO_FINE; } } } void Intercetta::Focus() { // Setto il Focus if(this->Colore.Zona.left > BORDO_FOCUS) this->Colore.Focus.left = (this->Colore.Zona.left - BORDO_FOCUS); else this->Colore.Focus.left = 0; if(this->Colore.Zona.top < (this->Height - BORDO_FOCUS)) this->Colore.Focus.top = (this->Colore.Zona.top + BORDO_FOCUS); else this->Colore.Focus.top = (this->Height - 1); if(this->Colore.Zona.right < (this->Width - BORDO_FOCUS)) this->Colore.Focus.right = (this->Colore.Zona.right + BORDO_FOCUS); else this->Colore.Focus.right = (this->Width - 1); if(this->Colore.Zona.bottom > BORDO_FOCUS) this->Colore.Focus.bottom = (this->Colore.Zona.bottom - BORDO_FOCUS); else this->Colore.Focus.bottom = 0; } bool Intercetta::GetPosizioneII() { int errore; imgdes Temp; Camera->GetFoto(); errore = clienttoimage(FinestraSorgente,&Temp); errore = copyimage(&Temp,&Immagine); freeimage(&Temp); return true; } Decisore void bool PUNTO Decisore::SetAttivo(bool Attivo) Decisore::GetAttivo() *Decisore::GetPunto() { this->Attivo = Attivo; } { return this->Attivo; } { return &this->Punto; } void Decisore::Set(RETTANGOLI *Rettangoli,CELLA_POSIZIONE *Posizione,int X,int Y) { int i; this->Rettangoli = Rettangoli; this->Contatore = 0; this->NumeroDiLinee = Rettangoli->Dominio.top - Rettangoli->Dominio.bottom; this->NumeroDiColonne = Rettangoli->Dominio.right - Rettangoli->Dominio.left; this->Click.Attinenze = 0; this->Punto.X = this->Punto.Y = 1; SetRect(&this->Click.Regione,0,0,0,0); Temp = new(CELLA_LINEA); Temp->NumeroLinea = NumeroDiLinee;; Temp->Linea = new SCOSTAMENTO[NumeroDiColonne]; Temp->Precedente = NULL; Temp->Successivo = NULL; Testa = Coda = Temp; i = NumeroDiLinee; while(i > 0) 35 { Temp Temp->NumeroLinea Temp->Linea Temp->Precedente Temp->Successivo Coda->Successivo Coda i--; = = = = = = = new(CELLA_LINEA); i - 1; new SCOSTAMENTO[NumeroDiColonne]; Coda; NULL; Temp; Temp; } this->Attivo = true; } void Decisore::Pulisci() { // Elimino gli elementi della lista e con essi gli array associati: Indice = Coda->Precedente; delete[] Coda->Linea; delete Coda; while(Indice != NULL) { Coda = Indice; Indice = Coda->Precedente; delete[] Coda->Linea; delete Coda; } Testa = Coda = Indice = Temp = NULL; } UINT Decisore::GetDecisione() { Contatore++; if(Contatore >= (NUMERO_AQUISIZIONI - 1)) { Contatore = 0; // Trovo il punto mediano di Lento: X = (float)((Rettangoli->Lento.right + Rettangoli->Lento.left) / 2); Y = (float)((Rettangoli->Lento.top + Rettangoli->Lento.bottom) / 2); // Controllo se il Punto mediano di Lento è nelle "vicinanze" del Dominio: if((X > (Rettangoli->Dominio.left - BORDO_DOMINIO)) && (X < Rettangoli->Dominio.left)) X = (float)Rettangoli->Dominio.left; if((X < (Rettangoli->Dominio.right + BORDO_DOMINIO)) && (X > Rettangoli->Dominio.right)) X = (float)Rettangoli->Dominio.right; if((Y > (Rettangoli->Dominio.bottom - BORDO_DOMINIO)) && (Y < Rettangoli->Dominio.bottom)) Y = (float)Rettangoli->Dominio.bottom; if((Y < (Rettangoli->Dominio.top + BORDO_DOMINIO)) && (Y > Rettangoli->Dominio.top)) Y = (float)Rettangoli->Dominio.top; // Se il Punto mediano di Lento è contenuto nel Dominio: if((X >= Rettangoli->Dominio.left) && (X <= Rettangoli->Dominio.right) && (Y >= Rettangoli->Dominio.bottom) && (Y <= Rettangoli->Dominio.top)) { // Lo setto nel SdR del Dominio: X = X - Rettangoli->Dominio.left; Y = Y - Rettangoli->Dominio.bottom; // Gli aggiungo lo Scostamento relativo: /* if((Scostamento = GetScostamento(X,Y)) != NULL) { X = X + Scostamento->x; Y = Y + Scostamento->y; }*/ // Lo traslo sulla Finestra Applicazione e lo metto nell'area publica: Punto.X = (1 - (X / NumeroDiColonne)); Punto.Y = (1 - (Y / NumeroDiLinee)); // Controllo se il punto è attinente al Click: if((X >= Click.Regione.left) && (X <= Click.Regione.right) && (Y >= Click.Regione.bottom) && (Y <= Click.Regione.top)) { Click.Attinenze++; if(Click.Attinenze > NUMERO_AQUISIZIONI_AREA_CLICK) { Click.Attinenze = 0; SetRect(&this->Click.Regione,(int)X-BORDO_CLICK,(int)Y+BORDO_CLICK, (int)X+BORDO_CLICK,(int)Y-BORDO_CLICK); return AN_RET_CLICK_TEMPORALE; } } else 36 { Click.Attinenze = 0; SetRect(&this->Click.Regione,(int)X-BORDO_CLICK,(int)Y+BORDO_CLICK, (int)X+BORDO_CLICK,(int)Y-BORDO_CLICK); } return AN_RET_SI_DECISIONE; } } return AN_RET_NO_DECISIONE; } Decisore::SCOSTAMENTO*Decisore::GetScostamento(long X,long Y) { Indice = Testa; while(Indice != NULL) { if(Temp->NumeroLinea == Y) return &Temp->Linea[X]; Temp = Temp->Successivo; } return NULL; } Thread principale void ThreadIntercettaFrontale(PVOID pvoid) { UINT StatoCattura; UINT StatoProgresso; UINT TemporaneoCattura; UINT TemporaneoStato; UINT TemporaneoStatoProgresso; PPARAMS pparams = (PPARAMS) pvoid; while (!pparams->Ucciso) { StatoCattura = STATO_CATTURA_INIZIO; if(pparams->Stato == STATO_ZERO) { pparams->Mago->Lock(); while ((!pparams->Ucciso) && (pparams->Stato == STATO_ZERO)) { TemporaneoCattura = pparams->Mago->GetPosizioneI(); if(StatoCattura != TemporaneoCattura) { StatoCattura = TemporaneoCattura; pparams->Angelo->Messaggio(AN_ASK_STATO_CATTURA_MUTATO, pparams->Tipo,StatoCattura); } pparams->Rettangoli = pparams->Mago->GetRettangoli(); InvalidateRect(pparams->hwnd,NULL,false); Sleep(SLEEP_THREAD_FRONTALE); } } if((pparams->Stato >= STATO_UNO) && (pparams->Stato <= STATO_NOVE)) { pparams->Mago->UnLock(pparams->Stato); TemporaneoStato = pparams->Stato; StatoProgresso = STATO_PROGRESSO_INIZIO; while ((!pparams->Ucciso) && (pparams->Stato == TemporaneoStato)) { TemporaneoCattura = pparams->Mago->GetPosizioneI(); if(StatoCattura != TemporaneoCattura) { StatoCattura = TemporaneoCattura; pparams->Angelo->Messaggio(AN_ASK_STATO_CATTURA_MUTATO, pparams->Tipo,StatoCattura); } TemporaneoStatoProgresso = pparams->Mago->GetProgresso(); if(StatoProgresso != TemporaneoStatoProgresso) { StatoProgresso = TemporaneoStatoProgresso; if(StatoProgresso == STATO_PROGRESSO_FINE) { pparams->Angelo->HandShake(AN_ASK_FINE_STATO, AN_THREAD_INTERCETTA,pparams->Stato); pparams->Mago->Lock(); StatoProgresso = STATO_PROGRESSO_INIZIO; } pparams->Angelo->Messaggio(AN_ASK_STATO_PROGRESSO_MUTATO, pparams->Tipo,StatoProgresso); } Sleep(SLEEP_THREAD_FRONTALE); } 37 } if(pparams->Stato == STATO_MANUALE) { pparams->Mago->UnLock(pparams->Stato); while ((!pparams->Ucciso) && (pparams->Stato == STATO_MANUALE)) { TemporaneoCattura = pparams->Mago->GetPosizioneI(); if(StatoCattura != TemporaneoCattura) { StatoCattura = TemporaneoCattura; pparams->Angelo->Messaggio(AN_ASK_STATO_CATTURA_MUTATO, pparams->Tipo,StatoCattura); } pparams->Rettangoli = pparams->Mago->GetRettangoli(); InvalidateRect(pparams->hwnd,NULL,false); Sleep(SLEEP_THREAD_FRONTALE); } } if(pparams->Stato == STATO_LAVORO) { pparams->Mago->Lock(); while ((!pparams->Ucciso) && (pparams->Stato == STATO_LAVORO)) { TemporaneoCattura = pparams->Mago->GetPosizioneI(); if(StatoCattura != TemporaneoCattura) { StatoCattura = TemporaneoCattura; pparams->Angelo->Messaggio(AN_ASK_STATO_CATTURA_MUTATO, pparams->Tipo,StatoCattura); } switch (pparams->Oz->GetDecisione()) { case AN_RET_NO_DECISIONE: break; case AN_RET_SI_DECISIONE: pparams->Angelo->Messaggio(AN_ASK_DECISIONE_AVVENUTA); break; case AN_RET_CLICK_TEMPORALE: pparams->Angelo->Messaggio(AN_ASK_CLICK_TEMPORALE_AVVENUTA); break; } Sleep(SLEEP_THREAD_FRONTALE); } } } pparams->Mago->Free(); _endthread(); } Thread di calibrazione void ThreadPosiziona(PVOID pvoid) { UINT TemporaneoStato; PPARAMS_POSIZIONA pparams = (PPARAMS_POSIZIONA) pvoid; Sleep(1000); while(!pparams->Ucciso) { InvalidateRect(pparams->hwnd,NULL,true); if(pparams->Stato <= STATO_NOVE) { TemporaneoStato = pparams->Stato; pparams->Angelo->HandShake(AN_ASK_INIZIO_STATO,AN_THREAD_POSIZIONA, pparams->Stato); while((!pparams->Ucciso) && (pparams->Stato == TemporaneoStato)) Sleep(200); } else { Sleep(2000); pparams->Angelo->HandShake(AN_ASK_FINE_SETTAGGIO,AN_THREAD_POSIZIONA,0); pparams->Ucciso = true; } } pparams->Stato = STATO_ZERO; _endthread(); } 38 Appendice C Windows procedure delle sottofinestre Tavolozza LRESULT CALLBACK PaletteFrontaleProc (HWND hwnd,UINT message,WPARAM wParam,LPARAM lParam) { HDC hdc; PAINTSTRUCT ps; static HPEN PennaContorno; static COLORE ColoreScelto; static CELLA_COLORE_LS *Testa,*Indice; static int Elementi; static bool TavolozzaAttiva; int i; switch (message) { case WM_CREATE: PennaContorno = CreatePen(PS_SOLID,1,RGB(0,0,0)); ColoreScelto.Blu = ColoreScelto.Verde = ColoreScelto.Rosso = 255; return 0; case WM_PAINT: hdc = BeginPaint(hwnd,&ps); if(TavolozzaAttiva) { SelectObject(hdc,PennaContorno); SelectObject(hdc,CreateSolidBrush (RGB(ColoreScelto.Rosso,ColoreScelto.Verde,ColoreScelto.Blu))); Rectangle(hdc,10,10,40,40); i = 0; Elementi = 0; for(Indice = Testa; Indice != NULL; Indice = Indice->Successivo) { SelectObject(hdc,CreateSolidBrush( RGB(Indice->Rosso,Indice->Verde,Indice->Blu))); Rectangle(hdc,50+i,10,61+i,40); i = i + 10; Elementi++; DeleteObject(SelectObject(hdc,GetStockObject(WHITE_BRUSH))); } } DeleteObject(SelectObject(hdc,GetStockObject(WHITE_BRUSH))); EndPaint(hwnd,&ps); return 0; case WM_STOP_THREAD_I: TavolozzaAttiva = false; InvalidateRect(hwnd,NULL,true); return 0; case WM_TAVOLOZZA_PRONTA: Testa = (CELLA_COLORE_LS*) lParam; TavolozzaAttiva = true; InvalidateRect(hwnd,NULL,true); return 0; case WM_LBUTTONDOWN: int x,y ; x = LOWORD(lParam); y = HIWORD(lParam); if(TavolozzaAttiva) { Indice = Testa; if((y >= 10) && (y < 40) && (x >= 50) && (x < ((Elementi*10)+50))) { for(i = 0; i < ((x-50)/10); i++) Indice = Indice->Successivo; ColoreScelto.Blu = Indice->Blu; ColoreScelto.Verde = Indice->Verde; ColoreScelto.Rosso = Indice->Rosso; Angelo.Messaggio(AN_ASK_INTERCETTA_COLORE,AN_WEB_FRONTALE,Indice->TipoColore); InvalidateRect(hwnd,NULL,true); } } return 0; case WM_DESTROY: 39 DeleteObject(PennaContorno); return 0; } return DefWindowProc (hwnd, message, wParam, lParam) ; } Immagine LRESULT CALLBACK ImmagineFrontaleProc(HWND hwnd,UINT message,WPARAM wParam,LPARAM lParam) { HDC hdc; PAINTSTRUCT ps; static PARAMS params; static CELLA_POSIZIONE *Testa,*Temp; static bool ImmagineAttiva,ColoreAttivo,PosizioniAttive; static HPEN PennaZona,PennaFocus,PennaDominio,PennaLento,PennaPosizioni; switch (message) { case WM_CREATE: PennaZona = CreatePen(PS_SOLID,1,RGB(0,255,0)); PennaDominio = CreatePen(PS_SOLID,1,RGB(255,0,0)); PennaFocus = CreatePen(PS_SOLID,1,RGB(0,0,255)); PennaLento = CreatePen(PS_SOLID,1,RGB(255,255,0)); PennaPosizioni = CreatePen(PS_SOLID,1,RGB(255,0,255)); ImmagineAttiva = false; ColoreAttivo = false; PosizioniAttive = false; params.Stato = STATO_ZERO; params.Tipo = AN_WEB_FRONTALE; params.hwnd = hwnd; params.Angelo = &Angelo; return 0; case WM_PAINT: hdc = BeginPaint (hwnd, &ps); if(ImmagineAttiva) { imgdes* Immagine; Immagine = params.Immagine; SetDIBitsToDevice(hdc,0,0, // X,Y Posizioni del DeviceContext (unsigned)Immagine->bmh->biWidth, // Larghezza Immagine (unsigned)Immagine->bmh->biHeight, // Altezza Immagine 0,0, // Posizione di partenza nell'immagine 0, // linea di partenza (unsigned)Immagine->bmh->biHeight, // Numero di linee da visualizzare Immagine->ibuff, // Indirizzo del Ibuffer (BITMAPINFO far *)Immagine->bmh, // Indirizzo del BITMAPINFO DIB_RGB_COLORS); // Tipo di colore usato } if(ColoreAttivo) { SelectObject(hdc,PennaZona); MoveToEx(hdc,params.Rettangoli->Zona.left,(240-params.Rettangoli->Zona.top),NULL); LineTo(hdc,params.Rettangoli->Zona.left,(240-params.Rettangoli->Zona.bottom)); LineTo(hdc,params.Rettangoli->Zona.right,(240-params.Rettangoli->Zona.bottom)); LineTo(hdc,params.Rettangoli->Zona.right,(240-params.Rettangoli->Zona.top)); LineTo(hdc,params.Rettangoli->Zona.left,(240-params.Rettangoli->Zona.top)); SelectObject(hdc,PennaDominio); MoveToEx(hdc,params.Rettangoli->Dominio.left, (240-params.Rettangoli->Dominio.top),NULL); LineTo(hdc,params.Rettangoli->Dominio.left, (240-params.Rettangoli->Dominio.bottom)); LineTo(hdc,params.Rettangoli->Dominio.right, (240-params.Rettangoli->Dominio.bottom)); LineTo(hdc,params.Rettangoli->Dominio.right,(240-params.Rettangoli->Dominio.top)); LineTo(hdc,params.Rettangoli->Dominio.left,(240-params.Rettangoli->Dominio.top)); SelectObject(hdc,PennaFocus); MoveToEx(hdc,params.Rettangoli->Focus.left,(240-params.Rettangoli->Focus.top),NULL); LineTo(hdc,params.Rettangoli->Focus.left,(240-params.Rettangoli->Focus.bottom)); LineTo(hdc,params.Rettangoli->Focus.right,(240-params.Rettangoli->Focus.bottom)); LineTo(hdc,params.Rettangoli->Focus.right,(240-params.Rettangoli->Focus.top)); LineTo(hdc,params.Rettangoli->Focus.left,(240-params.Rettangoli->Focus.top)); SelectObject(hdc,PennaLento); MoveToEx(hdc,params.Rettangoli->Lento.left,(240-params.Rettangoli->Lento.top),NULL); LineTo(hdc,params.Rettangoli->Lento.left,(240-params.Rettangoli->Lento.bottom)); LineTo(hdc,params.Rettangoli->Lento.right,(240-params.Rettangoli->Lento.bottom)); LineTo(hdc,params.Rettangoli->Lento.right,(240-params.Rettangoli->Lento.top)); LineTo(hdc,params.Rettangoli->Lento.left,(240-params.Rettangoli->Lento.top)); } if(PosizioniAttive) { SelectObject(hdc,PennaPosizioni); Temp = Testa; 40 while(Temp != NULL) { MoveToEx(hdc,Temp->Posizione.left,(240-Temp->Posizione.top),NULL); LineTo(hdc,Temp->Posizione.left,(240-Temp->Posizione.bottom)); LineTo(hdc,Temp->Posizione.right,(240-Temp->Posizione.bottom)); LineTo(hdc,Temp->Posizione.right,(240-Temp->Posizione.top)); LineTo(hdc,Temp->Posizione.left,(240-Temp->Posizione.top)); Temp = Temp->Successivo; } } EndPaint (hwnd, &ps); return 0 ; case WM_SINGOLA_IMMAGINE_I: params.Immagine = (imgdes*) lParam; ImmagineAttiva = true; InvalidateRect(hwnd,NULL,true); return 0; case WM_COLORE_PRONTO: params.Rettangoli = (RETTANGOLI*) lParam; ColoreAttivo = true; InvalidateRect(hwnd,NULL,true); return 0; case WM_POSIZIONI_PRONTE: Testa = ((CELLA_POSIZIONE*) lParam)->Successivo; PosizioniAttive = true; return 0; case WM_START_THREAD_I: params.Oz = (Decisore*) wParam; params.Mago = (Intercetta*) lParam; params.Ucciso = false; ColoreAttivo = true; ImmagineAttiva = true; _beginthread(ThreadIntercettaFrontale,0,¶ms); return 0; case WM_STATO_MUTATO: params.Stato = (UINT) lParam; break; case WM_STOP_THREAD_I: params.Ucciso = true; ImmagineAttiva = false; ColoreAttivo = false; PosizioniAttive = false; InvalidateRect(hwnd,NULL,true); return 0; case WM_SINGOLA_IMMAGINE_II: params.Immagine = (imgdes*) lParam; ImmagineAttiva = true; InvalidateRect(hwnd,NULL,true); return 0; case WM_START_THREAD_II: params.Mago = (Intercetta*) lParam; params.Ucciso = false; ImmagineAttiva = true; _beginthread(ThreadIntercettaII,0,¶ms); return 0; case WM_STOP_THREAD_II: params.Ucciso = true; ImmagineAttiva = false; return 0; case WM_DESTROY: DeleteObject(PennaZona); DeleteObject(PennaFocus); DeleteObject(PennaDominio); DeleteObject(PennaLento); DeleteObject(PennaPosizioni); params.Ucciso = true; return 0; } return DefWindowProc(hwnd,message,wParam,lParam); } Controlli LRESULT CALLBACK ControlliProc(HWND hwnd,UINT { const short VELOCITA_THREAD_FRONTALE = const short VELOCITA_THREAD_LATERALE = const short AQUISIZIONI = const short CLICK_TEMPORALE = const short BORDO_CLICH_TEMPORALE = const short FOCUS = HDC hdc; message,WPARAM wParam,LPARAM lParam) 1; 4; 7; 10; 13; 16; 41 PAINTSTRUCT HINSTANCE TCHAR static RECT static UINT static HPEN static HBRUSH static HWND ps; hInstance; Buffer[4]; Invalido; StatoFrontale,StatoLaterale; PennaContorno; PennelloSfondo,PennelloNero,PennelloStatoCatturaRosso, PennelloStatoCatturaGiallo,PennelloStatoCatturaVerde, PennelloStatoCatturaInizio,PennelloRossoSpento,PennelloGialloSpento, PennelloVerdeSpento; SleepThreadFrontale,LabelSleepThreadFrontale,ValueSleepThreadFrontale, SleepThreadLaterale,LabelSleepThreadLaterale,ValueSleepThreadLaterale, NumeroAquisizioni,LabelNumeroAquisizioni,ValueNumeroAquisizioni, ClickTemporale,LabelClickTemporale,ValueClickTemporale, BordoClickTemporale,LabelBordoClickTemporale,ValueBordoClickTemporale, Focus,LabelFocus,ValueFocus; Hit; cxClient,cyClient; static bool static int switch (message) { case WM_CREATE: hInstance PennaContorno PennelloSfondo PennelloNero PennelloStatoCatturaRosso PennelloStatoCatturaGiallo PennelloStatoCatturaVerde PennelloStatoCatturaInizio PennelloRossoSpento PennelloGialloSpento PennelloVerdeSpento SleepThreadFrontale = = = = = = = = = = = = (HINSTANCE) GetWindowLong(hwnd,GWL_HINSTANCE); CreatePen(PS_SOLID,1,RGB(0,0,0)); CreateSolidBrush(RGB(170,170,170)); CreateSolidBrush(RGB(100,100,100)); CreateSolidBrush(RGB(255,0,0)); CreateSolidBrush(RGB(255,255,0)); CreateSolidBrush(RGB(0,255,0)); CreateSolidBrush(RGB(255,255,255)); CreateSolidBrush(RGB(100,0,0)); CreateSolidBrush(RGB(100,100,0)); CreateSolidBrush(RGB(0,100,0)); CreateWindow(TEXT ("scrollbar"),NULL, WS_CHILD | WS_VISIBLE | WS_TABSTOP | SBS_HORZ, 0,0,0,0,hwnd,(HMENU) 1,hInstance,NULL); LabelSleepThreadFrontale = CreateWindow(TEXT ("static"), TEXT ("Velocità thread Frontale"), WS_CHILD | WS_VISIBLE | SS_CENTER, 0,0,0,0,hwnd,(HMENU) 2,hInstance,NULL); ValueSleepThreadFrontale = CreateWindow(TEXT ("static"),TEXT ("10"), WS_CHILD | WS_VISIBLE | SS_CENTER, 0,0,0,0,hwnd,(HMENU) 3,hInstance,NULL); SetScrollRange(SleepThreadFrontale,SB_CTL,1,100,false); SetScrollPos(SleepThreadFrontale,SB_CTL,10,false); SleepThreadLaterale = CreateWindow(TEXT ("scrollbar"),NULL, WS_CHILD | WS_VISIBLE | WS_TABSTOP | SBS_HORZ, 0,0,0,0,hwnd,(HMENU) 4,hInstance,NULL); LabelSleepThreadLaterale = CreateWindow(TEXT ("static"), TEXT ("Velocità thread Laterale"), WS_CHILD | WS_VISIBLE | SS_CENTER, 0,0,0,0,hwnd,(HMENU) 5,hInstance,NULL); ValueSleepThreadLaterale = CreateWindow(TEXT ("static"),TEXT ("10"), WS_CHILD | WS_VISIBLE | SS_CENTER, 0,0,0,0,hwnd,(HMENU) 6,hInstance,NULL); SetScrollRange(SleepThreadLaterale,SB_CTL,1,100,false); SetScrollPos(SleepThreadLaterale,SB_CTL,10,false); NumeroAquisizioni = CreateWindow(TEXT ("scrollbar"),NULL, WS_CHILD | WS_VISIBLE | WS_TABSTOP | SBS_HORZ, 0,0,0,0,hwnd,(HMENU) 7,hInstance,NULL); LabelNumeroAquisizioni = CreateWindow(TEXT ("static"), TEXT ("Aquisizioni Lento"), WS_CHILD | WS_VISIBLE | SS_CENTER, 0,0,0,0,hwnd,(HMENU) 8,hInstance,NULL); ValueNumeroAquisizioni = CreateWindow(TEXT ("static"),TEXT ("10"), WS_CHILD | WS_VISIBLE | SS_CENTER, 0,0,0,0,hwnd,(HMENU) 9,hInstance,NULL); SetScrollRange(NumeroAquisizioni,SB_CTL,1,30,false); SetScrollPos(NumeroAquisizioni,SB_CTL,10,false); ClickTemporale = CreateWindow(TEXT ("scrollbar"),NULL, WS_CHILD | WS_VISIBLE | WS_TABSTOP | SBS_HORZ, 0,0,0,0,hwnd,(HMENU) 10,hInstance,NULL); LabelClickTemporale = CreateWindow(TEXT ("static"), TEXT ("Aquisizioni Click"), WS_CHILD | WS_VISIBLE | SS_CENTER, 0,0,0,0,hwnd,(HMENU) 11,hInstance,NULL); ValueClickTemporale = CreateWindow(TEXT ("static"),TEXT ("4"), WS_CHILD | WS_VISIBLE | SS_CENTER, 0,0,0,0,hwnd,(HMENU) 12,hInstance,NULL); SetScrollRange(ClickTemporale,SB_CTL,1,20,false); SetScrollPos(ClickTemporale,SB_CTL,4,false); BordoClickTemporale = CreateWindow(TEXT ("scrollbar"),NULL, WS_CHILD | WS_VISIBLE | WS_TABSTOP | SBS_HORZ, 0,0,0,0,hwnd,(HMENU) 13,hInstance,NULL); 42 LabelBordoClickTemporale = CreateWindow(TEXT ("static"), TEXT ("Dimensione Click"), WS_CHILD | WS_VISIBLE | SS_CENTER, 0,0,0,0,hwnd,(HMENU) 14,hInstance,NULL); ValueBordoClickTemporale = CreateWindow(TEXT ("static"),TEXT ("2"), WS_CHILD | WS_VISIBLE | SS_CENTER, 0,0,0,0,hwnd,(HMENU) 15,hInstance,NULL); SetScrollRange(BordoClickTemporale,SB_CTL,1,10,false); SetScrollPos(BordoClickTemporale,SB_CTL,2,false); Focus = CreateWindow(TEXT ("scrollbar"),NULL, WS_CHILD | WS_VISIBLE | WS_TABSTOP | SBS_HORZ, 0,0,0,0,hwnd,(HMENU) 16,hInstance,NULL); LabelFocus = CreateWindow(TEXT ("static"), TEXT ("Dimensione Focus"), WS_CHILD | WS_VISIBLE | SS_CENTER, 0,0,0,0,hwnd,(HMENU) 17,hInstance,NULL); ValueFocus = CreateWindow(TEXT ("static"),TEXT ("20"), WS_CHILD | WS_VISIBLE | SS_CENTER, 0,0,0,0,hwnd,(HMENU) 18,hInstance,NULL); SetScrollRange(Focus,SB_CTL,1,30,false); SetScrollPos(Focus,SB_CTL,20,false); StatoFrontale = STATO_CATTURA_INIZIO; StatoLaterale = STATO_CATTURA_INIZIO; return 0; case WM_SIZE: cxClient = LOWORD (lParam); cyClient = HIWORD (lParam); return 0; case WM_PAINT: MoveWindow(SleepThreadFrontale,(cxClient*3/17), (cyClient - 70),(cxClient*2/17),20,true); MoveWindow(LabelSleepThreadFrontale,(cxClient*3/17), (cyClient - 90),(cxClient*3/17),20,true); MoveWindow(ValueSleepThreadFrontale,(cxClient*5/17), (cyClient - 70),(cxClient*1/17),20,true); if(Angelo.GetDueWeb()) { MoveWindow(SleepThreadLaterale,(cxClient*3/17), (cyClient - 30),(cxClient*2/17),20,true); MoveWindow(LabelSleepThreadLaterale,(cxClient*3/17), (cyClient - 50),(cxClient*3/17),20,true); MoveWindow(ValueSleepThreadLaterale,(cxClient*5/17), (cyClient - 30),(cxClient*1/17),20,true); } else { MoveWindow(SleepThreadLaterale,0,0,0,0,true); MoveWindow(LabelSleepThreadLaterale,0,0,0,0,true); MoveWindow(ValueSleepThreadLaterale,0,0,0,0,true); } MoveWindow(NumeroAquisizioni,(cxClient*6/17), (cyClient - 70),(cxClient*2/17),20,true); MoveWindow(LabelNumeroAquisizioni,(cxClient*6/17), (cyClient - 90),(cxClient*3/17),20,true); MoveWindow(ValueNumeroAquisizioni,(cxClient*8/17), (cyClient - 70),(cxClient*1/17),20,true); MoveWindow(ClickTemporale,(cxClient*9/17),(cyClient - 70),(cxClient*2/17),20,true); MoveWindow(LabelClickTemporale,(cxClient*9/17), (cyClient - 90),(cxClient*3/17),20,true); MoveWindow(ValueClickTemporale,(cxClient*11/17), (cyClient - 70),(cxClient*1/17),20,true); MoveWindow(BordoClickTemporale,(cxClient*9/17), (cyClient - 30),(cxClient*2/17),20,true); MoveWindow(LabelBordoClickTemporale,(cxClient*9/17), (cyClient - 50),(cxClient*3/17),20,true); MoveWindow(ValueBordoClickTemporale,(cxClient*11/17), (cyClient - 30),(cxClient*1/17),20,true); MoveWindow(Focus,(cxClient*12/17),(cyClient - 70),(cxClient*2/17),20,true); MoveWindow(LabelFocus,(cxClient*12/17),(cyClient - 90),(cxClient*3/17),20,true); MoveWindow(ValueFocus,(cxClient*14/17),(cyClient - 70),(cxClient*1/17),20,true); hdc = BeginPaint(hwnd,&ps); SelectObject(hdc,PennelloSfondo); Rectangle(hdc,0,0,cxClient,100); SelectObject(hdc,PennaContorno); SelectObject(hdc,PennelloNero); RoundRect(hdc,(cxClient*1/17),10,(cxClient*1/17)+20,70,5,5); SelectObject(hdc,PennelloRossoSpento); RoundRect(hdc,(cxClient*1/17),10,(cxClient*1/17)+20,30,20,20); SelectObject(hdc,PennelloGialloSpento); RoundRect(hdc,(cxClient*1/17),30,(cxClient*1/17)+20,50,20,20); SelectObject(hdc,PennelloVerdeSpento); RoundRect(hdc,(cxClient*1/17),50,(cxClient*1/17)+20,70,20,20); if(Angelo.GetDueWeb()) 43 { SelectObject(hdc,PennelloNero); RoundRect(hdc,(cxClient*2/17),10,(cxClient*2/17)+20,70,5,5); SelectObject(hdc,PennelloRossoSpento); RoundRect(hdc,(cxClient*2/17),10,(cxClient*2/17)+20,30,20,20); SelectObject(hdc,PennelloGialloSpento); RoundRect(hdc,(cxClient*2/17),30,(cxClient*2/17)+20,50,20,20); SelectObject(hdc,PennelloVerdeSpento); RoundRect(hdc,(cxClient*2/17),50,(cxClient*2/17)+20,70,20,20); } switch (StatoFrontale) { case STATO_CATTURA_INIZIO: break; case STATO_CATTURA_VERDE: SelectObject(hdc,PennelloStatoCatturaVerde); RoundRect(hdc,(cxClient*1/17),50,(cxClient*1/17)+20,70,20,20); break; case STATO_CATTURA_GIALLO: SelectObject(hdc,PennelloStatoCatturaGiallo); RoundRect(hdc,(cxClient*1/17),30,(cxClient*1/17)+20,50,20,20); break; case STATO_CATTURA_ROSSO: SelectObject(hdc,PennelloStatoCatturaRosso); RoundRect(hdc,(cxClient*1/17),10,(cxClient*1/17)+20,30,20,20); break; } if(Angelo.GetDueWeb()) { switch (StatoLaterale) { case STATO_CATTURA_INIZIO: break; case STATO_CATTURA_VERDE: SelectObject(hdc,PennelloStatoCatturaVerde); RoundRect(hdc,(cxClient*2/17),50,(cxClient*2/17)+20,70,20,20); break; case STATO_CATTURA_GIALLO: SelectObject(hdc,PennelloStatoCatturaGiallo); RoundRect(hdc,(cxClient*2/17),30,(cxClient*2/17)+20,50,20,20); break; case STATO_CATTURA_ROSSO: SelectObject(hdc,PennelloStatoCatturaRosso); RoundRect(hdc,(cxClient*2/17),10,(cxClient*2/17)+20,30,20,20); break; } } EndPaint (hwnd, &ps); return 0; case WM_STATO_CATTURA_MUTATO_FRONTALE: StatoFrontale = (UINT) lParam; SetRect(&Invalido,(cxClient*1/17),10,(cxClient*1/17)+20,70); InvalidateRect(hwnd,&Invalido,true); return 0; case WM_STATO_CATTURA_MUTATO_LATERALE: StatoLaterale = (UINT) lParam; SetRect(&Invalido,(cxClient*2/17),10,(cxClient*2/17)+20,70); InvalidateRect(hwnd,&Invalido,true); return 0; case WM_HSCROLL: Hit = false; switch(GetWindowLong((HWND) lParam,GWL_ID)) { case VELOCITA_THREAD_FRONTALE: switch(LOWORD(wParam)) { case SB_LINEUP: SLEEP_THREAD_FRONTALE = max(1,SLEEP_THREAD_FRONTALE - 1); Hit = true; break; case SB_LINEDOWN: SLEEP_THREAD_FRONTALE = min(100,SLEEP_THREAD_FRONTALE + 1); Hit = true; break; case SB_THUMBTRACK: SLEEP_THREAD_FRONTALE = HIWORD(wParam); Hit = true; break; case SB_PAGEDOWN: SLEEP_THREAD_FRONTALE = min(100,SLEEP_THREAD_FRONTALE + 10); Hit = true; break; 44 case SB_PAGEUP: SLEEP_THREAD_FRONTALE = max(1,SLEEP_THREAD_FRONTALE - 10); Hit = true; break; } if(Hit) { SetScrollPos(SleepThreadFrontale,SB_CTL,SLEEP_THREAD_FRONTALE,true); wsprintf(Buffer,TEXT("%i"),SLEEP_THREAD_FRONTALE); SetWindowText(ValueSleepThreadFrontale,Buffer); SetRect(&Invalido,(cxClient*5/17),(cyClient - 70), (cxClient*6/17),(cyClient - 50)); InvalidateRect(hwnd,&Invalido,true); } break; case VELOCITA_THREAD_LATERALE: switch(LOWORD(wParam)) { case SB_LINEUP: SLEEP_THREAD_LATERALE = max(1,SLEEP_THREAD_LATERALE - 1); Hit = true; break; case SB_LINEDOWN: SLEEP_THREAD_LATERALE = min(100,SLEEP_THREAD_LATERALE + 1); Hit = true; break; case SB_THUMBTRACK: SLEEP_THREAD_LATERALE = HIWORD(wParam); Hit = true; break; case SB_PAGEDOWN: SLEEP_THREAD_LATERALE = min(100,SLEEP_THREAD_LATERALE + 10); Hit = true; break; case SB_PAGEUP: SLEEP_THREAD_LATERALE = max(1,SLEEP_THREAD_LATERALE - 10); Hit = true; break; } if(Hit) { SetScrollPos(SleepThreadLaterale,SB_CTL,SLEEP_THREAD_LATERALE,true); wsprintf(Buffer,TEXT("%i"),SLEEP_THREAD_LATERALE); SetWindowText(ValueSleepThreadLaterale,Buffer); SetRect(&Invalido,(cxClient*5/17), (cyClient - 30),(cxClient*6/17),(cyClient - 10)); InvalidateRect(hwnd,&Invalido,true); } break; case AQUISIZIONI: switch(LOWORD(wParam)) { case SB_LINEUP: NUMERO_AQUISIZIONI = max(1,NUMERO_AQUISIZIONI - 1); Hit = true; break; case SB_LINEDOWN: NUMERO_AQUISIZIONI = min(30,NUMERO_AQUISIZIONI + 1); Hit = true; break; case SB_THUMBTRACK: NUMERO_AQUISIZIONI = HIWORD(wParam); Hit = true; break; case SB_PAGEDOWN: NUMERO_AQUISIZIONI = min(30,NUMERO_AQUISIZIONI + 10); Hit = true; break; case SB_PAGEUP: NUMERO_AQUISIZIONI = max(1,NUMERO_AQUISIZIONI - 10); Hit = true; break; } if(Hit) { SetScrollPos(NumeroAquisizioni,SB_CTL,NUMERO_AQUISIZIONI,true); wsprintf(Buffer,TEXT("%i"),NUMERO_AQUISIZIONI); SetWindowText(ValueNumeroAquisizioni,Buffer); SetRect(&Invalido,(cxClient*8/17),(cyClient - 70), (cxClient*9/17),(cyClient - 50)); InvalidateRect(hwnd,&Invalido,true); } break; 45 case CLICK_TEMPORALE: switch(LOWORD(wParam)) { case SB_LINEUP: NUMERO_AQUISIZIONI_AREA_CLICK = max(1,NUMERO_AQUISIZIONI_AREA_CLICK - 1); Hit = true; break; case SB_LINEDOWN: NUMERO_AQUISIZIONI_AREA_CLICK = min(20,NUMERO_AQUISIZIONI_AREA_CLICK + 1); Hit = true; break; case SB_THUMBTRACK: NUMERO_AQUISIZIONI_AREA_CLICK = HIWORD(wParam); Hit = true; break; case SB_PAGEDOWN: NUMERO_AQUISIZIONI_AREA_CLICK = min(20,NUMERO_AQUISIZIONI_AREA_CLICK + 10); Hit = true; break; case SB_PAGEUP: NUMERO_AQUISIZIONI_AREA_CLICK = max(1,NUMERO_AQUISIZIONI_AREA_CLICK - 10); Hit = true; break; } if(Hit) { SetScrollPos(ClickTemporale,SB_CTL,NUMERO_AQUISIZIONI_AREA_CLICK,true); wsprintf(Buffer,TEXT("%i"),NUMERO_AQUISIZIONI_AREA_CLICK); SetWindowText(ValueClickTemporale,Buffer); SetRect(&Invalido,(cxClient*11/17),(cyClient - 70), (cxClient*12/17),(cyClient - 50)); InvalidateRect(hwnd,&Invalido,true); } break; case BORDO_CLICH_TEMPORALE: switch(LOWORD(wParam)) { case SB_LINEUP: BORDO_CLICK = max(1,BORDO_CLICK - 1); Hit = true; break; case SB_LINEDOWN: BORDO_CLICK = min(10,BORDO_CLICK + 1); Hit = true; break; case SB_THUMBTRACK: BORDO_CLICK = HIWORD(wParam); Hit = true; break; case SB_PAGEDOWN: BORDO_CLICK = min(10,BORDO_CLICK + 2); Hit = true; break; case SB_PAGEUP: BORDO_CLICK = max(1,BORDO_CLICK - 2); Hit = true; break; } if(Hit) { SetScrollPos(BordoClickTemporale,SB_CTL,BORDO_CLICK,true); wsprintf(Buffer,TEXT("%i"),BORDO_CLICK); SetWindowText(ValueBordoClickTemporale,Buffer); SetRect(&Invalido,(cxClient*11/17),(cyClient - 30), (cxClient*12/17),(cyClient - 10)); InvalidateRect(hwnd,&Invalido,true); } break; case FOCUS: switch(LOWORD(wParam)) { case SB_LINEUP: BORDO_FOCUS = max(1,BORDO_FOCUS - 1); Hit = true; break; case SB_LINEDOWN: BORDO_FOCUS = min(30,BORDO_FOCUS + 1); Hit = true; break; case SB_THUMBTRACK: BORDO_FOCUS = HIWORD(wParam); Hit = true; break; 46 case SB_PAGEDOWN: BORDO_FOCUS = min(30,BORDO_FOCUS + 5); Hit = true; break; case SB_PAGEUP: BORDO_FOCUS = max(1,BORDO_FOCUS - 5); Hit = true; break; } if(Hit) { SetScrollPos(Focus,SB_CTL,BORDO_FOCUS,true); wsprintf(Buffer,TEXT("%i"),BORDO_FOCUS); SetWindowText(ValueFocus,Buffer); SetRect(&Invalido,(cxClient*14/17),(cyClient - 70), (cxClient*15/17),(cyClient - 50)); InvalidateRect(hwnd,&Invalido,true); } break; } return 0; case WM_DESTROY: DeleteObject(PennaContorno); DeleteObject(PennelloSfondo); DeleteObject(PennelloNero); DeleteObject(PennelloStatoCatturaRosso); DeleteObject(PennelloStatoCatturaGiallo); DeleteObject(PennelloStatoCatturaVerde); DeleteObject(PennelloStatoCatturaInizio); DeleteObject(PennelloRossoSpento); DeleteObject(PennelloGialloSpento); DeleteObject(PennelloVerdeSpento); return 0; } return DefWindowProc (hwnd, message, wParam, lParam) ; } Posiziona LRESULT CALLBACK PosizionaProc(HWND hwnd,UINT message,WPARAM wParam,LPARAM lParam) { HDC hdc; PAINTSTRUCT ps; static PARAMS_POSIZIONA params; static HPEN PennaPuntoRosso,PennaPuntoGiallo,PennaPuntoVerde,PennaPuntoInizio, PennaBackRosso,PennaBackGiallo,PennaBackVerde,PennaBackInizio; RECT Rect; static UINT StatoCatturaFrontale,StatoCatturaLaterale,StatoProgresso; switch (message) { case WM_CREATE: PennaPuntoRosso = CreatePen(PS_SOLID,18,RGB(255,0,0)); PennaPuntoGiallo = CreatePen(PS_SOLID,18,RGB(255,255,0)); PennaPuntoVerde = CreatePen(PS_SOLID,18,RGB(0,255,0)); PennaPuntoInizio = CreatePen(PS_SOLID,18,RGB(0,255,0)); PennaBackRosso = CreatePen(PS_SOLID,20,RGB(255,0,0)); PennaBackGiallo = CreatePen(PS_SOLID,20,RGB(255,255,0)); PennaBackVerde = CreatePen(PS_SOLID,20,RGB(0,255,0)); PennaBackInizio = CreatePen(PS_SOLID,20,RGB(255,255,255)); params.hwnd = hwnd; params.Angelo = &Angelo; params.Stato = STATO_ZERO; StatoCatturaFrontale = STATO_CATTURA_INIZIO; StatoCatturaLaterale = STATO_CATTURA_INIZIO; StatoProgresso = STATO_PROGRESSO_INIZIO; return 0; case WM_PAINT: hdc = BeginPaint(hwnd,&ps); switch (StatoProgresso) { case STATO_PROGRESSO_INIZIO: SelectObject(hdc,PennaBackInizio); break; case STATO_PROGRESSO_UNO: SelectObject(hdc,PennaBackRosso); break; case STATO_PROGRESSO_DUE: SelectObject(hdc,PennaBackGiallo); break; case STATO_PROGRESSO_TRE: SelectObject(hdc,PennaBackVerde); break; 47 } GetClientRect(hwnd,&Rect); switch (params.Stato) { case STATO_UNO: DrawText(hdc,TEXT ("Segui il pallino !"),-1, &Rect,DT_SINGLELINE | DT_CENTER ); MoveToEx(hdc,Rect.right/2,Rect.bottom/2,NULL); LineTo(hdc,Rect.right/2,Rect.bottom/2); break; case STATO_DUE: MoveToEx(hdc,10,Rect.bottom/2,NULL); LineTo(hdc,10,Rect.bottom/2); break; case STATO_TRE: MoveToEx(hdc,10,Rect.bottom-10,NULL); LineTo(hdc,10,Rect.bottom-10); break; case STATO_QUATTRO: MoveToEx(hdc,Rect.right/2,Rect.bottom-10,NULL); LineTo(hdc,Rect.right/2,Rect.bottom-10); break; case STATO_CINQUE: MoveToEx(hdc,Rect.right-10,Rect.bottom-10,NULL); LineTo(hdc,Rect.right-10,Rect.bottom-10); break; case STATO_SEI: MoveToEx(hdc,Rect.right-10,Rect.bottom/2,NULL); LineTo(hdc,Rect.right-10,Rect.bottom/2); break; case STATO_SETTE: MoveToEx(hdc,Rect.right-10,10,NULL); LineTo(hdc,Rect.right-10,10); break; case STATO_OTTO: MoveToEx(hdc,Rect.right/2,10,NULL); LineTo(hdc,Rect.right/2,10); break; case STATO_NOVE: MoveToEx(hdc,10,10,NULL); LineTo(hdc,10,10); break; case STATO_FINE: DrawText(hdc,TEXT ("Settaggio concluso !"),-1, &Rect,DT_SINGLELINE | DT_CENTER ); break; } switch (StatoCatturaFrontale) { case STATO_CATTURA_INIZIO: case STATO_CATTURA_VERDE: switch (StatoCatturaLaterale) { case STATO_CATTURA_INIZIO: case STATO_CATTURA_VERDE: SelectObject(hdc,PennaPuntoVerde); break; case STATO_CATTURA_GIALLO: SelectObject(hdc,PennaPuntoGiallo); break; case STATO_CATTURA_ROSSO: SelectObject(hdc,PennaPuntoRosso); break; } break; case STATO_CATTURA_GIALLO: switch (StatoCatturaLaterale) { case STATO_CATTURA_INIZIO: case STATO_CATTURA_VERDE: case STATO_CATTURA_GIALLO: SelectObject(hdc,PennaPuntoGiallo); break; case STATO_CATTURA_ROSSO: SelectObject(hdc,PennaPuntoRosso); break; } break; case STATO_CATTURA_ROSSO: SelectObject(hdc,PennaPuntoRosso); break; } switch (params.Stato) 48 { case STATO_UNO: MoveToEx(hdc,Rect.right/2,Rect.bottom/2,NULL); LineTo(hdc,Rect.right/2,Rect.bottom/2); break; case STATO_DUE: MoveToEx(hdc,10,Rect.bottom/2,NULL); LineTo(hdc,10,Rect.bottom/2); break; case STATO_TRE: MoveToEx(hdc,10,Rect.bottom-10,NULL); LineTo(hdc,10,Rect.bottom-10); break; case STATO_QUATTRO: MoveToEx(hdc,Rect.right/2,Rect.bottom-10,NULL); LineTo(hdc,Rect.right/2,Rect.bottom-10); break; case STATO_CINQUE: MoveToEx(hdc,Rect.right-10,Rect.bottom-10,NULL); LineTo(hdc,Rect.right-10,Rect.bottom-10); break; case STATO_SEI: MoveToEx(hdc,Rect.right-10,Rect.bottom/2,NULL); LineTo(hdc,Rect.right-10,Rect.bottom/2); break; case STATO_SETTE: MoveToEx(hdc,Rect.right-10,10,NULL); LineTo(hdc,Rect.right-10,10); break; case STATO_OTTO: MoveToEx(hdc,Rect.right/2,10,NULL); LineTo(hdc,Rect.right/2,10); break; case STATO_NOVE: MoveToEx(hdc,10,10,NULL); LineTo(hdc,10,10); break; case STATO_FINE: DrawText(hdc,TEXT ("Settaggio concluso !"),-1,&Rect, DT_SINGLELINE | DT_CENTER ); break; } EndPaint(hwnd,&ps); return 0 ; case WM_STATO_CATTURA_MUTATO_FRONTALE: StatoCatturaFrontale = (UINT) lParam; InvalidateRect(hwnd,NULL,true); return 0; case WM_STATO_CATTURA_MUTATO_LATERALE: StatoCatturaLaterale = (UINT) lParam; InvalidateRect(hwnd,NULL,true); return 0; case WM_STATO_PROGRESSO_MUTATO: StatoProgresso = (UINT) lParam; InvalidateRect(hwnd,NULL,true); return 0; case WM_STATO_MUTATO: params.Stato = (UINT) lParam; return 0; case WM_START_POSIZIONA: params.Stato = STATO_UNO; StatoCatturaFrontale = STATO_CATTURA_INIZIO; StatoCatturaLaterale = STATO_CATTURA_INIZIO; params.Ucciso= false; _beginthread(ThreadPosiziona,0,¶ms); return 0; case WM_STOP_POSIZIONA: params.Ucciso = true; return 0; case WM_DESTROY: params.Ucciso = true; DeleteObject(PennaPuntoRosso); DeleteObject(PennaPuntoGiallo); DeleteObject(PennaPuntoVerde); DeleteObject(PennaPuntoInizio); DeleteObject(PennaBackRosso); DeleteObject(PennaBackGiallo); DeleteObject(PennaBackVerde); DeleteObject(PennaBackInizio); return 0; } return DefWindowProc(hwnd,message,wParam,lParam) ; } 49 50 Appendice D Windows procedure delle applicazioni indipendenti Applicazione Uno: addestramento LRESULT CALLBACK DemoUnoProc (HWND hwnd,UINT message,WPARAM wParam,LPARAM lParam) { const short DIMENSIONE_BERSAGLIO = 40; const short BORDO_INVALIDO = 5; HDC hdc; PAINTSTRUCT ps; static PUNTO *Punto,Precedente; static bool Click,PuntoAttivo; static UINT StatoCattura; static HPEN PennaPunto,PennaBersaglio,PennaContorno; static HBRUSH PennelloStatoCatturaRosso,PennelloStatoCatturaGiallo, PennelloStatoCatturaVerde,PennelloBersaglio; static RECT Bersaglio,Invalido; static int cxClient,cyClient; switch (message) { case WM_CREATE: PennaPunto = CreatePen(PS_SOLID,10,RGB(0,0,255)); PennaBersaglio = CreatePen(PS_SOLID,2,RGB(255,0,0)); PennaContorno = CreatePen(PS_SOLID,1,RGB(0,0,0)); PennelloStatoCatturaRosso = CreateSolidBrush(RGB(255,0,0)); PennelloStatoCatturaGiallo = CreateSolidBrush(RGB(255,255,0)); PennelloStatoCatturaVerde = CreateSolidBrush(RGB(0,255,0)); PennelloBersaglio = CreateSolidBrush(RGB(255,255,255)); PuntoAttivo = false; Click = false; StatoCattura = STATO_CATTURA_INIZIO; return 0; case WM_SIZE: cxClient = LOWORD (lParam); cyClient = HIWORD (lParam); Bersaglio.left = (rand() % (cxClient - DIMENSIONE_BERSAGLIO)); Bersaglio.top = (rand() % (cyClient - DIMENSIONE_BERSAGLIO)); Bersaglio.right = Bersaglio.left + DIMENSIONE_BERSAGLIO; Bersaglio.bottom = Bersaglio.top + DIMENSIONE_BERSAGLIO; return 0; case WM_CREATE_PUNTO: Punto = (PUNTO*) lParam; Precedente.X = Punto->X; Precedente.Y = Punto->Y; PuntoAttivo = true; return 0; case WM_NON_VALIDO: if(Precedente.X >= Punto->X) { Invalido.left = (int)(Punto->X * cxClient) - BORDO_INVALIDO; Invalido.right = (int)(Precedente.X * cxClient) + BORDO_INVALIDO; } else { Invalido.left = (int)(Precedente.X * cxClient) - BORDO_INVALIDO; Invalido.right = (int)(Punto->X * cxClient) + BORDO_INVALIDO; } if(Precedente.Y >= Punto->Y) { Invalido.top = (int)(Punto->Y * cyClient) - BORDO_INVALIDO; Invalido.bottom = (int)(Precedente.Y * cyClient) + BORDO_INVALIDO; } else { Invalido.top = (int)(Precedente.Y * cyClient) - BORDO_INVALIDO; Invalido.bottom = (int)(Punto->Y * cyClient) + BORDO_INVALIDO; } InvalidateRect(hwnd,&Invalido,true); return 0; case WM_CLICK: Click = true; InvalidateRect(hwnd,NULL,true); return 0; 51 case WM_STATO_CATTURA_MUTATO: StatoCattura = (UINT) lParam; InvalidateRect(hwnd,NULL,true); return 0; case WM_PAINT: hdc = BeginPaint(hwnd,&ps); switch (StatoCattura) { case STATO_CATTURA_VERDE: SelectObject(hdc,PennelloStatoCatturaVerde); break; case STATO_CATTURA_GIALLO: SelectObject(hdc,PennelloStatoCatturaGiallo); break; case STATO_CATTURA_ROSSO: SelectObject(hdc,PennelloStatoCatturaRosso); break; } SelectObject(hdc,PennaContorno); RoundRect(hdc,10,10,30,50,10,10); SelectObject(hdc,PennaBersaglio); SelectObject(hdc,PennelloBersaglio); Rectangle(hdc,Bersaglio.left,Bersaglio.top,Bersaglio.right,Bersaglio.bottom); if(PuntoAttivo) { if(Click) { Click = false; if(((int)(Punto->X * cxClient) >= Bersaglio.left) && ((int)(Punto->X * cxClient) <= Bersaglio.right) && ((int)(Punto->Y * cyClient) >= Bersaglio.top) && ((int)(Punto->Y * cyClient) <= Bersaglio.bottom)) { Bersaglio.left = (rand() % (cxClient - DIMENSIONE_BERSAGLIO)); Bersaglio.top = (rand() % (cyClient - DIMENSIONE_BERSAGLIO)); Bersaglio.right = Bersaglio.left + DIMENSIONE_BERSAGLIO; Bersaglio.bottom = Bersaglio.top + DIMENSIONE_BERSAGLIO; InvalidateRect(hwnd,NULL,true); } } SelectObject(hdc,PennaPunto); MoveToEx(hdc,(int)(Punto->X * cxClient),(int)(Punto->Y * cyClient),NULL); LineTo(hdc,(int)(Punto->X * cxClient),(int)(Punto->Y * cyClient)); Precedente.X = Punto->X; Precedente.Y = Punto->Y; } EndPaint(hwnd,&ps); return 0; case WM_DESTROY: DeleteObject(PennaPunto); DeleteObject(PennaBersaglio); DeleteObject(PennaContorno); DeleteObject(PennelloStatoCatturaRosso); DeleteObject(PennelloStatoCatturaGiallo); DeleteObject(PennelloStatoCatturaVerde); DeleteObject(PennelloBersaglio); return 0; } return DefWindowProc (hwnd,message,wParam,lParam) ; } Applicazione Due: tastiera #include "Spaziatore.h" LRESULT CALLBACK DemoDueProc(HWND hwnd,UINT message,WPARAM wParam,LPARAM lParam) { const short BORDO_INVALIDO = 5; HDC hdc; PAINTSTRUCT ps; TCHAR Buffer[3]; static PUNTO *Punto,Precedente; static bool Click,PuntoAttivo; static RECT Invalido; static HPEN PennaPunto,PennaCella,PennaFrase,PennaClick; static HBRUSH PennelloStatoCatturaRosso,PennelloStatoCatturaGiallo, PennelloStatoCatturaVerde; static UINT StatoCattura; static HWND Scroll,Label,Value,Frase; static Spaziatore Griglia; HINSTANCE hInstance; static int Definizione,cxClient,cyClient; 52 bool Hit; switch (message) { case WM_CREATE: hInstance = (HINSTANCE) GetWindowLong(hwnd,GWL_HINSTANCE); PennaPunto = CreatePen(PS_SOLID,10,RGB(255,0,0)); PennaClick = CreatePen(PS_SOLID,10,RGB(0,255,0)); PennaCella = CreatePen(PS_SOLID,2,RGB(40,190,230)); PennaFrase = CreatePen(PS_SOLID,1,RGB(0,0,0)); PennelloStatoCatturaRosso = CreateSolidBrush(RGB(255,0,0)); PennelloStatoCatturaGiallo = CreateSolidBrush(RGB(255,255,0)); PennelloStatoCatturaVerde = CreateSolidBrush(RGB(0,255,0)); StatoCattura = STATO_CATTURA_VERDE; PuntoAttivo = false; Click = false; Definizione = 1; Scroll = CreateWindow(TEXT ("scrollbar"),NULL, WS_CHILD | WS_VISIBLE | WS_TABSTOP | SBS_HORZ, 0,0,0,0,hwnd,(HMENU) 1,hInstance,NULL) ; SetScrollRange(Scroll,SB_CTL,1,5,FALSE); SetScrollPos(Scroll,SB_CTL,1,FALSE); Label = CreateWindow(TEXT ("static"),TEXT ("Dimensione Tasti"), WS_CHILD | WS_VISIBLE | SS_CENTER, 0,0,0,0,hwnd,(HMENU) 2,hInstance,NULL); Value = CreateWindow(TEXT ("static"),TEXT ("1"), WS_CHILD | WS_VISIBLE | SS_CENTER, 0,0,0,0,hwnd,(HMENU) 3,hInstance,NULL); return 0; case WM_SIZE: cxClient = LOWORD (lParam); cyClient = HIWORD (lParam); Griglia.Set(cxClient,(cyClient - 80),Definizione); MoveWindow(Scroll,(cxClient-140),(cyClient - 40),100,20,true); MoveWindow(Label,(cxClient-200),(cyClient - 60),160,20,true); MoveWindow(Value,(cxClient-200),(cyClient - 40),60,20,true); return 0; case WM_CREATE_PUNTO: Punto = (PUNTO*) lParam; Precedente.X = Punto->X; Precedente.Y = Punto->Y; PuntoAttivo = true; return 0; case WM_NON_VALIDO: if(Precedente.X >= Punto->X) { Invalido.left = (int)(Punto->X * cxClient) - BORDO_INVALIDO; Invalido.right = (int)(Precedente.X * cxClient) + BORDO_INVALIDO; } else { Invalido.left = (int)(Precedente.X * cxClient) - BORDO_INVALIDO; Invalido.right = (int)(Punto->X * cxClient) + BORDO_INVALIDO; } if(Precedente.Y >= Punto->Y) { Invalido.top = (int)(Punto->Y * cyClient) - BORDO_INVALIDO; Invalido.bottom = (int)(Precedente.Y * cyClient) + BORDO_INVALIDO; } else { Invalido.top = (int)(Precedente.Y * cyClient) - BORDO_INVALIDO; Invalido.bottom = (int)(Punto->Y * cyClient) + BORDO_INVALIDO; } InvalidateRect(hwnd,&Invalido,true); return 0; case WM_CLICK: Griglia.GetFrase((int)(Punto->X * cxClient),(int)(Punto->Y * cyClient)); Click = true; if(Precedente.X >= Punto->X) { Invalido.left = (int)(Punto->X * cxClient) - BORDO_INVALIDO; Invalido.right = (int)(Precedente.X * cxClient) + BORDO_INVALIDO; } else { Invalido.left = (int)(Precedente.X * cxClient) - BORDO_INVALIDO; Invalido.right = (int)(Punto->X * cxClient) + BORDO_INVALIDO; } if(Precedente.Y >= Punto->Y) { Invalido.top = (int)(Punto->Y * cyClient) - BORDO_INVALIDO; Invalido.bottom = (int)(Precedente.Y * cyClient) + BORDO_INVALIDO; } 53 else { Invalido.top = (int)(Precedente.Y * cyClient) - BORDO_INVALIDO; Invalido.bottom = (int)(Punto->Y * cyClient) + BORDO_INVALIDO; } InvalidateRect(hwnd,&Invalido,true); SetRect(&Invalido,10,(cyClient - 60),(cxClient - 200),(cyClient - 20)); InvalidateRect(hwnd,&Invalido,true); return 0; case WM_STATO_CATTURA_MUTATO: StatoCattura = (UINT) lParam; SetRect(&Invalido,(cxClient-30),(cyClient-60),(cxClient-10),(cyClient-20)); InvalidateRect(hwnd,NULL,true); return 0; case WM_PAINT: hdc = BeginPaint (hwnd, &ps); SelectObject(hdc,PennaCella); MoveToEx(hdc,0,0,NULL); while(Griglia.Get()) { MoveToEx(hdc,Griglia.Cella.left,Griglia.Cella.top,NULL); LineTo(hdc,Griglia.Cella.left,Griglia.Cella.bottom); LineTo(hdc,Griglia.Cella.right,Griglia.Cella.bottom); LineTo(hdc,Griglia.Cella.right,Griglia.Cella.top); LineTo(hdc,Griglia.Cella.left,Griglia.Cella.top); TextOut(hdc,(Griglia.Cella.left + 5),(Griglia.Cella.top +5), &Griglia.Lettera,1); } TextOut(hdc,5,5,TEXT ("Canc"),4); SelectObject(hdc,PennaFrase); SelectObject(hdc,WHITE_BRUSH); RoundRect(hdc,10,(cyClient - 60),(cxClient - 220),(cyClient - 20),10,10); TextOut(hdc,15,(cyClient - 50),(char*)&Griglia.Frase,Griglia.ContatoreFrase); switch (StatoCattura) { case STATO_CATTURA_VERDE: SelectObject(hdc,PennelloStatoCatturaVerde); break; case STATO_CATTURA_GIALLO: SelectObject(hdc,PennelloStatoCatturaGiallo); break; case STATO_CATTURA_ROSSO: SelectObject(hdc,PennelloStatoCatturaRosso); break; } RoundRect(hdc,(cxClient - 30),(cyClient - 60),(cxClient - 10),(cyClient - 20),10,10); if(PuntoAttivo) { if(Click) { SelectObject(hdc,PennaClick); Click = false; } else SelectObject(hdc,PennaPunto); MoveToEx(hdc,(int)(Punto->X * cxClient),(int)(Punto->Y * cyClient),NULL); LineTo(hdc,(int)(Punto->X * cxClient),(int)(Punto->Y * cyClient)); Precedente.X = Punto->X; Precedente.Y = Punto->Y; } EndPaint (hwnd, &ps); return 0; case WM_HSCROLL: Hit = false; switch(LOWORD(wParam)) { case SB_LINEUP: Definizione = max(1,Definizione - 1); Hit = true; break; case SB_LINEDOWN: Definizione = min(5,Definizione + 1); Hit = true; break; case SB_THUMBTRACK: Definizione = HIWORD(wParam); Hit = true; break; case SB_PAGEDOWN: Definizione = min(5,Definizione + 2); Hit = true; break; case SB_PAGEUP: 54 Definizione = max(1,Definizione - 2); Hit = true; break; } if(Hit) { Griglia.Set(cxClient,(cyClient - 80),Definizione); SetScrollPos(Scroll,SB_CTL,Definizione,true); wsprintf(Buffer,TEXT("%i"),Definizione); SetWindowText(Value,Buffer); InvalidateRect(hwnd,NULL,true); } return 0; case WM_DESTROY: DeleteObject(PennaPunto); DeleteObject(PennaCella); DeleteObject(PennaFrase); DeleteObject(PennelloStatoCatturaRosso); DeleteObject(PennelloStatoCatturaGiallo); DeleteObject(PennelloStatoCatturaVerde); return 0; } return DefWindowProc (hwnd,message,wParam,lParam) ; } Spaziatore #include <math.h> class Spaziatore { public: RECT Cella; char Lettera; char Frase[100]; short ContatoreFrase; public: inline Spaziatore() {} void Set(int,int,short); bool Get(); void GetFrase(int,int); private: int A,B,X,Y; short Definizione; short NumeroCelle,NumeroCelleX,NumeroCelleY, ContatoreX,ContatoreY,ContatoreLettere; static char Celle1[25],Celle2[31],Celle3[41],Celle4[51],Celle5[81]; char* Lettere; }; char char char char char Spaziatore::Celle1[25] Spaziatore::Celle2[31] Spaziatore::Celle3[41] Spaziatore::Celle4[51] Spaziatore::Celle5[81] = = = = = " " " " " abcdefghilmnopqrstuvz'"; abcdefghijklmnopqrstuvwxyz'\""; abcdefghijklmnopqrstuvwxyz'\"0123456789"; abcdefghijklmnopqrstuvwxyz'\"0123456789,.;:*+-<>_"; abcdefghijklmnopqrstuvwxyz'\"0123456789àèéìòù^°ç§\\|£$%&()[],.;:_<>@#!+*/ "; void Spaziatore::Set(int A,int B,short Definizione) { this->A = A; this->B = B; switch (Definizione) { case 1: NumeroCelle = 24; NumeroCelleX = 6; NumeroCelleY = 4; Lettere = (char*)&Celle1; break; case 2: NumeroCelle = 30; NumeroCelleX = 6; NumeroCelleY = 5; Lettere = (char*)&Celle2; break; case 3: NumeroCelle = 40; NumeroCelleX = 8; NumeroCelleY = 5; Lettere = (char*)&Celle3; break; case 4: 55 NumeroCelle = 50; NumeroCelleX = 10; NumeroCelleY = 5; Lettere = (char*)&Celle4; break; case 5: NumeroCelle = 80; NumeroCelleX = 10; NumeroCelleY = 8; Lettere = (char*)&Celle5; break; } if((A > 0) && (B > 0)) { X = A / NumeroCelleX; Y = B / NumeroCelleY; } for(ContatoreFrase = 0; ContatoreFrase < 100; ContatoreFrase++) Frase[ContatoreFrase] = ' '; ContatoreFrase = ContatoreLettere = ContatoreX = ContatoreY = 0; } bool Spaziatore::Get() { if(ContatoreY < NumeroCelleY) { SetRect(&Cella,ContatoreX * X,ContatoreY * Y,(ContatoreX + 1) * X, (ContatoreY + 1) * Y); Lettera = Lettere[ContatoreLettere]; if(ContatoreX < (NumeroCelleX - 1)) ContatoreX++; else { ContatoreX = 0; ContatoreY++; } ContatoreLettere++; return true; } ContatoreLettere = ContatoreY = 0; return false; } void Spaziatore::GetFrase(int x,int y) { short PosizioneX,PosizioneY; if((x <= (NumeroCelleX * X)) && (y <= (NumeroCelleY * Y))) { PosizioneX = x / X; PosizioneY = y / Y; if(ContatoreFrase == 100) { for(ContatoreFrase = 0; ContatoreFrase < 100; ContatoreFrase++) Frase[ContatoreFrase] = ' '; ContatoreFrase = 0; } if((PosizioneX == 0) && (PosizioneY == 0)) { if(ContatoreFrase > 0) { ContatoreFrase--; Frase[ContatoreFrase] = ' '; } } else { Frase[ContatoreFrase] = Lettere[(NumeroCelleX * PosizioneY) + PosizioneX]; ContatoreFrase++; } } } Applicazione Tre LRESULT CALLBACK DemoTreProc { HDC PAINTSTRUCT static PUNTO static HPEN static bool static RECT switch (message) { (HWND hwnd,UINT message,WPARAM wParam,LPARAM lParam) hdc; ps; *Punto; PennaPunto; PuntoAttivo; Rect; 56 case WM_CREATE: PennaPunto = CreatePen(PS_SOLID,10,RGB(0,255,0)); PuntoAttivo = false; return 0; case WM_SIZE: GetClientRect(hwnd,&Rect); return 0; case WM_CREATE_PUNTO: Punto = (PUNTO*) lParam; PuntoAttivo = true; return 0; case WM_NON_VALIDO: InvalidateRect(hwnd,NULL,true); return 0; case WM_PAINT: hdc = BeginPaint (hwnd, &ps); SelectObject(hdc,PennaPunto); if (PuntoAttivo) { MoveToEx(hdc,(int)(Punto->X * Rect.right), (int)(Punto->Y * Rect.bottom),NULL); LineTo(hdc,(int)(Punto->X * Rect.right),(int)(Punto->Y } EndPaint (hwnd, &ps); return 0; case WM_DESTROY: DeleteObject(PennaPunto); return 0; } return DefWindowProc (hwnd,message,wParam,lParam) ; } 57 * Rect.bottom)); 58 Appendice E Messaggi definiti da Windows Resource #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define IDR_MENUPRINCIPALE IDI_ICONA_PRINCIPALE IDD_OLE_PROPPAGE_LARGE IDC_OK IDC_STATIC_VAR_NOME IDC_VAR_IMAGEWIDTH IDC_STATIC_NOME IDC_VAR_IMAGEHEIGHT IDC_VAR_LIVEWINDOW IDC_VAR_OVERLAYWINDOW IDC_VAR_SCALE IDC_VAR_USINGDEFAULTPALETTE IDC_VAR_AUDIOHARDWARE IDC_VAR_CAPFILEEXISTS IDC_VAR_CAPTURINGNOW IDC_STATIC_STATO IDC_STATIC_CAPACITA IDC_VAR_HASOVERLAY IDC_VAR_HASDLGVIDEOSOURCE IDC_VAR_HASDLGVIDEOFORMAT IDC_VAR_HASDLGVIDEODISPLAY IDC_VAR_NOMEDRIVER IDC_OK_PROPRIETA ID_CATTURA_AGGANCIO ID_CATTURA_DATI ID_CATTURA_RILASCIO ID_CATTURA_FOTO ID_CATTURA_FINESTRA_FORMATO ID_CATTURA_FINESTRA_SORGENTE ID_CATTURA_SETTAGGI_OVERLAY ID_CATTURA_SETTAGGI_INFORMAZIONI ID_CATTURA_ONOFF_2WEB ID_CATTURA_TARGET_FILE ID_CATTURA_SETTAGGI_SETTAGGIUNO ID_CATTURA_PROPRIETA_PROPRIETAUNO ID_CATTURA_SETTAGGI_SETTAGGIDUE ID_CATTURA_PROPRIETA_PROPRIETADUE ID_INTERCETTA_VISUALIZZA_FRONTALE ID_INTERCETTA_START_FRONTALE ID_INTERCETTA_STOP_FRONTALE ID_INTERCETTA_VISUALIZZA_FRONTALE_II ID_INTERCETTA_START_FRONTALE_II ID_INTERCETTA_STOP_FRONTALE_II ID_INTERCETTA_PALETTE_FRONTALE ID_INTERCETTA_VISUALIZZA_LATERALE ID_INTERCETTA_START_LATERALE ID_INTERCETTA_STOP_LATERALE ID_INTERCETTA_PALETTE_LATERALE ID_POSIZIONA_VAI ID_POSIZIONA_AUTOMATICO ID_FINESTRE_FINESTRAIMMAGINEFRONTALE ID_FINESTRE_FINESTRAIMMAGINELATERALE ID_FINESTRE_FINESTRAPALETTEFRONTALE ID_FINESTRE_FINESTRAPALETTELATERALE ID_FINESTRE_FINESTRACONTROLLI ID_FINESTRE_FINESTRAWEBUNO ID_FINESTRE_FINESTRAWEBDUE ID_FINESTRE_FINESTRAPOSIZIONA ID_POSIZIONA_INTERROMPI ID_POSIZIONA_MANUALE ID_POSIZIONA_STOP ID_APPLICAZIONI_DEMOUNO ID_POSIZIONA_OK ID_APPLICAZIONI_DEMODUE ID_APPLICAZIONI_DEMOTRE ID_POSIZIONA_RITORNA 101 102 105 1034 1036 1036 1037 1039 1041 1043 1045 1047 1049 1051 1053 1054 1055 1057 1059 1061 1063 1066 1069 40001 40002 40003 40004 40005 40006 40008 40009 40012 40015 40016 40016 40017 40017 40018 40020 40021 40022 40023 40024 40025 40031 40032 40033 40034 40052 40052 40053 40054 40055 40056 40057 40058 40059 40060 40061 40062 40063 40065 40066 40067 40068 40069 // Next default values for new objects // #ifdef APSTUDIO_INVOKED 59 #ifndef #define #define #define #define #endif #endif APSTUDIO_READONLY_SYMBOLS _APS_NEXT_RESOURCE_VALUE _APS_NEXT_COMMAND_VALUE _APS_NEXT_CONTROL_VALUE _APS_NEXT_SYMED_VALUE 103 40070 1070 101 Oggetti relativi alla gestione Menu void GestoreMenu::Set(HMENU MenuPrincipale,PCUBE Cube,GestoreFinestre *Finestre) { this->MenuPrincipale = MenuPrincipale; this->Finestre = Finestre; this->Cube = Cube; } void GestoreMenu::Messaggio(UINT Messaggio) { switch (Messaggio) { // Gestione dei comandi di Cattura: case AN_OK_AGGANCIO: EnableMenuItem(MenuPrincipale,ID_CATTURA_AGGANCIO,MF_GRAYED); EnableMenuItem(MenuPrincipale,ID_CATTURA_ONOFF_2WEB,MF_GRAYED); EnableMenuItem(MenuPrincipale,ID_CATTURA_FINESTRA_FORMATO,MF_ENABLED); EnableMenuItem(MenuPrincipale,ID_CATTURA_FINESTRA_SORGENTE,MF_ENABLED); EnableMenuItem(MenuPrincipale,ID_CATTURA_FOTO,MF_ENABLED); EnableMenuItem(MenuPrincipale,ID_CATTURA_RILASCIO,MF_ENABLED); EnableMenuItem(MenuPrincipale,ID_CATTURA_SETTAGGI_OVERLAY,MF_ENABLED); EnableMenuItem(MenuPrincipale,ID_CATTURA_SETTAGGI_INFORMAZIONI,MF_ENABLED); EnableMenuItem(MenuPrincipale,ID_CATTURA_TARGET_FILE,MF_ENABLED); EnableMenuItem(MenuPrincipale,ID_CATTURA_SETTAGGI_SETTAGGIUNO,MF_ENABLED); if(Cube->DueWeb) { EnableMenuItem(MenuPrincipale,ID_CATTURA_SETTAGGI_SETTAGGIDUE,MF_ENABLED); EnableMenuItem(MenuPrincipale,ID_INTERCETTA_VISUALIZZA_LATERALE,MF_ENABLED); } EnableMenuItem(MenuPrincipale,ID_INTERCETTA_VISUALIZZA_FRONTALE,MF_ENABLED); EnableMenuItem(MenuPrincipale,ID_INTERCETTA_VISUALIZZA_FRONTALE_II,MF_ENABLED); break; case AN_OK_RILASCIO: EnableMenuItem(MenuPrincipale,ID_CATTURA_RILASCIO,MF_GRAYED); EnableMenuItem(MenuPrincipale,ID_CATTURA_FINESTRA_FORMATO,MF_GRAYED); EnableMenuItem(MenuPrincipale,ID_CATTURA_FINESTRA_SORGENTE,MF_GRAYED); EnableMenuItem(MenuPrincipale,ID_CATTURA_FOTO,MF_GRAYED); EnableMenuItem(MenuPrincipale,ID_CATTURA_SETTAGGI_OVERLAY,MF_GRAYED); EnableMenuItem(MenuPrincipale,ID_CATTURA_SETTAGGI_INFORMAZIONI,MF_GRAYED); EnableMenuItem(MenuPrincipale,ID_CATTURA_TARGET_FILE,MF_GRAYED); EnableMenuItem(MenuPrincipale,ID_CATTURA_SETTAGGI_SETTAGGIUNO,MF_GRAYED); EnableMenuItem(MenuPrincipale,ID_CATTURA_SETTAGGI_SETTAGGIDUE,MF_GRAYED); EnableMenuItem(MenuPrincipale,ID_INTERCETTA_VISUALIZZA_FRONTALE,MF_GRAYED); EnableMenuItem(MenuPrincipale,ID_INTERCETTA_START_FRONTALE,MF_GRAYED); EnableMenuItem(MenuPrincipale,ID_INTERCETTA_STOP_FRONTALE,MF_GRAYED); EnableMenuItem(MenuPrincipale,ID_INTERCETTA_VISUALIZZA_LATERALE,MF_GRAYED); EnableMenuItem(MenuPrincipale,ID_INTERCETTA_START_LATERALE,MF_GRAYED); EnableMenuItem(MenuPrincipale,ID_INTERCETTA_STOP_LATERALE,MF_GRAYED); EnableMenuItem(MenuPrincipale,ID_INTERCETTA_VISUALIZZA_FRONTALE_II ,MF_GRAYED); EnableMenuItem(MenuPrincipale,ID_INTERCETTA_START_FRONTALE_II,MF_GRAYED); EnableMenuItem(MenuPrincipale,ID_INTERCETTA_STOP_FRONTALE_II,MF_GRAYED); EnableMenuItem(MenuPrincipale,ID_INTERCETTA_PALETTE_FRONTALE,MF_GRAYED); EnableMenuItem(MenuPrincipale,ID_INTERCETTA_PALETTE_LATERALE,MF_GRAYED); EnableMenuItem(MenuPrincipale,ID_CATTURA_ONOFF_2WEB,MF_ENABLED); EnableMenuItem(MenuPrincipale,ID_CATTURA_AGGANCIO,MF_ENABLED); break; // Gestione dei comandi di posiziona: case AN_OK_START_POSIZIONA: CheckMenuItem(MenuPrincipale,ID_FINESTRE_FINESTRAIMMAGINEFRONTALE,MF_UNCHECKED); CheckMenuItem(MenuPrincipale,ID_FINESTRE_FINESTRAIMMAGINELATERALE,MF_UNCHECKED); CheckMenuItem(MenuPrincipale,ID_FINESTRE_FINESTRAPALETTEFRONTALE,MF_UNCHECKED); CheckMenuItem(MenuPrincipale,ID_FINESTRE_FINESTRAPALETTELATERALE,MF_UNCHECKED); CheckMenuItem(MenuPrincipale,ID_FINESTRE_FINESTRACONTROLLI,MF_UNCHECKED); CheckMenuItem(MenuPrincipale,ID_FINESTRE_FINESTRAWEBUNO,MF_UNCHECKED); CheckMenuItem(MenuPrincipale,ID_FINESTRE_FINESTRAWEBDUE,MF_UNCHECKED); CheckMenuItem(MenuPrincipale,ID_FINESTRE_FINESTRAPOSIZIONA,MF_CHECKED); EnableMenuItem(MenuPrincipale,ID_FINESTRE_FINESTRAIMMAGINEFRONTALE,MF_GRAYED); EnableMenuItem(MenuPrincipale,ID_FINESTRE_FINESTRAIMMAGINELATERALE,MF_GRAYED); EnableMenuItem(MenuPrincipale,ID_FINESTRE_FINESTRAPALETTEFRONTALE,MF_GRAYED); 60 EnableMenuItem(MenuPrincipale,ID_FINESTRE_FINESTRAPALETTELATERALE,MF_GRAYED); EnableMenuItem(MenuPrincipale,ID_FINESTRE_FINESTRACONTROLLI,MF_GRAYED); EnableMenuItem(MenuPrincipale,ID_FINESTRE_FINESTRAWEBUNO,MF_GRAYED); EnableMenuItem(MenuPrincipale,ID_FINESTRE_FINESTRAWEBDUE,MF_GRAYED); EnableMenuItem(MenuPrincipale,ID_FINESTRE_FINESTRAPOSIZIONA,MF_GRAYED); EnableMenuItem(MenuPrincipale,ID_INTERCETTA_STOP_FRONTALE,MF_GRAYED); EnableMenuItem(MenuPrincipale,ID_INTERCETTA_STOP_LATERALE,MF_GRAYED); EnableMenuItem(MenuPrincipale,ID_POSIZIONA_AUTOMATICO,MF_GRAYED); EnableMenuItem(MenuPrincipale,ID_POSIZIONA_MANUALE,MF_GRAYED); EnableMenuItem(MenuPrincipale,ID_POSIZIONA_OK,MF_GRAYED); EnableMenuItem(MenuPrincipale,ID_POSIZIONA_INTERROMPI,MF_ENABLED); break; case AN_OK_STOP_POSIZIONA: if(Finestre->GetVisibileSecondarie(FINESTRA_IMMAGINE_FRONTALE)) CheckMenuItem(MenuPrincipale,ID_FINESTRE_FINESTRAIMMAGINEFRONTALE,MF_CHECKED); if(Finestre->GetVisibileSecondarie(FINESTRA_IMMAGINE_LATERALE)) CheckMenuItem(MenuPrincipale,ID_FINESTRE_FINESTRAIMMAGINELATERALE,MF_CHECKED); if(Finestre->GetVisibileSecondarie(FINESTRA_PALETTE_FRONTALE)) CheckMenuItem(MenuPrincipale,ID_FINESTRE_FINESTRAPALETTEFRONTALE,MF_CHECKED); if(Finestre->GetVisibileSecondarie(FINESTRA_PALETTE_LATERALE)) CheckMenuItem(MenuPrincipale,ID_FINESTRE_FINESTRAPALETTELATERALE,MF_CHECKED); if(Finestre->GetVisibileSecondarie(FINESTRA_CONTROLLI)) CheckMenuItem(MenuPrincipale,ID_FINESTRE_FINESTRACONTROLLI,MF_CHECKED); if(Finestre->GetVisibileSecondarie(FINESTRA_WEB_UNO)) CheckMenuItem(MenuPrincipale,ID_FINESTRE_FINESTRAWEBUNO,MF_CHECKED); if(Finestre->GetVisibileSecondarie(FINESTRA_WEB_DUE)) CheckMenuItem(MenuPrincipale,ID_FINESTRE_FINESTRAWEBDUE,MF_CHECKED); CheckMenuItem(MenuPrincipale,ID_FINESTRE_FINESTRAPOSIZIONA,MF_UNCHECKED); EnableMenuItem(MenuPrincipale,ID_FINESTRE_FINESTRAIMMAGINEFRONTALE,MF_ENABLED); EnableMenuItem(MenuPrincipale,ID_FINESTRE_FINESTRAIMMAGINELATERALE,MF_ENABLED); EnableMenuItem(MenuPrincipale,ID_FINESTRE_FINESTRAPALETTEFRONTALE,MF_ENABLED); EnableMenuItem(MenuPrincipale,ID_FINESTRE_FINESTRAPALETTELATERALE,MF_ENABLED); EnableMenuItem(MenuPrincipale,ID_FINESTRE_FINESTRACONTROLLI,MF_ENABLED); EnableMenuItem(MenuPrincipale,ID_FINESTRE_FINESTRAWEBUNO,MF_ENABLED); EnableMenuItem(MenuPrincipale,ID_FINESTRE_FINESTRAWEBDUE,MF_ENABLED); EnableMenuItem(MenuPrincipale,ID_FINESTRE_FINESTRAPOSIZIONA,MF_ENABLED); EnableMenuItem(MenuPrincipale,ID_INTERCETTA_STOP_FRONTALE,MF_ENABLED); EnableMenuItem(MenuPrincipale,ID_POSIZIONA_AUTOMATICO,MF_ENABLED); EnableMenuItem(MenuPrincipale,ID_POSIZIONA_MANUALE,MF_ENABLED); EnableMenuItem(MenuPrincipale,ID_POSIZIONA_OK,MF_ENABLED); EnableMenuItem(MenuPrincipale,ID_POSIZIONA_INTERROMPI,MF_GRAYED); break; case AN_OK_START_MANUALE: EnableMenuItem(MenuPrincipale,ID_POSIZIONA_AUTOMATICO,MF_GRAYED); EnableMenuItem(MenuPrincipale,ID_POSIZIONA_MANUALE,MF_GRAYED); EnableMenuItem(MenuPrincipale,ID_POSIZIONA_OK,MF_GRAYED); EnableMenuItem(MenuPrincipale,ID_POSIZIONA_STOP,MF_ENABLED); break; case AN_OK_STOP_MANUALE: EnableMenuItem(MenuPrincipale,ID_POSIZIONA_AUTOMATICO,MF_ENABLED); EnableMenuItem(MenuPrincipale,ID_POSIZIONA_MANUALE,MF_ENABLED); EnableMenuItem(MenuPrincipale,ID_POSIZIONA_OK,MF_ENABLED); EnableMenuItem(MenuPrincipale,ID_POSIZIONA_STOP,MF_GRAYED); break; case AN_OK_OK: EnableMenuItem(MenuPrincipale,ID_APPLICAZIONI_DEMOUNO,MF_GRAYED); EnableMenuItem(MenuPrincipale,ID_APPLICAZIONI_DEMODUE,MF_GRAYED); EnableMenuItem(MenuPrincipale,ID_APPLICAZIONI_DEMOTRE,MF_GRAYED); //... EnableMenuItem(MenuPrincipale,ID_FINESTRE_FINESTRAIMMAGINEFRONTALE ,MF_GRAYED); EnableMenuItem(MenuPrincipale,ID_FINESTRE_FINESTRAIMMAGINELATERALE ,MF_GRAYED); EnableMenuItem(MenuPrincipale,ID_FINESTRE_FINESTRAPALETTEFRONTALE,MF_GRAYED); EnableMenuItem(MenuPrincipale,ID_FINESTRE_FINESTRAPALETTELATERALE,MF_GRAYED); EnableMenuItem(MenuPrincipale,ID_FINESTRE_FINESTRACONTROLLI,MF_GRAYED); EnableMenuItem(MenuPrincipale,ID_FINESTRE_FINESTRAWEBUNO,MF_GRAYED); EnableMenuItem(MenuPrincipale,ID_FINESTRE_FINESTRAWEBDUE,MF_GRAYED); EnableMenuItem(MenuPrincipale,ID_POSIZIONA_AUTOMATICO,MF_GRAYED); EnableMenuItem(MenuPrincipale,ID_POSIZIONA_MANUALE,MF_GRAYED); EnableMenuItem(MenuPrincipale,ID_POSIZIONA_OK,MF_GRAYED); EnableMenuItem(MenuPrincipale,ID_POSIZIONA_RITORNA,MF_ENABLED); break; case AN_OK_RITORNA: EnableMenuItem(MenuPrincipale,ID_APPLICAZIONI_DEMOUNO,MF_ENABLED); EnableMenuItem(MenuPrincipale,ID_APPLICAZIONI_DEMODUE,MF_ENABLED); EnableMenuItem(MenuPrincipale,ID_APPLICAZIONI_DEMOTRE,MF_ENABLED); //... EnableMenuItem(MenuPrincipale,ID_FINESTRE_FINESTRAIMMAGINEFRONTALE ,MF_ENABLED); EnableMenuItem(MenuPrincipale,ID_FINESTRE_FINESTRAIMMAGINELATERALE ,MF_ENABLED); EnableMenuItem(MenuPrincipale,ID_FINESTRE_FINESTRAPALETTEFRONTALE,MF_ENABLED); 61 EnableMenuItem(MenuPrincipale,ID_FINESTRE_FINESTRAPALETTELATERALE,MF_ENABLED); EnableMenuItem(MenuPrincipale,ID_FINESTRE_FINESTRACONTROLLI,MF_ENABLED); EnableMenuItem(MenuPrincipale,ID_FINESTRE_FINESTRAWEBUNO,MF_ENABLED); EnableMenuItem(MenuPrincipale,ID_FINESTRE_FINESTRAWEBDUE,MF_ENABLED); EnableMenuItem(MenuPrincipale,ID_POSIZIONA_AUTOMATICO,MF_ENABLED); EnableMenuItem(MenuPrincipale,ID_POSIZIONA_MANUALE,MF_ENABLED); EnableMenuItem(MenuPrincipale,ID_POSIZIONA_OK,MF_ENABLED); EnableMenuItem(MenuPrincipale,ID_POSIZIONA_RITORNA,MF_GRAYED); break; // Gestione sottomenu Applicazioni: case AN_OK_APPLICAZIONE_PRESCELTA: if(Finestre->GetPresceltaApplicazione(FINESTRA_APPLICAZIONE_DEMO_UNO)) { CheckMenuItem(MenuPrincipale,ID_APPLICAZIONI_DEMOUNO,MF_CHECKED); CheckMenuItem(MenuPrincipale,ID_APPLICAZIONI_DEMODUE,MF_UNCHECKED); CheckMenuItem(MenuPrincipale,ID_APPLICAZIONI_DEMOTRE,MF_UNCHECKED); //... } if(Finestre->GetPresceltaApplicazione(FINESTRA_APPLICAZIONE_DEMO_DUE)) { CheckMenuItem(MenuPrincipale,ID_APPLICAZIONI_DEMOUNO,MF_UNCHECKED); CheckMenuItem(MenuPrincipale,ID_APPLICAZIONI_DEMODUE,MF_CHECKED); CheckMenuItem(MenuPrincipale,ID_APPLICAZIONI_DEMOTRE,MF_UNCHECKED); //... } if(Finestre->GetPresceltaApplicazione(FINESTRA_APPLICAZIONE_DEMO_TRE)) { CheckMenuItem(MenuPrincipale,ID_APPLICAZIONI_DEMOUNO,MF_UNCHECKED); CheckMenuItem(MenuPrincipale,ID_APPLICAZIONI_DEMODUE,MF_UNCHECKED); CheckMenuItem(MenuPrincipale,ID_APPLICAZIONI_DEMOTRE,MF_CHECKED); //... } //... break; // Gestione dei comandi per l'Intercettazione manuale: case AN_OK_VISUALIZZA_FRONTALE: EnableMenuItem(MenuPrincipale,ID_INTERCETTA_PALETTE_FRONTALE,MF_ENABLED); EnableMenuItem(MenuPrincipale,ID_INTERCETTA_VISUALIZZA_FRONTALE_II,MF_GRAYED); EnableMenuItem(MenuPrincipale,ID_INTERCETTA_START_FRONTALE_II,MF_GRAYED); break; case AN_OK_VISUALIZZA_FRONTALE_II: EnableMenuItem(MenuPrincipale,ID_INTERCETTA_START_FRONTALE_II,MF_ENABLED); EnableMenuItem(MenuPrincipale,ID_INTERCETTA_VISUALIZZA_FRONTALE,MF_GRAYED); EnableMenuItem(MenuPrincipale,ID_INTERCETTA_START_FRONTALE,MF_GRAYED); break; case AN_OK_VISUALIZZA_LATERALE: EnableMenuItem(MenuPrincipale,ID_INTERCETTA_PALETTE_LATERALE,MF_ENABLED); break; case AN_OK_COLORE_SCELTO_FRONTALE: EnableMenuItem(MenuPrincipale,ID_INTERCETTA_START_FRONTALE,MF_ENABLED); break; case AN_OK_COLORE_SCELTO_LATERALE: EnableMenuItem(MenuPrincipale,ID_INTERCETTA_START_LATERALE,MF_ENABLED); break; // Thread Frontale: case AN_OK_START_FRONTALE: EnableMenuItem(MenuPrincipale,ID_INTERCETTA_VISUALIZZA_FRONTALE,MF_GRAYED); EnableMenuItem(MenuPrincipale,ID_INTERCETTA_START_FRONTALE,MF_GRAYED); EnableMenuItem(MenuPrincipale,ID_INTERCETTA_VISUALIZZA_FRONTALE_II,MF_GRAYED); EnableMenuItem(MenuPrincipale,ID_INTERCETTA_START_FRONTALE_II,MF_GRAYED); EnableMenuItem(MenuPrincipale,ID_CATTURA_RILASCIO,MF_GRAYED); EnableMenuItem(MenuPrincipale,ID_INTERCETTA_PALETTE_FRONTALE,MF_GRAYED); EnableMenuItem(MenuPrincipale,ID_INTERCETTA_STOP_FRONTALE,MF_ENABLED); EnableMenuItem(MenuPrincipale,ID_POSIZIONA_AUTOMATICO,MF_ENABLED); EnableMenuItem(MenuPrincipale,ID_POSIZIONA_MANUALE,MF_ENABLED); break; case AN_OK_STOP_FRONTALE: EnableMenuItem(MenuPrincipale,ID_INTERCETTA_STOP_FRONTALE,MF_GRAYED); EnableMenuItem(MenuPrincipale,ID_POSIZIONA_AUTOMATICO,MF_GRAYED); EnableMenuItem(MenuPrincipale,ID_INTERCETTA_PALETTE_FRONTALE,MF_GRAYED); EnableMenuItem(MenuPrincipale,ID_POSIZIONA_MANUALE,MF_GRAYED); EnableMenuItem(MenuPrincipale,ID_INTERCETTA_VISUALIZZA_FRONTALE,MF_ENABLED); EnableMenuItem(MenuPrincipale,ID_INTERCETTA_VISUALIZZA_FRONTALE_II,MF_ENABLED); EnableMenuItem(MenuPrincipale,ID_CATTURA_RILASCIO,MF_ENABLED); break; // Thread Laterale: 62 case AN_OK_START_LATERALE: EnableMenuItem(MenuPrincipale,ID_INTERCETTA_VISUALIZZA_LATERALE,MF_GRAYED); EnableMenuItem(MenuPrincipale,ID_INTERCETTA_START_LATERALE,MF_GRAYED); EnableMenuItem(MenuPrincipale,ID_CATTURA_RILASCIO,MF_GRAYED); EnableMenuItem(MenuPrincipale,ID_INTERCETTA_PALETTE_LATERALE,MF_GRAYED); EnableMenuItem(MenuPrincipale,ID_INTERCETTA_STOP_LATERALE,MF_ENABLED); break; case AN_OK_STOP_LATERALE: EnableMenuItem(MenuPrincipale,ID_INTERCETTA_STOP_LATERALE,MF_GRAYED); EnableMenuItem(MenuPrincipale,ID_INTERCETTA_VISUALIZZA_LATERALE,MF_ENABLED); EnableMenuItem(MenuPrincipale,ID_CATTURA_RILASCIO,MF_ENABLED); break; // Thread Frontale II: case AN_OK_START_FRONTALE_II: EnableMenuItem(MenuPrincipale,ID_INTERCETTA_VISUALIZZA_FRONTALE,MF_GRAYED); EnableMenuItem(MenuPrincipale,ID_INTERCETTA_START_FRONTALE,MF_GRAYED); EnableMenuItem(MenuPrincipale,ID_INTERCETTA_VISUALIZZA_FRONTALE_II,MF_GRAYED); EnableMenuItem(MenuPrincipale,ID_INTERCETTA_START_FRONTALE_II,MF_GRAYED); EnableMenuItem(MenuPrincipale,ID_CATTURA_RILASCIO,MF_GRAYED); EnableMenuItem(MenuPrincipale,ID_INTERCETTA_PALETTE_FRONTALE,MF_GRAYED); EnableMenuItem(MenuPrincipale,ID_INTERCETTA_STOP_FRONTALE_II,MF_ENABLED); break; case AN_OK_STOP_FRONTALE_II: EnableMenuItem(MenuPrincipale,ID_INTERCETTA_STOP_FRONTALE_II,MF_GRAYED); EnableMenuItem(MenuPrincipale,ID_INTERCETTA_VISUALIZZA_FRONTALE,MF_ENABLED); EnableMenuItem(MenuPrincipale,ID_INTERCETTA_VISUALIZZA_FRONTALE_II,MF_ENABLED); EnableMenuItem(MenuPrincipale,ID_CATTURA_RILASCIO,MF_ENABLED); break; // Gestione Due Web: case AN_OK_DUE_WEB_NO: CheckMenuItem(MenuPrincipale,ID_CATTURA_ONOFF_2WEB,MF_UNCHECKED); break; case AN_OK_DUE_WEB_SI: CheckMenuItem(MenuPrincipale,ID_CATTURA_ONOFF_2WEB,MF_CHECKED); break; // Gestione Finestra Immagine Frontale: case AN_OK_FINESTRA_IMMAGINE_FRONTALE_VISIBILE_NO: CheckMenuItem(MenuPrincipale,ID_FINESTRE_FINESTRAIMMAGINEFRONTALE,MF_UNCHECKED); break; case AN_OK_FINESTRA_IMMAGINE_FRONTALE_VISIBILE_SI: CheckMenuItem(MenuPrincipale,ID_FINESTRE_FINESTRAIMMAGINEFRONTALE,MF_CHECKED); break; // Gestione Finestra Immagine Laterale: case AN_OK_FINESTRA_IMMAGINE_LATERALE_VISIBILE_NO: CheckMenuItem(MenuPrincipale,ID_FINESTRE_FINESTRAIMMAGINELATERALE,MF_UNCHECKED); break; case AN_OK_FINESTRA_IMMAGINE_LATERALE_VISIBILE_SI: CheckMenuItem(MenuPrincipale,ID_FINESTRE_FINESTRAIMMAGINELATERALE,MF_CHECKED); break; // Gestione Finestra Palette Frontale: case AN_OK_FINESTRA_PALETTE_FRONTALE_VISIBILE_NO: CheckMenuItem(MenuPrincipale,ID_FINESTRE_FINESTRAPALETTEFRONTALE,MF_UNCHECKED); break; case AN_OK_FINESTRA_PALETTE_FRONTALE_VISIBILE_SI: CheckMenuItem(MenuPrincipale,ID_FINESTRE_FINESTRAPALETTEFRONTALE,MF_CHECKED); break; // Gestione Finestra Palette Laterale: case AN_OK_FINESTRA_PALETTE_LATERALE_VISIBILE_NO: CheckMenuItem(MenuPrincipale,ID_FINESTRE_FINESTRAPALETTELATERALE,MF_UNCHECKED); break; case AN_OK_FINESTRA_PALETTE_LATERALE_VISIBILE_SI: CheckMenuItem(MenuPrincipale,ID_FINESTRE_FINESTRAPALETTELATERALE,MF_CHECKED); break; // Gestione Finestra Semafori: case AN_OK_FINESTRA_CONTROLLI_VISIBILE_NO: CheckMenuItem(MenuPrincipale,ID_FINESTRE_FINESTRACONTROLLI,MF_UNCHECKED); break; case AN_OK_FINESTRA_CONTROLLI_VISIBILE_SI: 63 CheckMenuItem(MenuPrincipale,ID_FINESTRE_FINESTRACONTROLLI,MF_CHECKED); break; // Gestione Finestra Web Uno: case AN_OK_FINESTRA_WEB_UNO_VISIBILE_NO: CheckMenuItem(MenuPrincipale,ID_FINESTRE_FINESTRAWEBUNO,MF_UNCHECKED); break; case AN_OK_FINESTRA_WEB_UNO_VISIBILE_SI: CheckMenuItem(MenuPrincipale,ID_FINESTRE_FINESTRAWEBUNO,MF_CHECKED); break; // Gestione Finestra Web Due: case AN_OK_FINESTRA_WEB_DUE_VISIBILE_NO: CheckMenuItem(MenuPrincipale,ID_FINESTRE_FINESTRAWEBDUE,MF_UNCHECKED); break; case AN_OK_FINESTRA_WEB_DUE_VISIBILE_SI: CheckMenuItem(MenuPrincipale,ID_FINESTRE_FINESTRAWEBDUE,MF_CHECKED); break; } } Errore void GestoreErrore::SetMessageBox(char* Testo) { MessageBox(FinestraPrincipale,TEXT (Testo),szAppName,MB_ICONINFORMATION | MB_OK);} void GestoreErrore::Set(HWND FinestraPrincipale,char* szAppName) { this->FinestraPrincipale = FinestraPrincipale; this->szAppName = szAppName; } void GestoreErrore::Messaggio(UINT Messaggio) { switch (Messaggio) { case AN_NO_AGGANCIO: SetMessageBox("Non riesco ad agganciarmi alla finestra, Aggancio fallito!"); break; case AN_NO_DATI: SetMessageBox("Non riesco ad agganciare i dati della cattura, Aggancio fallito!"); break; case AN_NO_FINESTRA_FORMATO: SetMessageBox("Non esiste finestra di dialogo per il formato!"); break; case AN_NO_FORMATO_ERRORE: SetMessageBox("Errore nel visualizzare la finestra di dialogo per il formato!"); break; case AN_NO_FINESTRA_SORGENTE: SetMessageBox("Non posso aprire finestra del sorgente!"); break; case AN_NO_SORGENTE_ERRORE: SetMessageBox("Errore nel visualizzare la finestra di dialogo per il sorgente!"); break; case AN_NO_FOTO: SetMessageBox("Non riesco a fare una cattura!"); break; case AN_NO_FILE: SetMessageBox("Problemi a salvare su file"); break; case AN_NO_ELABORAZIONE: SetMessageBox("Problemi a elaborare l'immagine"); break; case AN_NO_RILASCIO_WEB_UNO: SetMessageBox("Non riesco a disconnettere Camera1!"); break; case AN_NO_RILASCIO_WEB_DUE: SetMessageBox("Non riesco a disconnettere Camera2!"); break; case AN_NO_ALLOCIMAGE: SetMessageBox("Non Riesco a riservare memoria per l'immagine!"); break; } } Finestre void GestoreFinestre::Set(HWND FinestraPrincipale) { short i; this->FinestraPrincipale = FinestraPrincipale; 64 // Creazione della lista delle Finestre Secondarie: Testa = Coda = Indice = NULL; // Cella della Finestra Principale: Temp = new(CELLA_FINESTRA); Temp->hwndFinestra = FinestraPrincipale; Temp->Tipo = FINESTRA_PRINCIPALE; Temp->Visibile = true; // Ininfluente Temp->Successivo = NULL; Testa = Coda = Temp; for(i=FINESTRA_IMMAGINE_FRONTALE; i <= FINESTRA_CONTROLLI; i++) { Temp = new(CELLA_FINESTRA); Temp->hwndFinestra = NULL; Temp->Tipo = i; Temp->Visibile = true; Temp->Successivo = NULL; Coda->Successivo = Temp; Coda = Temp; } // Cella della Finestra Posiziona: Temp = new(CELLA_FINESTRA); Temp->hwndFinestra = NULL; Temp->Tipo = FINESTRA_POSIZIONA; Temp->Visibile = false; Temp->Successivo = NULL; Coda->Successivo = Temp; Coda = Temp; // Creazione della lista delle Finestre di Applicazione: TestaLA = CodaLA = IndiceLA = NULL; } // Setto l'handler delle Finestre Secondarie e d'Applicazione: void GestoreFinestre::SetFinestreSecondarie(UINT Tipo,HWND hwndFinestra) { Temp = Testa; while(Temp != NULL) { if(Temp->Tipo == Tipo) Temp->hwndFinestra = hwndFinestra; Temp = Temp->Successivo; } } void GestoreFinestre::SetFinestreApplicazione(UINT Tipo,HWND Finestra,bool Prescelta) { if(TestaLA == NULL) { TempLA = new (CELLA_APPLICAZIONE); TempLA->Tipo = Tipo; TempLA->hwndApplicazione = Finestra; TempLA->Visibile = false; TempLA->Prescelta = Prescelta; TempLA->Successivo = NULL; TestaLA = CodaLA = TempLA; } else { TempLA = new (CELLA_APPLICAZIONE); TempLA->Tipo = Tipo; TempLA->hwndApplicazione = Finestra; TempLA->Visibile = false; TempLA->Prescelta = Prescelta; TempLA->Successivo = NULL; CodaLA->Successivo = TempLA; CodaLA = TempLA; } } // Ottengo l'handler delle Finestre Secondarie e d'Applicazione: HWND GestoreFinestre::GetFinestreSecondarie(UINT Tipo) { Temp = Testa; while(Temp != NULL) { if(Temp->Tipo == Tipo) return Temp->hwndFinestra; Temp = Temp->Successivo; 65 } return NULL; } HWND GestoreFinestre::GetFinestreApplicazione(UINT Tipo) { TempLA = TestaLA; while(TempLA != NULL) { if(TempLA->Tipo == Tipo) return TempLA->hwndApplicazione; TempLA = TempLA->Successivo; } return NULL; } HWND GestoreFinestre::GetFinestreApplicazione() { TempLA = TestaLA; while(TempLA != NULL) { if(TempLA->Prescelta) return TempLA->hwndApplicazione; TempLA = TempLA->Successivo; } return NULL; } // Controllo la visibilità delle Finestre Secondarie e d'Applicazione: bool GestoreFinestre::GetVisibileSecondarie(UINT Tipo) { Temp = Testa; while(Temp != NULL) { if(Temp->Tipo == Tipo) return Temp->Visibile; Temp = Temp->Successivo; } return false; } bool GestoreFinestre::GetVisibileApplicazione(UINT Tipo) { TempLA = TestaLA; while(TempLA != NULL) { if(TempLA->Tipo == Tipo) return TempLA->Visibile; TempLA = TempLA->Successivo; } return false; } // Setto la visibilità delle Finestre Secondarie: void GestoreFinestre::SetVisibileSecondarie(UINT Tipo,bool Visibile) { Temp = Testa; while(Temp != NULL) { if(Temp->Tipo == Tipo) Temp->Visibile = Visibile; Temp = Temp->Successivo; } } // Setto l'Applicazione prescelta: void GestoreFinestre::SetPresceltaApplicazione(UINT Tipo) { TempLA = TestaLA; while(TempLA != NULL) { if(TempLA->Tipo == Tipo) TempLA->Prescelta = true; else TempLA->Prescelta = false; TempLA = TempLA->Successivo; } } // Verifico se l'Applicazione è prescelta: bool GestoreFinestre::GetPresceltaApplicazione(UINT Tipo) { TempLA = TestaLA; 66 while(TempLA != NULL) { if(TempLA->Tipo == Tipo) return TempLA->Prescelta; TempLA = TempLA->Successivo; } return false; } // Gestore dei Messaggi: void GestoreFinestre::Messaggio(UINT Messaggio) { switch (Messaggio) { case FINESTRA_POSIZIONA: Temp = Testa; while(Temp != NULL) { if(Temp->Tipo != FINESTRA_POSIZIONA) { Temp->Ripristino = Temp->Visibile; Temp->Visibile = false; } else Temp->Visibile = true; Temp = Temp->Successivo; } break; case FINESTRE_ATTIVE: Temp = Testa; while(Temp != NULL) { if(Temp->Tipo != FINESTRA_POSIZIONA) Temp->Visibile = Temp->Ripristino; else Temp->Visibile = false; Temp = Temp->Successivo; } break; case FINESTRA_APPLICAZIONE: Temp = Testa; while(Temp != NULL) { Temp->Ripristino = Temp->Visibile; Temp->Visibile = false; Temp = Temp->Successivo; } TempLA = TestaLA; while(TempLA != NULL) { if(TempLA->Prescelta) { TempLA->Visibile = true; break; } TempLA = TempLA->Successivo; } break; case FINESTRE_SECONDARIE: Temp = Testa; while(Temp != NULL) { Temp->Visibile = Temp->Ripristino; Temp = Temp->Successivo; } TempLA = TestaLA; while(TempLA != NULL) { if(TempLA->Prescelta) { TempLA->Visibile = false; break; } TempLA = TempLA->Successivo; } break; } } 67 Procedure di selezione comandi Menu Cattura void MenuAggancio(WPARAM wParam,Supervisore* Angelo) { switch (LOWORD (wParam)) { case ID_CATTURA_AGGANCIO: Angelo->Messaggio(AN_ASK_AGGANCIO,AN_WEB_FRONTALE); break; case ID_CATTURA_FINESTRA_FORMATO: Angelo->Messaggio(AN_ASK_FINESTRA_FORMATO,AN_WEB_FRONTALE); break; case ID_CATTURA_FINESTRA_SORGENTE: Angelo->Messaggio(AN_ASK_FINESTRA_SORGENTE,AN_WEB_FRONTALE); break; case ID_CATTURA_FOTO: Angelo->Messaggio(AN_ASK_FOTO,AN_WEB_FRONTALE); break; case ID_CATTURA_RILASCIO: Angelo->Messaggio(AN_ASK_RILASCIO,AN_WEB_FRONTALE); break; /* case ID_CATTURA_SETTAGGI_OVERLAY: if(!CameraUno->OverLay()) Angelo->SetMessageBox("Problemi sull'overlay"); break; */ case ID_CATTURA_SETTAGGI_SETTAGGIUNO: Angelo->Messaggio(AN_ASK_DLG_PROPRIETA,AN_WEB_FRONTALE); break; case ID_CATTURA_ONOFF_2WEB: Angelo->Messaggio(AN_ONOFF_DUE_WEB); break; case ID_CATTURA_TARGET_FILE: Angelo->Messaggio(AN_ASK_FILE,AN_WEB_FRONTALE); break; } if (Angelo->GetDueWeb()) { switch (LOWORD (wParam)) { case ID_CATTURA_AGGANCIO: Angelo->Messaggio(AN_ASK_AGGANCIO,AN_WEB_LATERALE); break; case ID_CATTURA_FINESTRA_FORMATO: Angelo->Messaggio(AN_ASK_FINESTRA_FORMATO,AN_WEB_LATERALE); break; case ID_CATTURA_FINESTRA_SORGENTE: Angelo->Messaggio(AN_ASK_FINESTRA_SORGENTE,AN_WEB_LATERALE); break; case ID_CATTURA_FOTO: Angelo->Messaggio(AN_ASK_FOTO,AN_WEB_LATERALE); break; case ID_CATTURA_RILASCIO: Angelo->Messaggio(AN_ASK_RILASCIO,AN_WEB_LATERALE); break; case ID_CATTURA_SETTAGGI_SETTAGGIDUE: Angelo->Messaggio(AN_ASK_DLG_PROPRIETA,AN_WEB_LATERALE); break; case ID_CATTURA_TARGET_FILE: Angelo->Messaggio(AN_ASK_FILE,AN_WEB_LATERALE); break; } } } Menu Intercetta void MenuIntercetta(WPARAM wParam,Supervisore* Angelo) { switch (LOWORD (wParam)) { case ID_INTERCETTA_PALETTE_FRONTALE: Angelo->Messaggio(AN_ASK_PALETTE,AN_WEB_FRONTALE); break; case ID_INTERCETTA_PALETTE_LATERALE: Angelo->Messaggio(AN_ASK_PALETTE,AN_WEB_LATERALE); break; case ID_INTERCETTA_VISUALIZZA_FRONTALE: Angelo->Messaggio(AN_ASK_VISUALIZZA,AN_WEB_FRONTALE); 68 break; case ID_INTERCETTA_START_FRONTALE: Angelo->Messaggio(AN_ASK_START,AN_WEB_FRONTALE); break; case ID_INTERCETTA_STOP_FRONTALE: Angelo->Messaggio(AN_ASK_STOP,AN_WEB_FRONTALE); break; case ID_INTERCETTA_VISUALIZZA_LATERALE: Angelo->Messaggio(AN_ASK_VISUALIZZA,AN_WEB_LATERALE); break; case ID_INTERCETTA_START_LATERALE: Angelo->Messaggio(AN_ASK_START,AN_WEB_LATERALE); break; case ID_INTERCETTA_STOP_LATERALE: Angelo->Messaggio(AN_ASK_STOP,AN_WEB_LATERALE); break; case ID_INTERCETTA_VISUALIZZA_FRONTALE_II: Angelo->Messaggio(AN_ASK_VISUALIZZA_II,AN_WEB_FRONTALE); break; case ID_INTERCETTA_START_FRONTALE_II: Angelo->Messaggio(AN_ASK_START_II,AN_WEB_FRONTALE); break; case ID_INTERCETTA_STOP_FRONTALE_II: Angelo->Messaggio(AN_ASK_STOP_II,AN_WEB_FRONTALE); break; } } Menu Posiziona void MenuPosiziona(WPARAM wParam,Supervisore* Angelo) { switch (LOWORD (wParam)) { case ID_POSIZIONA_AUTOMATICO: Angelo->Messaggio(AN_ASK_START_POSIZIONA); break; case ID_POSIZIONA_INTERROMPI: Angelo->Messaggio(AN_ASK_STOP_POSIZIONA); break; case ID_POSIZIONA_MANUALE: Angelo->Messaggio(AN_ASK_START_MANUALE); break; case ID_POSIZIONA_STOP: Angelo->Messaggio(AN_ASK_STOP_MANUALE); break; case ID_POSIZIONA_OK: Angelo->Messaggio(AN_ASK_OK); break; case ID_POSIZIONA_RITORNA: Angelo->Messaggio(AN_ASK_RITORNA); break; } } Menu Applicazioni void MenuDemo(WPARAM wParam,Supervisore* Angelo) { switch (LOWORD (wParam)) { case ID_APPLICAZIONI_DEMOUNO: Angelo->Prescelta(FINESTRA_APPLICAZIONE_DEMO_UNO); break; case ID_APPLICAZIONI_DEMODUE: Angelo->Prescelta(FINESTRA_APPLICAZIONE_DEMO_DUE); break; case ID_APPLICAZIONI_DEMOTRE: Angelo->Prescelta(FINESTRA_APPLICAZIONE_DEMO_TRE); break; //... } } Menu finestre void MenuFinestre(WPARAM wParam,Supervisore* Angelo) { switch (LOWORD (wParam)) 69 { case ID_FINESTRE_FINESTRAIMMAGINEFRONTALE: Angelo->Messaggio(AN_ASK_FINESTRA_IMMAGINE_FRONTALE); break; case ID_FINESTRE_FINESTRAIMMAGINELATERALE: Angelo->Messaggio(AN_ASK_FINESTRA_IMMAGINE_LATERALE); break; case ID_FINESTRE_FINESTRAPALETTEFRONTALE: Angelo->Messaggio(AN_ASK_FINESTRA_PALETTE_FRONTALE); break; case ID_FINESTRE_FINESTRAPALETTELATERALE: Angelo->Messaggio(AN_ASK_FINESTRA_PALETTE_LATERALE); break; case ID_FINESTRE_FINESTRACONTROLLI: Angelo->Messaggio(AN_ASK_FINESTRA_CONTROLLI); break; case ID_FINESTRE_FINESTRAWEBUNO: Angelo->Messaggio(AN_ASK_FINESTRA_WEB_UNO); break; case ID_FINESTRE_FINESTRAWEBDUE: Angelo->Messaggio(AN_ASK_FINESTRA_WEB_DUE); break; case ID_FINESTRE_FINESTRAPOSIZIONA: break; } } 70