DirectX Factor

Disegno a mano libera con le geometrie Direct2D

Charles Petzold

Scarica il codice di esempio

Come sistemi operativi si sono evoluti nel corso degli anni, così sono le applicazioni base archetipiche che ogni sviluppatore dovrebbe sapere come il codice. Per vecchi ambienti della riga di comando, un esercizio comune era un hex dump — un programma che elenca il contenuto di un file in byte esadecimali. Per le interfacce grafiche di mouse e tastiera, calcolatrici e quaderni erano popolari.

In un ambiente multi-touch come Windows 8, vuoi nominare due applicazioni archetipiche: Photo paint scatter e dito. La dispersione della foto è un buon modo per imparare a utilizzare due dita per ridimensionare e ruotare gli oggetti visivi, mentre la pittura dito comporta il rilevamento delle singole dita per tracciare le linee sullo schermo.

Ho esplorato vari approcci alla pittura dito Windows 8 nel capitolo 13 del mio libro, "Programming Windows, 6a edizione" (Microsoft Press, 2012). Quei programmi utilizzati solo il Windows Runtime (WinRT) per riprodurre le linee, ma ora mi piacerebbe rivedere l'esercizio e l'uso di DirectX invece. Questo sarà un buon modo per familiarizzare con alcuni aspetti importanti di DirectX, ma ho il sospetto che ci permetterà anche alla fine certa flessibilità aggiuntive non disponibili nel Runtime di Windows.

Il modello di Visual Studio

Come Doug Erickson ha discusso nel suo articolo del marzo 2013, "Utilizzando XAML con DirectX e C++ in Windows Store Apps" ( msdn.microsoft.com/­rivista/jj991975), ci sono tre modi per combinare XAML e DirectX all'interno di un'applicazione Windows Store. Sarò utilizzando l'approccio che coinvolge un SwapChainBackgroundPanel come elemento figlio di radice di un derivato della pagina XAML. Questo oggetto serve come una superficie di disegno per la grafica Direct2D e Direct3D, ma esso può anche essere overlaid con WinRT controlli, quali barre di applicazione.

Visual Studio 2012 include un modello di progetto per tale programma. Nella finestra di dialogo nuovo progetto scegliere Visual C++ e Windows Store a sinistra e poi il modello chiamato Direct2D App (XAML).  L'altro modello di DirectX è chiamato App Direct3D e crea un programma solo DirectX senza controlli WinRT o grafica. Tuttavia, questi due modelli sono alquanto misnamed, perché si può fare grafica 2D o 3D con uno di loro.

Il modello Direct2D App (XAML) crea una semplice applicazione Windows Store con logica di programma diviso tra un basato su XAML DirectX e interfaccia utente grafica uscita. L'interfaccia utente è costituita da una classe denominata DirectXPage che deriva dalla pagina (proprio come in una normale applicazione Windows Store) e consiste in un file XAML, file di intestazione e file di codice. Si utilizza DirectXPage per la gestione degli input dell'utente, interfacciamento con WinRT controlli e la visualizzazione di testo e grafica basata su XAML. L'elemento principale di DirectXPage è il SwapChainBackgroundPanel, che si può trattare come un elemento Grid regolare in XAML e una superficie di rendering DirectX.

Il modello di progetto crea anche una classe denominata DirectXBase che gestisce la maggior parte del sovraccarico DirectX e una classe denominata SimpleTextRenderer che deriva da DirectXBase ed esegue l'output di grafica DirectX specifici dell'applicazione. Il nome SimpleTextRenderer si riferisce a ciò che fa di questa classe all'interno dell'applicazione creata dal modello di progetto. Ti consigliamo di rinominare questa classe, o sostituirlo con qualcosa che ha un nome più appropriato.

Dal modello di applicazione

Tra i codici scaricabili per questa colonna è un progetto di Visual Studio denominato BasicFingerPaint che ho creato utilizzando il modello Direct2D (XAML). Ho rinominato SimpleTextRenderer di pittura con le dita­Renderer e aggiunto alcune altre classi.

Il modello Direct2D (XAML) implica un'architettura che separa le parti XAML e DirectX dell'applicazione: Codice DirectX dell'applicazione dovrebbe essere limitata a DirectXBase (che non è necessario alterare), la classe renderer che deriva da DirectXBase (in questo caso FingerPaintRenderer) e altre classi o strutture di che queste due classi potrebbero aver bisogno. Nonostante il suo nome, DirectXPage non necessario contenere qualsiasi codice DirectX. Invece, DirectXPage crea un'istanza della classe renderer, che salva come un membro di dati privati denominato m_renderer. DirectXPage effettua molte chiamate nella classe renderer (e indirettamente a DirectXBase) per visualizzare l'output grafico e notificare DirectX della finestra cambia dimensione e altri eventi importanti. La classe renderer non chiama in DirectXPage.

Nel file DirectXPage.xaml ho aggiunto caselle combinate per la barra delle applicazioni che consentono di selezionare un colore di disegno e spessore della linea e i pulsanti per salvare, caricare e chiaro disegni. (Il file logica i/o è estremamente rudimentale e non comprende i servizi, come avvisa se stai per cancellare un disegno che non hai salvato)

Come si tocca un dito sullo schermo, spostarlo e sollevarla, vengono generati eventi PointerPressed, PointerMoved e PointerReleased per indicare il progresso del dito. Ogni evento è accompagnato da un numero di ID che consente di registrare le singole dita e un valore che indica la posizione corrente del dito. Conservare e collegare questi punti, e tu hai reso un singolo colpo. Più tratti di rendering, e hai un disegno completo. Figura 1 Mostra un disegno di BasicFingerPaint composto da nove colpi.


Disegno di figura 1 BasicFingerPaint

Nel file code-behind DirectXPage, ho aggiunto l'override dei metodi di evento puntatore. Questi metodi di chiamano i metodi corrispondenti in FingerPaintRenderer che ho chiamato BeginStroke, ContinueStroke, EndStroke e CancelStroke, come mostrato Figura 2.

Figura 2 puntatore evento metodi chiamate alla classe Renderer


          [%$TOPIC/dn342879_it-it_MSDN_10_47_0_0%]
          [%$TOPIC/dn342879_it-it_MSDN_10_47_0_1%]
          [%$TOPIC/dn342879_it-it_MSDN_10_47_0_2%]
        

L'oggetto PointerId è un valore integer univoco che differenzia le dita, mouse e penna. I valori di colore e punto passati a questi metodi sono tipi fondamentali di WinRT, ma non sono tipi di DirectX. DirectX ha il proprio colore e punto strutture denominate D2D1_POINT_2F e D2D1::ColorF. DirectXPage non sa nulla di DirectX, quindi la classe di FingerPaintRenderer ha la responsabilità dell'esecuzione di tutte le conversioni tra tipi di dati di WinRT e tipi di dati di DirectX.

Costruzione di geometrie di percorso

In BasicFingerPaint, ogni tratto è un insieme di linee collegate breve costruito da una serie di eventi del puntatore di rilevamento. In genere, un hai applicazione renderà queste linee su una bitmap che può poi essere salvato in un file. Ho deciso di non farlo. I file si salva e si carica da BasicFingerPaint sono insiemi di tratti, che sono essi stessi insiemi di punti.

Come si utilizza Direct2D per eseguire il rendering di questi tratti sullo schermo? Se si guarda attraverso i metodi di disegno definiti da ID2D1DeviceContext (che sono principalmente i metodi definiti da ID2D1RenderTarget), tre candidati saltano fuori: DrawLine, DrawGeometry e FillGeometry.

DrawLine disegna una singola linea retta tra due punti con una particolare larghezza, pennello e stile. È ragionevole eseguire il rendering di un tratto con una serie di chiamate DrawLine, ma è probabilmente più efficiente per consolidare le singole linee in una singola polilinea. Per questo, è necessario DrawGeometry.

Direct2D, una geometria è fondamentalmente un insieme di punti che definiscono le linee rette, curve di Bezier e archi. Non non c'è nessun concetto di larghezza di linea, colore o stile in una geometria. Sebbene Direct2D supporta diversi tipi di geometrie semplici (rettangolo, rettangolo arrotondato, ellisse), il più versatile geometria è rappresentata dall'oggetto ID2D1PathGeometry.

Una geometria del percorso è costituito da uno o più "personaggi". Ogni figura è una serie di curve e linee collegate. I singoli componenti della figura sono conosciuti come "segmenti". Una figura potrebbe essere chiusa — vale a dire l'ultimo punto potrebbe connettersi con il primo punto — ma non deve essere.

Per eseguire il rendering di una geometria, si chiama DrawGeometry nel contesto del dispositivo con una larghezza di linea particolare, pennello e stile. Il metodo FillGeometry riempie all'interno di aree chiuse della geometria con un pennello.

Per incapsulare un ictus, FingerPaintRenderer definisce una struttura privata denominata StrokeInfo, come mostrato Figura 3.

Figura 3 il Renderer StrokeInfo struttura e due collezioni

Figura 3 Mostra anche due insiemi utilizzati per salvare gli oggetti StrokeInfo: Il completedStrokes è un vettore insieme, mentre strokesInProgress è una collezione di mappa utilizzando il puntatore ID come chiave.

Il membro di punti della struttura StrokeInfo si accumula tutti i punti che compongono un ictus. Da questi punti, è possibile costruire un oggetto ID2D1PathGeometry. Figura 4 illustra il metodo che esegue questo lavoro. (Per chiarezza, l'elenco non viene visualizzato il codice che controlla i valori HRESULT erranti.)

Figura 4 creazione di una geometria di percorso da punti

Un oggetto ID2D1PathGeometry è una raccolta di figure e segmenti. Per definire il contenuto di una geometria del percorso, chiamare prima aperto sull'oggetto per ottenere un ID2D1GeometrySink. Su questo sink di geometria, si chiama BeginFigure ed EndFigure per delimitare ogni figura e tra quelle chiamate, AddLines, AddArc, AddBezier e altri per aggiungere segmenti a quella cifra. (Le geometrie del percorso create da FingerPaintRenderer hanno solo una singola figura contenente più segmenti di linea retta). Dopo la chiamata a chiudere sul dissipatore della geometria, la geometria del percorso è pronta all'uso, ma è diventato immutabile. Non è possibile riaprirlo o cambiare nulla in essa.

Per questo motivo, come le dita si muovono attraverso lo schermo e il programma si accumula punti e Mostra tratti in corso, nuove geometrie del percorso devono essere continuamente quelli costruiti e vecchi abbandonati.

Quando devono essere create queste nuove geometrie di percorso? Tieni presente che un'applicazione può ricevere PointerMoved eventi più veloce rispetto alla frequenza di aggiornamento dei video, quindi non ha senso per creare la geometria del percorso nel gestore PointerMoved. Invece, il programma gestisce questo evento semplicemente salvando il nuovo punto, ma non se Duplica il punto precedente (che a volte accade).

Figura 5 Mostra i tre metodi principali in FingerPaintRenderer coinvolti nell'accumulo di punti che compongono un ictus. Un nuovo StrokeInfo viene aggiunto all'insieme strokeInProgress durante BeginStroke; ha aggiornato durante la ContinueStroke e trasferito alla raccolta completedStrokes in EndStroke.

Figura 5 accumulando colpi in FingerPaintRenderer

Si noti che ciascuno di questi metodi imposta IsRenderNeeded su true, che indica che lo schermo deve essere ridisegnato. Questo rappresenta uno dei cambiamenti strutturali che dovevo fare per il progetto. In un nuovo progetto basato sul modello di Direct2D (XAML), sia DirectXPage che SimpleTextRenderer di dichiarare un membro di dati Boolean privato denominato m_renderNeeded. Tuttavia, solo in DirectXPage è il membro di dati effettivamente utilizzato. Questo non è abbastanza come dovrebbe essere: Spesso il codice di rendering deve determinare quando lo schermo deve essere ridisegnato. Ho sostituito i due membri di dati m_renderNeeded con una sola proprietà pubblica in FingerPaintRenderer denominato IsRender­necessarie. La proprietà IsRenderNeeded può essere impostata da sia DirectXPage e FingerPaintRenderer, ma è usato solo da DirectXPage.

Il ciclo di Rendering

Nel caso generale, un programma DirectX può ridisegnare l'intero schermo alla frequenza di aggiornamento dei video, che è spesso 60 fotogrammi al secondo o giù di lì. Questa struttura offre la massima flessibilità di programma per la visualizzazione grafica che coinvolgono l'animazione o la trasparenza. Invece di capire da quale parte dello schermo deve essere aggiornato e come evitare di rovinare la grafica esistente, l'intero schermo viene semplicemente ridisegnato.

In un programma come BasicFingerPaint, lo schermo deve solo essere ridisegnato quando qualcosa cambia, che è indicato da una vera impostazione della proprietà IsRenderNeeded. Inoltre, il ridisegno plausibilmente potrebbe essere limitato a determinate aree dello schermo, ma questo non è abbastanza facile con un'applicazione creata dal modello Direct2D (XAML).

Per aggiornare lo schermo, DirectXPage utilizza la composizione handy­Target::Rendering evento che viene generato in sincronizzazione con l'aggiornamento video hardware. In un programma di DirectX, il gestore per questo evento è a volte conosciuto come il ciclo di rendering ed è mostrato Figura 6.

Figura 6 il ciclo di Rendering in DirectXPage

Il metodo di aggiornamento è definito dal renderer. Questo è dove gli oggetti visivi sono preparati per il rendering, specialmente se si richiedono informazioni di temporizzazione forniti da una classe timer creata dal modello di progetto. FingerPaintRenderer utilizza il metodo Update per creare geometrie di percorso da insiemi di punti, se necessario. Il metodo Render viene dichiarato da DirectXBase ma definita da FingerPaintRenderer ed è responsabile del rendering tutte le grafiche. Il metodo denominato presente — è un verbo, non un sostantivo — è definita da DirectXBase e trasferisce le visuali mescolate all'hardware video.

Il metodo Render comincia chiamando BeginDraw sull'oggetto ID3D11DeviceContext del programma e si conclude chiamando EndDraw. Nel mezzo, è possibile chiamare funzioni di disegno. Il rendering di ogni corsa durante il metodo Render è semplicemente:

Gli oggetti m_solidColorBrush e m_strokeStyle sono i membri dati.

Qual è il prossimo passo?

Come suggerisce il nome, BasicFingerPaint è un'applicazione molto semplice. Perché esso non render tratti di un'immagine bitmap, un pittore desideroso e persistente dito potrebbe causare il programma per generare ed eseguire il rendering di migliaia di geometrie. A un certo punto, potrebbe soffrire di refresh dello schermo.

Tuttavia, perché il programma mantiene geometrie discrete, piuttosto che mescolare tutto insieme in una bitmap, il programma potrebbe consentire singolo tratti per essere successivamente eliminato o modificato, forse cambiando il colore o la larghezza o anche spostato in un'altra posizione sullo schermo.

Perché ogni colpo è una geometria del percorso unico, applicando diversi styling è abbastanza facile. Ad esempio, provare a modificare una sola riga nella finestra Crea­Metodo DeviceIndependentResources in FingerPaintRenderer:

Ora il programma disegna linee tratteggiate, piuttosto che linee continue, con il risultato mostrato Figura 7. Questa tecnica funziona solo perché ogni colpo è una singola geometria; che non avrebbe funzionato se i singoli segmenti che comprendono i tratti erano tutte le linee separate.


Figura 7 Rendering di una geometria del percorso con una linea tratteggiata

Un altro possibile miglioramento è un pennello sfumato. Il programma GradientFingerPaint è molto simile al BasicFingerPaint tranne che ha due caselle combinate per colore e un pennello sfumato lineare viene utilizzato per eseguire il rendering della geometria del percorso. Il risultato viene illustrato nella Figura 8.


Figura 8 il programma GradientFingerPaint

Sebbene ogni tratto ha un proprio pennello sfumatura lineare, il punto di inizio della sfumatura è sempre impostato l'angolo superiore sinistro dei limiti di corsa e il punto finale all'angolo in basso a destra. Come si disegna un tratto con un dito, è possibile spesso vedere il gradiente cambiando come il tratto si allunga. Ma a seconda di come viene disegnato il tratto, a volte la pendenza va lungo la lunghezza del tratto, e a volte appena vedete una sfumatura a tutti, come è ovvio, con i due tratti della X in Figura 8.

Non sarebbe meglio se si potrebbe definire una sfumatura che si estendeva lungo l'intera lunghezza del tratto, indipendentemente dalla forma o orientamento del tratto? O come circa una sfumatura che è sempre perpendicolare al tratto, indipendentemente da come il tratto colpi di scena e si trasforma?

Come chiedono nei film di fantascienza: Come è anche possibile una cosa simile?

Charles Petzold è un collaboratore di lunga data di MSDN Magazine e autore di "Programming Windows, 6a edizione," (Microsoft Press, 2012) un libro sulla scrittura di applicazioni per Windows 8. Il suo sito Web è charlespetzold.com.

Grazie ai seguenti esperti tecnici per la revisione di questo articolo: James McNellis (Microsoft)
James McNellis è un aficionado di C++ e sviluppatori di software, il team di Visual C++ di Microsoft, dove egli dove costruisce le librerie C++ e mantiene le librerie di Runtime C (CRT).  Egli tweets a @JamesMcNellise può essere trovato altrove via http://jamesmcnellis.com/.

 

MSDN Magazine Blog

MSDN Magazine Right Rail

14 Top Features of Visual Basic 14: The Q&A
Leading off the feature in the January issue of MSDN Magazine is Lucian Wischik’s fantastic look at Visual Basic .NET 14. As Wischik writes, the newes... More...
mercoledì, gen 7
Big Start to the New Year at MSDN Magazine
Folks, things are hopping over here at MSDN Magazine. We are kicking off the new year with a pair of issues: Our regularly scheduled January issue and... More...
venerdì, gen 2

More MSDN Magazine Blog entries >


Receive the MSDN Flash e-mail newsletter every other week, with news and information personalized to your interests and areas of focus.